Bug 1707954: Part 1 - Change mscom::ProcessRuntime to ensure MTA creation during startup; r=Jamie

This patch does the following:

* General cleanup:
  * More explicit restrictions of how/when the various constructors are available.
  * `InitializeSecurity` is made `static`, since it doesn't really manipulate instance variables.
  * We move some logic for resolving `CoInitializeEx` flags into `GetDesiredApartmentType`.
* Addition of `PostInit`:
  * This doesn't do anything at the moment, but since I'm already making a bunch
    of changes, I wanted to add this too. `PostInit` is a static method that
    is invoked once the `ProcessRuntime` is finished initializing, and, when
    present, the sandbox is fully enabled.
* We call `EnsureMTA`'s default constructor to eagerly bring up the MTA. This
  causes all background threads to implicitly become members of the MTA, which
  means that we can eliminate `CoInitializeEx` calls throughout our codebase,
  since they're slow and most developers do not have a clear understanding of
  what those functions actually do.
  * This also simplifies the COM initialization in sandboxed content processes
    with Win32K lockdown, since if our main thread is in the implicit MTA, we
    can immediately initialize ourselves without needing to punt that work
    over to the persistent MTA thread.

Differential Revision: https://phabricator.services.mozilla.com/D113560
This commit is contained in:
Aaron Klotz 2021-06-14 21:53:17 +00:00
Родитель e46f2fb521
Коммит 0785438543
3 изменённых файлов: 203 добавлений и 89 удалений

Просмотреть файл

