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