nsBrowserApp.cpp (17353B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsXULAppAPI.h" 7 #include "mozilla/XREAppData.h" 8 #include "XREChildData.h" 9 #include "XREShellData.h" 10 #include "application.ini.h" 11 #include "mozilla/Bootstrap.h" 12 #include "mozilla/ProcessType.h" 13 #include "mozilla/RuntimeExceptionModule.h" 14 #include "mozilla/ScopeExit.h" 15 #include "BrowserDefines.h" 16 #if defined(XP_WIN) 17 # include <windows.h> 18 # include <stdlib.h> 19 #elif defined(XP_UNIX) 20 # include <sys/resource.h> 21 # include <unistd.h> 22 # include <fcntl.h> 23 #endif 24 25 #include <stdio.h> 26 #include <stdarg.h> 27 #include <time.h> 28 29 #include "nsCOMPtr.h" 30 31 #ifdef XP_WIN 32 # include "mozilla/PreXULSkeletonUI.h" 33 # include "freestanding/SharedSection.h" 34 # include "LauncherProcessWin.h" 35 # include "mozilla/GeckoArgs.h" 36 # include "mozilla/mscom/ProcessRuntime.h" 37 # include "mozilla/WindowsDllBlocklist.h" 38 # include "mozilla/WindowsDpiInitialization.h" 39 # include "mozilla/WindowsProcessMitigations.h" 40 41 # define XRE_WANT_ENVIRON 42 # include "nsWindowsWMain.cpp" 43 44 # define strcasecmp _stricmp 45 # ifdef MOZ_SANDBOX 46 # include "mozilla/sandboxing/SandboxInitialization.h" 47 # include "mozilla/sandboxing/sandboxLogging.h" 48 # endif 49 #endif 50 #include "BinaryPath.h" 51 52 #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL 53 54 #include "mozilla/BaseProfiler.h" 55 #include "mozilla/Sprintf.h" 56 #include "mozilla/StartupTimeline.h" 57 58 #ifdef LIBFUZZER 59 # include "FuzzerDefs.h" 60 #endif 61 62 #ifdef MOZ_LINUX_32_SSE2_STARTUP_ERROR 63 # include <cpuid.h> 64 65 static bool IsSSE2Available() { 66 // The rest of the app has been compiled to assume that SSE2 is present 67 // unconditionally, so we can't use the normal copy of SSE.cpp here. 68 // Since SSE.cpp caches the results and we need them only transiently, 69 // instead of #including SSE.cpp here, let's just inline the specific check 70 // that's needed. 71 unsigned int level = 1u; 72 unsigned int eax, ebx, ecx, edx; 73 unsigned int bits = (1u << 26); 74 unsigned int max = __get_cpuid_max(0, nullptr); 75 if (level > max) { 76 return false; 77 } 78 __cpuid_count(level, 0, eax, ebx, ecx, edx); 79 return (edx & bits) == bits; 80 } 81 82 static const char sSSE2Message[] = 83 "This browser version requires a processor with the SSE2 instruction " 84 "set extension.\nYou may be able to obtain a version that does not " 85 "require SSE2 from your Linux distribution.\n"; 86 87 __attribute__((constructor)) static void SSE2Check() { 88 if (IsSSE2Available()) { 89 return; 90 } 91 // Using write() in order to avoid jemalloc-based buffering. Ignoring return 92 // values, since there isn't much we could do on failure and there is no 93 // point in trying to recover from errors. 94 (void)write(STDERR_FILENO, sSSE2Message, std::size(sSSE2Message) - 1); 95 // _exit() instead of exit() to avoid running the usual "at exit" code. 96 _exit(255); 97 } 98 #endif 99 100 #if !defined(MOZ_WIDGET_COCOA) && !defined(MOZ_WIDGET_ANDROID) 101 # define MOZ_BROWSER_CAN_BE_CONTENTPROC 102 #endif 103 104 using namespace mozilla; 105 106 #define kDesktopFolder "browser" 107 108 #ifdef MOZ_BACKGROUNDTASKS 109 static bool gIsBackgroundTask = false; 110 #endif 111 112 static MOZ_FORMAT_PRINTF(1, 2) void Output(const char* fmt, ...) { 113 va_list ap; 114 va_start(ap, fmt); 115 116 #ifndef XP_WIN 117 vfprintf(stderr, fmt, ap); 118 #else 119 bool showMessageBox = true; 120 121 char msg[2048]; 122 vsnprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap); 123 124 wchar_t wide_msg[2048]; 125 MultiByteToWideChar(CP_UTF8, 0, msg, -1, wide_msg, _countof(wide_msg)); 126 127 # if MOZ_WINCONSOLE 128 showMessageBox = false; 129 # elif defined(MOZ_BACKGROUNDTASKS) 130 // Only show a UI if this isn't a background tasks. 131 showMessageBox = !gIsBackgroundTask; 132 # endif 133 134 if (showMessageBox) { 135 // Linking user32 at load-time interferes with the DLL blocklist (bug 136 // 932100). This is a rare codepath, so we can load user32 at run-time 137 // instead. 138 139 // If we fail to display the message box, we set showMessageBox to false to 140 // fall back to printing to stderr below. 141 HMODULE user32 = LoadLibraryW(L"user32.dll"); 142 if (user32) { 143 decltype(MessageBoxW)* messageBoxW = 144 (decltype(MessageBoxW)*)GetProcAddress(user32, "MessageBoxW"); 145 if (messageBoxW) { 146 messageBoxW(nullptr, wide_msg, L"Firefox", 147 MB_OK | MB_ICONERROR | MB_SETFOREGROUND); 148 } else { 149 showMessageBox = false; 150 } 151 FreeLibrary(user32); 152 } else { 153 showMessageBox = false; 154 } 155 } 156 157 if (!showMessageBox) { 158 fwprintf_s(stderr, wide_msg); 159 } 160 #endif 161 162 va_end(ap); 163 } 164 165 /** 166 * Return true if |arg| is a flag with the given string. 167 * 168 * A flag starts with one or two `-` characters, or a `/` character on windows. 169 */ 170 static bool IsFlag(const char* arg, const char* s) { 171 if (*arg == '-') { 172 if (*++arg == '-') ++arg; 173 return !strcasecmp(arg, s); 174 } 175 176 #if defined(XP_WIN) 177 if (*arg == '/') return !strcasecmp(++arg, s); 178 #endif 179 180 return false; 181 } 182 183 /** 184 * Return true if any arguments are flags with the given string. 185 * 186 * A flag is defined per `IsFlag`. 187 */ 188 static bool HasFlag(int argc, char* argv[], const char* s) { 189 for (int i = 1; i < argc; i++) { 190 if (IsFlag(argv[i], s)) { 191 return true; 192 } 193 } 194 return false; 195 } 196 197 constinit Bootstrap::UniquePtr gBootstrap; 198 199 static int do_main(int argc, char* argv[], char* envp[]) { 200 // Allow firefox.exe to launch XULRunner apps via -app <application.ini> 201 // Note that -app must be the *first* argument. 202 const char* appDataFile = getenv("XUL_APP_FILE"); 203 if ((!appDataFile || !*appDataFile) && (argc > 1 && IsFlag(argv[1], "app"))) { 204 if (argc == 2) { 205 Output("Incorrect number of arguments passed to -app"); 206 return 255; 207 } 208 appDataFile = argv[2]; 209 210 char appEnv[MAXPATHLEN]; 211 SprintfLiteral(appEnv, "XUL_APP_FILE=%s", argv[2]); 212 if (putenv(strdup(appEnv))) { 213 Output("Couldn't set %s.\n", appEnv); 214 return 255; 215 } 216 argv[2] = argv[0]; 217 argv += 2; 218 argc -= 2; 219 } else if (argc > 1 && IsFlag(argv[1], "xpcshell")) { 220 for (int i = 1; i < argc; i++) { 221 argv[i] = argv[i + 1]; 222 } 223 224 XREShellData shellData; 225 #if defined(XP_WIN) && defined(MOZ_SANDBOX) 226 shellData.sandboxBrokerServices = 227 sandboxing::GetInitializedBrokerServices(); 228 #endif 229 230 #ifdef LIBFUZZER 231 shellData.fuzzerDriver = fuzzer::FuzzerDriver; 232 #endif 233 #ifdef AFLFUZZ 234 shellData.fuzzerDriver = afl_interface_raw; 235 #endif 236 237 return gBootstrap->XRE_XPCShellMain(--argc, argv, envp, &shellData); 238 } 239 240 BootstrapConfig config; 241 242 if (appDataFile && *appDataFile) { 243 config.appData = nullptr; 244 config.appDataPath = appDataFile; 245 } else { 246 // no -app flag so we use the compiled-in app data 247 config.appData = &sAppData; 248 config.appDataPath = kDesktopFolder; 249 } 250 251 #if defined(XP_WIN) && defined(MOZ_SANDBOX) 252 sandbox::BrokerServices* brokerServices = 253 sandboxing::GetInitializedBrokerServices(); 254 if (!brokerServices) { 255 Output("Couldn't initialize the broker services.\n"); 256 return 255; 257 } 258 config.sandboxBrokerServices = brokerServices; 259 #endif 260 261 #ifdef LIBFUZZER 262 if (getenv("FUZZER")) 263 gBootstrap->XRE_LibFuzzerSetDriver(fuzzer::FuzzerDriver); 264 #endif 265 266 EnsureBrowserCommandlineSafe(argc, argv); 267 268 return gBootstrap->XRE_main(argc, argv, config); 269 } 270 271 static nsresult InitXPCOMGlue(LibLoadingStrategy aLibLoadingStrategy) { 272 if (gBootstrap) { 273 return NS_OK; 274 } 275 276 UniqueFreePtr<char> exePath = BinaryPath::Get(); 277 if (!exePath) { 278 Output("Couldn't find the application directory.\n"); 279 return NS_ERROR_FAILURE; 280 } 281 282 auto bootstrapResult = 283 mozilla::GetBootstrap(exePath.get(), aLibLoadingStrategy); 284 if (bootstrapResult.isErr()) { 285 Output("Couldn't load XPCOM.\n"); 286 return NS_ERROR_FAILURE; 287 } 288 289 gBootstrap = bootstrapResult.unwrap(); 290 291 // This will set this thread as the main thread. 292 gBootstrap->NS_LogInit(); 293 294 return NS_OK; 295 } 296 297 #ifdef HAS_DLL_BLOCKLIST 298 // NB: This must be extern, as this value is checked elsewhere 299 uint32_t gBlocklistInitFlags = eDllBlocklistInitFlagDefault; 300 #endif 301 302 #if defined(XP_UNIX) 303 static void ReserveDefaultFileDescriptors() { 304 // Reserve the lower positions of the file descriptors to make sure 305 // we don't reuse stdin/stdout/stderr in case they were closed 306 // before launch. 307 // Otherwise code explicitly writing to fd 1 or 2 might accidentally 308 // write to something else, like in bug 1820896 where FD 1 is 309 // reused for the X server display connection. 310 int fd = open("/dev/null", O_RDONLY); 311 for (int i = 0; i < 2; i++) { 312 [[maybe_unused]] int r = dup(fd); 313 } 314 } 315 #endif 316 317 #ifdef XP_LINUX 318 // Linux performance hack: when the fd table expands to the next power 319 // of two, in a multithreaded process it blocks for 30-50ms due to 320 // RCU, and this applies separately to each process, so it can add up. 321 // But, for a single-threaded process it's basically free, so we can 322 // pre-expand early in startup to a value that will usually be enough. 323 // The table takes one machine word per entry, so it's cheap in memory. 324 // 325 // Idea from https://chromium-review.googlesource.com/c/chromium/src/+/6845921 326 static void ExpandFileDescriptorTable() { 327 // Expand the table to size 512 by creating a fd with the first 328 // unused number greater than 255 (unlike dup2, this won't overwrite 329 // an existing fd). Empirically, 512 seems to be enough for our 330 // content processes in most cases. 331 mozilla::UniqueFileHandle fdTableExpander(fcntl(0, F_DUPFD, 256)); 332 // The fd is immediately closed (if it was created; we ignore errors 333 // because this is just an optimization). 334 } 335 #endif 336 337 int main(int argc, char* argv[], char* envp[]) { 338 #if defined(XP_UNIX) 339 ReserveDefaultFileDescriptors(); 340 #endif 341 342 #ifdef MOZ_BACKGROUNDTASKS 343 // Check whether this is a background task very early, as the `Output` 344 // function uses this information. 345 gIsBackgroundTask = HasFlag(argc, argv, "backgroundtask"); 346 #endif 347 348 #ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC 349 if (argc > 1 && IsFlag(argv[1], "contentproc")) { 350 // Set the process type and gecko child id. 351 SetGeckoProcessType(argv[--argc]); 352 SetGeckoChildID(argv[--argc]); 353 354 # if defined(MOZ_ENABLE_FORKSERVER) 355 if (GetGeckoProcessType() == GeckoProcessType_ForkServer) { 356 nsresult rv = InitXPCOMGlue(LibLoadingStrategy::NoReadAhead); 357 if (NS_FAILED(rv)) { 358 return 255; 359 } 360 361 // Run a fork server in this process, single thread. When it returns, it 362 // means the fork server have been stopped or a new child process is 363 // created. 364 // 365 // For the latter case, XRE_ForkServer() will return false, running in a 366 // child process just forked from the fork server process. argc & argv 367 // will be updated with the values passing from the chrome process, as 368 // will GeckoProcessType and GeckoChildID. With the new values, this 369 // function continues the reset of the code acting as a child process. 370 if (gBootstrap->XRE_ForkServer(&argc, &argv)) { 371 // Return from the fork server in the fork server process. 372 // Stop the fork server. 373 // InitXPCOMGlue calls NS_LogInit, so we need to balance it here. 374 gBootstrap->NS_LogTerm(); 375 return 0; 376 } 377 } 378 # endif 379 } 380 #endif 381 382 #ifdef XP_LINUX 383 // Do this as early as possible but after the fork server hook, 384 // because forking resets the fd table size. 385 ExpandFileDescriptorTable(); 386 #endif 387 388 mozilla::TimeStamp start = mozilla::TimeStamp::Now(); 389 390 AUTO_BASE_PROFILER_INIT; 391 AUTO_BASE_PROFILER_LABEL("nsBrowserApp main", OTHER); 392 393 // Register an external module to report on otherwise uncatchable exceptions. 394 // Note that in child processes this must be called after Gecko process type 395 // has been set. 396 CrashReporter::RegisterRuntimeExceptionModule(); 397 398 // Make sure we unregister the runtime exception module before returning. 399 auto unregisterRuntimeExceptionModule = 400 MakeScopeExit([] { CrashReporter::UnregisterRuntimeExceptionModule(); }); 401 402 #ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC 403 // We are launching as a content process, delegate to the appropriate 404 // main 405 if (GetGeckoProcessType() != GeckoProcessType_Default) { 406 # if defined(XP_WIN) && defined(MOZ_SANDBOX) 407 // We need to set whether our process is supposed to have win32k locked down 408 // from the command line setting before DllBlocklist_Initialize, 409 // GetInitializedTargetServices and WindowsDpiInitialization. 410 Maybe<bool> win32kLockedDown = 411 mozilla::geckoargs::sWin32kLockedDown.Get(argc, argv); 412 if (win32kLockedDown.isSome() && *win32kLockedDown) { 413 mozilla::SetWin32kLockedDownInPolicy(); 414 } 415 # endif 416 417 # ifdef HAS_DLL_BLOCKLIST 418 uint32_t initFlags = 419 gBlocklistInitFlags | eDllBlocklistInitFlagIsChildProcess; 420 SetDllBlocklistProcessTypeFlags(initFlags, GetGeckoProcessType()); 421 DllBlocklist_Initialize(initFlags); 422 # endif // HAS_DLL_BLOCKLIST 423 424 # if defined(XP_WIN) && defined(MOZ_SANDBOX) 425 // We need to initialize the sandbox TargetServices before InitXPCOMGlue 426 // because we might need the sandbox broker to give access to some files. 427 if (IsSandboxedProcess() && !sandboxing::GetInitializedTargetServices()) { 428 Output("Failed to initialize the sandbox target services."); 429 return 255; 430 } 431 # endif 432 # if defined(XP_WIN) 433 // Ideally, we would be able to set our DPI awareness in 434 // firefox.exe.manifest Unfortunately, that would cause Win32k calls when 435 // user32.dll gets loaded, which would be incompatible with Win32k Lockdown 436 // 437 // MSDN says that it's allowed-but-not-recommended to initialize DPI 438 // programatically, as long as it's done before any HWNDs are created. 439 // Thus, we do it almost as soon as we possibly can 440 { 441 auto result = mozilla::WindowsDpiInitialization(); 442 (void)result; // Ignore errors since some tools block DPI calls 443 } 444 # endif 445 446 nsresult rv = InitXPCOMGlue(LibLoadingStrategy::NoReadAhead); 447 if (NS_FAILED(rv)) { 448 return 255; 449 } 450 451 XREChildData childData; 452 453 # if defined(XP_WIN) && defined(MOZ_SANDBOX) 454 if (IsSandboxedProcess()) { 455 childData.sandboxTargetServices = 456 mozilla::sandboxing::GetInitializedTargetServices(); 457 if (!childData.sandboxTargetServices) { 458 return 1; 459 } 460 461 childData.ProvideLogFunction = mozilla::sandboxing::ProvideLogFunction; 462 } 463 # endif 464 465 rv = gBootstrap->XRE_InitChildProcess(argc, argv, &childData); 466 467 # if defined(DEBUG) && defined(HAS_DLL_BLOCKLIST) 468 DllBlocklist_Shutdown(); 469 # endif 470 471 // InitXPCOMGlue calls NS_LogInit, so we need to balance it here. 472 gBootstrap->NS_LogTerm(); 473 474 return NS_FAILED(rv) ? 1 : 0; 475 } 476 #endif 477 478 #ifdef HAS_DLL_BLOCKLIST 479 DllBlocklist_Initialize(gBlocklistInitFlags); 480 #endif 481 482 // We will likely only ever support this as a command line argument on Windows 483 // and OSX, so we're ifdefing here just to not create any expectations. 484 #if defined(XP_WIN) || defined(XP_MACOSX) 485 if (argc > 1 && IsFlag(argv[1], "silentmode")) { 486 ::putenv(const_cast<char*>("MOZ_APP_SILENT_START=1")); 487 # if defined(XP_WIN) 488 // On windows We also want to set a separate variable, which we want to 489 // persist across restarts, which will let us keep the process alive 490 // even if the last window is closed. 491 ::putenv(const_cast<char*>("MOZ_APP_ALLOW_WINDOWLESS=1")); 492 # endif 493 # if defined(XP_MACOSX) 494 ::putenv(const_cast<char*>("MOZ_APP_NO_DOCK=1")); 495 # endif 496 } 497 #endif 498 499 #if defined(XP_WIN) 500 501 // Ideally, we would be able to set our DPI awareness in firefox.exe.manifest 502 // Unfortunately, that would cause Win32k calls when user32.dll gets loaded, 503 // which would be incompatible with Win32k Lockdown 504 // 505 // MSDN says that it's allowed-but-not-recommended to initialize DPI 506 // programatically, as long as it's done before any HWNDs are created. 507 // Thus, we do it almost as soon as we possibly can 508 { 509 auto result = mozilla::WindowsDpiInitialization(); 510 (void)result; // Ignore errors since some tools block DPI calls 511 } 512 513 // Once the browser process hits the main function, we no longer need 514 // a writable section handle because all dependent modules have been 515 // loaded. 516 mozilla::freestanding::gSharedSection.ConvertToReadOnly(); 517 518 mozilla::CreateAndStorePreXULSkeletonUI(GetModuleHandle(nullptr), argc, argv); 519 #endif 520 521 nsresult rv = InitXPCOMGlue(LibLoadingStrategy::ReadAhead); 522 if (NS_FAILED(rv)) { 523 return 255; 524 } 525 526 gBootstrap->XRE_StartupTimelineRecord(mozilla::StartupTimeline::START, start); 527 528 #ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC 529 gBootstrap->XRE_EnableSameExecutableForContentProc(); 530 #endif 531 532 int result = do_main(argc, argv, envp); 533 534 #if defined(XP_WIN) 535 CleanupProcessRuntime(); 536 #endif 537 538 gBootstrap->NS_LogTerm(); 539 540 #if defined(DEBUG) && defined(HAS_DLL_BLOCKLIST) 541 DllBlocklist_Shutdown(); 542 #endif 543 544 #ifdef XP_MACOSX 545 // Allow writes again. While we would like to catch writes from static 546 // destructors to allow early exits to use _exit, we know that there is 547 // at least one such write that we don't control (see bug 826029). For 548 // now we enable writes again and early exits will have to use exit instead 549 // of _exit. 550 gBootstrap->XRE_StopLateWriteChecks(); 551 #endif 552 553 gBootstrap.reset(); 554 555 return result; 556 }