@ -16,14 +16,15 @@
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Vector.h"
#include "mozilla/WindowsProcessMitigations.h"
#include "mozilla/WindowsVersion.h"
#include "nsWindowsHelpers.h"
#if defined(MOZILLA_INTERNAL_API) && defined(MOZ_SANDBOX)
#if defined(MOZILLA_INTERNAL_API)
# include "mozilla/mscom/EnsureMTA.h"
# include "mozilla/sandboxTarget.h"
# include "nsThreadManager.h"
#endif // defined(MOZILLA_INTERNAL_API) && defined(MOZ_SANDBOX)
# if defined(MOZ_SANDBOX)
# include "mozilla/sandboxTarget.h"
# endif // defined(MOZ_SANDBOX)
#endif // defined(MOZILLA_INTERNAL_API)
#include <accctrl.h>
#include <aclapi.h>
@ -36,12 +37,18 @@ extern "C" void __cdecl SetOaNoCache(void);
namespace mozilla {
namespace mscom {
ProcessRuntime::ProcessRuntime(GeckoProcessType aProcessType)
#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(ProcessRuntime::ProcessCategory aProcessCategory)
ProcessRuntime::ProcessRuntime(const ProcessCategory aProcessCategory)
: mInitResult(CO_E_NOTINITIALIZED),
mProcessCategory(aProcessCategory)
#if defined(ACCESSIBILITY) && defined(MOZILLA_INTERNAL_API)
@ -49,23 +56,39 @@ ProcessRuntime::ProcessRuntime(ProcessRuntime::ProcessCategory aProcessCategory)
mActCtxRgn(a11y::Compatibility::GetActCtxResourceId())
#endif // defined(ACCESSIBILITY) && defined(MOZILLA_INTERNAL_API)
{
#if defined(MOZILLA_INTERNAL_API) && defined(MOZ_SANDBOX)
// If our process is running under Win32k lockdown, we cannot initialize
// COM with single-threaded apartments. This is because STAs create a hidden
// window, which implicitly requires user32 and Win32k, which are blocked.
// Instead we start a multi-threaded apartment and conduct our process-wide
// COM initialization on that MTA background thread.
if (mProcessCategory == ProcessCategory::GeckoChild && IsWin32kLockedDown()) {
// It is possible that we're running so early that we might need to start
// the thread manager ourselves.
nsresult rv = nsThreadManager::get().Init();
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
if (NS_FAILED(rv)) {
return;
}
#if defined(MOZILLA_INTERNAL_API)
MOZ_DIAGNOSTIC_ASSERT(!sInstance);
sInstance = this;
// Use the current thread's impersonation token to initialize COM, as
// it might fail otherwise (depending on sandbox policy).
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)) {
@ -74,51 +97,26 @@ ProcessRuntime::ProcessRuntime(ProcessRuntime::ProcessCategory aProcessCategory)
}
nsAutoHandle curThreadImpToken(rawCurThreadImpToken);
# if defined(DEBUG)
// 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_ASSERT(::GetTokenInformation(rawCurThreadImpToken, TokenType,
&tokenType, sizeof(tokenType), &len) &&
len == sizeof(tokenType) && tokenType == TokenImpersonation);
# endif // defined(DEBUG)
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());
// 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;
}
nsAutoHandle mtaThreadImpToken(rawMtaThreadImpToken);
SandboxTarget::Instance()->RegisterSandboxStartCallback([]() -> 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::ForceDispatch);
});
// Impersonate and initialize.
EnsureMTA(
[this, rawMtaThreadImpToken]() -> void {
if (!::SetThreadToken(nullptr, rawMtaThreadImpToken)) {
mInitResult = HRESULT_FROM_WIN32(::GetLastError());
return;
}
InitInsideApartment();
},
EnsureMTA::Option::ForceDispatch);
return;
}
#endif // defined(MOZILLA_INTERNAL_API)
# endif // defined(MOZ_SANDBOX)
#endif // defined(MOZILLA_INTERNAL_API)
mAptRegion.Init(GetDesiredApartmentType(mProcessCategory));
@ -131,18 +129,100 @@ ProcessRuntime::ProcessRuntime(ProcessRuntime::ProcessCategory aProcessCategory)
}
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(
ProcessRuntime::ProcessCategory aProcessCategory) {
// Gecko processes get single-threaded apartments, others get multithreaded
// apartments. We should revisit the GeckoChild case as soon as we deploy
// Win32k lockdown.
const ProcessRuntime::ProcessCategory aProcessCategory) {
switch (aProcessCategory) {
case ProcessCategory::GeckoBrowserParent:
case ProcessCategory::GeckoChild:
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;
}
@ -157,16 +237,20 @@ void ProcessRuntime::InitInsideApartment() {
}
// We are required to initialize security prior to configuring global options.
mInitResult = InitializeSecurity();
MOZ_ASSERT(SUCCEEDED(mInitResult));
if (FAILED(mInitResult)) {
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;
}
RefPtr<IGlobalOptions> globalOpts;
mInitResult = ::CoCreateInstance(CLSID_GlobalOptions, nullptr,
CLSCTX_INPROC_SERVER, IID_IGlobalOptions,
(void**)getter_AddRefs(globalOpts));
mInitResult =
::CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC_SERVER,
IID_IGlobalOptions, getter_AddRefs(globalOpts));
MOZ_ASSERT(SUCCEEDED(mInitResult));
if (FAILED(mInitResult)) {
return;
@ -187,6 +271,16 @@ void ProcessRuntime::InitInsideApartment() {
lock.SetInitialized();
}
#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() {
@ -201,8 +295,9 @@ ProcessRuntime::GetClientThreadId() {
return callerTid;
}
/* static */
HRESULT
ProcessRuntime::InitializeSecurity() {
ProcessRuntime::InitializeSecurity(const ProcessCategory aProcessCategory) {
HANDLE rawToken = nullptr;
BOOL ok = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawToken);
if (!ok) {
@ -259,10 +354,13 @@ ProcessRuntime::InitializeSecurity() {
return HRESULT_FROM_WIN32(::GetLastError());
}
const bool allowAppContainers =
aProcessCategory == ProcessCategory::GeckoBrowserParent &&
IsWin8OrLater();
BYTE appContainersSid[SECURITY_MAX_SID_SIZE];
DWORD appContainersSidSize = sizeof(appContainersSid);
if (mProcessCategory == ProcessCategory::GeckoBrowserParent &&
IsWin8OrLater()) {
if (allowAppContainers) {
if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr,
appContainersSid, &appContainersSidSize)) {
return HRESULT_FROM_WIN32(::GetLastError());
@ -295,8 +393,7 @@ ProcessRuntime::InitializeSecurity() {
{nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER,
reinterpret_cast<LPWSTR>(tokenUser.User.Sid)}});
if (mProcessCategory == ProcessCategory::GeckoBrowserParent &&
IsWin8OrLater()) {
if (allowAppContainers) {
Unused << entries.append(
EXPLICIT_ACCESS_W{COM_RIGHTS_EXECUTE,
GRANT_ACCESS,

Просмотреть файл

@ -12,8 +12,10 @@
# include "mozilla/mscom/ActivationContext.h"
#endif // defined(ACCESSIBILITY) && defined(MOZILLA_INTERNAL_API)
#include "mozilla/mscom/ApartmentRegion.h"
#include "mozilla/WindowsProcessMitigations.h"
#include "nsXULAppAPI.h"
#include "nsWindowsHelpers.h"
#if defined(MOZILLA_INTERNAL_API)
# include "nsXULAppAPI.h"
#endif // defined(MOZILLA_INTERNAL_API)
namespace mozilla {
namespace mscom {
@ -32,23 +34,22 @@ class MOZ_NON_TEMPORARY_CLASS ProcessRuntime final {
};
// This constructor is only public when compiled outside of XUL
explicit ProcessRuntime(ProcessCategory aProcessCategory);
explicit ProcessRuntime(const ProcessCategory aProcessCategory);
public:
#if defined(MOZILLA_INTERNAL_API)
ProcessRuntime() : ProcessRuntime(XRE_GetProcessType()) {}
#endif // defined(MOZILLA_INTERNAL_API)
explicit ProcessRuntime(GeckoProcessType aProcessType);
ProcessRuntime();
~ProcessRuntime();
#else
~ProcessRuntime() = default;
#endif // defined(MOZILLA_INTERNAL_API)
explicit operator bool() const { return SUCCEEDED(mInitResult); }
HRESULT GetHResult() const { return mInitResult; }
ProcessRuntime(ProcessRuntime&) = delete;
ProcessRuntime(const ProcessRuntime&) = delete;
ProcessRuntime(ProcessRuntime&&) = delete;
ProcessRuntime& operator=(ProcessRuntime&) = delete;
ProcessRuntime& operator=(const ProcessRuntime&) = delete;
ProcessRuntime& operator=(ProcessRuntime&&) = delete;
/**
@ -58,16 +59,32 @@ class MOZ_NON_TEMPORARY_CLASS ProcessRuntime final {
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();
HRESULT InitializeSecurity();
static COINIT GetDesiredApartmentType(ProcessCategory aProcessCategory);
#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;
#if defined(ACCESSIBILITY) && defined(MOZILLA_INTERNAL_API)
ActivationContextRegion mActCtxRgn;
#endif // defined(ACCESSIBILITY) && defined(MOZILLA_INTERNAL_API)
ApartmentRegion mAptRegion;
private:
#if defined(MOZILLA_INTERNAL_API)
static ProcessRuntime* sInstance;
#endif // defined(MOZILLA_INTERNAL_API)
};
} // namespace mscom

Просмотреть файл

@ -25,6 +25,6 @@ MFBT_API bool& BeginProcessRuntimeInit() {
MFBT_API void EndProcessRuntimeInit() { gLock.UnlockExclusive(); }
} // namespace detail
} // namespace detail
} // namespace mscom
} // namespace mozilla