diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /ipc/mscom | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/mscom')
-rw-r--r-- | ipc/mscom/Aggregation.h | 83 | ||||
-rw-r--r-- | ipc/mscom/AgileReference.cpp | 64 | ||||
-rw-r--r-- | ipc/mscom/AgileReference.h | 143 | ||||
-rw-r--r-- | ipc/mscom/ApartmentRegion.h | 106 | ||||
-rw-r--r-- | ipc/mscom/COMWrappers.cpp | 101 | ||||
-rw-r--r-- | ipc/mscom/COMWrappers.h | 38 | ||||
-rw-r--r-- | ipc/mscom/EnsureMTA.cpp | 189 | ||||
-rw-r--r-- | ipc/mscom/EnsureMTA.h | 129 | ||||
-rw-r--r-- | ipc/mscom/ProcessRuntime.cpp | 486 | ||||
-rw-r--r-- | ipc/mscom/ProcessRuntime.h | 87 | ||||
-rw-r--r-- | ipc/mscom/ProfilerMarkers.cpp | 236 | ||||
-rw-r--r-- | ipc/mscom/ProfilerMarkers.h | 18 | ||||
-rw-r--r-- | ipc/mscom/Utils.cpp | 324 | ||||
-rw-r--r-- | ipc/mscom/Utils.h | 123 | ||||
-rw-r--r-- | ipc/mscom/moz.build | 42 | ||||
-rw-r--r-- | ipc/mscom/mozglue/ProcessRuntimeShared.cpp | 31 | ||||
-rw-r--r-- | ipc/mscom/mozglue/ProcessRuntimeShared.h | 55 | ||||
-rw-r--r-- | ipc/mscom/mozglue/moz.build | 15 |
18 files changed, 2270 insertions, 0 deletions
diff --git a/ipc/mscom/Aggregation.h b/ipc/mscom/Aggregation.h new file mode 100644 index 0000000000..ca171e39cc --- /dev/null +++ b/ipc/mscom/Aggregation.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_Aggregation_h +#define mozilla_mscom_Aggregation_h + +#include "mozilla/Attributes.h" + +#include <stddef.h> +#include <unknwn.h> + +namespace mozilla { +namespace mscom { + +/** + * This is used for stabilizing a COM object's reference count during + * construction when that object aggregates other objects. Since the aggregated + * object(s) may AddRef() or Release(), we need to artifically boost the + * refcount to prevent premature destruction. Note that we increment/decrement + * instead of AddRef()/Release() in this class because we want to adjust the + * refcount without causing any other side effects (like object destruction). + */ +template <typename RefCntT> +class MOZ_RAII StabilizedRefCount { + public: + explicit StabilizedRefCount(RefCntT& aRefCnt) : mRefCnt(aRefCnt) { + ++aRefCnt; + } + + ~StabilizedRefCount() { --mRefCnt; } + + StabilizedRefCount(const StabilizedRefCount&) = delete; + StabilizedRefCount(StabilizedRefCount&&) = delete; + StabilizedRefCount& operator=(const StabilizedRefCount&) = delete; + StabilizedRefCount& operator=(StabilizedRefCount&&) = delete; + + private: + RefCntT& mRefCnt; +}; + +namespace detail { + +template <typename T> +class InternalUnknown : public IUnknown { + public: + STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override { + return This()->InternalQueryInterface(aIid, aOutInterface); + } + + STDMETHODIMP_(ULONG) AddRef() override { return This()->InternalAddRef(); } + + STDMETHODIMP_(ULONG) Release() override { return This()->InternalRelease(); } + + private: + T* This() { + return reinterpret_cast<T*>(reinterpret_cast<char*>(this) - + offsetof(T, mInternalUnknown)); + } +}; + +} // namespace detail +} // namespace mscom +} // namespace mozilla + +#define DECLARE_AGGREGATABLE(Type) \ + public: \ + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override { \ + return mOuter->QueryInterface(riid, ppv); \ + } \ + STDMETHODIMP_(ULONG) AddRef() override { return mOuter->AddRef(); } \ + STDMETHODIMP_(ULONG) Release() override { return mOuter->Release(); } \ + \ + protected: \ + STDMETHODIMP InternalQueryInterface(REFIID riid, void** ppv); \ + STDMETHODIMP_(ULONG) InternalAddRef(); \ + STDMETHODIMP_(ULONG) InternalRelease(); \ + friend class mozilla::mscom::detail::InternalUnknown<Type>; \ + mozilla::mscom::detail::InternalUnknown<Type> mInternalUnknown + +#endif // mozilla_mscom_Aggregation_h diff --git a/ipc/mscom/AgileReference.cpp b/ipc/mscom/AgileReference.cpp new file mode 100644 index 0000000000..76fe773b0a --- /dev/null +++ b/ipc/mscom/AgileReference.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mscom/AgileReference.h" + +#include <utility> +#include "mozilla/Assertions.h" +#include "mozilla/mscom/Utils.h" + +#if defined(__MINGW32__) + +// Declarations from Windows SDK specific to Windows 8.1 + +enum AgileReferenceOptions { + AGILEREFERENCE_DEFAULT = 0, + AGILEREFERENCE_DELAYEDMARSHAL = 1, +}; + +HRESULT WINAPI RoGetAgileReference(AgileReferenceOptions options, REFIID riid, + IUnknown* pUnk, + IAgileReference** ppAgileReference); + +// Unfortunately, at time of writing, MinGW doesn't know how to statically link +// to RoGetAgileReference. On these builds only, we substitute a runtime-linked +// function pointer. + +# include "mozilla/DynamicallyLinkedFunctionPtr.h" + +static const mozilla::StaticDynamicallyLinkedFunctionPtr< + decltype(&::RoGetAgileReference)> + pRoGetAgileReference(L"ole32.dll", "RoGetAgileReference"); + +# define RoGetAgileReference pRoGetAgileReference + +#endif // defined(__MINGW32__) + +namespace mozilla::mscom::detail { + +HRESULT AgileReference_CreateImpl(RefPtr<IAgileReference>& aRefPtr, REFIID riid, + IUnknown* aObject) { + MOZ_ASSERT(aObject); + MOZ_ASSERT(IsCOMInitializedOnCurrentThread()); + return ::RoGetAgileReference(AGILEREFERENCE_DEFAULT, riid, aObject, + getter_AddRefs(aRefPtr)); +} + +HRESULT AgileReference_ResolveImpl(RefPtr<IAgileReference> const& aRefPtr, + REFIID riid, void** aOutInterface) { + MOZ_ASSERT(aRefPtr); + MOZ_ASSERT(aOutInterface); + MOZ_ASSERT(IsCOMInitializedOnCurrentThread()); + + if (!aRefPtr || !aOutInterface) { + return E_INVALIDARG; + } + + *aOutInterface = nullptr; + return aRefPtr->Resolve(riid, aOutInterface); +} + +} // namespace mozilla::mscom::detail diff --git a/ipc/mscom/AgileReference.h b/ipc/mscom/AgileReference.h new file mode 100644 index 0000000000..384c391ac7 --- /dev/null +++ b/ipc/mscom/AgileReference.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_AgileReference_h +#define mozilla_mscom_AgileReference_h + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" +#include "mozilla/Unused.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" + +#include <objidl.h> + +namespace mozilla::mscom { + +namespace detail { +// Detemplatized implementation details of `AgileReference`. +HRESULT AgileReference_CreateImpl(RefPtr<IAgileReference>&, REFIID, IUnknown*); +HRESULT AgileReference_ResolveImpl(RefPtr<IAgileReference> const&, REFIID, + void**); +} // namespace detail + +/** + * This class encapsulates an "agile reference". These are references that allow + * you to pass COM interfaces between apartments. When you have an interface + * that you would like to pass between apartments, you wrap that interface in an + * AgileReference and pass that instead. Then you can "unwrap" the interface by + * calling Resolve(), which will return a proxy object implementing the same + * interface. + * + * Sample usage: + * + * ``` + * // From a non-main thread, where `foo` is an `IFoo*` or `RefPtr<IFoo>`: + * auto myAgileRef = AgileReference(foo); + * NS_DispatchToMainThread([mar = std::move(myAgileRef)] { + * RefPtr<IFoo> foo = mar.Resolve(); + * // Now methods may be invoked on `foo` + * }); + * ``` + */ +template <typename InterfaceT> +class AgileReference final { + static_assert( + std::is_base_of_v<IUnknown, InterfaceT>, + "template parameter of AgileReference must be a COM interface type"); + + public: + AgileReference() = default; + ~AgileReference() = default; + + AgileReference(const AgileReference& aOther) = default; + AgileReference(AgileReference&& aOther) noexcept = default; + + AgileReference& operator=(const AgileReference& aOther) = default; + AgileReference& operator=(AgileReference&& aOther) noexcept = default; + + AgileReference& operator=(std::nullptr_t) { + mAgileRef = nullptr; + return *this; + } + + // Create a new AgileReference from an existing COM object. + // + // These constructors do not provide the HRESULT on failure. If that's + // desired, use `AgileReference::Create()`, below. + explicit AgileReference(InterfaceT* aObject) { + HRESULT const hr = detail::AgileReference_CreateImpl( + mAgileRef, __uuidof(InterfaceT), aObject); + Unused << NS_WARN_IF(FAILED(hr)); + } + explicit AgileReference(RefPtr<InterfaceT> const& aObject) + : AgileReference(aObject.get()) {} + + // Create a new AgileReference from an existing COM object, or alternatively, + // return the HRESULT explaining why one couldn't be created. + // + // A convenience wrapper `MakeAgileReference()` which infers `InterfaceT` from + // the RefPtr's concrete type is provided below. + static Result<AgileReference<InterfaceT>, HRESULT> Create( + RefPtr<InterfaceT> const& aObject) { + AgileReference ret; + HRESULT const hr = detail::AgileReference_CreateImpl( + ret.mAgileRef, __uuidof(InterfaceT), aObject.get()); + if (FAILED(hr)) { + return Err(hr); + } + return ret; + } + + explicit operator bool() const { return !!mAgileRef; } + + // Common case: resolve directly to the originally-specified interface-type. + RefPtr<InterfaceT> Resolve() const { + auto res = ResolveAs<InterfaceT>(); + if (res.isErr()) return nullptr; + return res.unwrap(); + } + + // Uncommon cases: resolve directly to a different interface type, and/or + // provide IAgileReference::Resolve()'s HRESULT. + // + // When used in other COM apartments, `IAgileInterface::Resolve()` returns a + // proxy object which (at time of writing) is not documented to provide any + // interface other than the one for which it was instantiated. (Calling + // `QueryInterface` _might_ work, but isn't explicitly guaranteed.) + // + template <typename OtherInterface = InterfaceT> + Result<RefPtr<OtherInterface>, HRESULT> ResolveAs() const { + RefPtr<OtherInterface> p; + auto const hr = ResolveRaw(__uuidof(OtherInterface), getter_AddRefs(p)); + if (FAILED(hr)) { + return Err(hr); + } + return p; + } + + // Raw version of Resolve/ResolveAs. Rarely, if ever, preferable to the + // statically-typed versions. + HRESULT ResolveRaw(REFIID aIid, void** aOutInterface) const { + return detail::AgileReference_ResolveImpl(mAgileRef, aIid, aOutInterface); + } + + private: + RefPtr<IAgileReference> mAgileRef; +}; + +// Attempt to create an AgileReference from a refcounted interface pointer, +// providing the HRESULT as a secondary return-value. +template <typename InterfaceT> +inline Result<AgileReference<InterfaceT>, HRESULT> MakeAgileReference( + RefPtr<InterfaceT> const& aObj) { + return AgileReference<InterfaceT>::Create(aObj); +} + +} // namespace mozilla::mscom + +#endif // mozilla_mscom_AgileReference_h diff --git a/ipc/mscom/ApartmentRegion.h b/ipc/mscom/ApartmentRegion.h new file mode 100644 index 0000000000..ea942b95b5 --- /dev/null +++ b/ipc/mscom/ApartmentRegion.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_ApartmentRegion_h +#define mozilla_mscom_ApartmentRegion_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/mscom/COMWrappers.h" + +namespace mozilla::mscom { + +// This runtime-dynamic apartment class is used in ProcessRuntime.cpp, to +// initialize the process's main thread. Do not use it in new contexts if at all +// possible; instead, prefer ApartmentRegionT, below. +// +// For backwards compatibility, this class does not yet automatically disable +// OLE1/DDE, although there is believed to be no code relying on it. +// +// (TODO: phase out all uses of CoInitialize without `COINIT_DISABLE_OLE1DDE`?) +class MOZ_NON_TEMPORARY_CLASS ApartmentRegion final { + public: + /** + * This constructor is to be used when we want to instantiate the object but + * we do not yet know which type of apartment we want. Call Init() to + * complete initialization. + */ + constexpr ApartmentRegion() : mInitResult(CO_E_NOTINITIALIZED) {} + + explicit ApartmentRegion(COINIT aAptType) + : mInitResult(wrapped::CoInitializeEx(nullptr, aAptType)) { + // If this fires then we're probably mixing apartments on the same thread + MOZ_ASSERT(IsValid()); + } + + ~ApartmentRegion() { + if (IsValid()) { + wrapped::CoUninitialize(); + } + } + + explicit operator bool() const { return IsValid(); } + + bool IsValidOutermost() const { return mInitResult == S_OK; } + + bool IsValid() const { return SUCCEEDED(mInitResult); } + + bool Init(COINIT aAptType) { + MOZ_ASSERT(mInitResult == CO_E_NOTINITIALIZED); + mInitResult = wrapped::CoInitializeEx(nullptr, aAptType); + MOZ_ASSERT(IsValid()); + return IsValid(); + } + + HRESULT GetHResult() const { return mInitResult; } + + ApartmentRegion(const ApartmentRegion&) = delete; + ApartmentRegion& operator=(const ApartmentRegion&) = delete; + ApartmentRegion(ApartmentRegion&&) = delete; + ApartmentRegion& operator=(ApartmentRegion&&) = delete; + + private: + HRESULT mInitResult; +}; + +template <COINIT AptType, bool UseOLE1 = false> +class MOZ_NON_TEMPORARY_CLASS ApartmentRegionT final { + static COINIT ActualType() { + static_assert( + !((AptType & COINIT_DISABLE_OLE1DDE) == 0 && UseOLE1), + "only one of `UseOLE1` and `COINIT_DISABLE_OLE1DDE` permitted"); + if (UseOLE1) return AptType; + return static_cast<COINIT>(AptType | COINIT_DISABLE_OLE1DDE); + } + + public: + ApartmentRegionT() : mAptRgn(ActualType()) {} + + ~ApartmentRegionT() = default; + + explicit operator bool() const { return mAptRgn.IsValid(); } + + bool IsValidOutermost() const { return mAptRgn.IsValidOutermost(); } + + bool IsValid() const { return mAptRgn.IsValid(); } + + HRESULT GetHResult() const { return mAptRgn.GetHResult(); } + + ApartmentRegionT(const ApartmentRegionT&) = delete; + ApartmentRegionT& operator=(const ApartmentRegionT&) = delete; + ApartmentRegionT(ApartmentRegionT&&) = delete; + ApartmentRegionT& operator=(ApartmentRegionT&&) = delete; + + private: + ApartmentRegion mAptRgn; +}; + +using STARegion = ApartmentRegionT<COINIT_APARTMENTTHREADED>; +using MTARegion = ApartmentRegionT<COINIT_MULTITHREADED>; + +} // namespace mozilla::mscom + +#endif // mozilla_mscom_ApartmentRegion_h diff --git a/ipc/mscom/COMWrappers.cpp b/ipc/mscom/COMWrappers.cpp new file mode 100644 index 0000000000..1bc48a4927 --- /dev/null +++ b/ipc/mscom/COMWrappers.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mscom/COMWrappers.h" + +#include <objbase.h> + +#include "mozilla/Assertions.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" + +namespace mozilla::mscom::wrapped { + +HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit) { + static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoInitializeEx)> + pCoInitializeEx(L"combase.dll", "CoInitializeEx"); + if (!pCoInitializeEx) { + return ::CoInitializeEx(pvReserved, dwCoInit); + } + + return pCoInitializeEx(pvReserved, dwCoInit); +} + +void CoUninitialize() { + static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoUninitialize)> + pCoUninitialize(L"combase.dll", "CoUninitialize"); + if (!pCoUninitialize) { + return ::CoUninitialize(); + } + + return pCoUninitialize(); +} + +HRESULT CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie) { + static const StaticDynamicallyLinkedFunctionPtr< + decltype(&::CoIncrementMTAUsage)> + pCoIncrementMTAUsage(L"combase.dll", "CoIncrementMTAUsage"); + // This API is only available beginning with Windows 8. + if (!pCoIncrementMTAUsage) { + return E_NOTIMPL; + } + + HRESULT hr = pCoIncrementMTAUsage(pCookie); + MOZ_ASSERT(SUCCEEDED(hr)); + return hr; +} + +HRESULT CoGetApartmentType(APTTYPE* pAptType, APTTYPEQUALIFIER* pAptQualifier) { + static const StaticDynamicallyLinkedFunctionPtr< + decltype(&::CoGetApartmentType)> + pCoGetApartmentType(L"combase.dll", "CoGetApartmentType"); + if (!pCoGetApartmentType) { + return ::CoGetApartmentType(pAptType, pAptQualifier); + } + + return pCoGetApartmentType(pAptType, pAptQualifier); +} + +HRESULT CoInitializeSecurity(PSECURITY_DESCRIPTOR pSecDesc, LONG cAuthSvc, + SOLE_AUTHENTICATION_SERVICE* asAuthSvc, + void* pReserved1, DWORD dwAuthnLevel, + DWORD dwImpLevel, void* pAuthList, + DWORD dwCapabilities, void* pReserved3) { + static const StaticDynamicallyLinkedFunctionPtr< + decltype(&::CoInitializeSecurity)> + pCoInitializeSecurity(L"combase.dll", "CoInitializeSecurity"); + if (!pCoInitializeSecurity) { + return ::CoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1, + dwAuthnLevel, dwImpLevel, pAuthList, + dwCapabilities, pReserved3); + } + + return pCoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1, + dwAuthnLevel, dwImpLevel, pAuthList, + dwCapabilities, pReserved3); +} + +HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, + DWORD dwClsContext, REFIID riid, LPVOID* ppv) { + static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoCreateInstance)> + pCoCreateInstance(L"combase.dll", "CoCreateInstance"); + if (!pCoCreateInstance) { + return ::CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv); + } + + return pCoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv); +} + +HRESULT CoCreateGuid(GUID* pguid) { + static const StaticDynamicallyLinkedFunctionPtr<decltype(&::CoCreateGuid)> + pCoCreateGuid(L"combase.dll", "CoCreateGuid"); + if (!pCoCreateGuid) { + return ::CoCreateGuid(pguid); + } + + return pCoCreateGuid(pguid); +} + +} // namespace mozilla::mscom::wrapped diff --git a/ipc/mscom/COMWrappers.h b/ipc/mscom/COMWrappers.h new file mode 100644 index 0000000000..4550d4d347 --- /dev/null +++ b/ipc/mscom/COMWrappers.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_COMWrappers_h +#define mozilla_mscom_COMWrappers_h + +#include <objbase.h> + +// A set of wrapped COM functions, so that we can dynamically link to the +// functions in combase.dll on win8+. This prevents ole32.dll and many other +// DLLs loading, which are not required when we have win32k locked down. +namespace mozilla::mscom::wrapped { + +HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit); + +void CoUninitialize(); + +HRESULT CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie); + +HRESULT CoGetApartmentType(APTTYPE* pAptType, APTTYPEQUALIFIER* pAptQualifier); + +HRESULT CoInitializeSecurity(PSECURITY_DESCRIPTOR pSecDesc, LONG cAuthSvc, + SOLE_AUTHENTICATION_SERVICE* asAuthSvc, + void* pReserved1, DWORD dwAuthnLevel, + DWORD dwImpLevel, void* pAuthList, + DWORD dwCapabilities, void* pReserved3); + +HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, + DWORD dwClsContext, REFIID riid, LPVOID* ppv); + +HRESULT CoCreateGuid(GUID* pguid); + +} // namespace mozilla::mscom::wrapped + +#endif // mozilla_mscom_COMWrappers_h diff --git a/ipc/mscom/EnsureMTA.cpp b/ipc/mscom/EnsureMTA.cpp new file mode 100644 index 0000000000..49d8446fa2 --- /dev/null +++ b/ipc/mscom/EnsureMTA.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mscom/EnsureMTA.h" + +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/mscom/COMWrappers.h" +#include "mozilla/mscom/ProcessRuntime.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticLocalPtr.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" + +#include "private/pprthred.h" + +namespace { + +class EnterMTARunnable : public mozilla::Runnable { + public: + EnterMTARunnable() : mozilla::Runnable("EnterMTARunnable") {} + NS_IMETHOD Run() override { + mozilla::DebugOnly<HRESULT> hr = + mozilla::mscom::wrapped::CoInitializeEx(nullptr, COINIT_MULTITHREADED); + MOZ_ASSERT(SUCCEEDED(hr)); + return NS_OK; + } +}; + +class BackgroundMTAData { + public: + BackgroundMTAData() { + nsCOMPtr<nsIRunnable> runnable = new EnterMTARunnable(); + mozilla::DebugOnly<nsresult> rv = NS_NewNamedThread( + "COM MTA", getter_AddRefs(mThread), runnable.forget()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_NewNamedThread failed"); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + ~BackgroundMTAData() { + if (mThread) { + mThread->Dispatch( + NS_NewRunnableFunction("BackgroundMTAData::~BackgroundMTAData", + &mozilla::mscom::wrapped::CoUninitialize), + NS_DISPATCH_NORMAL); + mThread->Shutdown(); + } + } + + nsCOMPtr<nsIThread> GetThread() const { return mThread; } + + private: + nsCOMPtr<nsIThread> mThread; +}; + +} // anonymous namespace + +namespace mozilla { +namespace mscom { + +EnsureMTA::EnsureMTA() { + MOZ_ASSERT(NS_IsMainThread()); + + // It is possible that we're running so early that we might need to start + // the thread manager ourselves. We do this here to guarantee that we have + // the ability to start the persistent MTA thread at any moment beyond this + // point. + nsresult rv = nsThreadManager::get().Init(); + // We intentionally don't check rv unless we need it when + // CoIncremementMTAUsage is unavailable. + + // Calling this function initializes the MTA without needing to explicitly + // create a thread and call CoInitializeEx to do it. + // We don't retain the cookie because once we've incremented the MTA, we + // leave it that way for the lifetime of the process. + CO_MTA_USAGE_COOKIE mtaCookie = nullptr; + HRESULT hr = wrapped::CoIncrementMTAUsage(&mtaCookie); + if (SUCCEEDED(hr)) { + if (NS_SUCCEEDED(rv)) { + // Start the persistent MTA thread (mostly) asynchronously. + Unused << GetPersistentMTAThread(); + } + + return; + } + + // In the fallback case, we simply initialize our persistent MTA thread. + + // Make sure thread manager init succeeded before trying to initialize the + // persistent MTA thread. + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return; + } + + // Before proceeding any further, pump a runnable through the persistent MTA + // thread to ensure that it is up and running and has finished initializing + // the multi-threaded apartment. + nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction( + "EnsureMTA::EnsureMTA()", + []() { MOZ_RELEASE_ASSERT(IsCurrentThreadExplicitMTA()); })); + SyncDispatchToPersistentThread(runnable); +} + +/* static */ +nsCOMPtr<nsIThread> EnsureMTA::GetPersistentMTAThread() { + static StaticLocalAutoPtr<BackgroundMTAData> sMTAData( + []() -> BackgroundMTAData* { + BackgroundMTAData* bgData = new BackgroundMTAData(); + + auto setClearOnShutdown = [ptr = &sMTAData]() -> void { + ClearOnShutdown(ptr, ShutdownPhase::XPCOMShutdownThreads); + }; + + if (NS_IsMainThread()) { + setClearOnShutdown(); + return bgData; + } + + SchedulerGroup::Dispatch( + NS_NewRunnableFunction("mscom::EnsureMTA::GetPersistentMTAThread", + std::move(setClearOnShutdown))); + + return bgData; + }()); + + MOZ_ASSERT(sMTAData); + + return sMTAData->GetThread(); +} + +/* static */ +void EnsureMTA::SyncDispatchToPersistentThread(nsIRunnable* aRunnable) { + nsCOMPtr<nsIThread> thread(GetPersistentMTAThread()); + MOZ_ASSERT(thread); + if (!thread) { + return; + } + + // Note that, due to APC dispatch, we might reenter this function while we + // wait on this event. We therefore need a unique event object for each + // entry into this function. If perf becomes an issue then we will want to + // maintain an array of events where the Nth event is unique to the Nth + // reentry. + nsAutoHandle event(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!event) { + return; + } + + HANDLE eventHandle = event.get(); + auto eventSetter = [&aRunnable, eventHandle]() -> void { + aRunnable->Run(); + ::SetEvent(eventHandle); + }; + + nsresult rv = thread->Dispatch( + NS_NewRunnableFunction("mscom::EnsureMTA::SyncDispatchToPersistentThread", + std::move(eventSetter)), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return; + } + + AUTO_PROFILER_THREAD_SLEEP; + DWORD waitResult; + while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, FALSE)) == + WAIT_IO_COMPLETION) { + } + MOZ_ASSERT(waitResult == WAIT_OBJECT_0); +} + +/** + * While this function currently appears to be redundant, it may become more + * sophisticated in the future. For example, we could optionally dispatch to an + * MTA context if we wanted to utilize the MTA thread pool. + */ +/* static */ +void EnsureMTA::SyncDispatch(nsCOMPtr<nsIRunnable>&& aRunnable, Option aOpt) { + SyncDispatchToPersistentThread(aRunnable); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/EnsureMTA.h b/ipc/mscom/EnsureMTA.h new file mode 100644 index 0000000000..662192c476 --- /dev/null +++ b/ipc/mscom/EnsureMTA.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_EnsureMTA_h +#define mozilla_mscom_EnsureMTA_h + +#include "MainThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Unused.h" +#include "mozilla/mscom/AgileReference.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nsWindowsHelpers.h" + +#include <windows.h> + +namespace mozilla { +namespace mscom { +namespace detail { + +// Forward declarations +template <typename T> +struct MTADelete; + +template <typename T> +struct MTARelease; + +template <typename T> +struct MTAReleaseInChildProcess; + +struct PreservedStreamDeleter; + +} // namespace detail + +class ProcessRuntime; + +// This class is OK to use as a temporary on the stack. +class MOZ_STACK_CLASS EnsureMTA final { + public: + enum class Option { + Default, + // Forcibly dispatch to the thread returned by GetPersistentMTAThread(), + // even if the current thread is already inside a MTA. + ForceDispatchToPersistentThread, + }; + + /** + * Synchronously run |aClosure| on a thread living in the COM multithreaded + * apartment. If the current thread lives inside the COM MTA, then it runs + * |aClosure| immediately unless |aOpt| == + * Option::ForceDispatchToPersistentThread. + */ + template <typename FuncT> + explicit EnsureMTA(FuncT&& aClosure, Option aOpt = Option::Default) { + if (aOpt != Option::ForceDispatchToPersistentThread && + IsCurrentThreadMTA()) { + // We're already on the MTA, we can run aClosure directly + aClosure(); + return; + } + + // In this case we need to run aClosure on a background thread in the MTA + nsCOMPtr<nsIRunnable> runnable( + NS_NewRunnableFunction("EnsureMTA::EnsureMTA", std::move(aClosure))); + SyncDispatch(std::move(runnable), aOpt); + } + + private: + static nsCOMPtr<nsIThread> GetPersistentMTAThread(); + + static void SyncDispatch(nsCOMPtr<nsIRunnable>&& aRunnable, Option aOpt); + static void SyncDispatchToPersistentThread(nsIRunnable* aRunnable); + + // The following function is private in order to force any consumers to be + // declared as friends of EnsureMTA. The intention is to prevent + // AsyncOperation from becoming some kind of free-for-all mechanism for + // asynchronously executing work on a background thread. + template <typename FuncT> + static void AsyncOperation(FuncT&& aClosure) { + if (IsCurrentThreadMTA()) { + aClosure(); + return; + } + + nsCOMPtr<nsIThread> thread(GetPersistentMTAThread()); + MOZ_ASSERT(thread); + if (!thread) { + return; + } + + DebugOnly<nsresult> rv = thread->Dispatch( + NS_NewRunnableFunction("mscom::EnsureMTA::AsyncOperation", + std::move(aClosure)), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + /** + * This constructor just ensures that the MTA is up and running. This should + * only be called by ProcessRuntime. + */ + EnsureMTA(); + + friend class mozilla::mscom::ProcessRuntime; + + template <typename T> + friend struct mozilla::mscom::detail::MTADelete; + + template <typename T> + friend struct mozilla::mscom::detail::MTARelease; + + template <typename T> + friend struct mozilla::mscom::detail::MTAReleaseInChildProcess; + + friend struct mozilla::mscom::detail::PreservedStreamDeleter; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_EnsureMTA_h diff --git a/ipc/mscom/ProcessRuntime.cpp b/ipc/mscom/ProcessRuntime.cpp new file mode 100644 index 0000000000..ae4e32247c --- /dev/null +++ b/ipc/mscom/ProcessRuntime.cpp @@ -0,0 +1,486 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mscom/ProcessRuntime.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/mscom/COMWrappers.h" +#include "mozilla/mscom/ProcessRuntimeShared.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" +#include "mozilla/WindowsProcessMitigations.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "mozilla/mscom/EnsureMTA.h" +# if defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +# endif // defined(MOZ_SANDBOX) +#endif // defined(MOZILLA_INTERNAL_API) + +#include <accctrl.h> +#include <aclapi.h> +#include <objbase.h> +#include <objidl.h> + +// This API from oleaut32.dll is not declared in Windows SDK headers +extern "C" void __cdecl SetOaNoCache(void); + +using namespace mozilla::mscom::detail; + +namespace mozilla { +namespace mscom { + +#if defined(MOZILLA_INTERNAL_API) +ProcessRuntime* ProcessRuntime::sInstance = nullptr; + +ProcessRuntime::ProcessRuntime() : ProcessRuntime(XRE_GetProcessType()) {} + +ProcessRuntime::ProcessRuntime(const GeckoProcessType aProcessType) + : ProcessRuntime(aProcessType == GeckoProcessType_Default + ? ProcessCategory::GeckoBrowserParent + : ProcessCategory::GeckoChild) {} +#endif // defined(MOZILLA_INTERNAL_API) + +ProcessRuntime::ProcessRuntime(const ProcessCategory aProcessCategory) + : mInitResult(CO_E_NOTINITIALIZED), mProcessCategory(aProcessCategory) { +#if defined(MOZILLA_INTERNAL_API) + MOZ_DIAGNOSTIC_ASSERT(!sInstance); + sInstance = this; + + EnsureMTA(); + /** + * From this point forward, all threads in this process are implicitly + * members of the multi-threaded apartment, with the following exceptions: + * 1. If any Win32 GUI APIs were called on the current thread prior to + * executing this constructor, then this thread has already been implicitly + * initialized as the process's main STA thread; or + * 2. A thread explicitly and successfully calls CoInitialize(Ex) to specify + * otherwise. + */ + + const bool isCurThreadImplicitMTA = IsCurrentThreadImplicitMTA(); + // We only assert that the implicit MTA precondition holds when not running + // as the Gecko parent process. + MOZ_DIAGNOSTIC_ASSERT(aProcessCategory == + ProcessCategory::GeckoBrowserParent || + isCurThreadImplicitMTA); + +# if defined(MOZ_SANDBOX) + const bool isLockedDownChildProcess = + mProcessCategory == ProcessCategory::GeckoChild && IsWin32kLockedDown(); + // If our process is running under Win32k lockdown, we cannot initialize + // COM with a single-threaded apartment. This is because STAs create a hidden + // window, which implicitly requires user32 and Win32k, which are blocked. + // Instead we start the multi-threaded apartment and conduct our process-wide + // COM initialization there. + if (isLockedDownChildProcess) { + // Make sure we're still running with the sandbox's privileged impersonation + // token. + HANDLE rawCurThreadImpToken; + if (!::OpenThreadToken(::GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, + FALSE, &rawCurThreadImpToken)) { + mInitResult = HRESULT_FROM_WIN32(::GetLastError()); + return; + } + nsAutoHandle curThreadImpToken(rawCurThreadImpToken); + + // Ensure that our current token is still an impersonation token (ie, we + // have not yet called RevertToSelf() on this thread). + DWORD len; + TOKEN_TYPE tokenType; + MOZ_RELEASE_ASSERT( + ::GetTokenInformation(rawCurThreadImpToken, TokenType, &tokenType, + sizeof(tokenType), &len) && + len == sizeof(tokenType) && tokenType == TokenImpersonation); + + // Ideally we want our current thread to be running implicitly inside the + // MTA, but if for some wacky reason we did not end up with that, we may + // compensate by completing initialization via EnsureMTA's persistent + // thread. + if (!isCurThreadImplicitMTA) { + InitUsingPersistentMTAThread(curThreadImpToken); + return; + } + } +# endif // defined(MOZ_SANDBOX) +#endif // defined(MOZILLA_INTERNAL_API) + + mAptRegion.Init(GetDesiredApartmentType(mProcessCategory)); + + // It can happen that we are not the outermost COM initialization on this + // thread. In fact it should regularly be the case that the outermost + // initialization occurs from outside of XUL, before we show the skeleton UI, + // at which point we still need to run some things here from within XUL. + if (!mAptRegion.IsValidOutermost()) { + mInitResult = mAptRegion.GetHResult(); +#if defined(MOZILLA_INTERNAL_API) + MOZ_ASSERT(mProcessCategory == ProcessCategory::GeckoBrowserParent); + if (mProcessCategory != ProcessCategory::GeckoBrowserParent) { + // This is unexpected unless we're GeckoBrowserParent + return; + } + + ProcessInitLock lock; + + // Is another instance of ProcessRuntime responsible for the outer + // initialization? + const bool prevInit = + lock.GetInitState() == ProcessInitState::FullyInitialized; + MOZ_ASSERT(prevInit); + if (prevInit) { + PostInit(); + } +#endif // defined(MOZILLA_INTERNAL_API) + return; + } + + InitInsideApartment(); + if (FAILED(mInitResult)) { + return; + } + +#if defined(MOZILLA_INTERNAL_API) +# if defined(MOZ_SANDBOX) + if (isLockedDownChildProcess) { + // In locked-down child processes, defer PostInit until priv drop + SandboxTarget::Instance()->RegisterSandboxStartCallback([self = this]() { + // Ensure that we're still live and the init was successful before + // calling PostInit() + if (self == sInstance && SUCCEEDED(self->mInitResult)) { + PostInit(); + } + }); + return; + } +# endif // defined(MOZ_SANDBOX) + + PostInit(); +#endif // defined(MOZILLA_INTERNAL_API) +} + +#if defined(MOZILLA_INTERNAL_API) +ProcessRuntime::~ProcessRuntime() { + MOZ_DIAGNOSTIC_ASSERT(sInstance == this); + sInstance = nullptr; +} + +# if defined(MOZ_SANDBOX) +void ProcessRuntime::InitUsingPersistentMTAThread( + const nsAutoHandle& aCurThreadToken) { + // Create an impersonation token based on the current thread's token + HANDLE rawMtaThreadImpToken = nullptr; + if (!::DuplicateToken(aCurThreadToken, SecurityImpersonation, + &rawMtaThreadImpToken)) { + mInitResult = HRESULT_FROM_WIN32(::GetLastError()); + return; + } + nsAutoHandle mtaThreadImpToken(rawMtaThreadImpToken); + + // Impersonate and initialize. + bool tokenSet = false; + EnsureMTA( + [this, rawMtaThreadImpToken, &tokenSet]() -> void { + if (!::SetThreadToken(nullptr, rawMtaThreadImpToken)) { + mInitResult = HRESULT_FROM_WIN32(::GetLastError()); + return; + } + + tokenSet = true; + InitInsideApartment(); + }, + EnsureMTA::Option::ForceDispatchToPersistentThread); + + if (!tokenSet) { + return; + } + + SandboxTarget::Instance()->RegisterSandboxStartCallback( + [self = this]() -> void { + EnsureMTA( + []() -> void { + // This is a security risk if it fails, so we release assert + MOZ_RELEASE_ASSERT(::RevertToSelf(), + "mscom::ProcessRuntime RevertToSelf failed"); + }, + EnsureMTA::Option::ForceDispatchToPersistentThread); + + // Ensure that we're still live and the init was successful before + // calling PostInit() + if (self == sInstance && SUCCEEDED(self->mInitResult)) { + PostInit(); + } + }); +} +# endif // defined(MOZ_SANDBOX) +#endif // defined(MOZILLA_INTERNAL_API) + +/* static */ +COINIT ProcessRuntime::GetDesiredApartmentType( + const ProcessRuntime::ProcessCategory aProcessCategory) { + switch (aProcessCategory) { + case ProcessCategory::GeckoBrowserParent: + return COINIT_APARTMENTTHREADED; + case ProcessCategory::GeckoChild: + if (!IsWin32kLockedDown()) { + // If Win32k is not locked down then we probably still need STA. + // We disable DDE since that is not usable from child processes. + return static_cast<COINIT>(COINIT_APARTMENTTHREADED | + COINIT_DISABLE_OLE1DDE); + } + + [[fallthrough]]; + default: + return COINIT_MULTITHREADED; + } +} + +void ProcessRuntime::InitInsideApartment() { + ProcessInitLock lock; + const ProcessInitState prevInitState = lock.GetInitState(); + if (prevInitState == ProcessInitState::FullyInitialized) { + // COM has already been initialized by a previous ProcessRuntime instance + mInitResult = S_OK; + return; + } + + if (prevInitState < ProcessInitState::PartialSecurityInitialized) { + // We are required to initialize security prior to configuring global + // options. + mInitResult = InitializeSecurity(mProcessCategory); + MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(mInitResult)); + + // Even though this isn't great, we should try to proceed even when + // CoInitializeSecurity has previously been called: the additional settings + // we want to change are important enough that we don't want to skip them. + if (FAILED(mInitResult) && mInitResult != RPC_E_TOO_LATE) { + return; + } + + lock.SetInitState(ProcessInitState::PartialSecurityInitialized); + } + + if (prevInitState < ProcessInitState::PartialGlobalOptions) { + RefPtr<IGlobalOptions> globalOpts; + mInitResult = wrapped::CoCreateInstance( + CLSID_GlobalOptions, nullptr, CLSCTX_INPROC_SERVER, IID_IGlobalOptions, + getter_AddRefs(globalOpts)); + MOZ_ASSERT(SUCCEEDED(mInitResult)); + if (FAILED(mInitResult)) { + return; + } + + // Disable COM's catch-all exception handler + mInitResult = globalOpts->Set(COMGLB_EXCEPTION_HANDLING, + COMGLB_EXCEPTION_DONOT_HANDLE_ANY); + MOZ_ASSERT(SUCCEEDED(mInitResult)); + if (FAILED(mInitResult)) { + return; + } + + lock.SetInitState(ProcessInitState::PartialGlobalOptions); + } + + // Disable the BSTR cache (as it never invalidates, thus leaking memory) + // (This function is itself idempotent, so we do not concern ourselves with + // tracking whether or not we've already called it.) + ::SetOaNoCache(); + + lock.SetInitState(ProcessInitState::FullyInitialized); +} + +#if defined(MOZILLA_INTERNAL_API) +/** + * Guaranteed to run *after* the COM (and possible sandboxing) initialization + * has successfully completed and stabilized. This method MUST BE IDEMPOTENT! + */ +/* static */ void ProcessRuntime::PostInit() { + // Currently "roughed-in" but unused. +} +#endif // defined(MOZILLA_INTERNAL_API) + +/* static */ +DWORD +ProcessRuntime::GetClientThreadId() { + DWORD callerTid; + HRESULT hr = ::CoGetCallerTID(&callerTid); + // Don't return callerTid unless the call succeeded and returned S_FALSE, + // indicating that the caller originates from a different process. + if (hr != S_FALSE) { + return 0; + } + + return callerTid; +} + +/* static */ +HRESULT +ProcessRuntime::InitializeSecurity(const ProcessCategory aProcessCategory) { + HANDLE rawToken = nullptr; + BOOL ok = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawToken); + if (!ok) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + nsAutoHandle token(rawToken); + + DWORD len = 0; + ok = ::GetTokenInformation(token, TokenUser, nullptr, len, &len); + DWORD win32Error = ::GetLastError(); + if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) { + return HRESULT_FROM_WIN32(win32Error); + } + + auto tokenUserBuf = MakeUnique<BYTE[]>(len); + TOKEN_USER& tokenUser = *reinterpret_cast<TOKEN_USER*>(tokenUserBuf.get()); + ok = ::GetTokenInformation(token, TokenUser, tokenUserBuf.get(), len, &len); + if (!ok) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + len = 0; + ok = ::GetTokenInformation(token, TokenPrimaryGroup, nullptr, len, &len); + win32Error = ::GetLastError(); + if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) { + return HRESULT_FROM_WIN32(win32Error); + } + + auto tokenPrimaryGroupBuf = MakeUnique<BYTE[]>(len); + TOKEN_PRIMARY_GROUP& tokenPrimaryGroup = + *reinterpret_cast<TOKEN_PRIMARY_GROUP*>(tokenPrimaryGroupBuf.get()); + ok = ::GetTokenInformation(token, TokenPrimaryGroup, + tokenPrimaryGroupBuf.get(), len, &len); + if (!ok) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + SECURITY_DESCRIPTOR sd; + if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + BYTE systemSid[SECURITY_MAX_SID_SIZE]; + DWORD systemSidSize = sizeof(systemSid); + if (!::CreateWellKnownSid(WinLocalSystemSid, nullptr, systemSid, + &systemSidSize)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + BYTE adminSid[SECURITY_MAX_SID_SIZE]; + DWORD adminSidSize = sizeof(adminSid); + if (!::CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, adminSid, + &adminSidSize)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + const bool allowAllNonRestrictedAppContainers = + aProcessCategory == ProcessCategory::GeckoBrowserParent; + + BYTE appContainersSid[SECURITY_MAX_SID_SIZE]; + DWORD appContainersSidSize = sizeof(appContainersSid); + if (allowAllNonRestrictedAppContainers) { + if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr, + appContainersSid, &appContainersSidSize)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + } + + UniquePtr<BYTE[]> tokenAppContainerInfBuf; + len = 0; + ::GetTokenInformation(token, TokenAppContainerSid, nullptr, len, &len); + if (len) { + tokenAppContainerInfBuf = MakeUnique<BYTE[]>(len); + ok = ::GetTokenInformation(token, TokenAppContainerSid, + tokenAppContainerInfBuf.get(), len, &len); + if (!ok) { + // Don't fail if we get an error retrieving an app container SID. + tokenAppContainerInfBuf = nullptr; + } + } + + // Grant access to SYSTEM, Administrators, the user, our app container (if in + // one) and when running as the browser process on Windows 8+, all non + // restricted app containers. + const size_t kMaxInlineEntries = 5; + mozilla::Vector<EXPLICIT_ACCESS_W, kMaxInlineEntries> entries; + + Unused << entries.append(EXPLICIT_ACCESS_W{ + COM_RIGHTS_EXECUTE, + GRANT_ACCESS, + NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, + reinterpret_cast<LPWSTR>(systemSid)}}); + + Unused << entries.append(EXPLICIT_ACCESS_W{ + COM_RIGHTS_EXECUTE, + GRANT_ACCESS, + NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, + TRUSTEE_IS_WELL_KNOWN_GROUP, reinterpret_cast<LPWSTR>(adminSid)}}); + + Unused << entries.append(EXPLICIT_ACCESS_W{ + COM_RIGHTS_EXECUTE, + GRANT_ACCESS, + NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, + reinterpret_cast<LPWSTR>(tokenUser.User.Sid)}}); + + if (allowAllNonRestrictedAppContainers) { + Unused << entries.append( + EXPLICIT_ACCESS_W{COM_RIGHTS_EXECUTE, + GRANT_ACCESS, + NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, + TRUSTEE_IS_WELL_KNOWN_GROUP, + reinterpret_cast<LPWSTR>(appContainersSid)}}); + } + + if (tokenAppContainerInfBuf) { + TOKEN_APPCONTAINER_INFORMATION& tokenAppContainerInf = + *reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>( + tokenAppContainerInfBuf.get()); + + // TokenAppContainer will be null if we are not in an app container. + if (tokenAppContainerInf.TokenAppContainer) { + Unused << entries.append(EXPLICIT_ACCESS_W{ + COM_RIGHTS_EXECUTE, + GRANT_ACCESS, + NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, + reinterpret_cast<LPWSTR>(tokenAppContainerInf.TokenAppContainer)}}); + } + } + + PACL rawDacl = nullptr; + win32Error = + ::SetEntriesInAclW(entries.length(), entries.begin(), nullptr, &rawDacl); + if (win32Error != ERROR_SUCCESS) { + return HRESULT_FROM_WIN32(win32Error); + } + + UniquePtr<ACL, LocalFreeDeleter> dacl(rawDacl); + + if (!::SetSecurityDescriptorDacl(&sd, TRUE, dacl.get(), FALSE)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + if (!::SetSecurityDescriptorOwner(&sd, tokenUser.User.Sid, FALSE)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + if (!::SetSecurityDescriptorGroup(&sd, tokenPrimaryGroup.PrimaryGroup, + FALSE)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + return wrapped::CoInitializeSecurity( + &sd, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_NONE, nullptr); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/ProcessRuntime.h b/ipc/mscom/ProcessRuntime.h new file mode 100644 index 0000000000..568281f360 --- /dev/null +++ b/ipc/mscom/ProcessRuntime.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_ProcessRuntime_h +#define mozilla_mscom_ProcessRuntime_h + +#include "mozilla/Attributes.h" +#include "mozilla/mscom/ApartmentRegion.h" +#include "nsWindowsHelpers.h" +#if defined(MOZILLA_INTERNAL_API) +# include "nsXULAppAPI.h" +#endif // defined(MOZILLA_INTERNAL_API) + +namespace mozilla { +namespace mscom { + +class MOZ_NON_TEMPORARY_CLASS ProcessRuntime final { +#if !defined(MOZILLA_INTERNAL_API) + public: +#endif // defined(MOZILLA_INTERNAL_API) + enum class ProcessCategory { + GeckoBrowserParent, + // We give Launcher its own process category, but internally to this class + // it should be treated identically to GeckoBrowserParent. + Launcher = GeckoBrowserParent, + GeckoChild, + Service, + }; + + // This constructor is only public when compiled outside of XUL + explicit ProcessRuntime(const ProcessCategory aProcessCategory); + + public: +#if defined(MOZILLA_INTERNAL_API) + ProcessRuntime(); + ~ProcessRuntime(); +#else + ~ProcessRuntime() = default; +#endif // defined(MOZILLA_INTERNAL_API) + + explicit operator bool() const { return SUCCEEDED(mInitResult); } + HRESULT GetHResult() const { return mInitResult; } + + ProcessRuntime(const ProcessRuntime&) = delete; + ProcessRuntime(ProcessRuntime&&) = delete; + ProcessRuntime& operator=(const ProcessRuntime&) = delete; + ProcessRuntime& operator=(ProcessRuntime&&) = delete; + + /** + * @return 0 if call is in-process or resolving the calling thread failed, + * otherwise contains the thread id of the calling thread. + */ + static DWORD GetClientThreadId(); + + private: +#if defined(MOZILLA_INTERNAL_API) + explicit ProcessRuntime(const GeckoProcessType aProcessType); +# if defined(MOZ_SANDBOX) + void InitUsingPersistentMTAThread(const nsAutoHandle& aCurThreadToken); +# endif // defined(MOZ_SANDBOX) +#endif // defined(MOZILLA_INTERNAL_API) + void InitInsideApartment(); + +#if defined(MOZILLA_INTERNAL_API) + static void PostInit(); +#endif // defined(MOZILLA_INTERNAL_API) + static HRESULT InitializeSecurity(const ProcessCategory aProcessCategory); + static COINIT GetDesiredApartmentType(const ProcessCategory aProcessCategory); + + private: + HRESULT mInitResult; + const ProcessCategory mProcessCategory; + ApartmentRegion mAptRegion; + + private: +#if defined(MOZILLA_INTERNAL_API) + static ProcessRuntime* sInstance; +#endif // defined(MOZILLA_INTERNAL_API) +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_ProcessRuntime_h diff --git a/ipc/mscom/ProfilerMarkers.cpp b/ipc/mscom/ProfilerMarkers.cpp new file mode 100644 index 0000000000..29d033723f --- /dev/null +++ b/ipc/mscom/ProfilerMarkers.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProfilerMarkers.h" + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Services.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsXULAppAPI.h" + +#include <objbase.h> +#include <objidlbase.h> + +// {9DBE6B28-E5E7-4FDE-AF00-9404604E74DC} +static const GUID GUID_MozProfilerMarkerExtension = { + 0x9dbe6b28, 0xe5e7, 0x4fde, {0xaf, 0x0, 0x94, 0x4, 0x60, 0x4e, 0x74, 0xdc}}; + +namespace { + +class ProfilerMarkerChannelHook final : public IChannelHook { + ~ProfilerMarkerChannelHook() = default; + + public: + ProfilerMarkerChannelHook() : mRefCnt(0) {} + + // IUnknown + STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + /** + * IChannelHook exposes six methods: The Client* methods are called when + * a client is sending an IPC request, whereas the Server* methods are called + * when a server is receiving an IPC request. + * + * For our purposes, we only care about the client-side methods. The COM + * runtime invokes the methods in the following order: + * 1. ClientGetSize, where the hook specifies the size of its payload; + * 2. ClientFillBuffer, where the hook fills the channel's buffer with its + * payload information. NOTE: This method is only called when ClientGetSize + * specifies a non-zero payload size. For our purposes, since we are not + * sending a payload, this method will never be called! + * 3. ClientNotify, when the response has been received from the server. + * + * Since we want to use these hooks to record the beginning and end of a COM + * IPC call, we use ClientGetSize for logging the start, and ClientNotify for + * logging the end. + * + * Finally, our implementation responds to any request matching our extension + * ID, however we only care about main thread COM calls. + */ + + // IChannelHook + STDMETHODIMP_(void) + ClientGetSize(REFGUID aExtensionId, REFIID aIid, + ULONG* aOutDataSize) override; + + // No-op (see the large comment above) + STDMETHODIMP_(void) + ClientFillBuffer(REFGUID aExtensionId, REFIID aIid, ULONG* aDataSize, + void* aDataBuf) override {} + + STDMETHODIMP_(void) + ClientNotify(REFGUID aExtensionId, REFIID aIid, ULONG aDataSize, + void* aDataBuffer, DWORD aDataRep, HRESULT aFault) override; + + // We don't care about the server-side notifications, so leave as no-ops. + STDMETHODIMP_(void) + ServerNotify(REFGUID aExtensionId, REFIID aIid, ULONG aDataSize, + void* aDataBuf, DWORD aDataRep) override {} + + STDMETHODIMP_(void) + ServerGetSize(REFGUID aExtensionId, REFIID aIid, HRESULT aFault, + ULONG* aOutDataSize) override {} + + STDMETHODIMP_(void) + ServerFillBuffer(REFGUID aExtensionId, REFIID aIid, ULONG* aDataSize, + void* aDataBuf, HRESULT aFault) override {} + + private: + void BuildMarkerName(REFIID aIid, nsACString& aOutMarkerName); + + private: + mozilla::Atomic<ULONG> mRefCnt; +}; + +HRESULT ProfilerMarkerChannelHook::QueryInterface(REFIID aIid, + void** aOutInterface) { + if (aIid == IID_IChannelHook || aIid == IID_IUnknown) { + RefPtr<IChannelHook> ptr(this); + ptr.forget(aOutInterface); + return S_OK; + } + + return E_NOINTERFACE; +} + +ULONG ProfilerMarkerChannelHook::AddRef() { return ++mRefCnt; } + +ULONG ProfilerMarkerChannelHook::Release() { + ULONG result = --mRefCnt; + if (!result) { + delete this; + } + + return result; +} + +void ProfilerMarkerChannelHook::BuildMarkerName(REFIID aIid, + nsACString& aOutMarkerName) { + aOutMarkerName.AssignLiteral("ORPC Call for "); + + nsAutoCString iidStr; + mozilla::mscom::DiagnosticNameForIID(aIid, iidStr); + aOutMarkerName.Append(iidStr); +} + +void ProfilerMarkerChannelHook::ClientGetSize(REFGUID aExtensionId, REFIID aIid, + ULONG* aOutDataSize) { + if (aExtensionId == GUID_MozProfilerMarkerExtension) { + if (NS_IsMainThread()) { + nsAutoCString markerName; + BuildMarkerName(aIid, markerName); + PROFILER_MARKER(markerName, IPC, mozilla::MarkerTiming::IntervalStart(), + Tracing, "MSCOM"); + } + + if (aOutDataSize) { + // We don't add any payload data to the channel + *aOutDataSize = 0UL; + } + } +} + +void ProfilerMarkerChannelHook::ClientNotify(REFGUID aExtensionId, REFIID aIid, + ULONG aDataSize, void* aDataBuffer, + DWORD aDataRep, HRESULT aFault) { + if (NS_IsMainThread() && aExtensionId == GUID_MozProfilerMarkerExtension) { + nsAutoCString markerName; + BuildMarkerName(aIid, markerName); + PROFILER_MARKER(markerName, IPC, mozilla::MarkerTiming::IntervalEnd(), + Tracing, "MSCOM"); + } +} + +} // anonymous namespace + +static void RegisterChannelHook() { + RefPtr<ProfilerMarkerChannelHook> hook(new ProfilerMarkerChannelHook()); + mozilla::DebugOnly<HRESULT> hr = + ::CoRegisterChannelHook(GUID_MozProfilerMarkerExtension, hook); + MOZ_ASSERT(SUCCEEDED(hr)); +} + +namespace { + +class ProfilerStartupObserver final : public nsIObserver { + ~ProfilerStartupObserver() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +NS_IMPL_ISUPPORTS(ProfilerStartupObserver, nsIObserver) + +NS_IMETHODIMP ProfilerStartupObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (strcmp(aTopic, "profiler-started")) { + return NS_OK; + } + + RegisterChannelHook(); + + // Once we've set the channel hook, we don't care about this notification + // anymore; our channel hook will remain set for the lifetime of the process. + nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService()); + MOZ_ASSERT(!!obsServ); + if (!obsServ) { + return NS_OK; + } + + obsServ->RemoveObserver(this, "profiler-started"); + return NS_OK; +} + +} // anonymous namespace + +namespace mozilla { +namespace mscom { + +void InitProfilerMarkers() { + if (!XRE_IsParentProcess()) { + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return; + } + + if (profiler_is_active()) { + // If the profiler is already running, we'll immediately register our + // channel hook. + RegisterChannelHook(); + return; + } + + // The profiler is not running yet. To avoid unnecessary invocations of the + // channel hook, we won't bother with installing it until the profiler starts. + // Set up an observer to watch for this. + nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService()); + MOZ_ASSERT(!!obsServ); + if (!obsServ) { + return; + } + + nsCOMPtr<nsIObserver> obs(new ProfilerStartupObserver()); + obsServ->AddObserver(obs, "profiler-started", false); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/ProfilerMarkers.h b/ipc/mscom/ProfilerMarkers.h new file mode 100644 index 0000000000..a21d5b375a --- /dev/null +++ b/ipc/mscom/ProfilerMarkers.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_ProfilerMarkers_h +#define mozilla_mscom_ProfilerMarkers_h + +namespace mozilla { +namespace mscom { + +void InitProfilerMarkers(); + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_ProfilerMarkers_h diff --git a/ipc/mscom/Utils.cpp b/ipc/mscom/Utils.cpp new file mode 100644 index 0000000000..d93e012587 --- /dev/null +++ b/ipc/mscom/Utils.cpp @@ -0,0 +1,324 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# include "MainThreadUtils.h" +# include "mozilla/dom/ContentChild.h" +#endif + +#include "mozilla/ArrayUtils.h" +#include "mozilla/mscom/COMWrappers.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/RefPtr.h" + +#include <objidl.h> +#include <winnt.h> + +#include <utility> + +#if defined(_MSC_VER) +extern "C" IMAGE_DOS_HEADER __ImageBase; +#endif + +namespace mozilla { +namespace mscom { + +bool IsCOMInitializedOnCurrentThread() { + APTTYPE aptType; + APTTYPEQUALIFIER aptTypeQualifier; + HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); + return hr != CO_E_NOTINITIALIZED; +} + +bool IsCurrentThreadMTA() { + APTTYPE aptType; + APTTYPEQUALIFIER aptTypeQualifier; + HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); + if (FAILED(hr)) { + return false; + } + + return aptType == APTTYPE_MTA; +} + +bool IsCurrentThreadExplicitMTA() { + APTTYPE aptType; + APTTYPEQUALIFIER aptTypeQualifier; + HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); + if (FAILED(hr)) { + return false; + } + + return aptType == APTTYPE_MTA && + aptTypeQualifier != APTTYPEQUALIFIER_IMPLICIT_MTA; +} + +bool IsCurrentThreadImplicitMTA() { + APTTYPE aptType; + APTTYPEQUALIFIER aptTypeQualifier; + HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); + if (FAILED(hr)) { + return false; + } + + return aptType == APTTYPE_MTA && + aptTypeQualifier == APTTYPEQUALIFIER_IMPLICIT_MTA; +} + +#if defined(MOZILLA_INTERNAL_API) +bool IsCurrentThreadNonMainMTA() { + if (NS_IsMainThread()) { + return false; + } + + return IsCurrentThreadMTA(); +} +#endif // defined(MOZILLA_INTERNAL_API) + +bool IsProxy(IUnknown* aUnknown) { + if (!aUnknown) { + return false; + } + + // Only proxies implement this interface, so if it is present then we must + // be dealing with a proxied object. + RefPtr<IClientSecurity> clientSecurity; + HRESULT hr = aUnknown->QueryInterface(IID_IClientSecurity, + (void**)getter_AddRefs(clientSecurity)); + if (SUCCEEDED(hr) || hr == RPC_E_WRONG_THREAD) { + return true; + } + return false; +} + +bool IsValidGUID(REFGUID aCheckGuid) { + // This function determines whether or not aCheckGuid conforms to RFC4122 + // as it applies to Microsoft COM. + + BYTE variant = aCheckGuid.Data4[0]; + if (!(variant & 0x80)) { + // NCS Reserved + return false; + } + if ((variant & 0xE0) == 0xE0) { + // Reserved for future use + return false; + } + if ((variant & 0xC0) == 0xC0) { + // Microsoft Reserved. + return true; + } + + BYTE version = HIBYTE(aCheckGuid.Data3) >> 4; + // Other versions are specified in RFC4122 but these are the two used by COM. + return version == 1 || version == 4; +} + +uintptr_t GetContainingModuleHandle() { + HMODULE thisModule = nullptr; +#if defined(_MSC_VER) + thisModule = reinterpret_cast<HMODULE>(&__ImageBase); +#else + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCTSTR>(&GetContainingModuleHandle), + &thisModule)) { + return 0; + } +#endif + return reinterpret_cast<uintptr_t>(thisModule); +} + +namespace detail { + +long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf, + const size_t aBufLen) { + constexpr wchar_t kClsid[] = L"CLSID\\"; + constexpr wchar_t kAppid[] = L"AppID\\"; + constexpr wchar_t kSubkeyBase[] = L"SOFTWARE\\Classes\\"; + + // We exclude null terminators in these length calculations because we include + // the stringified GUID's null terminator at the end. Since kClsid and kAppid + // have identical lengths, we just choose one to compute this length. + constexpr size_t kSubkeyBaseLen = mozilla::ArrayLength(kSubkeyBase) - 1; + constexpr size_t kSubkeyLen = + kSubkeyBaseLen + mozilla::ArrayLength(kClsid) - 1; + // Guid length as formatted for the registry (including curlies and dashes), + // but excluding null terminator. + constexpr size_t kGuidLen = kGuidRegFormatCharLenInclNul - 1; + constexpr size_t kExpectedPathLenInclNul = kSubkeyLen + kGuidLen + 1; + + if (aBufLen < kExpectedPathLenInclNul) { + // Buffer is too short + return E_INVALIDARG; + } + + if (wcscpy_s(aBuf, aBufLen, kSubkeyBase)) { + return E_INVALIDARG; + } + + const wchar_t* strGuidType = aGuidType == GuidType::CLSID ? kClsid : kAppid; + if (wcscat_s(aBuf, aBufLen, strGuidType)) { + return E_INVALIDARG; + } + + int guidConversionResult = + ::StringFromGUID2(aGuid, &aBuf[kSubkeyLen], aBufLen - kSubkeyLen); + if (!guidConversionResult) { + return E_INVALIDARG; + } + + return S_OK; +} + +} // namespace detail + +long CreateStream(const uint8_t* aInitBuf, const uint32_t aInitBufSize, + IStream** aOutStream) { + if (!aInitBufSize || !aOutStream) { + return E_INVALIDARG; + } + + *aOutStream = nullptr; + + HRESULT hr; + RefPtr<IStream> stream; + + // If aInitBuf is null then initSize must be 0. + UINT initSize = aInitBuf ? aInitBufSize : 0; + stream = already_AddRefed<IStream>(::SHCreateMemStream(aInitBuf, initSize)); + if (!stream) { + return E_OUTOFMEMORY; + } + + if (!aInitBuf) { + // Now we'll set the required size + ULARGE_INTEGER newSize; + newSize.QuadPart = aInitBufSize; + hr = stream->SetSize(newSize); + if (FAILED(hr)) { + return hr; + } + } + + // Ensure that the stream is rewound + LARGE_INTEGER streamOffset; + streamOffset.QuadPart = 0LL; + hr = stream->Seek(streamOffset, STREAM_SEEK_SET, nullptr); + if (FAILED(hr)) { + return hr; + } + + stream.forget(aOutStream); + return S_OK; +} + +#if defined(MOZILLA_INTERNAL_API) + +void GUIDToString(REFGUID aGuid, nsAString& aOutString) { + // This buffer length is long enough to hold a GUID string that is formatted + // to include curly braces and dashes. + const int kBufLenWithNul = 39; + aOutString.SetLength(kBufLenWithNul); + int result = StringFromGUID2(aGuid, char16ptr_t(aOutString.BeginWriting()), + kBufLenWithNul); + MOZ_ASSERT(result); + if (result) { + // Truncate the terminator + aOutString.SetLength(result - 1); + } +} + +// Undocumented IIDs that are relevant for diagnostic purposes +static const IID IID_ISCMLocalActivator = { + 0x00000136, + 0x0000, + 0x0000, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; +static const IID IID_IRundown = { + 0x00000134, + 0x0000, + 0x0000, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; +static const IID IID_IRemUnknown = { + 0x00000131, + 0x0000, + 0x0000, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; +static const IID IID_IRemUnknown2 = { + 0x00000143, + 0x0000, + 0x0000, + {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; + +struct IIDToLiteralMapEntry { + constexpr IIDToLiteralMapEntry(REFIID aIid, nsLiteralCString&& aStr) + : mIid(aIid), mStr(std::forward<nsLiteralCString>(aStr)) {} + + REFIID mIid; + const nsLiteralCString mStr; +}; + +/** + * Given the name of an interface, the IID_ENTRY macro generates a pair + * containing a reference to the interface ID and a stringified version of + * the interface name. + * + * For example: + * + * {IID_ENTRY(IUnknown)} + * is expanded to: + * {IID_IUnknown, "IUnknown"_ns} + * + */ +// clang-format off +# define IID_ENTRY_STRINGIFY(iface) #iface##_ns +# define IID_ENTRY(iface) IID_##iface, IID_ENTRY_STRINGIFY(iface) +// clang-format on + +// Mapping of selected IIDs to friendly, human readable descriptions for each +// interface. +static constexpr IIDToLiteralMapEntry sIidDiagStrs[] = { + {IID_ENTRY(IUnknown)}, + {IID_IRemUnknown, "cross-apartment IUnknown"_ns}, + {IID_IRundown, "cross-apartment object management"_ns}, + {IID_ISCMLocalActivator, "out-of-process object instantiation"_ns}, + {IID_IRemUnknown2, "cross-apartment IUnknown"_ns}}; + +# undef IID_ENTRY +# undef IID_ENTRY_STRINGIFY + +void DiagnosticNameForIID(REFIID aIid, nsACString& aOutString) { + // If the IID matches something in sIidDiagStrs, output its string. + for (const auto& curEntry : sIidDiagStrs) { + if (curEntry.mIid == aIid) { + aOutString.Assign(curEntry.mStr); + return; + } + } + + // Otherwise just convert the IID to string form and output that. + nsAutoString strIid; + GUIDToString(aIid, strIid); + + aOutString.AssignLiteral("IID "); + AppendUTF16toUTF8(strIid, aOutString); +} + +#else + +void GUIDToString(REFGUID aGuid, + wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]) { + DebugOnly<int> result = + ::StringFromGUID2(aGuid, aOutBuf, ArrayLength(aOutBuf)); + MOZ_ASSERT(result); +} + +#endif // defined(MOZILLA_INTERNAL_API) + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/Utils.h b/ipc/mscom/Utils.h new file mode 100644 index 0000000000..aabf5f5891 --- /dev/null +++ b/ipc/mscom/Utils.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_Utils_h +#define mozilla_mscom_Utils_h + +#if defined(MOZILLA_INTERNAL_API) +# include "nsString.h" +#endif // defined(MOZILLA_INTERNAL_API) + +#include "mozilla/Attributes.h" +#include <guiddef.h> +#include <stdint.h> + +struct IStream; +struct IUnknown; + +namespace mozilla { +namespace mscom { +namespace detail { + +enum class GuidType { + CLSID, + AppID, +}; + +long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf, + const size_t aBufLen); + +} // namespace detail + +bool IsCOMInitializedOnCurrentThread(); +bool IsCurrentThreadMTA(); +bool IsCurrentThreadExplicitMTA(); +bool IsCurrentThreadImplicitMTA(); +#if defined(MOZILLA_INTERNAL_API) +bool IsCurrentThreadNonMainMTA(); +#endif // defined(MOZILLA_INTERNAL_API) +bool IsProxy(IUnknown* aUnknown); +bool IsValidGUID(REFGUID aCheckGuid); +uintptr_t GetContainingModuleHandle(); + +template <size_t N> +inline long BuildAppidPath(REFGUID aAppId, wchar_t (&aPath)[N]) { + return detail::BuildRegGuidPath(aAppId, detail::GuidType::AppID, aPath, N); +} + +template <size_t N> +inline long BuildClsidPath(REFCLSID aClsid, wchar_t (&aPath)[N]) { + return detail::BuildRegGuidPath(aClsid, detail::GuidType::CLSID, aPath, N); +} + +/** + * Given a buffer, create a new IStream object. + * @param aBuf Buffer containing data to initialize the stream. This parameter + * may be nullptr, causing the stream to be created with aBufLen + * bytes of uninitialized data. + * @param aBufLen Length of data in aBuf, or desired stream size if aBuf is + * nullptr. + * @param aOutStream Outparam to receive the newly created stream. + * @return HRESULT error code. + */ +long CreateStream(const uint8_t* aBuf, const uint32_t aBufLen, + IStream** aOutStream); + +/** + * Length of a stringified GUID as formatted for the registry, i.e. including + * curly-braces and dashes. + */ +constexpr size_t kGuidRegFormatCharLenInclNul = 39; + +#if defined(MOZILLA_INTERNAL_API) +void GUIDToString(REFGUID aGuid, nsAString& aOutString); + +/** + * Converts an IID to a human-readable string for the purposes of diagnostic + * tools such as the profiler. For some special cases, we output a friendly + * string that describes the purpose of the interface. If no such description + * exists, we simply fall back to outputting the IID as a string formatted by + * GUIDToString(). + */ +void DiagnosticNameForIID(REFIID aIid, nsACString& aOutString); +#else +void GUIDToString(REFGUID aGuid, + wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]); +#endif // defined(MOZILLA_INTERNAL_API) + +/** + * Execute cleanup code when going out of scope if a condition is met. + * This is useful when, for example, particular cleanup needs to be performed + * whenever a call returns a failure HRESULT. + * Both the condition and cleanup code are provided as functions (usually + * lambdas). + */ +template <typename CondFnT, typename ExeFnT> +class MOZ_RAII ExecuteWhen final { + public: + ExecuteWhen(CondFnT& aCondFn, ExeFnT& aExeFn) + : mCondFn(aCondFn), mExeFn(aExeFn) {} + + ~ExecuteWhen() { + if (mCondFn()) { + mExeFn(); + } + } + + ExecuteWhen(const ExecuteWhen&) = delete; + ExecuteWhen(ExecuteWhen&&) = delete; + ExecuteWhen& operator=(const ExecuteWhen&) = delete; + ExecuteWhen& operator=(ExecuteWhen&&) = delete; + + private: + CondFnT& mCondFn; + ExeFnT& mExeFn; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_Utils_h diff --git a/ipc/mscom/moz.build b/ipc/mscom/moz.build new file mode 100644 index 0000000000..e11b7314f4 --- /dev/null +++ b/ipc/mscom/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.mscom += [ + "Aggregation.h", + "AgileReference.h", + "ApartmentRegion.h", + "COMWrappers.h", + "EnsureMTA.h", + "ProcessRuntime.h", + "ProfilerMarkers.h", + "Utils.h", +] + +DIRS += [ + "mozglue", +] + +UNIFIED_SOURCES += [ + "AgileReference.cpp", + "COMWrappers.cpp", + "EnsureMTA.cpp", + "ProcessRuntime.cpp", + "ProfilerMarkers.cpp", + "Utils.cpp", +] + +LOCAL_INCLUDES += [ + "/xpcom/base", + "/xpcom/build", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +with Files("**"): + BUG_COMPONENT = ("Core", "IPC: MSCOM") + SCHEDULES.exclusive = ["windows"] diff --git a/ipc/mscom/mozglue/ProcessRuntimeShared.cpp b/ipc/mscom/mozglue/ProcessRuntimeShared.cpp new file mode 100644 index 0000000000..ee4f6b221f --- /dev/null +++ b/ipc/mscom/mozglue/ProcessRuntimeShared.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mscom/ProcessRuntimeShared.h" + +#include "mozilla/glue/WinUtils.h" + +// We allow multiple ProcessRuntime instances to exist simultaneously (even +// on separate threads), but only one should be doing the process-wide +// initialization. These variables provide that mutual exclusion. +static mozilla::glue::Win32SRWLock gLock; +static mozilla::mscom::detail::ProcessInitState gProcessInitState = + mozilla::mscom::detail::ProcessInitState::Uninitialized; + +namespace mozilla { +namespace mscom { +namespace detail { + +MFBT_API ProcessInitState& BeginProcessRuntimeInit() { + gLock.LockExclusive(); + return gProcessInitState; +} + +MFBT_API void EndProcessRuntimeInit() { gLock.UnlockExclusive(); } + +} // namespace detail +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/mozglue/ProcessRuntimeShared.h b/ipc/mscom/mozglue/ProcessRuntimeShared.h new file mode 100644 index 0000000000..d5a58a7b92 --- /dev/null +++ b/ipc/mscom/mozglue/ProcessRuntimeShared.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_mscom_ProcessRuntimeShared_h +#define mozilla_mscom_ProcessRuntimeShared_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Types.h" + +namespace mozilla { +namespace mscom { +namespace detail { + +enum class ProcessInitState : uint32_t { + Uninitialized = 0, + PartialSecurityInitialized, + PartialGlobalOptions, + FullyInitialized, +}; + +MFBT_API ProcessInitState& BeginProcessRuntimeInit(); +MFBT_API void EndProcessRuntimeInit(); + +} // namespace detail + +class MOZ_RAII ProcessInitLock final { + public: + ProcessInitLock() : mInitState(detail::BeginProcessRuntimeInit()) {} + + ~ProcessInitLock() { detail::EndProcessRuntimeInit(); } + + detail::ProcessInitState GetInitState() const { return mInitState; } + + void SetInitState(const detail::ProcessInitState aNewState) { + MOZ_DIAGNOSTIC_ASSERT(aNewState > mInitState); + mInitState = aNewState; + } + + ProcessInitLock(const ProcessInitLock&) = delete; + ProcessInitLock(ProcessInitLock&&) = delete; + ProcessInitLock operator=(const ProcessInitLock&) = delete; + ProcessInitLock operator=(ProcessInitLock&&) = delete; + + private: + detail::ProcessInitState& mInitState; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_ProcessRuntimeShared_h diff --git a/ipc/mscom/mozglue/moz.build b/ipc/mscom/mozglue/moz.build new file mode 100644 index 0000000000..f52a772324 --- /dev/null +++ b/ipc/mscom/mozglue/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_LIBRARY = "mozglue" + +EXPORTS.mozilla.mscom += [ + "ProcessRuntimeShared.h", +] + +UNIFIED_SOURCES += [ + "ProcessRuntimeShared.cpp", +] |