ProcessRuntime.cpp (18025B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/mscom/ProcessRuntime.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/DynamicallyLinkedFunctionPtr.h" 11 #include "mozilla/mscom/COMWrappers.h" 12 #include "mozilla/mscom/ProcessRuntimeShared.h" 13 #include "mozilla/RefPtr.h" 14 #include "mozilla/UniquePtr.h" 15 #include "mozilla/Vector.h" 16 #include "mozilla/WindowsProcessMitigations.h" 17 18 #if defined(MOZILLA_INTERNAL_API) 19 # include "mozilla/mscom/EnsureMTA.h" 20 # if defined(MOZ_SANDBOX) 21 # include "mozilla/sandboxTarget.h" 22 # endif // defined(MOZ_SANDBOX) 23 #endif // defined(MOZILLA_INTERNAL_API) 24 25 #include <accctrl.h> 26 #include <aclapi.h> 27 #include <objbase.h> 28 #include <objidl.h> 29 30 // This API from oleaut32.dll is not declared in Windows SDK headers 31 extern "C" void __cdecl SetOaNoCache(void); 32 33 using namespace mozilla::mscom::detail; 34 35 namespace mozilla { 36 namespace mscom { 37 38 #if defined(MOZILLA_INTERNAL_API) 39 ProcessRuntime* ProcessRuntime::sInstance = nullptr; 40 41 ProcessRuntime::ProcessRuntime() : ProcessRuntime(XRE_GetProcessType()) {} 42 43 ProcessRuntime::ProcessRuntime(const GeckoProcessType aProcessType) 44 : ProcessRuntime(aProcessType == GeckoProcessType_Default 45 ? ProcessCategory::GeckoBrowserParent 46 : ProcessCategory::GeckoChild) {} 47 #endif // defined(MOZILLA_INTERNAL_API) 48 49 ProcessRuntime::ProcessRuntime(const ProcessCategory aProcessCategory) 50 : mInitResult(CO_E_NOTINITIALIZED), mProcessCategory(aProcessCategory) { 51 #if defined(MOZILLA_INTERNAL_API) 52 MOZ_DIAGNOSTIC_ASSERT(!sInstance); 53 sInstance = this; 54 55 EnsureMTA(); 56 /** 57 * From this point forward, all threads in this process are implicitly 58 * members of the multi-threaded apartment, with the following exceptions: 59 * 1. If any Win32 GUI APIs were called on the current thread prior to 60 * executing this constructor, then this thread has already been implicitly 61 * initialized as the process's main STA thread; or 62 * 2. A thread explicitly and successfully calls CoInitialize(Ex) to specify 63 * otherwise. 64 */ 65 66 const bool isCurThreadImplicitMTA = IsCurrentThreadImplicitMTA(); 67 // We only assert that the implicit MTA precondition holds when not running 68 // as the Gecko parent process. 69 MOZ_DIAGNOSTIC_ASSERT(aProcessCategory == 70 ProcessCategory::GeckoBrowserParent || 71 isCurThreadImplicitMTA); 72 73 # if defined(MOZ_SANDBOX) 74 const bool isLockedDownChildProcess = 75 mProcessCategory == ProcessCategory::GeckoChild && IsWin32kLockedDown(); 76 // If our process is running under Win32k lockdown, we cannot initialize 77 // COM with a single-threaded apartment. This is because STAs create a hidden 78 // window, which implicitly requires user32 and Win32k, which are blocked. 79 // Instead we start the multi-threaded apartment and conduct our process-wide 80 // COM initialization there. 81 if (isLockedDownChildProcess) { 82 // Make sure we're still running with the sandbox's privileged impersonation 83 // token. 84 HANDLE rawCurThreadImpToken; 85 if (!::OpenThreadToken(::GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, 86 FALSE, &rawCurThreadImpToken)) { 87 mInitResult = HRESULT_FROM_WIN32(::GetLastError()); 88 return; 89 } 90 nsAutoHandle curThreadImpToken(rawCurThreadImpToken); 91 92 // Ensure that our current token is still an impersonation token (ie, we 93 // have not yet called RevertToSelf() on this thread). 94 DWORD len; 95 TOKEN_TYPE tokenType; 96 MOZ_RELEASE_ASSERT( 97 ::GetTokenInformation(rawCurThreadImpToken, TokenType, &tokenType, 98 sizeof(tokenType), &len) && 99 len == sizeof(tokenType) && tokenType == TokenImpersonation); 100 101 // Ideally we want our current thread to be running implicitly inside the 102 // MTA, but if for some wacky reason we did not end up with that, we may 103 // compensate by completing initialization via EnsureMTA's persistent 104 // thread. 105 if (!isCurThreadImplicitMTA) { 106 InitUsingPersistentMTAThread(curThreadImpToken); 107 return; 108 } 109 } 110 # endif // defined(MOZ_SANDBOX) 111 #endif // defined(MOZILLA_INTERNAL_API) 112 113 mAptRegion.Init(GetDesiredApartmentType(mProcessCategory)); 114 115 // It can happen that we are not the outermost COM initialization on this 116 // thread. In fact it should regularly be the case that the outermost 117 // initialization occurs from outside of XUL, before we show the skeleton UI, 118 // at which point we still need to run some things here from within XUL. 119 if (!mAptRegion.IsValidOutermost()) { 120 mInitResult = mAptRegion.GetHResult(); 121 #if defined(MOZILLA_INTERNAL_API) 122 MOZ_ASSERT(mProcessCategory == ProcessCategory::GeckoBrowserParent); 123 if (mProcessCategory != ProcessCategory::GeckoBrowserParent) { 124 // This is unexpected unless we're GeckoBrowserParent 125 return; 126 } 127 128 ProcessInitLock lock; 129 130 // Is another instance of ProcessRuntime responsible for the outer 131 // initialization? 132 const bool prevInit = 133 lock.GetInitState() == ProcessInitState::FullyInitialized; 134 MOZ_ASSERT(prevInit); 135 if (prevInit) { 136 PostInit(); 137 } 138 #endif // defined(MOZILLA_INTERNAL_API) 139 return; 140 } 141 142 InitInsideApartment(); 143 if (FAILED(mInitResult)) { 144 return; 145 } 146 147 #if defined(MOZILLA_INTERNAL_API) 148 # if defined(MOZ_SANDBOX) 149 if (isLockedDownChildProcess) { 150 // In locked-down child processes, defer PostInit until priv drop 151 SandboxTarget::Instance()->RegisterSandboxStartCallback([self = this]() { 152 // Ensure that we're still live and the init was successful before 153 // calling PostInit() 154 if (self == sInstance && SUCCEEDED(self->mInitResult)) { 155 PostInit(); 156 } 157 }); 158 return; 159 } 160 # endif // defined(MOZ_SANDBOX) 161 162 PostInit(); 163 #endif // defined(MOZILLA_INTERNAL_API) 164 } 165 166 #if defined(MOZILLA_INTERNAL_API) 167 ProcessRuntime::~ProcessRuntime() { 168 MOZ_DIAGNOSTIC_ASSERT(sInstance == this); 169 sInstance = nullptr; 170 } 171 172 # if defined(MOZ_SANDBOX) 173 void ProcessRuntime::InitUsingPersistentMTAThread( 174 const nsAutoHandle& aCurThreadToken) { 175 // Create an impersonation token based on the current thread's token 176 HANDLE rawMtaThreadImpToken = nullptr; 177 if (!::DuplicateToken(aCurThreadToken, SecurityImpersonation, 178 &rawMtaThreadImpToken)) { 179 mInitResult = HRESULT_FROM_WIN32(::GetLastError()); 180 return; 181 } 182 nsAutoHandle mtaThreadImpToken(rawMtaThreadImpToken); 183 184 // Impersonate and initialize. 185 bool tokenSet = false; 186 EnsureMTA( 187 [this, rawMtaThreadImpToken, &tokenSet]() -> void { 188 if (!::SetThreadToken(nullptr, rawMtaThreadImpToken)) { 189 mInitResult = HRESULT_FROM_WIN32(::GetLastError()); 190 return; 191 } 192 193 tokenSet = true; 194 InitInsideApartment(); 195 }, 196 EnsureMTA::Option::ForceDispatchToPersistentThread); 197 198 if (!tokenSet) { 199 return; 200 } 201 202 SandboxTarget::Instance()->RegisterSandboxStartCallback( 203 [self = this]() -> void { 204 EnsureMTA( 205 []() -> void { 206 // This is a security risk if it fails, so we release assert 207 MOZ_RELEASE_ASSERT(::RevertToSelf(), 208 "mscom::ProcessRuntime RevertToSelf failed"); 209 }, 210 EnsureMTA::Option::ForceDispatchToPersistentThread); 211 212 // Ensure that we're still live and the init was successful before 213 // calling PostInit() 214 if (self == sInstance && SUCCEEDED(self->mInitResult)) { 215 PostInit(); 216 } 217 }); 218 } 219 # endif // defined(MOZ_SANDBOX) 220 #endif // defined(MOZILLA_INTERNAL_API) 221 222 /* static */ 223 COINIT ProcessRuntime::GetDesiredApartmentType( 224 const ProcessRuntime::ProcessCategory aProcessCategory) { 225 switch (aProcessCategory) { 226 case ProcessCategory::GeckoBrowserParent: 227 return COINIT_APARTMENTTHREADED; 228 case ProcessCategory::GeckoChild: 229 if (!IsWin32kLockedDown()) { 230 // If Win32k is not locked down then we probably still need STA. 231 // We disable DDE since that is not usable from child processes. 232 return static_cast<COINIT>(COINIT_APARTMENTTHREADED | 233 COINIT_DISABLE_OLE1DDE); 234 } 235 236 [[fallthrough]]; 237 default: 238 return COINIT_MULTITHREADED; 239 } 240 } 241 242 void ProcessRuntime::InitInsideApartment() { 243 ProcessInitLock lock; 244 const ProcessInitState prevInitState = lock.GetInitState(); 245 if (prevInitState == ProcessInitState::FullyInitialized) { 246 // COM has already been initialized by a previous ProcessRuntime instance 247 mInitResult = S_OK; 248 return; 249 } 250 251 if (prevInitState < ProcessInitState::PartialSecurityInitialized) { 252 // We are required to initialize security prior to configuring global 253 // options. 254 mInitResult = InitializeSecurity(mProcessCategory); 255 // Downgrading from a MOZ_DIAGNOSTIC_ASSERT while investigating 256 // bug 1930846. 257 MOZ_ASSERT(SUCCEEDED(mInitResult)); 258 259 // Even though this isn't great, we should try to proceed even when 260 // CoInitializeSecurity has previously been called: the additional settings 261 // we want to change are important enough that we don't want to skip them. 262 if (FAILED(mInitResult) && mInitResult != RPC_E_TOO_LATE) { 263 return; 264 } 265 266 lock.SetInitState(ProcessInitState::PartialSecurityInitialized); 267 } 268 269 if (prevInitState < ProcessInitState::PartialGlobalOptions) { 270 RefPtr<IGlobalOptions> globalOpts; 271 mInitResult = wrapped::CoCreateInstance( 272 CLSID_GlobalOptions, nullptr, CLSCTX_INPROC_SERVER, IID_IGlobalOptions, 273 getter_AddRefs(globalOpts)); 274 MOZ_ASSERT(SUCCEEDED(mInitResult)); 275 if (FAILED(mInitResult)) { 276 return; 277 } 278 279 // Disable COM's catch-all exception handler 280 mInitResult = globalOpts->Set(COMGLB_EXCEPTION_HANDLING, 281 COMGLB_EXCEPTION_DONOT_HANDLE_ANY); 282 MOZ_ASSERT(SUCCEEDED(mInitResult)); 283 if (FAILED(mInitResult)) { 284 return; 285 } 286 287 lock.SetInitState(ProcessInitState::PartialGlobalOptions); 288 } 289 290 // Disable the BSTR cache (as it never invalidates, thus leaking memory) 291 // (This function is itself idempotent, so we do not concern ourselves with 292 // tracking whether or not we've already called it.) 293 ::SetOaNoCache(); 294 295 lock.SetInitState(ProcessInitState::FullyInitialized); 296 } 297 298 #if defined(MOZILLA_INTERNAL_API) 299 /** 300 * Guaranteed to run *after* the COM (and possible sandboxing) initialization 301 * has successfully completed and stabilized. This method MUST BE IDEMPOTENT! 302 */ 303 /* static */ void ProcessRuntime::PostInit() { 304 // Currently "roughed-in" but unused. 305 } 306 #endif // defined(MOZILLA_INTERNAL_API) 307 308 /* static */ 309 DWORD 310 ProcessRuntime::GetClientThreadId() { 311 DWORD callerTid; 312 HRESULT hr = ::CoGetCallerTID(&callerTid); 313 // Don't return callerTid unless the call succeeded and returned S_FALSE, 314 // indicating that the caller originates from a different process. 315 if (hr != S_FALSE) { 316 return 0; 317 } 318 319 return callerTid; 320 } 321 322 /* static */ 323 HRESULT 324 ProcessRuntime::InitializeSecurity(const ProcessCategory aProcessCategory) { 325 HANDLE rawToken = nullptr; 326 BOOL ok = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawToken); 327 if (!ok) { 328 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 329 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 330 return hr; 331 } 332 nsAutoHandle token(rawToken); 333 334 DWORD len = 0; 335 ok = ::GetTokenInformation(token, TokenUser, nullptr, len, &len); 336 DWORD win32Error = ::GetLastError(); 337 if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) { 338 HRESULT hr = HRESULT_FROM_WIN32(win32Error); 339 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 340 return hr; 341 } 342 343 auto tokenUserBuf = MakeUnique<BYTE[]>(len); 344 TOKEN_USER& tokenUser = *reinterpret_cast<TOKEN_USER*>(tokenUserBuf.get()); 345 ok = ::GetTokenInformation(token, TokenUser, tokenUserBuf.get(), len, &len); 346 if (!ok) { 347 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 348 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 349 return hr; 350 } 351 352 len = 0; 353 ok = ::GetTokenInformation(token, TokenPrimaryGroup, nullptr, len, &len); 354 win32Error = ::GetLastError(); 355 if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) { 356 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 357 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 358 return hr; 359 } 360 361 auto tokenPrimaryGroupBuf = MakeUnique<BYTE[]>(len); 362 TOKEN_PRIMARY_GROUP& tokenPrimaryGroup = 363 *reinterpret_cast<TOKEN_PRIMARY_GROUP*>(tokenPrimaryGroupBuf.get()); 364 ok = ::GetTokenInformation(token, TokenPrimaryGroup, 365 tokenPrimaryGroupBuf.get(), len, &len); 366 if (!ok) { 367 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 368 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 369 return hr; 370 } 371 372 SECURITY_DESCRIPTOR sd; 373 if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { 374 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 375 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 376 return hr; 377 } 378 379 BYTE systemSid[SECURITY_MAX_SID_SIZE]; 380 DWORD systemSidSize = sizeof(systemSid); 381 if (!::CreateWellKnownSid(WinLocalSystemSid, nullptr, systemSid, 382 &systemSidSize)) { 383 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 384 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 385 return hr; 386 } 387 388 BYTE adminSid[SECURITY_MAX_SID_SIZE]; 389 DWORD adminSidSize = sizeof(adminSid); 390 if (!::CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, adminSid, 391 &adminSidSize)) { 392 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 393 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 394 return hr; 395 } 396 397 const bool allowAllNonRestrictedAppContainers = 398 aProcessCategory == ProcessCategory::GeckoBrowserParent; 399 400 BYTE appContainersSid[SECURITY_MAX_SID_SIZE]; 401 DWORD appContainersSidSize = sizeof(appContainersSid); 402 if (allowAllNonRestrictedAppContainers) { 403 if (!::CreateWellKnownSid(WinBuiltinAnyPackageSid, nullptr, 404 appContainersSid, &appContainersSidSize)) { 405 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); 406 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 407 return hr; 408 } 409 } 410 411 UniquePtr<BYTE[]> tokenAppContainerInfBuf; 412 len = 0; 413 ::GetTokenInformation(token, TokenAppContainerSid, nullptr, len, &len); 414 if (len) { 415 tokenAppContainerInfBuf = MakeUnique<BYTE[]>(len); 416 ok = ::GetTokenInformation(token, TokenAppContainerSid, 417 tokenAppContainerInfBuf.get(), len, &len); 418 if (!ok) { 419 // Don't fail if we get an error retrieving an app container SID. 420 tokenAppContainerInfBuf = nullptr; 421 } 422 } 423 424 // Grant access to SYSTEM, Administrators, the user, our app container (if in 425 // one) and when running as the browser process on Windows 8+, all non 426 // restricted app containers. 427 const size_t kMaxInlineEntries = 5; 428 mozilla::Vector<EXPLICIT_ACCESS_W, kMaxInlineEntries> entries; 429 430 (void)entries.append(EXPLICIT_ACCESS_W{ 431 COM_RIGHTS_EXECUTE, 432 GRANT_ACCESS, 433 NO_INHERITANCE, 434 {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, 435 reinterpret_cast<LPWSTR>(systemSid)}}); 436 437 (void)entries.append(EXPLICIT_ACCESS_W{ 438 COM_RIGHTS_EXECUTE, 439 GRANT_ACCESS, 440 NO_INHERITANCE, 441 {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, 442 TRUSTEE_IS_WELL_KNOWN_GROUP, reinterpret_cast<LPWSTR>(adminSid)}}); 443 444 (void)entries.append(EXPLICIT_ACCESS_W{ 445 COM_RIGHTS_EXECUTE, 446 GRANT_ACCESS, 447 NO_INHERITANCE, 448 {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, 449 reinterpret_cast<LPWSTR>(tokenUser.User.Sid)}}); 450 451 if (allowAllNonRestrictedAppContainers) { 452 (void)entries.append( 453 EXPLICIT_ACCESS_W{COM_RIGHTS_EXECUTE, 454 GRANT_ACCESS, 455 NO_INHERITANCE, 456 {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, 457 TRUSTEE_IS_WELL_KNOWN_GROUP, 458 reinterpret_cast<LPWSTR>(appContainersSid)}}); 459 } 460 461 if (tokenAppContainerInfBuf) { 462 TOKEN_APPCONTAINER_INFORMATION& tokenAppContainerInf = 463 *reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>( 464 tokenAppContainerInfBuf.get()); 465 466 // TokenAppContainer will be null if we are not in an app container. 467 if (tokenAppContainerInf.TokenAppContainer) { 468 (void)entries.append(EXPLICIT_ACCESS_W{ 469 COM_RIGHTS_EXECUTE, 470 GRANT_ACCESS, 471 NO_INHERITANCE, 472 {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, 473 reinterpret_cast<LPWSTR>(tokenAppContainerInf.TokenAppContainer)}}); 474 } 475 } 476 477 PACL rawDacl = nullptr; 478 win32Error = 479 ::SetEntriesInAclW(entries.length(), entries.begin(), nullptr, &rawDacl); 480 if (win32Error != ERROR_SUCCESS) { 481 HRESULT hr = HRESULT_FROM_WIN32(win32Error); 482 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 483 return hr; 484 } 485 486 UniquePtr<ACL, LocalFreeDeleter> dacl(rawDacl); 487 488 if (!::SetSecurityDescriptorDacl(&sd, TRUE, dacl.get(), FALSE)) { 489 HRESULT hr = HRESULT_FROM_WIN32(win32Error); 490 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 491 return hr; 492 } 493 494 if (!::SetSecurityDescriptorOwner(&sd, tokenUser.User.Sid, FALSE)) { 495 HRESULT hr = HRESULT_FROM_WIN32(win32Error); 496 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 497 return hr; 498 } 499 500 if (!::SetSecurityDescriptorGroup(&sd, tokenPrimaryGroup.PrimaryGroup, 501 FALSE)) { 502 HRESULT hr = HRESULT_FROM_WIN32(win32Error); 503 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 504 return hr; 505 } 506 507 HRESULT hr = wrapped::CoInitializeSecurity( 508 &sd, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, 509 RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_NONE, nullptr); 510 MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); 511 return hr; 512 } 513 514 } // namespace mscom 515 } // namespace mozilla