From 66457acccc91b34342cbae124186f0ce7467d356 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Sun, 25 May 2025 15:19:43 -0400 Subject: [PATCH] bootstrap: Initialize crash reporting (see #3935) This adds a runtime dependency on chrome_elf.dll and makes all runtime errors LOG(FATAL) to generate a crash report. Don't wait for libcef to load before running as the crashpad-handler process. --- BUILD.gn | 11 ++ libcef/browser/crashpad_runner.cc | 55 ++++++ libcef/browser/crashpad_runner.h | 22 +++ libcef/browser/main_runner.cc | 52 +----- libcef_dll/bootstrap/bootstrap_win.cc | 236 ++++++++++++++++++-------- 5 files changed, 259 insertions(+), 117 deletions(-) create mode 100644 libcef/browser/crashpad_runner.cc create mode 100644 libcef/browser/crashpad_runner.h diff --git a/BUILD.gn b/BUILD.gn index 0e6a1d031..1efccff86 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -558,6 +558,8 @@ source_set("libcef_static") { "libcef/browser/context.h", "libcef/browser/context_menu_params_impl.cc", "libcef/browser/context_menu_params_impl.h", + "libcef/browser/crashpad_runner.cc", + "libcef/browser/crashpad_runner.h", "libcef/browser/devtools/devtools_controller.cc", "libcef/browser/devtools/devtools_controller.h", "libcef/browser/devtools/devtools_protocol_manager.cc", @@ -1267,7 +1269,13 @@ if (is_win) { "libcef_dll/bootstrap/win/resource.h", "libcef_dll/wrapper/cef_certificate_util_win.cc", "libcef_dll/wrapper/cef_util_win.cc", + "libcef/browser/crashpad_runner.cc", + "libcef/browser/crashpad_runner.h", "libcef/browser/preferred_stack_size_win.inc", + "//chrome/app/delay_load_failure_hook_win.cc", + "//chrome/app/delay_load_failure_hook_win.h", + "//chrome/common/win/delay_load_failure_support.cc", + "//chrome/common/win/delay_load_failure_support.h", ] bootstrap_deps = [ @@ -1275,6 +1283,9 @@ if (is_win) { ":make_version_header", "//base", "//build/win:default_exe_manifest", + "//chrome/install_static:secondary_module", + "//chrome/chrome_elf", + "//third_party/crashpad/crashpad/handler", ] bootstrap_libs = [ diff --git a/libcef/browser/crashpad_runner.cc b/libcef/browser/crashpad_runner.cc new file mode 100644 index 000000000..d6a6f063c --- /dev/null +++ b/libcef/browser/crashpad_runner.cc @@ -0,0 +1,55 @@ +// Copyright 2020 The Chromium Embedded Framework Authors. +// Portions copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cef/libcef/browser/crashpad_runner.h" + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/crashpad/crashpad/handler/handler_main.h" + +namespace crashpad_runner { + +// Based on components/crash/core/app/run_as_crashpad_handler_win.cc +int RunAsCrashpadHandler(const base::CommandLine& command_line) { + // Remove the "--type=crashpad-handler" command-line flag that will otherwise + // confuse the crashpad handler. + base::CommandLine::StringVector argv = command_line.argv(); + const base::CommandLine::StringType process_type = + FILE_PATH_LITERAL("--type="); + argv.erase( + std::remove_if(argv.begin(), argv.end(), + [&process_type](const base::CommandLine::StringType& str) { + return base::StartsWith(str, process_type, + base::CompareCase::SENSITIVE) || + (!str.empty() && str[0] == L'/'); + }), + argv.end()); + +#if BUILDFLAG(IS_POSIX) + // HandlerMain on POSIX uses the system version of getopt_long which expects + // the first argument to be the program name. + argv.insert(argv.begin(), command_line.GetProgram().value()); +#endif + + std::unique_ptr argv_as_utf8(new char*[argv.size() + 1]); + std::vector storage; + storage.reserve(argv.size()); + for (size_t i = 0; i < argv.size(); ++i) { +#if BUILDFLAG(IS_WIN) + storage.push_back(base::WideToUTF8(argv[i])); +#else + storage.push_back(argv[i]); +#endif + argv_as_utf8[i] = &storage[i][0]; + } + argv_as_utf8[argv.size()] = nullptr; + argv.clear(); + return crashpad::HandlerMain(static_cast(storage.size()), + argv_as_utf8.get(), nullptr); +} + +} // namespace crashpad_runner diff --git a/libcef/browser/crashpad_runner.h b/libcef/browser/crashpad_runner.h new file mode 100644 index 000000000..c9d99dfa9 --- /dev/null +++ b/libcef/browser/crashpad_runner.h @@ -0,0 +1,22 @@ +// Copyright 2020 The Chromium Embedded Framework Authors. +// Portions copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CEF_LIBCEF_BROWSER_CRASHPAD_RUNNER_H_ +#define CEF_LIBCEF_BROWSER_CRASHPAD_RUNNER_H_ + +#include "base/command_line.h" + +namespace crashpad_runner { + +// Chrome uses an embedded crashpad handler on Windows only and imports this +// function via the existing "run_as_crashpad_handler" target defined in +// components/crash/core/app/BUILD.gn. CEF uses an embedded handler on all +// platforms so we define the function here instead of using the existing +// target (because we can't use that target on macOS). +int RunAsCrashpadHandler(const base::CommandLine& command_line); + +} // namespace crashpad_runner + +#endif // CEF_LIBCEF_BROWSER_CRASHPAD_RUNNER_H_ diff --git a/libcef/browser/main_runner.cc b/libcef/browser/main_runner.cc index 8ff5c96d1..b8c14773f 100644 --- a/libcef/browser/main_runner.cc +++ b/libcef/browser/main_runner.cc @@ -13,6 +13,7 @@ #include "base/synchronization/waitable_event.h" #include "cef/libcef/browser/browser_message_loop.h" #include "cef/libcef/browser/chrome/chrome_content_browser_client_cef.h" +#include "cef/libcef/browser/crashpad_runner.h" #include "cef/libcef/browser/thread_util.h" #include "cef/libcef/common/app_manager.h" #include "cef/libcef/common/cef_switches.h" @@ -28,7 +29,6 @@ #include "content/public/app/content_main.h" #include "content/public/browser/render_process_host.h" #include "content/public/common/content_switches.h" -#include "third_party/crashpad/crashpad/handler/handler_main.h" #if BUILDFLAG(IS_WIN) #include @@ -41,54 +41,6 @@ #include "sandbox/win/src/sandbox_types.h" #endif -namespace { - -// Based on components/crash/core/app/run_as_crashpad_handler_win.cc -// Remove the "--type=crashpad-handler" command-line flag that will otherwise -// confuse the crashpad handler. -// Chrome uses an embedded crashpad handler on Windows only and imports this -// function via the existing "run_as_crashpad_handler" target defined in -// components/crash/core/app/BUILD.gn. CEF uses an embedded handler on all -// platforms so we define the function here instead of using the existing -// target (because we can't use that target on macOS). -int RunAsCrashpadHandler(const base::CommandLine& command_line) { - base::CommandLine::StringVector argv = command_line.argv(); - const base::CommandLine::StringType process_type = - FILE_PATH_LITERAL("--type="); - argv.erase( - std::remove_if(argv.begin(), argv.end(), - [&process_type](const base::CommandLine::StringType& str) { - return base::StartsWith(str, process_type, - base::CompareCase::SENSITIVE) || - (!str.empty() && str[0] == L'/'); - }), - argv.end()); - -#if BUILDFLAG(IS_POSIX) - // HandlerMain on POSIX uses the system version of getopt_long which expects - // the first argument to be the program name. - argv.insert(argv.begin(), command_line.GetProgram().value()); -#endif - - std::unique_ptr argv_as_utf8(new char*[argv.size() + 1]); - std::vector storage; - storage.reserve(argv.size()); - for (size_t i = 0; i < argv.size(); ++i) { -#if BUILDFLAG(IS_WIN) - storage.push_back(base::WideToUTF8(argv[i])); -#else - storage.push_back(argv[i]); -#endif - argv_as_utf8[i] = &storage[i][0]; - } - argv_as_utf8[argv.size()] = nullptr; - argv.clear(); - return crashpad::HandlerMain(static_cast(storage.size()), - argv_as_utf8.get(), nullptr); -} - -} // namespace - CefMainRunner::CefMainRunner(bool multi_threaded_message_loop, bool external_message_pump) : multi_threaded_message_loop_(multi_threaded_message_loop), @@ -243,7 +195,7 @@ int CefMainRunner::RunAsHelperProcess(const CefMainArgs& args, BeforeMainInitialize(args); if (process_type == crash_reporter::switches::kCrashpadHandler) { - return RunAsCrashpadHandler(command_line); + return crashpad_runner::RunAsCrashpadHandler(command_line); } // Execute the secondary process. diff --git a/libcef_dll/bootstrap/bootstrap_win.cc b/libcef_dll/bootstrap/bootstrap_win.cc index f4ffac6fb..d1ba0c28e 100644 --- a/libcef_dll/bootstrap/bootstrap_win.cc +++ b/libcef_dll/bootstrap/bootstrap_win.cc @@ -8,6 +8,9 @@ #include "base/command_line.h" #include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/process/memory.h" #include "base/process/process_info.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" @@ -16,12 +19,37 @@ #include "cef/include/internal/cef_types.h" #include "cef/include/wrapper/cef_certificate_util_win.h" #include "cef/include/wrapper/cef_util_win.h" +#include "cef/libcef/browser/crashpad_runner.h" #include "cef/libcef/browser/preferred_stack_size_win.inc" #include "cef/libcef_dll/bootstrap/bootstrap_util_win.h" #include "cef/libcef_dll/bootstrap/win/resource.h" +#include "chrome/app/delay_load_failure_hook_win.h" +#include "chrome/chrome_elf/chrome_elf_main.h" +#include "chrome/install_static/initialize_from_primary_module.h" namespace { +// Sets the current working directory for the process to the directory holding +// the executable if this is the browser process. This avoids leaking a handle +// to an arbitrary directory to child processes (e.g., the crashpad handler +// process). +void SetCwdForBrowserProcess() { + if (!::IsBrowserProcess()) { + return; + } + + std::array buffer; + buffer[0] = L'\0'; + DWORD length = ::GetModuleFileName(nullptr, &buffer[0], buffer.size()); + if (!length || length >= buffer.size()) { + return; + } + + base::SetCurrentDirectory( + base::FilePath(base::FilePath::StringViewType(&buffer[0], length)) + .DirName()); +} + // Load a string from the string table in bootstrap.rc. std::wstring LoadString(int string_id) { const int kMaxSize = 100; @@ -53,6 +81,39 @@ void ShowError(const std::wstring& error) { #endif } +std::wstring NormalizeError(const std::wstring& err) { + std::wstring str = err; + // Replace newlines. + std::replace(str.begin(), str.end(), L'\n', L' '); + return str; +} + +// Verify DLL code signing requirements. +void CheckDllCodeSigning( + const base::FilePath& dll_path, + const cef_certificate_util::ThumbprintsInfo& exe_thumbprints) { + cef_certificate_util::ThumbprintsInfo dll_thumbprints; + cef_certificate_util::GetClientThumbprints( + dll_path.value(), /*verify_binary=*/true, dll_thumbprints); + + // The DLL and EXE must either both be unsigned or both have all valid + // signatures and the same primary thumbprint. + if (!dll_thumbprints.IsSame(exe_thumbprints, /*allow_unsigned=*/true)) { + // Some part of the certificate validation process failed. + const auto subst = std::to_array( + {base::WideToUTF16(dll_path.BaseName().value()), + base::WideToUTF16(dll_thumbprints.errors)}); + ShowError(FormatErrorString(IDS_ERROR_INVALID_CERT, subst)); + if (dll_thumbprints.errors.empty()) { + LOG(FATAL) << "Failed " << dll_path.value() + << " certificate requirements"; + } else { + LOG(FATAL) << "Failed " << dll_path.value() << " certificate checks: " + << NormalizeError(dll_thumbprints.errors); + } + } +} + } // namespace #if defined(CEF_BUILD_BOOTSTRAP_CONSOLE) @@ -87,17 +148,37 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } #endif + SetCwdForBrowserProcess(); + install_static::InitializeFromPrimaryModule(); + SignalInitializeCrashReporting(); + if (IsBrowserProcess()) { + chrome::DisableDelayLoadFailureHooksForMainExecutable(); + } + + // Done here to ensure that OOMs that happen early in process initialization + // are correctly signaled to the OS. + base::EnableTerminationOnOutOfMemory(); + logging::RegisterAbslAbortHook(); + // Parse command-line arguments. const base::CommandLine command_line = base::CommandLine::FromString(::GetCommandLineW()); constexpr char kProcessType[] = "type"; const bool is_subprocess = command_line.HasSwitch(kProcessType); - if (is_subprocess && command_line.GetSwitchValueASCII(kProcessType).empty()) { + const std::string& process_type = + command_line.GetSwitchValueASCII(kProcessType); + if (is_subprocess && process_type.empty()) { // Early exit on invalid process type. return CEF_RESULT_CODE_BAD_PROCESS_TYPE; } + // Run the crashpad handler now instead of waiting for libcef to load. + constexpr char kCrashpadHandler[] = "crashpad-handler"; + if (process_type == kCrashpadHandler) { + return crashpad_runner::RunAsCrashpadHandler(command_line); + } + // True if this is a sandboxed sub-process. Uses similar logic to // Sandbox::IsProcessSandboxed. const bool is_sandboxed = @@ -133,7 +214,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, if (bootstrap_util::IsDefaultExeName(dll_name)) { ShowError(LoadString(IDS_ERROR_NO_MODULE_NAME)); - return CEF_RESULT_CODE_KILLED; + LOG(FATAL) << "Missing module name"; } cef_certificate_util::GetClientThumbprints( @@ -146,14 +227,67 @@ int APIENTRY wWinMain(HINSTANCE hInstance, {base::WideToUTF16(exe_path.BaseName().value()), base::WideToUTF16(exe_thumbprints.errors)}); ShowError(FormatErrorString(IDS_ERROR_INVALID_CERT, subst)); - return CEF_RESULT_CODE_KILLED; + if (exe_thumbprints.errors.empty()) { + LOG(FATAL) << "Failed " << exe_path.value() + << " certificate requirements"; + } else { + LOG(FATAL) << "Failed " << exe_path.value() << " certificate checks: " + << NormalizeError(exe_thumbprints.errors); + } } } - // Manage the life span of the sandbox information object. This is necessary - // for sandbox support on Windows. See cef_sandbox_win.h for complete details. - CefScopedSandboxInfo scoped_sandbox; - void* sandbox_info = scoped_sandbox.sandbox_info(); + if (!is_sandboxed) { + // Check chrome_elf.dll which should be preloaded to support crash + // reporting. + if (HMODULE hModule = ::LoadLibrary(L"chrome_elf")) { + const auto& dll_path = bootstrap_util::GetModulePath(hModule); + + // Must be in the same directory as the EXE. + if (dll_path.DirName() != exe_path.DirName()) { + const auto subst = std::to_array({u"chrome_elf"}); + ShowError(FormatErrorString(IDS_ERROR_INVALID_LOCATION, subst)); + LOG(FATAL) << "Invalid location: " << dll_path.value(); + } + + CheckDllCodeSigning(dll_path, exe_thumbprints); + + FreeLibrary(hModule); + } else { + LOG(FATAL) << "Failed to load chrome_elf.dll with error " + << ::GetLastError(); + } + + // Load the client DLL as untrusted (e.g. without executing DllMain or + // loading additional modules) so that we can first check requirements. + // LoadLibrary's "default search order" is tricky and we don't want to + // guess about what DLL it will load. DONT_RESOLVE_DLL_REFERENCES is the + // only option that doesn't execute DllMain while still allowing us + // retrieve the path using GetModuleFileName. No execution of the DLL + // should be attempted while loaded in this mode. + if (HMODULE hModule = ::LoadLibraryEx(dll_name.c_str(), nullptr, + DONT_RESOLVE_DLL_REFERENCES)) { + const auto& dll_path = bootstrap_util::GetModulePath(hModule); + + if (!bootstrap_util::IsModulePathAllowed(dll_path, exe_path)) { + const auto subst = + std::to_array({base::WideToUTF16(dll_name)}); + ShowError(FormatErrorString(IDS_ERROR_INVALID_LOCATION, subst)); + LOG(FATAL) << "Invalid location: " << dll_path.value(); + } + + CheckDllCodeSigning(dll_path, exe_thumbprints); + + FreeLibrary(hModule); + } else { + const auto subst = std::to_array( + {base::WideToUTF16(dll_name), + base::WideToUTF16(cef_util::GetLastErrorAsString())}); + ShowError(FormatErrorString(IDS_ERROR_LOAD_FAILED, subst)); + LOG(FATAL) << "Failed to load " << dll_name << ".dll with error " + << ::GetLastError(); + } + } #if defined(CEF_BUILD_BOOTSTRAP_CONSOLE) constexpr char kProcName[] = "RunConsoleMain"; @@ -163,81 +297,49 @@ int APIENTRY wWinMain(HINSTANCE hInstance, using kProcType = decltype(&RunWinMain); #endif - std::wstring error; + int result_code; - if (!is_sandboxed) { - // Load the client DLL as untrusted (e.g. without executing DllMain or - // loading additional modules) so that we can first check requirements. - // LoadLibrary's "default search order" is tricky and we don't want to guess - // about what DLL it will load. DONT_RESOLVE_DLL_REFERENCES is the only - // option that doesn't execute DllMain while still allowing us retrieve the - // path using GetModuleFileName. No execution of the DLL should be attempted - // while loaded in this mode. - if (HMODULE hModule = ::LoadLibraryEx(dll_name.c_str(), nullptr, - DONT_RESOLVE_DLL_REFERENCES)) { - const auto& dll_path = bootstrap_util::GetModulePath(hModule); + // Load the client DLL normally. + if (HMODULE hModule = ::LoadLibrary(dll_name.c_str())) { + if (auto* pFunc = (kProcType)::GetProcAddress(hModule, kProcName)) { + // Manage the life span of the sandbox information object. This is + // necessary for sandbox support on Windows. See cef_sandbox_win.h for + // complete details. + CefScopedSandboxInfo scoped_sandbox; + void* sandbox_info = scoped_sandbox.sandbox_info(); - if (!bootstrap_util::IsModulePathAllowed(dll_path, exe_path)) { - const auto subst = - std::to_array({base::WideToUTF16(dll_name)}); - error = FormatErrorString(IDS_ERROR_INVALID_LOCATION, subst); - } - - if (error.empty()) { - cef_certificate_util::ThumbprintsInfo dll_thumbprints; - cef_certificate_util::GetClientThumbprints( - dll_path.value(), /*verify_binary=*/true, dll_thumbprints); - - // The DLL and EXE must either both be unsigned or both have all valid - // signatures and the same primary thumbprint. - if (!dll_thumbprints.IsSame(exe_thumbprints, /*allow_unsigned=*/true)) { - // Some part of the certificate validation process failed. - const auto subst = std::to_array( - {base::WideToUTF16(dll_name + TEXT(".dll")), - base::WideToUTF16(dll_thumbprints.errors)}); - error = FormatErrorString(IDS_ERROR_INVALID_CERT, subst); - } - } - - FreeLibrary(hModule); - } else { - const auto subst = std::to_array( - {base::WideToUTF16(dll_name), - base::WideToUTF16(cef_util::GetLastErrorAsString())}); - error = FormatErrorString(IDS_ERROR_LOAD_FAILED, subst); - } - } - - if (error.empty()) { - // Load the client DLL normally. - if (HMODULE hModule = ::LoadLibrary(dll_name.c_str())) { - if (auto* pFunc = (kProcType)::GetProcAddress(hModule, kProcName)) { #if defined(CEF_BUILD_BOOTSTRAP_CONSOLE) - return pFunc(argc, argv, sandbox_info); + result_code = pFunc(argc, argv, sandbox_info); #else - return pFunc(hInstance, lpCmdLine, nCmdShow, sandbox_info); + result_code = pFunc(hInstance, lpCmdLine, nCmdShow, sandbox_info); #endif - } else if (!is_sandboxed) { + } else { + if (!is_sandboxed) { const auto subst = std::to_array( {base::WideToUTF16(dll_name), base::WideToUTF16(cef_util::GetLastErrorAsString()), base::ASCIIToUTF16(std::string(kProcName))}); - error = FormatErrorString(IDS_ERROR_NO_PROC_EXPORT, subst); + ShowError(FormatErrorString(IDS_ERROR_NO_PROC_EXPORT, subst)); } - FreeLibrary(hModule); - } else if (!is_sandboxed) { + LOG(FATAL) << "Failed to find " << kProcName << " in " << dll_name + << ".dll with error " << ::GetLastError(); + } + + FreeLibrary(hModule); + } else { + if (!is_sandboxed) { const auto subst = std::to_array( {base::WideToUTF16(dll_name), base::WideToUTF16(cef_util::GetLastErrorAsString())}); - error = FormatErrorString(IDS_ERROR_LOAD_FAILED, subst); + ShowError(FormatErrorString(IDS_ERROR_LOAD_FAILED, subst)); } + + LOG(FATAL) << "Failed to load " << dll_name << ".dll with error " + << ::GetLastError(); } - // Don't try to show errors while sandboxed. - if (!error.empty() && !is_sandboxed) { - ShowError(error); - } - - return CEF_RESULT_CODE_KILLED; + // LOG(FATAL) is [[noreturn]], so we only reach this point if everything + // succeeded. + return result_code; }