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"
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"
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"
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
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
);
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
));
82 return mozilla::InitializeDllBlocklistOOPFromLauncher(
83 aFullImagePath
, aChildProcess
, aDisableDynamicBlocklist
,
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
) {
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
))) {
107 if (!::AssignProcessToJobObject(job
.get(), aProcess
)) {
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
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
;
176 static void MaybeBreakForBrowserDebugging() {
177 if (mozilla::EnvHasValue("MOZ_DEBUG_BROWSER_PROCESS")) {
182 const wchar_t* pauseLenS
= _wgetenv(L
"MOZ_DEBUG_BROWSER_PAUSE");
183 if (!pauseLenS
|| !(*pauseLenS
)) {
187 DWORD pauseLenMs
= wcstoul(pauseLenS
, nullptr, 10) * 1000;
188 printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n debug me @ %lu\n\n",
189 ::GetCurrentProcessId());
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
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();
206 result
= !isSame
.unwrap();
208 HandleLauncherError(isSame
.unwrapErr());
210 #endif // defined(MOZ_LAUNCHER_PROCESS)
212 if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
213 mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
218 mozilla::CheckArg(argc
, argv
, "launcher", nullptr,
219 mozilla::CheckArgFlag::RemoveArg
) == mozilla::ARG_FOUND
;
224 #if defined(MOZ_LAUNCHER_PROCESS)
225 static mozilla::Maybe
<bool> RunAsLauncherProcess(
226 mozilla::LauncherRegistryInfo
& aRegInfo
, int& argc
, wchar_t** argv
) {
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)
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
);
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.
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())
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)
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
));
328 #if defined(MOZ_SANDBOX)
329 // Ensure the relevant mitigations are enforced.
330 mozilla::sandboxing::ApplyParentProcessMitigations();
333 mozilla::UseParentConsole();
335 if (!SetArgv0ToFullBinaryPath(argv
)) {
336 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
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
);
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
;
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
);
379 // Otherwise, tell our caller to exit with a success code.
382 } else if (elevationState
.unwrap() == ElevationState::eNormalUser
) {
383 if (flags
& LauncherFlags::eDeelevating
) {
384 // Deelevation appears to have been successful!
385 deelevationStatus
= DeelevationStatus::SuccessfullyDeelevated
;
387 // We haven't done anything and we don't need to.
388 deelevationStatus
= DeelevationStatus::StartedUnprivileged
;
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
);
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
));
408 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
412 const Maybe
<bool> isSafeMode
=
413 IsSafeModeRequested(argc
, argv
, SafeModeFlag::NoKeyPressCheck
);
415 HandleLauncherError(LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_PARAMETER
));
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
;
431 LauncherResult
<bool> attrsOk
= attrs
.AssignTo(siex
);
432 if (attrsOk
.isErr()) {
433 HandleLauncherError(attrsOk
);
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(¤tStartupInfo
);
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
= {};
466 if (mediumIlToken
.get()) {
468 ::CreateProcessAsUserW(mediumIlToken
.get(), argv
[0], cmdLine
.get(),
469 nullptr, nullptr, inheritHandles
, creationFlags
,
470 nullptr, nullptr, &siex
.StartupInfo
, &pi
);
472 createOk
= ::CreateProcessW(argv
[0], cmdLine
.get(), nullptr, nullptr,
473 inheritHandles
, creationFlags
, nullptr, nullptr,
474 &siex
.StartupInfo
, &pi
);
478 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
482 nsAutoHandle
process(pi
.hProcess
);
483 nsAutoHandle
mainThread(pi
.hThread
);
486 if (flags
& LauncherFlags::eWaitForBrowser
) {
487 job
= CreateJobAndAssignProcess(process
.get());
490 bool disableDynamicBlocklist
= IsDynamicBlocklistDisabled(
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);
504 if (::ResumeThread(mainThread
.get()) == static_cast<DWORD
>(-1)) {
505 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
506 ::TerminateProcess(process
.get(), 1);
510 if (flags
& LauncherFlags::eWaitForBrowser
) {
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
));
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
);
530 } // namespace mozilla