diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /ipc/mscom | |
parent | Initial commit. (diff) | |
download | thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
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 | 223 | ||||
-rw-r--r-- | ipc/mscom/AgileReference.h | 143 | ||||
-rw-r--r-- | ipc/mscom/ApartmentRegion.h | 93 | ||||
-rw-r--r-- | ipc/mscom/COMWrappers.cpp | 101 | ||||
-rw-r--r-- | ipc/mscom/COMWrappers.h | 44 | ||||
-rw-r--r-- | ipc/mscom/EnsureMTA.cpp | 250 | ||||
-rw-r--r-- | ipc/mscom/EnsureMTA.h | 191 | ||||
-rw-r--r-- | ipc/mscom/ProcessRuntime.cpp | 458 | ||||
-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 | 404 | ||||
-rw-r--r-- | ipc/mscom/Utils.h | 146 | ||||
-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, 2620 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..7a1b94822d --- /dev/null +++ b/ipc/mscom/AgileReference.cpp @@ -0,0 +1,223 @@ +/* -*- 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/DebugOnly.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/mscom/Utils.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "nsDebug.h" +# include "nsPrintfCString.h" +#endif // defined(MOZILLA_INTERNAL_API) + +#if NTDDI_VERSION < NTDDI_WINBLUE + +// 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); + +#endif // NTDDI_VERSION < NTDDI_WINBLUE + +namespace mozilla { +namespace mscom { +namespace detail { + +GlobalInterfaceTableCookie::GlobalInterfaceTableCookie(IUnknown* aObject, + REFIID aIid, + HRESULT& aOutHResult) + : mCookie(0) { + IGlobalInterfaceTable* git = ObtainGit(); + MOZ_ASSERT(git); + if (!git) { + aOutHResult = E_POINTER; + return; + } + + aOutHResult = git->RegisterInterfaceInGlobal(aObject, aIid, &mCookie); + MOZ_ASSERT(SUCCEEDED(aOutHResult)); +} + +GlobalInterfaceTableCookie::~GlobalInterfaceTableCookie() { + IGlobalInterfaceTable* git = ObtainGit(); + MOZ_ASSERT(git); + if (!git) { + return; + } + + DebugOnly<HRESULT> hr = git->RevokeInterfaceFromGlobal(mCookie); +#if defined(MOZILLA_INTERNAL_API) + NS_WARNING_ASSERTION( + SUCCEEDED(hr), + nsPrintfCString("IGlobalInterfaceTable::RevokeInterfaceFromGlobal failed " + "with HRESULT 0x%08lX", + ((HRESULT)hr)) + .get()); +#else + MOZ_ASSERT(SUCCEEDED(hr)); +#endif // defined(MOZILLA_INTERNAL_API) + mCookie = 0; +} + +HRESULT GlobalInterfaceTableCookie::GetInterface(REFIID aIid, + void** aOutInterface) const { + IGlobalInterfaceTable* git = ObtainGit(); + MOZ_ASSERT(git); + if (!git) { + return E_UNEXPECTED; + } + + MOZ_ASSERT(IsValid()); + return git->GetInterfaceFromGlobal(mCookie, aIid, aOutInterface); +} + +/* static */ +IGlobalInterfaceTable* GlobalInterfaceTableCookie::ObtainGit() { + // Internally to COM, the Global Interface Table is a singleton, therefore we + // don't worry about holding onto this reference indefinitely. + static IGlobalInterfaceTable* sGit = []() -> IGlobalInterfaceTable* { + IGlobalInterfaceTable* result = nullptr; + DebugOnly<HRESULT> hr = ::CoCreateInstance( + CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER, + IID_IGlobalInterfaceTable, reinterpret_cast<void**>(&result)); + MOZ_ASSERT(SUCCEEDED(hr)); + return result; + }(); + + return sGit; +} + +} // namespace detail + +AgileReference::AgileReference() : mIid(), mHResult(E_NOINTERFACE) {} + +AgileReference::AgileReference(REFIID aIid, IUnknown* aObject) + : mIid(aIid), mHResult(E_UNEXPECTED) { + AssignInternal(aObject); +} + +AgileReference::AgileReference(AgileReference&& aOther) + : mIid(aOther.mIid), + mAgileRef(std::move(aOther.mAgileRef)), + mGitCookie(std::move(aOther.mGitCookie)), + mHResult(aOther.mHResult) { + aOther.mHResult = CO_E_RELEASED; +} + +void AgileReference::Assign(REFIID aIid, IUnknown* aObject) { + Clear(); + mIid = aIid; + AssignInternal(aObject); +} + +void AgileReference::AssignInternal(IUnknown* aObject) { + // We expect mIid to already be set + DebugOnly<IID> zeroIid = {}; + MOZ_ASSERT(mIid != zeroIid); + + /* + * There are two possible techniques for creating agile references. Starting + * with Windows 8.1, we may use the RoGetAgileReference API, which is faster. + * If that API is not available, we fall back to using the Global Interface + * Table. + */ + static const StaticDynamicallyLinkedFunctionPtr< + decltype(&::RoGetAgileReference)> + pRoGetAgileReference(L"ole32.dll", "RoGetAgileReference"); + + MOZ_ASSERT(aObject); + + if (pRoGetAgileReference && + SUCCEEDED(mHResult = + pRoGetAgileReference(AGILEREFERENCE_DEFAULT, mIid, aObject, + getter_AddRefs(mAgileRef)))) { + return; + } + + mGitCookie = new detail::GlobalInterfaceTableCookie(aObject, mIid, mHResult); + MOZ_ASSERT(mGitCookie->IsValid()); +} + +AgileReference::~AgileReference() { Clear(); } + +void AgileReference::Clear() { + mIid = {}; + mAgileRef = nullptr; + mGitCookie = nullptr; + mHResult = E_NOINTERFACE; +} + +AgileReference& AgileReference::operator=(const AgileReference& aOther) { + Clear(); + mIid = aOther.mIid; + mAgileRef = aOther.mAgileRef; + mGitCookie = aOther.mGitCookie; + mHResult = aOther.mHResult; + return *this; +} + +AgileReference& AgileReference::operator=(AgileReference&& aOther) { + Clear(); + mIid = aOther.mIid; + mAgileRef = std::move(aOther.mAgileRef); + mGitCookie = std::move(aOther.mGitCookie); + mHResult = aOther.mHResult; + aOther.mHResult = CO_E_RELEASED; + return *this; +} + +HRESULT +AgileReference::Resolve(REFIID aIid, void** aOutInterface) const { + MOZ_ASSERT(aOutInterface); + // This check is exclusive-OR; we should have one or the other, but not both + MOZ_ASSERT((mAgileRef || mGitCookie) && !(mAgileRef && mGitCookie)); + MOZ_ASSERT(IsCOMInitializedOnCurrentThread()); + + if (!aOutInterface) { + return E_INVALIDARG; + } + + *aOutInterface = nullptr; + + if (mAgileRef) { + // IAgileReference lets you directly resolve the interface you want... + return mAgileRef->Resolve(aIid, aOutInterface); + } + + if (!mGitCookie) { + return E_UNEXPECTED; + } + + RefPtr<IUnknown> originalInterface; + HRESULT hr = + mGitCookie->GetInterface(mIid, getter_AddRefs(originalInterface)); + if (FAILED(hr)) { + return hr; + } + + if (aIid == mIid) { + originalInterface.forget(aOutInterface); + return S_OK; + } + + // ...Whereas the GIT requires us to obtain the same interface that we + // requested and then QI for the desired interface afterward. + return originalInterface->QueryInterface(aIid, aOutInterface); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/AgileReference.h b/ipc/mscom/AgileReference.h new file mode 100644 index 0000000000..d39e444494 --- /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 "nsISupportsImpl.h" + +#include <objidl.h> + +namespace mozilla { +namespace mscom { +namespace detail { + +class MOZ_HEAP_CLASS GlobalInterfaceTableCookie final { + public: + GlobalInterfaceTableCookie(IUnknown* aObject, REFIID aIid, + HRESULT& aOutHResult); + + bool IsValid() const { return !!mCookie; } + HRESULT GetInterface(REFIID aIid, void** aOutInterface) const; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GlobalInterfaceTableCookie) + + GlobalInterfaceTableCookie(const GlobalInterfaceTableCookie&) = delete; + GlobalInterfaceTableCookie(GlobalInterfaceTableCookie&&) = delete; + + GlobalInterfaceTableCookie& operator=(const GlobalInterfaceTableCookie&) = + delete; + GlobalInterfaceTableCookie& operator=(GlobalInterfaceTableCookie&&) = delete; + + private: + ~GlobalInterfaceTableCookie(); + + private: + DWORD mCookie; + + private: + static IGlobalInterfaceTable* ObtainGit(); +}; + +} // 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 the agile reference instead. Then + * you unwrap the interface by calling AgileReference::Resolve. + * + * Sample usage: + * + * // In the multithreaded apartment, foo is an IFoo* + * auto myAgileRef = MakeUnique<AgileReference>(IID_IFoo, foo); + * + * // myAgileRef is passed to our main thread, which runs in a single-threaded + * // apartment: + * + * RefPtr<IFoo> foo; + * HRESULT hr = myAgileRef->Resolve(IID_IFoo, getter_AddRefs(foo)); + * // Now foo may be called from the main thread + */ +class AgileReference final { + public: + AgileReference(); + + template <typename InterfaceT> + explicit AgileReference(RefPtr<InterfaceT>& aObject) + : AgileReference(__uuidof(InterfaceT), aObject) {} + + AgileReference(REFIID aIid, IUnknown* aObject); + + AgileReference(const AgileReference& aOther) = default; + AgileReference(AgileReference&& aOther); + + ~AgileReference(); + + explicit operator bool() const { + return mAgileRef || (mGitCookie && mGitCookie->IsValid()); + } + + HRESULT GetHResult() const { return mHResult; } + + template <typename T> + void Assign(const RefPtr<T>& aOther) { + Assign(__uuidof(T), aOther); + } + + template <typename T> + AgileReference& operator=(const RefPtr<T>& aOther) { + Assign(aOther); + return *this; + } + + HRESULT Resolve(REFIID aIid, void** aOutInterface) const; + + AgileReference& operator=(const AgileReference& aOther); + AgileReference& operator=(AgileReference&& aOther); + + AgileReference& operator=(decltype(nullptr)) { + Clear(); + return *this; + } + + void Clear(); + + private: + void Assign(REFIID aIid, IUnknown* aObject); + void AssignInternal(IUnknown* aObject); + + private: + IID mIid; + RefPtr<IAgileReference> mAgileRef; + RefPtr<detail::GlobalInterfaceTableCookie> mGitCookie; + HRESULT mHResult; +}; + +} // namespace mscom +} // namespace mozilla + +template <typename T> +RefPtr<T>::RefPtr(const mozilla::mscom::AgileReference& aAgileRef) + : mRawPtr(nullptr) { + (*this) = aAgileRef; +} + +template <typename T> +RefPtr<T>& RefPtr<T>::operator=( + const mozilla::mscom::AgileReference& aAgileRef) { + void* newRawPtr; + if (FAILED(aAgileRef.Resolve(__uuidof(T), &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); + return *this; +} + +#endif // mozilla_mscom_AgileReference_h diff --git a/ipc/mscom/ApartmentRegion.h b/ipc/mscom/ApartmentRegion.h new file mode 100644 index 0000000000..41915710df --- /dev/null +++ b/ipc/mscom/ApartmentRegion.h @@ -0,0 +1,93 @@ +/* -*- 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 { +namespace mscom { + +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; } + + private: + ApartmentRegion(const ApartmentRegion&) = delete; + ApartmentRegion& operator=(const ApartmentRegion&) = delete; + ApartmentRegion(ApartmentRegion&&) = delete; + ApartmentRegion& operator=(ApartmentRegion&&) = delete; + + HRESULT mInitResult; +}; + +template <COINIT T> +class MOZ_NON_TEMPORARY_CLASS ApartmentRegionT final { + public: + ApartmentRegionT() : mAptRgn(T) {} + + ~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(); } + + private: + ApartmentRegionT(const ApartmentRegionT&) = delete; + ApartmentRegionT& operator=(const ApartmentRegionT&) = delete; + ApartmentRegionT(ApartmentRegionT&&) = delete; + ApartmentRegionT& operator=(ApartmentRegionT&&) = delete; + + ApartmentRegion mAptRgn; +}; + +typedef ApartmentRegionT<COINIT_APARTMENTTHREADED> STARegion; +typedef ApartmentRegionT<COINIT_MULTITHREADED> MTARegion; + +} // namespace mscom +} // namespace mozilla + +#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..38bef10749 --- /dev/null +++ b/ipc/mscom/COMWrappers.h @@ -0,0 +1,44 @@ +/* -*- 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> + +#if (NTDDI_VERSION < NTDDI_WIN8) +// Win8+ API that we use very carefully +DECLARE_HANDLE(CO_MTA_USAGE_COOKIE); +HRESULT WINAPI CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie); +#endif // (NTDDI_VERSION < NTDDI_WIN8) + +// 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..0a84eeabf9 --- /dev/null +++ b/ipc/mscom/EnsureMTA.cpp @@ -0,0 +1,250 @@ +/* -*- 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 */ +RefPtr<EnsureMTA::CreateInstanceAgileRefPromise> +EnsureMTA::CreateInstanceInternal(REFCLSID aClsid, REFIID aIid) { + MOZ_ASSERT(IsCurrentThreadExplicitMTA()); + + RefPtr<IUnknown> iface; + HRESULT hr = wrapped::CoCreateInstance(aClsid, nullptr, CLSCTX_INPROC_SERVER, + aIid, getter_AddRefs(iface)); + if (FAILED(hr)) { + return CreateInstanceAgileRefPromise::CreateAndReject(hr, __func__); + } + + // We need to use the two argument constructor for AgileReference because our + // RefPtr is not parameterized on the specific interface being requested. + AgileReference agileRef(aIid, iface); + if (!agileRef) { + return CreateInstanceAgileRefPromise::CreateAndReject(agileRef.GetHResult(), + __func__); + } + + return CreateInstanceAgileRefPromise::CreateAndResolve(std::move(agileRef), + __func__); +} + +/* static */ +RefPtr<EnsureMTA::CreateInstanceAgileRefPromise> EnsureMTA::CreateInstance( + REFCLSID aClsid, REFIID aIid) { + MOZ_ASSERT(IsCOMInitializedOnCurrentThread()); + + const bool isClassOk = IsClassThreadAwareInprocServer(aClsid); + MOZ_ASSERT(isClassOk, + "mozilla::mscom::EnsureMTA::CreateInstance is not " + "safe/performant/necessary to use with this CLSID. This CLSID " + "either does not support creation from within a multithreaded " + "apartment, or it is not an in-process server."); + if (!isClassOk) { + return CreateInstanceAgileRefPromise::CreateAndReject(CO_E_NOT_SUPPORTED, + __func__); + } + + if (IsCurrentThreadExplicitMTA()) { + // It's safe to immediately call CreateInstanceInternal + return CreateInstanceInternal(aClsid, aIid); + } + + // aClsid and aIid are references. Make local copies that we can put into the + // lambda in case the sources of aClsid or aIid are not static data + CLSID localClsid = aClsid; + IID localIid = aIid; + + auto invoker = [localClsid, + localIid]() -> RefPtr<CreateInstanceAgileRefPromise> { + return CreateInstanceInternal(localClsid, localIid); + }; + + nsCOMPtr<nsIThread> mtaThread(GetPersistentMTAThread()); + + return InvokeAsync(mtaThread, __func__, std::move(invoker)); +} + +/* 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( + TaskCategory::Other, + 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..5e410d4daa --- /dev/null +++ b/ipc/mscom/EnsureMTA.h @@ -0,0 +1,191 @@ +/* -*- 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); + } + + using CreateInstanceAgileRefPromise = + MozPromise<AgileReference, HRESULT, false>; + + /** + * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! *** + * + * Asynchronously instantiate a new COM object from a MTA thread, unless the + * current thread is already living inside the multithreaded apartment, in + * which case the object is immediately instantiated. + * + * This function only supports the most common configurations for creating + * a new object, so it only supports in-process servers. Furthermore, this + * function does not support aggregation (ie. the |pUnkOuter| parameter to + * CoCreateInstance). + * + * Given that attempting to instantiate an Apartment-threaded COM object + * inside the MTA results in a *loss* of performance, we assert when that + * situation arises. + * + * The resulting promise, once resolved, provides an AgileReference that may + * be passed between any COM-initialized thread in the current process. + * + * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! *** + * + * WARNING: + * Some COM objects do not support creation in the multithreaded apartment, + * in which case this function is not available as an option. In this case, + * the promise will always be rejected. In debug builds we will assert. + * + * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! *** + * + * WARNING: + * Any in-process COM objects whose interfaces accept HWNDs are probably + * *not* safe to instantiate in the multithreaded apartment! Even if this + * function succeeds when creating such an object, you *MUST NOT* do so, as + * these failures might not become apparent until your code is running out in + * the wild on the release channel! + * + * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! *** + * + * WARNING: + * When you obtain an interface from the AgileReference, it may or may not be + * a proxy to the real object. This depends entirely on the implementation of + * the underlying class and the multithreading capabilities that the class + * declares to the COM runtime. If the interface is proxied, it might be + * expensive to invoke methods on that interface! *Always* test the + * performance of your method calls when calling interfaces that are resolved + * via this function! + * + * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! *** + * + * (Despite this myriad of warnings, it is still *much* safer to use this + * function to asynchronously create COM objects than it is to roll your own!) + * + * *** A MSCOM PEER SHOULD REVIEW ALL NEW USES OF THIS API! *** + */ + static RefPtr<CreateInstanceAgileRefPromise> CreateInstance(REFCLSID aClsid, + REFIID aIid); + + private: + static RefPtr<CreateInstanceAgileRefPromise> CreateInstanceInternal( + REFCLSID aClsid, REFIID aIid); + + 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..f20b0658bb --- /dev/null +++ b/ipc/mscom/ProcessRuntime.cpp @@ -0,0 +1,458 @@ +/* -*- 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" +#include "mozilla/WindowsVersion.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 allowAppContainers = + aProcessCategory == ProcessCategory::GeckoBrowserParent && + IsWin8OrLater(); + + BYTE appContainersSid[SECURITY_MAX_SID_SIZE]; + DWORD appContainersSidSize = sizeof(appContainersSid); + if (allowAppContainers) { + if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr, + appContainersSid, &appContainersSidSize)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + } + + // Grant access to SYSTEM, Administrators, the user, and when running as the + // browser process on Windows 8+, all app containers. + const size_t kMaxInlineEntries = 4; + 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 (allowAppContainers) { + 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)}}); + } + + 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..a6aeebf12f --- /dev/null +++ b/ipc/mscom/Utils.cpp @@ -0,0 +1,404 @@ +/* -*- 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 "mozilla/WindowsVersion.h" + +#include <objidl.h> +#include <shlwapi.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 (IsWin8OrLater()) { + // SHCreateMemStream is not safe for us to use until Windows 8. On older + // versions of Windows it is not thread-safe and it creates IStreams that do + // not support the full IStream API. + + // 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; + } + } + } else { + HGLOBAL hglobal = ::GlobalAlloc(GMEM_MOVEABLE, aInitBufSize); + if (!hglobal) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + // stream takes ownership of hglobal if this call is successful + hr = ::CreateStreamOnHGlobal(hglobal, TRUE, getter_AddRefs(stream)); + if (FAILED(hr)) { + ::GlobalFree(hglobal); + return hr; + } + + // The default stream size is derived from ::GlobalSize(hglobal), which due + // to rounding may be larger than aInitBufSize. We forcibly set the correct + // stream size here. + ULARGE_INTEGER streamSize; + streamSize.QuadPart = aInitBufSize; + hr = stream->SetSize(streamSize); + if (FAILED(hr)) { + return hr; + } + + if (aInitBuf) { + ULONG bytesWritten; + hr = stream->Write(aInitBuf, aInitBufSize, &bytesWritten); + if (FAILED(hr)) { + return hr; + } + + if (bytesWritten != aInitBufSize) { + return E_UNEXPECTED; + } + } + } + + // 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) + +#if defined(MOZILLA_INTERNAL_API) +bool IsClassThreadAwareInprocServer(REFCLSID aClsid) { + nsAutoString strClsid; + GUIDToString(aClsid, strClsid); + + nsAutoString inprocServerSubkey(u"CLSID\\"_ns); + inprocServerSubkey.Append(strClsid); + inprocServerSubkey.Append(u"\\InprocServer32"_ns); + + // Of the possible values, "Apartment" is the longest, so we'll make this + // buffer large enough to hold that one. + wchar_t threadingModelBuf[ArrayLength(L"Apartment")] = {}; + + DWORD numBytes = sizeof(threadingModelBuf); + LONG result = ::RegGetValueW(HKEY_CLASSES_ROOT, inprocServerSubkey.get(), + L"ThreadingModel", RRF_RT_REG_SZ, nullptr, + threadingModelBuf, &numBytes); + if (result != ERROR_SUCCESS) { + // This will also handle the case where the CLSID is not an inproc server. + return false; + } + + DWORD numChars = numBytes / sizeof(wchar_t); + // numChars includes the null terminator + if (numChars <= 1) { + return false; + } + + nsDependentString threadingModel(threadingModelBuf, numChars - 1); + + // Ensure that the threading model is one of the known values that indicates + // that the class can operate natively (ie, no proxying) inside a MTA. + return threadingModel.LowerCaseEqualsLiteral("both") || + threadingModel.LowerCaseEqualsLiteral("free") || + threadingModel.LowerCaseEqualsLiteral("neutral"); +} +#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..214b15044d --- /dev/null +++ b/ipc/mscom/Utils.h @@ -0,0 +1,146 @@ +/* -*- 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) +/** + * Checks the registry to see if |aClsid| is a thread-aware in-process server. + * + * In DCOM, an in-process server is a server that is implemented inside a DLL + * that is loaded into the client's process for execution. If |aClsid| declares + * itself to be a local server (that is, a server that resides in another + * process), this function returns false. + * + * For the server to be thread-aware, its registry entry must declare a + * ThreadingModel that is one of "Free", "Both", or "Neutral". If the threading + * model is "Apartment" or some other, invalid value, the class is treated as + * being single-threaded. + * + * NB: This function cannot check CLSIDs that were registered via manifests, + * as unfortunately there is not a documented API available to query for those. + * This should not be an issue for most CLSIDs that Gecko is interested in, as + * we typically instantiate system CLSIDs which are available in the registry. + * + * @param aClsid The CLSID of the COM class to be checked. + * @return true if the class meets the above criteria, otherwise false. + */ +bool IsClassThreadAwareInprocServer(REFCLSID aClsid); + +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", +] |