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