Bug 1909613 - Enable <details name=''> everywhere, r=emilio
[gecko.git] / browser / app / winlauncher / LauncherProcessWin.cpp
blob8167d2b81c918e02ce757f7f448f22e07c29d140
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 https://mozilla.org/MPL/2.0/. */
7 #include "LauncherProcessWin.h"
9 #include <string.h>
11 #include "mozilla/Attributes.h"
12 #include "mozilla/CmdLineAndEnvUtils.h"
13 #include "mozilla/DebugOnly.h"
14 #include "mozilla/DynamicallyLinkedFunctionPtr.h"
15 #include "mozilla/glue/Debug.h"
16 #include "mozilla/GeckoArgs.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/NativeNt.h"
19 #include "mozilla/SafeMode.h"
20 #include "mozilla/UniquePtr.h"
21 #include "mozilla/WindowsConsole.h"
22 #include "mozilla/WindowsVersion.h"
23 #include "mozilla/WinHeaderOnlyUtils.h"
24 #include "nsWindowsHelpers.h"
26 #include <windows.h>
27 #include <processthreadsapi.h>
29 #include "DllBlocklistInit.h"
30 #include "ErrorHandler.h"
31 #include "LaunchUnelevated.h"
32 #include "ProcThreadAttributes.h"
33 #include "../BrowserDefines.h"
35 #if defined(MOZ_LAUNCHER_PROCESS)
36 # include "mozilla/LauncherRegistryInfo.h"
37 # include "SameBinary.h"
38 #endif // defined(MOZ_LAUNCHER_PROCESS)
40 #if defined(MOZ_SANDBOX)
41 # include "mozilla/sandboxing/SandboxInitialization.h"
42 #endif
44 namespace mozilla {
45 // "const" because nothing in this process modifies it.
46 // "volatile" because something in another process may.
47 const volatile DeelevationStatus gDeelevationStatus =
48 DeelevationStatus::DefaultStaticValue;
49 } // namespace mozilla
51 /**
52 * At this point the child process has been created in a suspended state. Any
53 * additional startup work (eg, blocklist setup) should go here.
55 * @return Ok if browser startup should proceed
57 static mozilla::LauncherVoidResult PostCreationSetup(
58 const wchar_t* aFullImagePath, HANDLE aChildProcess,
59 HANDLE aChildMainThread, mozilla::DeelevationStatus aDStatus,
60 const bool aIsSafeMode, const bool aDisableDynamicBlocklist,
61 mozilla::Maybe<std::wstring> aBlocklistFileName) {
62 /* scope for txManager */ {
63 mozilla::nt::CrossExecTransferManager txManager(aChildProcess);
64 if (!txManager) {
65 return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
68 using mozilla::gDeelevationStatus;
70 void* targetAddress = (LPVOID)&gDeelevationStatus;
72 auto const guard = txManager.Protect(
73 targetAddress, sizeof(gDeelevationStatus), PAGE_READWRITE);
75 mozilla::LauncherVoidResult result =
76 txManager.Transfer(targetAddress, &aDStatus, sizeof(aDStatus));
77 if (result.isErr()) {
78 return result;
82 return mozilla::InitializeDllBlocklistOOPFromLauncher(
83 aFullImagePath, aChildProcess, aDisableDynamicBlocklist,
84 aBlocklistFileName);
87 /**
88 * Create a new Job object and assign |aProcess| to it. If something fails
89 * in this function, we return nullptr but continue without recording
90 * a launcher failure because it's not a critical problem to launch
91 * the browser process.
93 static nsReturnRef<HANDLE> CreateJobAndAssignProcess(HANDLE aProcess) {
94 nsAutoHandle empty;
95 nsAutoHandle job(::CreateJobObjectW(nullptr, nullptr));
97 // Set JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK to put only browser process
98 // into a job without putting children of browser process into the job.
99 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
100 jobInfo.BasicLimitInformation.LimitFlags =
101 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
102 if (!::SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
103 &jobInfo, sizeof(jobInfo))) {
104 return empty.out();
107 if (!::AssignProcessToJobObject(job.get(), aProcess)) {
108 return empty.out();
111 return job.out();
114 #if !defined( \
115 PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON)
116 # define PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON \
117 (0x00000001ULL << 60)
118 #endif // !defined(PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON)
120 #if !defined(PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF)
121 # define PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF \
122 (0x00000002ULL << 40)
123 #endif // !defined(PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF)
126 * Any mitigation policies that should be set on the browser process should go
127 * here.
129 static void SetMitigationPolicies(mozilla::ProcThreadAttributes& aAttrs,
130 const bool aIsSafeMode) {
131 if (mozilla::IsWin10AnniversaryUpdateOrLater()) {
132 aAttrs.AddMitigationPolicy(
133 PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON);
136 #if defined(_M_ARM64)
137 // Disable CFG on older versions of ARM64 Windows to avoid a crash in COM.
138 if (!mozilla::IsWin10Sep2018UpdateOrLater()) {
139 aAttrs.AddMitigationPolicy(
140 PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF);
142 #endif // defined(_M_ARM64)
145 static mozilla::LauncherFlags ProcessCmdLine(int& aArgc, wchar_t* aArgv[]) {
146 mozilla::LauncherFlags result = mozilla::LauncherFlags::eNone;
148 if (mozilla::CheckArg(aArgc, aArgv, "wait-for-browser", nullptr,
149 mozilla::CheckArgFlag::RemoveArg) ==
150 mozilla::ARG_FOUND ||
151 mozilla::CheckArg(aArgc, aArgv, "marionette", nullptr,
152 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
153 mozilla::CheckArg(aArgc, aArgv, "backgroundtask", nullptr,
154 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
155 mozilla::CheckArg(aArgc, aArgv, "headless", nullptr,
156 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
157 mozilla::CheckArg(aArgc, aArgv, "remote-debugging-port", nullptr,
158 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
159 mozilla::EnvHasValue("MOZ_AUTOMATION") ||
160 mozilla::EnvHasValue("MOZ_HEADLESS")) {
161 result |= mozilla::LauncherFlags::eWaitForBrowser;
164 if (mozilla::CheckArg(aArgc, aArgv, "no-deelevate") == mozilla::ARG_FOUND) {
165 result |= mozilla::LauncherFlags::eNoDeelevate;
168 if (mozilla::CheckArg(aArgc, aArgv, ATTEMPTING_DEELEVATION_FLAG) ==
169 mozilla::ARG_FOUND) {
170 result |= mozilla::LauncherFlags::eDeelevating;
173 return result;
176 static void MaybeBreakForBrowserDebugging() {
177 if (mozilla::EnvHasValue("MOZ_DEBUG_BROWSER_PROCESS")) {
178 ::DebugBreak();
179 return;
182 const wchar_t* pauseLenS = _wgetenv(L"MOZ_DEBUG_BROWSER_PAUSE");
183 if (!pauseLenS || !(*pauseLenS)) {
184 return;
187 DWORD pauseLenMs = wcstoul(pauseLenS, nullptr, 10) * 1000;
188 printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n debug me @ %lu\n\n",
189 ::GetCurrentProcessId());
190 ::Sleep(pauseLenMs);
193 static bool DoLauncherProcessChecks(int& argc, wchar_t** argv) {
194 // NB: We run all tests in this function instead of returning early in order
195 // to ensure that all side effects take place, such as clearing environment
196 // variables.
197 bool result = false;
199 #if defined(MOZ_LAUNCHER_PROCESS)
200 // We still prefer to compare file ids. Comparing NT paths i.e. passing
201 // CompareNtPathsOnly to IsSameBinaryAsParentProcess is much faster, but
202 // we're not 100% sure that NT path comparison perfectly prevents the
203 // launching loop of the launcher process.
204 mozilla::LauncherResult<bool> isSame = mozilla::IsSameBinaryAsParentProcess();
205 if (isSame.isOk()) {
206 result = !isSame.unwrap();
207 } else {
208 HandleLauncherError(isSame.unwrapErr());
210 #endif // defined(MOZ_LAUNCHER_PROCESS)
212 if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
213 mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
214 result = true;
217 result |=
218 mozilla::CheckArg(argc, argv, "launcher", nullptr,
219 mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
221 return result;
224 #if defined(MOZ_LAUNCHER_PROCESS)
225 static mozilla::Maybe<bool> RunAsLauncherProcess(
226 mozilla::LauncherRegistryInfo& aRegInfo, int& argc, wchar_t** argv) {
227 #else
228 static mozilla::Maybe<bool> RunAsLauncherProcess(int& argc, wchar_t** argv) {
229 #endif // defined(MOZ_LAUNCHER_PROCESS)
230 bool runAsLauncher = DoLauncherProcessChecks(argc, argv);
232 #if defined(MOZ_LAUNCHER_PROCESS)
233 bool forceLauncher =
234 runAsLauncher &&
235 mozilla::CheckArg(argc, argv, "force-launcher", nullptr,
236 mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
238 mozilla::LauncherRegistryInfo::ProcessType desiredType =
239 runAsLauncher ? mozilla::LauncherRegistryInfo::ProcessType::Launcher
240 : mozilla::LauncherRegistryInfo::ProcessType::Browser;
242 mozilla::LauncherRegistryInfo::CheckOption checkOption =
243 forceLauncher ? mozilla::LauncherRegistryInfo::CheckOption::Force
244 : mozilla::LauncherRegistryInfo::CheckOption::Default;
246 mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType>
247 runAsType = aRegInfo.Check(desiredType, checkOption);
249 if (runAsType.isErr()) {
250 mozilla::HandleLauncherError(runAsType);
251 return mozilla::Nothing();
254 runAsLauncher = runAsType.unwrap() ==
255 mozilla::LauncherRegistryInfo::ProcessType::Launcher;
256 #endif // defined(MOZ_LAUNCHER_PROCESS)
258 if (!runAsLauncher) {
259 // In this case, we will be proceeding to run as the browser.
260 // We should check MOZ_DEBUG_BROWSER_* env vars.
261 MaybeBreakForBrowserDebugging();
264 return mozilla::Some(runAsLauncher);
267 namespace mozilla {
269 Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
270 const StaticXREAppData& aAppData) {
271 EnsureBrowserCommandlineSafe(argc, argv);
273 SetLauncherErrorAppData(aAppData);
275 if (CheckArg(argc, argv, "log-launcher-error", nullptr,
276 mozilla::CheckArgFlag::RemoveArg) == ARG_FOUND) {
277 SetLauncherErrorForceEventLog();
280 // return fast when we're a child process.
281 // (The remainder of this function has some side effects that are
282 // undesirable for content processes)
283 if (mozilla::CheckArg(argc, argv, "contentproc", nullptr,
284 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND) {
285 // A child process should not instantiate LauncherRegistryInfo.
286 return Nothing();
289 #if defined(MOZ_LAUNCHER_PROCESS)
290 LauncherRegistryInfo regInfo;
291 Maybe<bool> runAsLauncher = RunAsLauncherProcess(regInfo, argc, argv);
292 LauncherResult<std::wstring> blocklistFileNameResult =
293 regInfo.GetBlocklistFileName();
294 Maybe<std::wstring> blocklistFileName =
295 blocklistFileNameResult.isOk() ? Some(blocklistFileNameResult.unwrap())
296 : Nothing();
297 #else
298 Maybe<bool> runAsLauncher = RunAsLauncherProcess(argc, argv);
299 Maybe<std::wstring> blocklistFileName = Nothing();
300 #endif // defined(MOZ_LAUNCHER_PROCESS)
301 if (!runAsLauncher || !runAsLauncher.value()) {
302 #if defined(MOZ_LAUNCHER_PROCESS)
303 // Update the registry as Browser
304 LauncherVoidResult commitResult = regInfo.Commit();
305 if (commitResult.isErr()) {
306 mozilla::HandleLauncherError(commitResult);
308 #endif // defined(MOZ_LAUNCHER_PROCESS)
309 return Nothing();
312 // Make sure that the launcher process itself has image load policies set
313 if (IsWin10AnniversaryUpdateOrLater()) {
314 static const StaticDynamicallyLinkedFunctionPtr<
315 decltype(&SetProcessMitigationPolicy)>
316 pSetProcessMitigationPolicy(L"kernel32.dll",
317 "SetProcessMitigationPolicy");
318 if (pSetProcessMitigationPolicy) {
319 PROCESS_MITIGATION_IMAGE_LOAD_POLICY imgLoadPol = {};
320 imgLoadPol.PreferSystem32Images = 1;
322 DebugOnly<BOOL> setOk = pSetProcessMitigationPolicy(
323 ProcessImageLoadPolicy, &imgLoadPol, sizeof(imgLoadPol));
324 MOZ_ASSERT(setOk);
328 #if defined(MOZ_SANDBOX)
329 // Ensure the relevant mitigations are enforced.
330 mozilla::sandboxing::ApplyParentProcessMitigations();
331 #endif
333 mozilla::UseParentConsole();
335 if (!SetArgv0ToFullBinaryPath(argv)) {
336 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
337 return Nothing();
340 LauncherFlags flags = ProcessCmdLine(argc, argv);
342 nsAutoHandle mediumIlToken;
343 LauncherResult<ElevationState> elevationState =
344 GetElevationState(argv[0], flags, mediumIlToken);
345 if (elevationState.isErr()) {
346 HandleLauncherError(elevationState);
347 return Nothing();
350 // Distill deelevation status, and/or attempt to perform launcher deelevation
351 // via an indirect relaunch.
352 DeelevationStatus deelevationStatus = DeelevationStatus::Unknown;
353 if (mediumIlToken.get()) {
354 // Rather than indirectly relaunch the launcher, we'll attempt to directly
355 // launch the main process with a reduced-privilege security token.
356 deelevationStatus = DeelevationStatus::PartiallyDeelevated;
357 } else if (elevationState.unwrap() == ElevationState::eElevated) {
358 if (flags & LauncherFlags::eWaitForBrowser) {
359 // An indirect relaunch won't provide a process-handle to block on,
360 // so we have to continue onwards with this process.
361 deelevationStatus = DeelevationStatus::DeelevationProhibited;
362 } else if (flags & LauncherFlags::eNoDeelevate) {
363 // Our invoker (hopefully, the user) has explicitly requested that the
364 // launcher not deelevate itself.
365 deelevationStatus = DeelevationStatus::DeelevationProhibited;
366 } else if (flags & LauncherFlags::eDeelevating) {
367 // We've already tried to deelevate, to no effect. Continue onward.
368 deelevationStatus = DeelevationStatus::UnsuccessfullyDeelevated;
369 } else {
370 // Otherwise, attempt to relaunch the launcher process itself via the
371 // shell, which hopefully will not be elevated. (But see bug 1733821.)
372 LauncherVoidResult launchedUnelevated = LaunchUnelevated(argc, argv);
373 if (launchedUnelevated.isErr()) {
374 // On failure, don't even try for a launcher process. Continue onwards
375 // in this one. (TODO: why? This isn't technically fatal...)
376 HandleLauncherError(launchedUnelevated);
377 return Nothing();
379 // Otherwise, tell our caller to exit with a success code.
380 return Some(0);
382 } else if (elevationState.unwrap() == ElevationState::eNormalUser) {
383 if (flags & LauncherFlags::eDeelevating) {
384 // Deelevation appears to have been successful!
385 deelevationStatus = DeelevationStatus::SuccessfullyDeelevated;
386 } else {
387 // We haven't done anything and we don't need to.
388 deelevationStatus = DeelevationStatus::StartedUnprivileged;
390 } else {
391 // Some other elevation state with no medium-integrity token.
392 // (This should probably not happen.)
393 deelevationStatus = DeelevationStatus::Unknown;
396 #if defined(MOZ_LAUNCHER_PROCESS)
397 // Update the registry as Launcher
398 LauncherVoidResult commitResult = regInfo.Commit();
399 if (commitResult.isErr()) {
400 mozilla::HandleLauncherError(commitResult);
401 return Nothing();
403 #endif // defined(MOZ_LAUNCHER_PROCESS)
405 // Now proceed with setting up the parameters for process creation
406 UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(argc, argv));
407 if (!cmdLine) {
408 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
409 return Nothing();
412 const Maybe<bool> isSafeMode =
413 IsSafeModeRequested(argc, argv, SafeModeFlag::NoKeyPressCheck);
414 if (!isSafeMode) {
415 HandleLauncherError(LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_PARAMETER));
416 return Nothing();
419 ProcThreadAttributes attrs;
420 SetMitigationPolicies(attrs, isSafeMode.value());
422 HANDLE stdHandles[] = {::GetStdHandle(STD_INPUT_HANDLE),
423 ::GetStdHandle(STD_OUTPUT_HANDLE),
424 ::GetStdHandle(STD_ERROR_HANDLE)};
426 attrs.AddInheritableHandles(stdHandles);
428 DWORD creationFlags = CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT;
430 STARTUPINFOEXW siex;
431 LauncherResult<bool> attrsOk = attrs.AssignTo(siex);
432 if (attrsOk.isErr()) {
433 HandleLauncherError(attrsOk);
434 return Nothing();
437 BOOL inheritHandles = FALSE;
439 if (attrsOk.unwrap()) {
440 creationFlags |= EXTENDED_STARTUPINFO_PRESENT;
442 if (attrs.HasInheritableHandles()) {
443 siex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
444 siex.StartupInfo.hStdInput = stdHandles[0];
445 siex.StartupInfo.hStdOutput = stdHandles[1];
446 siex.StartupInfo.hStdError = stdHandles[2];
448 // Since attrsOk == true, we have successfully set the handle inheritance
449 // whitelist policy, so only the handles added to attrs will be inherited.
450 inheritHandles = TRUE;
454 // Pass on the path of the shortcut used to launch this process, if any.
455 STARTUPINFOW currentStartupInfo = {.cb = sizeof(STARTUPINFOW)};
456 GetStartupInfoW(&currentStartupInfo);
457 if ((currentStartupInfo.dwFlags & STARTF_TITLEISLINKNAME) &&
458 currentStartupInfo.lpTitle) {
459 siex.StartupInfo.dwFlags |= STARTF_TITLEISLINKNAME;
460 siex.StartupInfo.lpTitle = currentStartupInfo.lpTitle;
463 PROCESS_INFORMATION pi = {};
464 BOOL createOk;
466 if (mediumIlToken.get()) {
467 createOk =
468 ::CreateProcessAsUserW(mediumIlToken.get(), argv[0], cmdLine.get(),
469 nullptr, nullptr, inheritHandles, creationFlags,
470 nullptr, nullptr, &siex.StartupInfo, &pi);
471 } else {
472 createOk = ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr,
473 inheritHandles, creationFlags, nullptr, nullptr,
474 &siex.StartupInfo, &pi);
477 if (!createOk) {
478 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
479 return Nothing();
482 nsAutoHandle process(pi.hProcess);
483 nsAutoHandle mainThread(pi.hThread);
485 nsAutoHandle job;
486 if (flags & LauncherFlags::eWaitForBrowser) {
487 job = CreateJobAndAssignProcess(process.get());
490 bool disableDynamicBlocklist = IsDynamicBlocklistDisabled(
491 isSafeMode.value(),
492 mozilla::CheckArg(
493 argc, argv, mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch,
494 nullptr, mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND);
495 LauncherVoidResult setupResult = PostCreationSetup(
496 argv[0], process.get(), mainThread.get(), deelevationStatus,
497 isSafeMode.value(), disableDynamicBlocklist, blocklistFileName);
498 if (setupResult.isErr()) {
499 HandleLauncherError(setupResult);
500 ::TerminateProcess(process.get(), 1);
501 return Nothing();
504 if (::ResumeThread(mainThread.get()) == static_cast<DWORD>(-1)) {
505 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
506 ::TerminateProcess(process.get(), 1);
507 return Nothing();
510 if (flags & LauncherFlags::eWaitForBrowser) {
511 DWORD exitCode;
512 if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
513 ::GetExitCodeProcess(process.get(), &exitCode)) {
514 // Propagate the browser process's exit code as our exit code.
515 return Some(static_cast<int>(exitCode));
517 } else {
518 const DWORD timeout =
519 ::IsDebuggerPresent() ? INFINITE : kWaitForInputIdleTimeoutMS;
521 // Keep the current process around until the callback process has created
522 // its message queue, to avoid the launched process's windows being forced
523 // into the background.
524 mozilla::WaitForInputIdle(process.get(), timeout);
527 return Some(0);
530 } // namespace mozilla