/* 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 #include #include #include #include "mozilla/Assertions.h" #include "mozilla/mscom/COMWrappers.h" #include "mozilla/mscom/ProcessRuntimeShared.h" #include "mozilla/RefPtr.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) // 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 : aProcessType == GeckoProcessType_Content ? ProcessCategory::GeckoContent : 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); const bool needsMTAInit = GetDesiredApartmentType(mProcessCategory) == COINIT_MULTITHREADED; # if defined(MOZ_SANDBOX) // Child processes that want MTA and run under the sandbox need special // handling: the sandbox provides a privileged impersonation token during // early startup that must be active when COM is initialized, and PostInit // must be deferred until privilege drop. const bool needsSandboxedMTAInit = needsMTAInit && SandboxTarget::Instance()->IsSandboxed(); # else const bool needsSandboxedMTAInit = false; # endif // If we are initializing COM on the MTA, we ideally want our current thread // to be running implicitly inside the MTA, but if for some reason we did // not end up with that, we may compensate by completing initialization via // EnsureMTA's persistent thread. if (!isCurThreadImplicitMTA && needsMTAInit) { InitUsingPersistentMTAThread(needsSandboxedMTAInit); return; } #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 (needsSandboxedMTAInit) { // In sandboxed MTA 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; } void ProcessRuntime::InitUsingPersistentMTAThread(bool aNeedsSandboxedInit) { if (!aNeedsSandboxedInit) { EnsureMTA([this]() { InitInsideApartment(); }, EnsureMTA::Option::ForceDispatchToPersistentThread); if (SUCCEEDED(mInitResult)) { PostInit(); } return; } # if defined(MOZ_SANDBOX) 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); // Create an impersonation token based on the current thread's token HANDLE rawMtaThreadImpToken = nullptr; if (!::DuplicateToken(rawCurThreadImpToken, 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::GeckoContent: return COINIT_MULTITHREADED; 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_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); // Downgrading from a MOZ_DIAGNOSTIC_ASSERT while investigating // bug 1930846. MOZ_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 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; } MOZ_COLD static HRESULT DiagnosticAssertOrHResultFromLastError() { HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); return hr; } /* static */ HRESULT ProcessRuntime::InitializeSecurity(const ProcessCategory aProcessCategory) { // Reserve enough room on the stack for the ACL to hold the maximum number of // ACCESS_ALLOWED_ACEs we might add. The calculation is explained in the // Remarks section of the documentation for InitializeAcl. const size_t kMaxAceCount = 5; constexpr auto requiredACLSize = sizeof(ACL) + (kMaxAceCount * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(ACCESS_ALLOWED_ACE::SidStart) + SECURITY_MAX_SID_SIZE)); static_assert((requiredACLSize % sizeof(DWORD)) == 0, "ACL length must be DWORD aligned."); alignas(DWORD) BYTE aclBytes[requiredACLSize]; auto* pAcl = reinterpret_cast(aclBytes); if (!::InitializeAcl(pAcl, requiredACLSize, ACL_REVISION)) { return DiagnosticAssertOrHResultFromLastError(); } HANDLE rawProcessToken; if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawProcessToken)) { return DiagnosticAssertOrHResultFromLastError(); } nsAutoHandle processToken(rawProcessToken); // Grant access to the user's SID. DWORD len = 0; SE_TOKEN_USER tokenUser; if (!::GetTokenInformation(processToken, TokenUser, &tokenUser, sizeof(tokenUser), &len)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::AddAccessAllowedAce(pAcl, ACL_REVISION, COM_RIGHTS_EXECUTE, &tokenUser.Sid)) { return DiagnosticAssertOrHResultFromLastError(); } // Grant access to the SYSTEM SID. SE_SID wellKnownSid; DWORD wellKnownSidSize = sizeof(wellKnownSid); if (!::CreateWellKnownSid(WinLocalSystemSid, nullptr, &wellKnownSid, &wellKnownSidSize)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::AddAccessAllowedAce(pAcl, ACL_REVISION, COM_RIGHTS_EXECUTE, &wellKnownSid)) { return DiagnosticAssertOrHResultFromLastError(); } // Grant access to the Administrator SID. wellKnownSidSize = sizeof(wellKnownSid); if (!::CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, &wellKnownSid, &wellKnownSidSize)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::AddAccessAllowedAce(pAcl, ACL_REVISION, COM_RIGHTS_EXECUTE, &wellKnownSid)) { return DiagnosticAssertOrHResultFromLastError(); } // If we are the browser process, grant access to all non restricted app // containers. if (aProcessCategory == ProcessCategory::GeckoBrowserParent) { wellKnownSidSize = sizeof(wellKnownSid); if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr, &wellKnownSid, &wellKnownSidSize)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::AddAccessAllowedAce(pAcl, ACL_REVISION, COM_RIGHTS_EXECUTE, &wellKnownSid)) { return DiagnosticAssertOrHResultFromLastError(); } } // If in an app container grant access to it. Don't fail if we get an error // retrieving the app container SID. alignas(TOKEN_APPCONTAINER_INFORMATION) BYTE appContainerInfoBuf[TOKEN_APPCONTAINER_SID_MAX_SIZE]; auto* appContainerInfo = reinterpret_cast(appContainerInfoBuf); bool haveAppContainerSid = ::GetTokenInformation(processToken, TokenAppContainerSid, appContainerInfo, sizeof(appContainerInfoBuf), &len) && appContainerInfo->TokenAppContainer; if (haveAppContainerSid) { if (!::AddAccessAllowedAce(pAcl, ACL_REVISION, COM_RIGHTS_EXECUTE, appContainerInfo->TokenAppContainer)) { return DiagnosticAssertOrHResultFromLastError(); } } alignas(TOKEN_PRIMARY_GROUP) BYTE primaryGroupBuf[sizeof(TOKEN_PRIMARY_GROUP) + SECURITY_MAX_SID_SIZE]; auto* primaryGroup = reinterpret_cast(primaryGroupBuf); if (!::GetTokenInformation(processToken, TokenPrimaryGroup, primaryGroup, sizeof(primaryGroupBuf), &len)) { return DiagnosticAssertOrHResultFromLastError(); } SECURITY_DESCRIPTOR sd; if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::SetSecurityDescriptorDacl(&sd, TRUE, pAcl, FALSE)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::SetSecurityDescriptorOwner(&sd, tokenUser.User.Sid, FALSE)) { return DiagnosticAssertOrHResultFromLastError(); } if (!::SetSecurityDescriptorGroup(&sd, primaryGroup->PrimaryGroup, FALSE)) { return DiagnosticAssertOrHResultFromLastError(); } HRESULT hr = wrapped::CoInitializeSecurity( &sd, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_NONE, nullptr); MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); return hr; } } // namespace mscom } // namespace mozilla