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/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"
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"
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
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
);
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
));
81 return mozilla::InitializeDllBlocklistOOPFromLauncher(
82 aFullImagePath
, aChildProcess
, aDisableDynamicBlocklist
,
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
) {
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
))) {
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)
125 #if (_WIN32_WINNT < 0x0602)
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
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
;
182 static void MaybeBreakForBrowserDebugging() {
183 if (mozilla::EnvHasValue("MOZ_DEBUG_BROWSER_PROCESS")) {
188 const wchar_t* pauseLenS
= _wgetenv(L
"MOZ_DEBUG_BROWSER_PAUSE");
189 if (!pauseLenS
|| !(*pauseLenS
)) {
193 DWORD pauseLenMs
= wcstoul(pauseLenS
, nullptr, 10) * 1000;
194 printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n debug me @ %lu\n\n",
195 ::GetCurrentProcessId());
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
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();
212 result
= !isSame
.unwrap();
214 HandleLauncherError(isSame
.unwrapErr());
216 #endif // defined(MOZ_LAUNCHER_PROCESS)
218 if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
219 mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
224 mozilla::CheckArg(argc
, argv
, "launcher", nullptr,
225 mozilla::CheckArgFlag::RemoveArg
) == mozilla::ARG_FOUND
;
230 #if defined(MOZ_LAUNCHER_PROCESS)
231 static mozilla::Maybe
<bool> RunAsLauncherProcess(
232 mozilla::LauncherRegistryInfo
& aRegInfo
, int& argc
, wchar_t** argv
) {
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)
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
);
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.
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())
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)
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
));
334 #if defined(MOZ_SANDBOX)
335 // Ensure the relevant mitigations are enforced.
336 mozilla::sandboxing::ApplyParentProcessMitigations();
339 mozilla::UseParentConsole();
341 if (!SetArgv0ToFullBinaryPath(argv
)) {
342 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
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
);
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
;
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
);
385 // Otherwise, tell our caller to exit with a success code.
388 } else if (elevationState
.unwrap() == ElevationState::eNormalUser
) {
389 if (flags
& LauncherFlags::eDeelevating
) {
390 // Deelevation appears to have been successful!
391 deelevationStatus
= DeelevationStatus::SuccessfullyDeelevated
;
393 // We haven't done anything and we don't need to.
394 deelevationStatus
= DeelevationStatus::StartedUnprivileged
;
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
);
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
));
414 HandleLauncherError(LAUNCHER_ERROR_GENERIC());
418 const Maybe
<bool> isSafeMode
=
419 IsSafeModeRequested(argc
, argv
, SafeModeFlag::NoKeyPressCheck
);
421 HandleLauncherError(LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_PARAMETER
));
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
;
437 LauncherResult
<bool> attrsOk
= attrs
.AssignTo(siex
);
438 if (attrsOk
.isErr()) {
439 HandleLauncherError(attrsOk
);
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(¤tStartupInfo
);
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
= {};
472 if (mediumIlToken
.get()) {
474 ::CreateProcessAsUserW(mediumIlToken
.get(), argv
[0], cmdLine
.get(),
475 nullptr, nullptr, inheritHandles
, creationFlags
,
476 nullptr, nullptr, &siex
.StartupInfo
, &pi
);
478 createOk
= ::CreateProcessW(argv
[0], cmdLine
.get(), nullptr, nullptr,
479 inheritHandles
, creationFlags
, nullptr, nullptr,
480 &siex
.StartupInfo
, &pi
);
484 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
488 nsAutoHandle
process(pi
.hProcess
);
489 nsAutoHandle
mainThread(pi
.hThread
);
492 if (flags
& LauncherFlags::eWaitForBrowser
) {
493 job
= CreateJobAndAssignProcess(process
.get());
496 bool disableDynamicBlocklist
= IsDynamicBlocklistDisabled(
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);
510 if (::ResumeThread(mainThread
.get()) == static_cast<DWORD
>(-1)) {
511 HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
512 ::TerminateProcess(process
.get(), 1);
516 if (flags
& LauncherFlags::eWaitForBrowser
) {
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
));
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
);
536 } // namespace mozilla