Backed out 15 changesets (bug 1852806) for causing mda failures on test_video_low_pow...
[gecko.git] / browser / app / winlauncher / LauncherProcessWin.cpp
blob082ada9ea705cf62420c6bd409a23517a591df82
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/SafeMode.h"
19 #include "mozilla/UniquePtr.h"
20 #include "mozilla/WindowsConsole.h"
21 #include "mozilla/WindowsVersion.h"
22 #include "mozilla/WinHeaderOnlyUtils.h"
23 #include "nsWindowsHelpers.h"
25 #include <windows.h>
26 #include <processthreadsapi.h>
28 #include "DllBlocklistInit.h"
29 #include "ErrorHandler.h"
30 #include "LaunchUnelevated.h"
31 #include "ProcThreadAttributes.h"
32 #include "../BrowserDefines.h"
34 #if defined(MOZ_LAUNCHER_PROCESS)
35 # include "mozilla/LauncherRegistryInfo.h"
36 # include "SameBinary.h"
37 #endif // defined(MOZ_LAUNCHER_PROCESS)
39 #if defined(MOZ_SANDBOX)
40 # include "mozilla/sandboxing/SandboxInitialization.h"
41 #endif
43 namespace mozilla {
44 // "const" because nothing in this process modifies it.
45 // "volatile" because something in another process may.
46 const volatile DeelevationStatus gDeelevationStatus =
47 DeelevationStatus::DefaultStaticValue;
48 } // namespace mozilla
50 /**
51 * At this point the child process has been created in a suspended state. Any
52 * additional startup work (eg, blocklist setup) should go here.
54 * @return Ok if browser startup should proceed
56 static mozilla::LauncherVoidResult PostCreationSetup(
57 const wchar_t* aFullImagePath, HANDLE aChildProcess,
58 HANDLE aChildMainThread, mozilla::DeelevationStatus aDStatus,
59 const bool aIsSafeMode, const bool aDisableDynamicBlocklist,
60 mozilla::Maybe<std::wstring> aBlocklistFileName) {
61 /* scope for txManager */ {
62 mozilla::nt::CrossExecTransferManager txManager(aChildProcess);
63 if (!txManager) {
64 return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
67 using mozilla::gDeelevationStatus;
69 void* targetAddress = (LPVOID)&gDeelevationStatus;
71 auto const guard = txManager.Protect(
72 targetAddress, sizeof(gDeelevationStatus), PAGE_READWRITE);
74 mozilla::LauncherVoidResult result =
75 txManager.Transfer(targetAddress, &aDStatus, sizeof(aDStatus));
76 if (result.isErr()) {
77 return result;
81 return mozilla::InitializeDllBlocklistOOPFromLauncher(
82 aFullImagePath, aChildProcess, aDisableDynamicBlocklist,
83 aBlocklistFileName);
86 /**
87 * Create a new Job object and assign |aProcess| to it. If something fails
88 * in this function, we return nullptr but continue without recording
89 * a launcher failure because it's not a critical problem to launch
90 * the browser process.
92 static nsReturnRef<HANDLE> CreateJobAndAssignProcess(HANDLE aProcess) {
93 nsAutoHandle empty;
94 nsAutoHandle job(::CreateJobObjectW(nullptr, nullptr));
96 // Set JOB_OBJECT_LIMIT_BREAKAWAY_OK to allow the browser process
97 // to put child processes into a job on Win7, which does not support
98 // nested jobs. See CanUseJob() in sandboxBroker.cpp.
99 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
100 jobInfo.BasicLimitInformation.LimitFlags =
101 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_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)
125 #if (_WIN32_WINNT < 0x0602)
126 BOOL WINAPI
127 SetProcessMitigationPolicy(PROCESS_MITIGATION_POLICY aMitigationPolicy,
128 PVOID aBuffer, SIZE_T aBufferLen);
129 #endif // (_WIN32_WINNT >= 0x0602)
132 * Any mitigation policies that should be set on the browser process should go
133 * here.
135 static void SetMitigationPolicies(mozilla::ProcThreadAttributes& aAttrs,
136 const bool aIsSafeMode) {
137 if (mozilla::IsWin10AnniversaryUpdateOrLater()) {
138 aAttrs.AddMitigationPolicy(
139 PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON);
142 #if defined(_M_ARM64)
143 // Disable CFG on older versions of ARM64 Windows to avoid a crash in COM.
144 if (!mozilla::IsWin10Sep2018UpdateOrLater()) {
145 aAttrs.AddMitigationPolicy(
146 PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF);
148 #endif // defined(_M_ARM64)
151 static mozilla::LauncherFlags ProcessCmdLine(int& aArgc, wchar_t* aArgv[]) {
152 mozilla::LauncherFlags result = mozilla::LauncherFlags::eNone;
154 if (mozilla::CheckArg(aArgc, aArgv, "wait-for-browser", nullptr,
155 mozilla::CheckArgFlag::RemoveArg) ==
156 mozilla::ARG_FOUND ||
157 mozilla::CheckArg(aArgc, aArgv, "marionette", nullptr,
158 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
159 mozilla::CheckArg(aArgc, aArgv, "backgroundtask", nullptr,
160 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
161 mozilla::CheckArg(aArgc, aArgv, "headless", nullptr,
162 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
163 mozilla::CheckArg(aArgc, aArgv, "remote-debugging-port", nullptr,
164 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
165 mozilla::EnvHasValue("MOZ_AUTOMATION") ||
166 mozilla::EnvHasValue("MOZ_HEADLESS")) {
167 result |= mozilla::LauncherFlags::eWaitForBrowser;
170 if (mozilla::CheckArg(aArgc, aArgv, "no-deelevate") == mozilla::ARG_FOUND) {
171 result |= mozilla::LauncherFlags::eNoDeelevate;
174 if (mozilla::CheckArg(aArgc, aArgv, ATTEMPTING_DEELEVATION_FLAG) ==
175 mozilla::ARG_FOUND) {
176 result |= mozilla::LauncherFlags::eDeelevating;
179 return result;
182 static void MaybeBreakForBrowserDebugging() {
183 if (mozilla::EnvHasValue("MOZ_DEBUG_BROWSER_PROCESS")) {
184 ::DebugBreak();
185 return;
188 const wchar_t* pauseLenS = _wgetenv(L"MOZ_DEBUG_BROWSER_PAUSE");
189 if (!pauseLenS || !(*pauseLenS)) {
190 return;
193 DWORD pauseLenMs = wcstoul(pauseLenS, nullptr, 10) * 1000;
194 printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n debug me @ %lu\n\n",
195 ::GetCurrentProcessId());
196 ::Sleep(pauseLenMs);
199 static bool DoLauncherProcessChecks(int& argc, wchar_t** argv) {
200 // NB: We run all tests in this function instead of returning early in order
201 // to ensure that all side effects take place, such as clearing environment
202 // variables.
203 bool result = false;
205 #if defined(MOZ_LAUNCHER_PROCESS)
206 // We still prefer to compare file ids. Comparing NT paths i.e. passing
207 // CompareNtPathsOnly to IsSameBinaryAsParentProcess is much faster, but
208 // we're not 100% sure that NT path comparison perfectly prevents the
209 // launching loop of the launcher process.
210 mozilla::LauncherResult<bool> isSame = mozilla::IsSameBinaryAsParentProcess();
211 if (isSame.isOk()) {
212 result = !isSame.unwrap();
213 } else {
214 HandleLauncherError(isSame.unwrapErr());
216 #endif // defined(MOZ_LAUNCHER_PROCESS)
218 if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
219 mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
220 result = true;
223 result |=
224 mozilla::CheckArg(argc, argv, "launcher", nullptr,
225 mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
227 return result;
230 #if defined(MOZ_LAUNCHER_PROCESS)
231 static mozilla::Maybe<bool> RunAsLauncherProcess(
232 mozilla::LauncherRegistryInfo& aRegInfo, int& argc, wchar_t** argv) {
233 #else
234 static mozilla::Maybe<bool> RunAsLauncherProcess(int& argc, wchar_t** argv) {
235 #endif // defined(MOZ_LAUNCHER_PROCESS)
236 bool runAsLauncher = DoLauncherProcessChecks(argc, argv);
238 #if defined(MOZ_LAUNCHER_PROCESS)
239 bool forceLauncher =
240 runAsLauncher &&
241 mozilla::CheckArg(argc, argv, "force-launcher", nullptr,
242 mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
244 mozilla::LauncherRegistryInfo::ProcessType desiredType =
245 runAsLauncher ? mozilla::LauncherRegistryInfo::ProcessType::Launcher
246 : mozilla::LauncherRegistryInfo::ProcessType::Browser;
248 mozilla::LauncherRegistryInfo::CheckOption checkOption =
249 forceLauncher ? mozilla::LauncherRegistryInfo::CheckOption::Force
250 : mozilla::LauncherRegistryInfo::CheckOption::Default;
252 mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType>
253 runAsType = aRegInfo.Check(desiredType, checkOption);
255 if (runAsType.isErr()) {
256 mozilla::HandleLauncherError(runAsType);
257 return mozilla::Nothing();
260 runAsLauncher = runAsType.unwrap() ==
261 mozilla::LauncherRegistryInfo::ProcessType::Launcher;
262 #endif // defined(MOZ_LAUNCHER_PROCESS)
264 if (!runAsLauncher) {
265 // In this case, we will be proceeding to run as the browser.
266 // We should check MOZ_DEBUG_BROWSER_* env vars.
267 MaybeBreakForBrowserDebugging();
270 return mozilla::Some(runAsLauncher);
273 namespace mozilla {
275 Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
276 const StaticXREAppData& aAppData) {
277 EnsureBrowserCommandlineSafe(argc, argv);
279 SetLauncherErrorAppData(aAppData);
281 if (CheckArg(argc, argv, "log-launcher-error", nullptr,
282 mozilla::CheckArgFlag::RemoveArg) == ARG_FOUND) {
283 SetLauncherErrorForceEventLog();
286 // return fast when we're a child process.
287 // (The remainder of this function has some side effects that are
288 // undesirable for content processes)
289 if (mozilla::CheckArg(argc, argv, "contentproc", nullptr,
290 mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND) {
291 // A child process should not instantiate LauncherRegistryInfo.
292 return Nothing();
295 #if defined(MOZ_LAUNCHER_PROCESS)
296 LauncherRegistryInfo regInfo;
297 Maybe<bool> runAsLauncher = RunAsLauncherProcess(regInfo, argc, argv);
298 LauncherResult<std::wstring> blocklistFileNameResult =
299 regInfo.GetBlocklistFileName();
300 Maybe<std::wstring> blocklistFileName =
301 blocklistFileNameResult.isOk() ? Some(blocklistFileNameResult.unwrap())
302 : Nothing();
303 #else
304 Maybe<bool> runAsLauncher = RunAsLauncherProcess(argc, argv);
305 Maybe<std::wstring> blocklistFileName = Nothing();
306 #endif // defined(MOZ_LAUNCHER_PROCESS)
307 if (!runAsLauncher || !runAsLauncher.value()) {
308 #if defined(MOZ_LAUNCHER_PROCESS)
309 // Update the registry as Browser
310 LauncherVoidResult commitResult = regInfo.Commit();
311 if (commitResult.isErr()) {
312 mozilla::HandleLauncherError(commitResult);
314 #endif // defined(MOZ_LAUNCHER_PROCESS)
315 return Nothing();
318 // Make sure that the launcher process itself has image load policies set
319 if (IsWin10AnniversaryUpdateOrLater()) {
320 static const StaticDynamicallyLinkedFunctionPtr<
321 decltype(&SetProcessMitigationPolicy)>
322 pSetProcessMitigationPolicy(L"kernel32.dll",
323 "SetProcessMitigationPolicy");
324 if (pSetProcessMitigationPolicy) {
325 PROCESS_MITIGATION_IMAGE_LOAD_POLICY imgLoadPol = {};
326 imgLoadPol.PreferSystem32Images = 1;
328 DebugOnly<BOOL> setOk = pSetProcessMitigationPolicy(
329 ProcessImageLoadPolicy, &imgLoadPol, sizeof(imgLoadPol));
330 MOZ_ASSERT(setOk);
334 #if defined(MOZ_SANDBOX)
335 // Ensure the relevant mitigations are enforced.
336 mozilla::sandboxing::ApplyParentProcessMitigations();
337 #endif
339 mozilla::UseParentConsole();
341 if (!SetArgv0ToFullBinaryPath(argv)) {
342 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
343 return Nothing();
346 LauncherFlags flags = ProcessCmdLine(argc, argv);
348 nsAutoHandle mediumIlToken;
349 LauncherResult<ElevationState> elevationState =
350 GetElevationState(argv[0], flags, mediumIlToken);
351 if (elevationState.isErr()) {
352 HandleLauncherError(elevationState);
353 return Nothing();
356 // Distill deelevation status, and/or attempt to perform launcher deelevation
357 // via an indirect relaunch.
358 DeelevationStatus deelevationStatus = DeelevationStatus::Unknown;
359 if (mediumIlToken.get()) {
360 // Rather than indirectly relaunch the launcher, we'll attempt to directly
361 // launch the main process with a reduced-privilege security token.
362 deelevationStatus = DeelevationStatus::PartiallyDeelevated;
363 } else if (elevationState.unwrap() == ElevationState::eElevated) {
364 if (flags & LauncherFlags::eWaitForBrowser) {
365 // An indirect relaunch won't provide a process-handle to block on,
366 // so we have to continue onwards with this process.
367 deelevationStatus = DeelevationStatus::DeelevationProhibited;
368 } else if (flags & LauncherFlags::eNoDeelevate) {
369 // Our invoker (hopefully, the user) has explicitly requested that the
370 // launcher not deelevate itself.
371 deelevationStatus = DeelevationStatus::DeelevationProhibited;
372 } else if (flags & LauncherFlags::eDeelevating) {
373 // We've already tried to deelevate, to no effect. Continue onward.
374 deelevationStatus = DeelevationStatus::UnsuccessfullyDeelevated;
375 } else {
376 // Otherwise, attempt to relaunch the launcher process itself via the
377 // shell, which hopefully will not be elevated. (But see bug 1733821.)
378 LauncherVoidResult launchedUnelevated = LaunchUnelevated(argc, argv);
379 if (launchedUnelevated.isErr()) {
380 // On failure, don't even try for a launcher process. Continue onwards
381 // in this one. (TODO: why? This isn't technically fatal...)
382 HandleLauncherError(launchedUnelevated);
383 return Nothing();
385 // Otherwise, tell our caller to exit with a success code.
386 return Some(0);
388 } else if (elevationState.unwrap() == ElevationState::eNormalUser) {
389 if (flags & LauncherFlags::eDeelevating) {
390 // Deelevation appears to have been successful!
391 deelevationStatus = DeelevationStatus::SuccessfullyDeelevated;
392 } else {
393 // We haven't done anything and we don't need to.
394 deelevationStatus = DeelevationStatus::StartedUnprivileged;
396 } else {
397 // Some other elevation state with no medium-integrity token.
398 // (This should probably not happen.)
399 deelevationStatus = DeelevationStatus::Unknown;
402 #if defined(MOZ_LAUNCHER_PROCESS)
403 // Update the registry as Launcher
404 LauncherVoidResult commitResult = regInfo.Commit();
405 if (commitResult.isErr()) {
406 mozilla::HandleLauncherError(commitResult);
407 return Nothing();
409 #endif // defined(MOZ_LAUNCHER_PROCESS)
411 // Now proceed with setting up the parameters for process creation
412 UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(argc, argv));
413 if (!cmdLine) {
414 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
415 return Nothing();
418 const Maybe<bool> isSafeMode =
419 IsSafeModeRequested(argc, argv, SafeModeFlag::NoKeyPressCheck);
420 if (!isSafeMode) {
421 HandleLauncherError(LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_PARAMETER));
422 return Nothing();
425 ProcThreadAttributes attrs;
426 SetMitigationPolicies(attrs, isSafeMode.value());
428 HANDLE stdHandles[] = {::GetStdHandle(STD_INPUT_HANDLE),
429 ::GetStdHandle(STD_OUTPUT_HANDLE),
430 ::GetStdHandle(STD_ERROR_HANDLE)};
432 attrs.AddInheritableHandles(stdHandles);
434 DWORD creationFlags = CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT;
436 STARTUPINFOEXW siex;
437 LauncherResult<bool> attrsOk = attrs.AssignTo(siex);
438 if (attrsOk.isErr()) {
439 HandleLauncherError(attrsOk);
440 return Nothing();
443 BOOL inheritHandles = FALSE;
445 if (attrsOk.unwrap()) {
446 creationFlags |= EXTENDED_STARTUPINFO_PRESENT;
448 if (attrs.HasInheritableHandles()) {
449 siex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
450 siex.StartupInfo.hStdInput = stdHandles[0];
451 siex.StartupInfo.hStdOutput = stdHandles[1];
452 siex.StartupInfo.hStdError = stdHandles[2];
454 // Since attrsOk == true, we have successfully set the handle inheritance
455 // whitelist policy, so only the handles added to attrs will be inherited.
456 inheritHandles = TRUE;
460 // Pass on the path of the shortcut used to launch this process, if any.
461 STARTUPINFOW currentStartupInfo = {.cb = sizeof(STARTUPINFOW)};
462 GetStartupInfoW(&currentStartupInfo);
463 if ((currentStartupInfo.dwFlags & STARTF_TITLEISLINKNAME) &&
464 currentStartupInfo.lpTitle) {
465 siex.StartupInfo.dwFlags |= STARTF_TITLEISLINKNAME;
466 siex.StartupInfo.lpTitle = currentStartupInfo.lpTitle;
469 PROCESS_INFORMATION pi = {};
470 BOOL createOk;
472 if (mediumIlToken.get()) {
473 createOk =
474 ::CreateProcessAsUserW(mediumIlToken.get(), argv[0], cmdLine.get(),
475 nullptr, nullptr, inheritHandles, creationFlags,
476 nullptr, nullptr, &siex.StartupInfo, &pi);
477 } else {
478 createOk = ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr,
479 inheritHandles, creationFlags, nullptr, nullptr,
480 &siex.StartupInfo, &pi);
483 if (!createOk) {
484 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
485 return Nothing();
488 nsAutoHandle process(pi.hProcess);
489 nsAutoHandle mainThread(pi.hThread);
491 nsAutoHandle job;
492 if (flags & LauncherFlags::eWaitForBrowser) {
493 job = CreateJobAndAssignProcess(process.get());
496 bool disableDynamicBlocklist = IsDynamicBlocklistDisabled(
497 isSafeMode.value(),
498 mozilla::CheckArg(
499 argc, argv, mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch,
500 nullptr, mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND);
501 LauncherVoidResult setupResult = PostCreationSetup(
502 argv[0], process.get(), mainThread.get(), deelevationStatus,
503 isSafeMode.value(), disableDynamicBlocklist, blocklistFileName);
504 if (setupResult.isErr()) {
505 HandleLauncherError(setupResult);
506 ::TerminateProcess(process.get(), 1);
507 return Nothing();
510 if (::ResumeThread(mainThread.get()) == static_cast<DWORD>(-1)) {
511 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
512 ::TerminateProcess(process.get(), 1);
513 return Nothing();
516 if (flags & LauncherFlags::eWaitForBrowser) {
517 DWORD exitCode;
518 if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
519 ::GetExitCodeProcess(process.get(), &exitCode)) {
520 // Propagate the browser process's exit code as our exit code.
521 return Some(static_cast<int>(exitCode));
523 } else {
524 const DWORD timeout =
525 ::IsDebuggerPresent() ? INFINITE : kWaitForInputIdleTimeoutMS;
527 // Keep the current process around until the callback process has created
528 // its message queue, to avoid the launched process's windows being forced
529 // into the background.
530 mozilla::WaitForInputIdle(process.get(), timeout);
533 return Some(0);
536 } // namespace mozilla