diff --git a/BUILD.gn b/BUILD.gn index 802dc421f..0e6a1d031 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1169,6 +1169,9 @@ config("libcef_dll_wrapper_config") { # Increase the initial stack size to 8MiB from the default 1MiB. ldflags = [ "/STACK:0x800000" ] } + + # Required to support CefScopedLibraryLoader. + ldflags += [ "/DELAYLOAD:libcef.dll" ] } # Build using the minimum C++ version supported by the CEF binary distribution. @@ -1204,6 +1207,14 @@ static_library("libcef_dll_wrapper") { sources += gypi_paths2.libcef_dll_wrapper_sources_mac } + if (is_win) { + sources += gypi_paths2.libcef_dll_wrapper_sources_win + libs = [ + "crypt32.lib", + "wintrust.lib", + ] + } + defines = [ "WRAPPING_CEF_SHARED" ] configs += [ ":libcef_dll_wrapper_config" ] @@ -1247,13 +1258,15 @@ if (is_mac) { if (is_win) { bootstrap_sources = includes_common + includes_win + [ + "include/wrapper/cef_certificate_util_win.h", + "include/wrapper/cef_util_win.h", "libcef_dll/bootstrap/bootstrap_util_win.cc", "libcef_dll/bootstrap/bootstrap_util_win.h", "libcef_dll/bootstrap/bootstrap_win.cc", - "libcef_dll/bootstrap/certificate_util_win.cc", - "libcef_dll/bootstrap/certificate_util_win.h", "libcef_dll/bootstrap/win/bootstrap.rc", "libcef_dll/bootstrap/win/resource.h", + "libcef_dll/wrapper/cef_certificate_util_win.cc", + "libcef_dll/wrapper/cef_util_win.cc", "libcef/browser/preferred_stack_size_win.inc", ] @@ -1768,7 +1781,8 @@ if (is_mac) { ] # Delay-load as many DLLs as possible for sandbox and startup perf - # improvements. + # improvements. Don't use "delayloads_not_for_child_dll" here because + # we need some DLLs loaded in child processes before sandbox lockdown. configs += [ "//build/config/win:delayloads" ] libs = [ @@ -2264,6 +2278,7 @@ if (is_mac) { if (is_win) { sources += includes_win + + gypi_paths2.includes_wrapper_win + gypi_paths2.shared_sources_win + gypi_paths2.cefclient_sources_win + gypi_paths2.cefclient_sources_resources_win_rc @@ -2274,7 +2289,10 @@ if (is_mac) { # Delay-load as many DLLs as possible for sandbox and startup perf # improvements. - configs += [ "//build/config/win:delayloads" ] + configs += [ + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] defines += [ "CEF_USE_ATL", @@ -2350,6 +2368,7 @@ if (is_mac) { sources = includes_common + includes_win + gypi_paths2.includes_wrapper + + gypi_paths2.includes_wrapper_win + gypi_paths2.shared_sources_browser + gypi_paths2.shared_sources_common + gypi_paths2.shared_sources_renderer + @@ -2373,7 +2392,10 @@ if (is_mac) { # Delay-load as many DLLs as possible for sandbox and startup perf # improvements. - configs += [ "//build/config/win:delayloads" ] + configs += [ + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] libs = [ "comctl32.lib", @@ -2422,6 +2444,7 @@ if (is_mac) { if (is_win) { sources += includes_win + + gypi_paths2.includes_wrapper_win + gypi_paths2.cefsimple_sources_win + gypi_paths2.cefsimple_sources_resources_win_rc @@ -2431,7 +2454,10 @@ if (is_mac) { # Delay-load as many DLLs as possible for sandbox and startup perf # improvements. - configs += [ "//build/config/win:delayloads" ] + configs += [ + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] deps += [ ":cef_sandbox", @@ -2474,6 +2500,7 @@ if (is_mac) { sources = includes_common + includes_win + gypi_paths2.includes_wrapper + + gypi_paths2.includes_wrapper_win + gypi_paths2.cefsimple_sources_common + gypi_paths2.cefsimple_sources_win + gypi_paths2.cefsimple_sources_resources_win_rc @@ -2490,7 +2517,10 @@ if (is_mac) { # Delay-load as many DLLs as possible for sandbox and startup perf # improvements. - configs += [ "//build/config/win:delayloads" ] + configs += [ + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] libs = [ "comctl32.lib", @@ -2535,13 +2565,17 @@ if (is_mac) { ] if (is_win) { - sources += gypi_paths2.shared_sources_win + + sources += gypi_paths2.includes_wrapper_win + + gypi_paths2.shared_sources_win + gypi_paths2.ceftests_sources_win + gypi_paths2.ceftests_sources_resources_win_rc # Delay-load as many DLLs as possible for sandbox and startup perf # improvements. - configs += [ "//build/config/win:delayloads" ] + configs += [ + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] deps += [ ":cef_sandbox", @@ -2584,6 +2618,7 @@ if (is_mac) { sources = includes_common + gypi_paths2.includes_wrapper + + gypi_paths2.includes_wrapper_win + gypi_paths2.shared_sources_browser + gypi_paths2.shared_sources_common + gypi_paths2.shared_sources_renderer + @@ -2607,7 +2642,10 @@ if (is_mac) { # Delay-load as many DLLs as possible for sandbox and startup perf # improvements. - configs += [ "//build/config/win:delayloads" ] + configs += [ + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] } } } diff --git a/bazel/win/variables.bzl b/bazel/win/variables.bzl index dda74ea8e..d2b9d3808 100644 --- a/bazel/win/variables.bzl +++ b/bazel/win/variables.bzl @@ -32,9 +32,13 @@ DLLS_X64 = [ # processes. Conversely, some DLLs must be loaded before sandbox lockdown. In # unsandboxed processes they will load when first needed. The linker will # automatically ignore anything which is not linked to the binary at all (it is -# harmless to have an unmatched /delayload). This list should be kept in sync -# with Chromium's "delayloads" target from the //build/config/win/BUILD.gn file. +# harmless to have an unmatched /delayload). Lists should be kept in sync with +# targets from Chromium's //build/config/win/BUILD.gn file. DELAYLOAD_DLLS = [ + # Required to support CefScopedLibraryLoader. + "libcef.dll" + + # "delayloads" target. "api-ms-win-core-winrt-error-l1-1-0.dll", "api-ms-win-core-winrt-l1-1-0.dll", "api-ms-win-core-winrt-string-l1-1-0.dll", @@ -76,16 +80,33 @@ DELAYLOAD_DLLS = [ "winusb.dll", "wsock32.dll", "wtsapi32.dll", + + # "delayloads_not_for_child_dll" target. + "crypt32.dll", + "dbghelp.dll", + "dhcpcsvc.dll", + "dwrite.dll", + "iphlpapi.dll", + "oleaut32.dll", + "secur32.dll", + "userenv.dll", + "winhttp.dll", + "winmm.dll", + "winspool.drv", + "wintrust.dll", + "ws2_32.dll", ] # Standard link libraries. STANDARD_LIBS = [ "comctl32.lib", + "crypt32.lib", "delayimp.lib", "gdi32.lib", "rpcrt4.lib", "shlwapi.lib", "user32.lib", + "wintrust.lib", "ws2_32.lib", ] diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 8e5b8f89f..767c6f252 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -80,6 +80,11 @@ 'includes_wrapper_mac': [ 'include/wrapper/cef_library_loader.h', ], + 'includes_wrapper_win': [ + 'include/wrapper/cef_certificate_util_win.h', + 'include/wrapper/cef_library_loader.h', + 'include/wrapper/cef_util_win.h', + ], 'includes_win': [ 'include/cef_sandbox_win.h', 'include/internal/cef_win.h', @@ -166,10 +171,15 @@ 'libcef_dll/wrapper/libcef_dll_wrapper2.cc', ], 'libcef_dll_wrapper_sources_mac': [ - 'libcef_dll/wrapper/cef_library_loader_mac.mm', + 'libcef_dll/wrapper/cef_scoped_library_loader_mac.mm', 'libcef_dll/wrapper/cef_scoped_sandbox_context_mac.mm', 'libcef_dll/wrapper/libcef_dll_dylib.cc', ], + 'libcef_dll_wrapper_sources_win': [ + 'libcef_dll/wrapper/cef_certificate_util_win.cc', + 'libcef_dll/wrapper/cef_scoped_library_loader_win.cc', + 'libcef_dll/wrapper/cef_util_win.cc', + ], 'shared_sources_browser': [ 'tests/shared/browser/client_app_browser.cc', 'tests/shared/browser/client_app_browser.h', diff --git a/cmake/cef_variables.cmake.in b/cmake/cef_variables.cmake.in index 6ac0a9b5d..42463442f 100644 --- a/cmake/cef_variables.cmake.in +++ b/cmake/cef_variables.cmake.in @@ -432,15 +432,20 @@ if(OS_WINDOWS) list(APPEND CEF_LINKER_FLAGS_DEBUG /DEBUG # Generate debug information ) + + # Delayload most libraries as the dlls are simply not required at startup (or + # at all, depending on the process type). Some dlls open handles when they are + # loaded, and we may not want them to be loaded in renderers or other sandboxed + # processes. Conversely, some dlls must be loaded before sandbox lockdown. In + # unsandboxed processes they will load when first needed. The linker will + # automatically ignore anything which is not linked to the binary at all (it is + # harmless to have an unmatched /delayload). Lists should be kept in sync with + # targets from Chromium's //build/config/win/BUILD.gn file. set(CEF_DELAYLOAD_FLAGS - # Delayload most libraries as the dlls are simply not required at startup (or - # at all, depending on the process type). Some dlls open handles when they are - # loaded, and we may not want them to be loaded in renderers or other sandboxed - # processes. Conversely, some dlls must be loaded before sandbox lockdown. In - # unsandboxed processes they will load when first needed. The linker will - # automatically ignore anything which is not linked to the binary at all (it is - # harmless to have an unmatched /delayload). This list should be kept in sync - # with Chromium's "delayloads" target from the //build/config/win/BUILD.gn file. + # Required to support CefScopedLibraryLoader. + /DELAYLOAD:libcef.dll + + # "delayloads" target. /DELAYLOAD:api-ms-win-core-winrt-error-l1-1-0.dll /DELAYLOAD:api-ms-win-core-winrt-l1-1-0.dll /DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll @@ -482,6 +487,21 @@ if(OS_WINDOWS) /DELAYLOAD:winusb.dll /DELAYLOAD:wsock32.dll /DELAYLOAD:wtsapi32.dll + + # "delayloads_not_for_child_dll" target. + /DELAYLOAD:crypt32.dll + /DELAYLOAD:dbghelp.dll + /DELAYLOAD:dhcpcsvc.dll + /DELAYLOAD:dwrite.dll + /DELAYLOAD:iphlpapi.dll + /DELAYLOAD:oleaut32.dll + /DELAYLOAD:secur32.dll + /DELAYLOAD:userenv.dll + /DELAYLOAD:winhttp.dll + /DELAYLOAD:winmm.dll + /DELAYLOAD:winspool.drv + /DELAYLOAD:wintrust.dll + /DELAYLOAD:ws2_32.dll ) list(APPEND CEF_EXE_LINKER_FLAGS # For executable targets. @@ -530,10 +550,12 @@ if(OS_WINDOWS) # Standard libraries. set(CEF_STANDARD_LIBS comctl32.lib + crypt32.lib delayimp.lib gdi32.lib rpcrt4.lib shlwapi.lib + wintrust.lib ws2_32.lib ) diff --git a/include/wrapper/cef_certificate_util_win.h b/include/wrapper/cef_certificate_util_win.h new file mode 100644 index 000000000..4bd368220 --- /dev/null +++ b/include/wrapper/cef_certificate_util_win.h @@ -0,0 +1,152 @@ +// Copyright (c) 2025 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// The contents of this file are only available to applications that link +// against the libcef_dll_wrapper target. +// + +#ifndef CEF_INCLUDE_WRAPPER_CEF_CERTIFICATE_UTIL_WIN_H_ +#define CEF_INCLUDE_WRAPPER_CEF_CERTIFICATE_UTIL_WIN_H_ +#pragma once + +#include + +#include +#include + +namespace cef_certificate_util { + +// SHA1 upper-case hex encoded = 40 characters. +inline constexpr size_t kThumbprintLength = 40U; + +/// +/// Structure populated by GetClientThumbprints(). +/// +struct ThumbprintsInfo { + public: + /// + /// True if one or more signatures exist and all are valid. + /// + bool IsSignedAndValid() const { + return !valid_thumbprints.empty() && errors.empty(); + } + + /// + /// True if unsigned, or if one or more signatures exist and all are valid. + /// + bool IsUnsignedOrValid() const { + return !has_signature || IsSignedAndValid(); + } + + /// + /// True if this and |other| have the same signature status. If + /// |allow_unsigned| is true then both may be unsigned. Otherwise, one or more + /// signatures must exist, all must be valid, and the primary fingerprint must + /// be the same for both. + /// + bool IsSame(const ThumbprintsInfo& other, bool allow_unsigned) const { + if (allow_unsigned && !has_signature && !other.has_signature) { + return true; + } + + return IsSignedAndValid() && + other.HasPrimaryThumbprint(valid_thumbprints[0]); + } + + /// + /// True if a valid primary signature exists and it matches the specified + /// |thumbprint|. + /// + bool HasPrimaryThumbprint(const std::string& thumbprint) const { + return IsSignedAndValid() && valid_thumbprints[0] == thumbprint; + } + + /// + /// True if a primary signature exists, irrespective of validity. + /// + bool has_signature = false; + + /// + /// Thumbprints for signatures, if any, that passed verification. + /// + std::vector valid_thumbprints; + + /// + /// Thumbprints for signatures, if any, that failed verification. Will not be + /// populated if |verify_binary=true| was passed to GetClientThumbprints(). + /// + std::vector invalid_thumbprints; + + /// + /// Errors (newline delimited) if any signatures failed verification. + /// + std::wstring errors; +}; + +/// +/// Process client signatures for the binary at the specified abolute +/// |binary_path| and populate |info|. If |verify_binary| is true and the +/// primary signature fails verification then no further signatures will be +/// processed. For a code signing example and usage details see +/// https://github.com/chromiumembedded/cef/issues/3824#issuecomment-2892139995 +/// +void GetClientThumbprints(const std::wstring& binary_path, + bool verify_binary, + ThumbprintsInfo& info); + +/// +/// Evaluate the binary at the specified absolute |binary_path| for common +/// requirements and populate |info|. If the binary is code signed then all +/// signatures must be valid. If |thumbprint| is a SHA1 hash (e.g. 40 character +/// upper-case hex-encoded value) then the primary signature must match that +/// thumbprint. If |allow_unsigned| is true and |thumbprint| is nullptr then the +/// binary may be unsigned, otherwise it must be validly signed. Returns true if +/// all requirements are met. +/// +bool ValidateCodeSigning(const std::wstring& binary_path, + const char* thumbprint, + bool allow_unsigned, + ThumbprintsInfo& info); + +/// +/// Same as ValidateCodeSigning, but failures result in a FATAL error and +/// application termination. Optionally populate |info| is validation succeeds. +/// Usage must be protected by cef::logging::ScopedEarlySupport if called prior +/// to libcef loading. +/// +void ValidateCodeSigningAssert(const std::wstring& binary_path, + const char* thumbprint, + bool allow_unsigned, + ThumbprintsInfo* info = nullptr); + +} // namespace cef_certificate_util + +#endif // CEF_INCLUDE_WRAPPER_CEF_CERTIFICATE_UTIL_WIN_H_ diff --git a/include/wrapper/cef_library_loader.h b/include/wrapper/cef_library_loader.h index 604148669..f61927be6 100644 --- a/include/wrapper/cef_library_loader.h +++ b/include/wrapper/cef_library_loader.h @@ -33,11 +33,11 @@ #include "include/base/cef_build.h" -#ifdef __cplusplus -#include +#if defined(OS_MAC) +#ifdef __cplusplus extern "C" { -#endif // __cplusplus +#endif /// /// Load the CEF library at the specified |path|. Returns true (1) on @@ -53,6 +53,12 @@ int cef_unload_library(void); #ifdef __cplusplus } +#endif + +#endif // defined(OS_MAC) + +#ifdef __cplusplus +#include #if defined(OS_MAC) @@ -99,7 +105,7 @@ int cef_unload_library(void); /// } /// /// -class CefScopedLibraryLoader { +class CefScopedLibraryLoader final { public: CefScopedLibraryLoader(); @@ -125,10 +131,81 @@ class CefScopedLibraryLoader { private: bool Load(bool helper); - bool loaded_; + bool loaded_ = false; }; -#endif // defined(OS_MAC) +#elif defined(OS_WIN) +#include + +/// +/// Scoped helper for loading and unloading the CEF library at runtime from the +/// specific location on disk, with optional code signing verification. Must be +/// used in combination with the "/DELAYLOAD:libcef.dll" linker flag. +/// +/// Example usage: +/// +///
+///   #include "include/wrapper/cef_library_loader.h"
+///
+///   int APIENTRY wWinMain(HINSTANCE hInstance,
+///                         HINSTANCE hPrevInstance,
+///                         LPTSTR lpCmdLine,
+///                         int nCmdShow)
+///     // Dynamically load the CEF library.
+///     CefScopedLibraryLoader library_loader;
+///     if (!library_loader.LoadInSubProcess() &&
+///         !library_loader.LoadInMain(L"C:\Program Files\CEF\libcef.dll")) {
+///       return 1;
+///     }
+///
+///     // Continue with CEF initialization...
+///   }
+/// 
+/// +class CefScopedLibraryLoader final { + public: + CefScopedLibraryLoader(); + + CefScopedLibraryLoader(const CefScopedLibraryLoader&) = delete; + CefScopedLibraryLoader& operator=(const CefScopedLibraryLoader&) = delete; + + ~CefScopedLibraryLoader(); + + /// + /// Load the CEF library (libcef.dll) in the main process from the specified + /// absolute path. If libcef.dll is code signed then all signatures must be + /// valid. If |thumbprint| is a SHA1 hash (e.g. 40 character upper-case + /// hex-encoded value) then the primary signature must match that thumbprint. + /// If |allow_unsigned| is true and |thumbprint| is nullptr then libcef.dll + /// may be unsigned, otherwise it must be validly signed. Failure of code + /// signing requirements or DLL loading will result in a FATAL error and + /// application termination. Returns true if the load succeeds. Usage must be + /// protected by cef::logging::ScopedEarlySupport. + /// + bool LoadInMainAssert(const wchar_t* dll_path, + const char* thumbprint, + bool allow_unsigned); + + /// + /// Load the CEF library (libcef.dll) in a sub-process that may be sandboxed. + /// The path will be determined based on command-line arguments for the + /// current process. Failure of DLL loading will result in a FATAL error and + /// application termination. Returns true if the load succeeds. Usage must be + /// protected by cef::logging::ScopedEarlySupport. + /// + bool LoadInSubProcessAssert(); + + private: + HMODULE handle_ = nullptr; +}; + +namespace switches { +// Changes to this value require rebuilding libcef.dll. +inline constexpr char kLibcefPath[] = "libcef-path"; +inline constexpr wchar_t kLibcefPathW[] = L"libcef-path"; +} // namespace switches + +#endif // defined(OS_WIN) #endif // __cplusplus #endif // CEF_INCLUDE_WRAPPER_CEF_LIBRARY_LOADER_H_ diff --git a/include/wrapper/cef_util_win.h b/include/wrapper/cef_util_win.h new file mode 100644 index 000000000..3324ae3bd --- /dev/null +++ b/include/wrapper/cef_util_win.h @@ -0,0 +1,65 @@ +// Copyright (c) 2025 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// The contents of this file are only available to applications that link +// against the libcef_dll_wrapper target. +// + +#ifndef CEF_INCLUDE_WRAPPER_CEF_UTIL_WIN_H_ +#define CEF_INCLUDE_WRAPPER_CEF_UTIL_WIN_H_ +#pragma once + +#include + +#include +#include + +namespace cef_util { + +// Returns the fully qualified file path for the executable module. +std::wstring GetExePath(); + +// Returns the fully qualified file path for |module|. +std::wstring GetModulePath(HMODULE module); + +// Returns the value of GetLastError() as a string. +std::wstring GetLastErrorAsString(); + +// Parse command line arguments for |hInstance|. +std::vector ParseCommandLineArgs(const wchar_t* str); + +// Returns the value for |name| in |command_line|, if any. +std::wstring GetCommandLineValue(const std::vector& command_line, + const std::wstring& name); + +} // namespace cef_util + +#endif // CEF_INCLUDE_WRAPPER_CEF_UTIL_WIN_H_ diff --git a/libcef/browser/chrome/chrome_content_browser_client_cef.cc b/libcef/browser/chrome/chrome_content_browser_client_cef.cc index 6a947e69e..c8684f4d3 100644 --- a/libcef/browser/chrome/chrome_content_browser_client_cef.cc +++ b/libcef/browser/chrome/chrome_content_browser_client_cef.cc @@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/path_service.h" +#include "cef/include/wrapper/cef_library_loader.h" #include "cef/libcef/browser/browser_frame.h" #include "cef/libcef/browser/browser_host_base.h" #include "cef/libcef/browser/browser_info_manager.h" @@ -164,6 +165,18 @@ void HandleExternalProtocolHelper( isolation_info, nullptr); } +#if BUILDFLAG(IS_WIN) +// Returns the module handle that contains this code (e.g. libcef.dll). +HINSTANCE GetCodeModuleHandle() { + HMODULE hModule = nullptr; + CHECK(::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(GetCodeModuleHandle), + &hModule)); + return hModule; +} +#endif + } // namespace ChromeContentBrowserClientCef::ChromeContentBrowserClientCef() = default; @@ -220,12 +233,18 @@ void ChromeContentBrowserClientCef::AppendExtraCommandLineSwitches( #if BUILDFLAG(IS_WIN) { - const auto& module_value = bootstrap_util::GetValidatedModuleValue( - *browser_cmd, bootstrap_util::GetExePath()); + const auto& exe_path = bootstrap_util::GetExePath(); + const auto& module_value = + bootstrap_util::GetValidatedModuleValue(*browser_cmd, exe_path); if (!module_value.empty()) { command_line->AppendSwitchNative(bootstrap_util::switches::kModule, module_value); } + const auto& libcef_path = + bootstrap_util::GetModulePath(GetCodeModuleHandle()); + if (libcef_path.DirName() != exe_path.DirName()) { + command_line->AppendSwitchPath(switches::kLibcefPath, libcef_path); + } } #endif diff --git a/libcef_dll/CMakeLists.txt.in b/libcef_dll/CMakeLists.txt.in index 72a2d99df..7a46f5683 100644 --- a/libcef_dll/CMakeLists.txt.in +++ b/libcef_dll/CMakeLists.txt.in @@ -29,6 +29,7 @@ set(CEF_TARGET libcef_dll_wrapper) 'autogen_capi_includes', 'includes_wrapper', 'includes_wrapper_mac:MAC', + 'includes_wrapper_win:WINDOWS', 'includes_win:WINDOWS', 'includes_win_capi:WINDOWS', 'includes_mac:MAC', @@ -38,6 +39,7 @@ set(CEF_TARGET libcef_dll_wrapper) 'libcef_dll_wrapper_sources_base', 'libcef_dll_wrapper_sources_common', 'libcef_dll_wrapper_sources_mac:MAC', + 'libcef_dll_wrapper_sources_win:WINDOWS', 'autogen_client_side', ], }} diff --git a/libcef_dll/bootstrap/bootstrap_util_win.cc b/libcef_dll/bootstrap/bootstrap_util_win.cc index 10e708500..ad8adccba 100644 --- a/libcef_dll/bootstrap/bootstrap_util_win.cc +++ b/libcef_dll/bootstrap/bootstrap_util_win.cc @@ -11,6 +11,7 @@ namespace bootstrap_util { namespace { +// Changes to these values require rebuilding libcef.dll. constexpr wchar_t kWindowsSelfName[] = TEXT("bootstrap"); constexpr wchar_t kConsoleSelfName[] = TEXT("bootstrapc"); @@ -78,28 +79,4 @@ bool IsModulePathAllowed(const base::FilePath& module_path, return module_path.DirName() == exe_path.DirName(); } -std::wstring GetLastErrorAsString() { - std::wstring error_message; - - DWORD error_message_id = ::GetLastError(); - if (error_message_id == 0) { - return error_message; - } - - LPWSTR message_buffer = NULL; - - DWORD size = FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&message_buffer, 0, NULL); - - if (message_buffer) { - error_message = std::wstring(message_buffer, size); - LocalFree(message_buffer); - } - - return error_message; -} - } // namespace bootstrap_util diff --git a/libcef_dll/bootstrap/bootstrap_util_win.h b/libcef_dll/bootstrap/bootstrap_util_win.h index 992f280c9..01c8474b7 100644 --- a/libcef_dll/bootstrap/bootstrap_util_win.h +++ b/libcef_dll/bootstrap/bootstrap_util_win.h @@ -19,8 +19,9 @@ class CommandLine; namespace bootstrap_util { namespace switches { +// Changes to this value require rebuilding libcef.dll. inline constexpr char kModule[] = "module"; -} +} // namespace switches // Returns true if |name| is one of the default bootstrap executable names. bool IsDefaultExeName(const std::wstring& name); @@ -47,8 +48,6 @@ std::wstring GetDefaultModuleValue(const base::FilePath& exe_path); bool IsModulePathAllowed(const base::FilePath& module_path, const base::FilePath& exe_path); -std::wstring GetLastErrorAsString(); - } // namespace bootstrap_util #endif // CEF_LIBCEF_DLL_BOOTSTRAP_BOOTSTRAP_UTIL_WIN_H_ diff --git a/libcef_dll/bootstrap/bootstrap_win.cc b/libcef_dll/bootstrap/bootstrap_win.cc index 2ae98814a..9633f2c63 100644 --- a/libcef_dll/bootstrap/bootstrap_win.cc +++ b/libcef_dll/bootstrap/bootstrap_win.cc @@ -14,9 +14,10 @@ #include "base/strings/utf_string_conversions.h" #include "cef/include/cef_sandbox_win.h" #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/preferred_stack_size_win.inc" #include "cef/libcef_dll/bootstrap/bootstrap_util_win.h" -#include "cef/libcef_dll/bootstrap/certificate_util_win.h" #include "cef/libcef_dll/bootstrap/win/resource.h" namespace { @@ -105,7 +106,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, std::wstring dll_name; base::FilePath exe_path; - certificate_util::ThumbprintsInfo exe_thumbprints; + cef_certificate_util::ThumbprintsInfo exe_thumbprints; if (is_sandboxed) { // Running as a sandboxed sub-process. May already be locked down, so we @@ -135,7 +136,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, return CEF_RESULT_CODE_KILLED; } - certificate_util::GetClientThumbprints( + cef_certificate_util::GetClientThumbprints( exe_path.value(), /*verify_binary=*/true, exe_thumbprints); // The executable must either be unsigned or have all valid signatures. @@ -175,8 +176,8 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } if (error.empty()) { - certificate_util::ThumbprintsInfo dll_thumbprints; - certificate_util::GetClientThumbprints( + 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 @@ -201,7 +202,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } else if (!is_sandboxed) { const auto subst = std::to_array( {base::WideToUTF16(dll_name), - base::WideToUTF16(bootstrap_util::GetLastErrorAsString()), + base::WideToUTF16(cef_util::GetLastErrorAsString()), base::ASCIIToUTF16(std::string(kProcName))}); error = FormatErrorString(IDS_ERROR_NO_PROC_EXPORT, subst); } @@ -211,7 +212,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } else if (!is_sandboxed) { const auto subst = std::to_array( {base::WideToUTF16(dll_name), - base::WideToUTF16(bootstrap_util::GetLastErrorAsString())}); + base::WideToUTF16(cef_util::GetLastErrorAsString())}); error = FormatErrorString(IDS_ERROR_LOAD_FAILED, subst); } diff --git a/libcef_dll/bootstrap/certificate_util_win.h b/libcef_dll/bootstrap/certificate_util_win.h deleted file mode 100644 index 9b0229dfa..000000000 --- a/libcef_dll/bootstrap/certificate_util_win.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2025 The Chromium Embedded Framework 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_DLL_BOOTSTRAP_CERTIFICATE_UTIL_WIN_H_ -#define CEF_LIBCEF_DLL_BOOTSTRAP_CERTIFICATE_UTIL_WIN_H_ -#pragma once - -#include - -#include -#include - -namespace certificate_util { - -struct ThumbprintsInfo { - public: - bool IsSignedAndValid() const { - return !valid_thumbprints.empty() && errors.empty(); - } - - bool IsUnsignedOrValid() const { - return !has_signature || IsSignedAndValid(); - } - - bool IsSame(const ThumbprintsInfo& other, bool allow_unsigned) const { - if (allow_unsigned && !has_signature && !other.has_signature) { - return true; - } - - // Returns true if both are valid and have the same primary thumbprint. - return IsSignedAndValid() && other.IsSignedAndValid() && - valid_thumbprints[0] == other.valid_thumbprints[0]; - } - - // True if a primary signature exists, irrespective of validity. - bool has_signature = false; - - // Thumbprints for signatures, if any, that passed verification. - std::vector valid_thumbprints; - - // Thumbprints for signatures, if any, that failed verification. Will not be - // populated if |verify_binary=true| was passed to GetClientThumbprints(). - std::vector invalid_thumbprints; - - // Errors (newline delimited) if any signatures failed verification. - std::wstring errors; -}; - -// Process client signatures for the binary at |binary_path| and populate -// |info|. If |verify_binary| is true and the primary signature fails -// verification then no further signatures will be processed. -void GetClientThumbprints(const std::wstring& binary_path, - bool verify_binary, - ThumbprintsInfo& info); - -} // namespace certificate_util - -#endif // CEF_LIBCEF_DLL_BOOTSTRAP_CERTIFICATE_UTIL_WIN_H_ diff --git a/libcef_dll/bootstrap/certificate_util_win.cc b/libcef_dll/wrapper/cef_certificate_util_win.cc similarity index 61% rename from libcef_dll/bootstrap/certificate_util_win.cc rename to libcef_dll/wrapper/cef_certificate_util_win.cc index cf9112244..16a4616bc 100644 --- a/libcef_dll/bootstrap/certificate_util_win.cc +++ b/libcef_dll/wrapper/cef_certificate_util_win.cc @@ -2,19 +2,26 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#include "cef/libcef_dll/bootstrap/certificate_util_win.h" +#include "include/wrapper/cef_certificate_util_win.h" #include #include +#include #include #include -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "cef/libcef_dll/bootstrap/bootstrap_util_win.h" +#include -namespace certificate_util { +#include "include/wrapper/cef_util_win.h" + +#if defined(CEF_BUILD_BOOTSTRAP) +#include "base/logging.h" +#else +#include "include/base/cef_logging.h" +#endif + +namespace cef_certificate_util { namespace { @@ -37,23 +44,43 @@ bool InitCryptProviderStructs(WINTRUST_DATA& win_trust_data, return false; } -std::string BytesToHexString(const void* bytes, size_t length) { - const unsigned char* bytes_c = reinterpret_cast(bytes); - +// Returns bytes as an upper-case hex string. +std::string BytesToHexString(const BYTE* bytes, size_t length) { std::string hex_string; - hex_string.reserve(length * 2); + hex_string.resize(length * 2); for (size_t index = 0; index < length; ++index) { - hex_string.append(base::StringPrintf("%02x", bytes_c[index])); + sprintf_s(&hex_string[2 * index], 3, "%02X", bytes[index]); } return hex_string; } +std::wstring ErrorPrefix(DWORD i) { + return L"\nCertificate " + std::to_wstring(i) + L": "; +} + +std::wstring NormalizeError(const std::wstring& err) { + std::wstring str = err; + // Replace newlines. + std::replace(str.begin(), str.end(), L'\n', L' '); + return str; +} + +std::wstring GetBinaryName(const std::wstring& path) { + auto sep_pos = path.find_last_of(L"/\\"); + if (sep_pos == std::wstring::npos) { + return path; + } + return path.substr(0, sep_pos); +} + } // namespace void GetClientThumbprints(const std::wstring& binary_path, bool verify_binary, ThumbprintsInfo& info) { + info = {}; + const HWND wvt_handle = static_cast(INVALID_HANDLE_VALUE); GUID wvt_policy = WINTRUST_ACTION_GENERIC_VERIFY_V2; @@ -71,9 +98,6 @@ void GetClientThumbprints(const std::wstring& binary_path, // After the first WinVerifyTrust call succeeds, we will continue inspecting // the rest of the signatures. for (DWORD i = 0; i < sig_settings.cSecondarySigs + 1; ++i) { - const auto& error_prefix = - base::ASCIIToWide(base::StringPrintf("\nCertificate %d: ", i)); - WINTRUST_DATA win_trust_data = {0}; win_trust_data.cbStruct = sizeof(win_trust_data); win_trust_data.dwUIChoice = WTD_UI_NONE; @@ -102,8 +126,8 @@ void GetClientThumbprints(const std::wstring& binary_path, } } - info.errors += error_prefix + TEXT("WinVerifyTrust failed: ") + - bootstrap_util::GetLastErrorAsString(); + info.errors += ErrorPrefix(i) + L"WinVerifyTrust failed: " + + cef_util::GetLastErrorAsString(); // WinVerifyTrust will fail if the signing certificates can't be verified, // but it will still provide information about them in the StateData @@ -120,7 +144,7 @@ void GetClientThumbprints(const std::wstring& binary_path, } if (!win_trust_data.hWVTStateData) { - info.errors += error_prefix + TEXT("No WinVerifyTrust data"); + info.errors += ErrorPrefix(i) + L"No WinVerifyTrust data"; continue; } @@ -149,12 +173,12 @@ void GetClientThumbprints(const std::wstring& binary_path, thumbprints.emplace_back( BytesToHexString(sha1_bytes, sha1_bytes_count)); } else { - info.errors += error_prefix + - TEXT("CertGetCertificateContextProperty failed: ") + - bootstrap_util::GetLastErrorAsString(); + info.errors += ErrorPrefix(i) + + L"CertGetCertificateContextProperty failed: " + + cef_util::GetLastErrorAsString(); } } else { - info.errors += error_prefix + TEXT("Invalid WinVerifyTrust data"); + info.errors += ErrorPrefix(i) + L"Invalid WinVerifyTrust data"; } win_trust_data.dwStateAction = WTD_STATEACTION_CLOSE; @@ -164,4 +188,56 @@ void GetClientThumbprints(const std::wstring& binary_path, info.has_signature = true; } -} // namespace certificate_util +bool ValidateCodeSigning(const std::wstring& binary_path, + const char* thumbprint, + bool allow_unsigned, + ThumbprintsInfo& info) { + GetClientThumbprints(binary_path.data(), /*verify_binary=*/true, info); + if (!info.errors.empty()) { + // The binary is signed, but one or more of the signatures failed + // validation. + return false; + } + + const bool thumbprint_required = + thumbprint && std::strlen(thumbprint) == kThumbprintLength; + allow_unsigned &= !thumbprint_required; + + if (thumbprint_required) { + if (!info.HasPrimaryThumbprint(thumbprint)) { + // The DLL is unsigned or the primary signature does not match the + // required thumbprint. + return false; + } + } else if (!allow_unsigned && !info.has_signature) { + // The DLL in unsigned which is not allowed. + return false; + } + + return true; +} + +void ValidateCodeSigningAssert(const std::wstring& binary_path, + const char* thumbprint, + bool allow_unsigned, + ThumbprintsInfo* info) { + cef_certificate_util::ThumbprintsInfo thumbprints; + bool valid = cef_certificate_util::ValidateCodeSigning( + binary_path.data(), thumbprint, allow_unsigned, thumbprints); + if (!thumbprints.errors.empty()) { + // The DLL is signed, but one or more of the signatures failed validation. + LOG(FATAL) << "Failed " << GetBinaryName(binary_path) + << " certificate validation: " + << NormalizeError(thumbprints.errors); + } + if (!valid) { + // Failed other requirements. + LOG(FATAL) << "Failed " << GetBinaryName(binary_path) + << " validation requirements."; + } + if (info) { + *info = thumbprints; + } +} + +} // namespace cef_certificate_util diff --git a/libcef_dll/wrapper/cef_library_loader_mac.mm b/libcef_dll/wrapper/cef_scoped_library_loader_mac.mm similarity index 96% rename from libcef_dll/wrapper/cef_library_loader_mac.mm rename to libcef_dll/wrapper/cef_scoped_library_loader_mac.mm index 046f46b0b..31aaaa9d2 100644 --- a/libcef_dll/wrapper/cef_library_loader_mac.mm +++ b/libcef_dll/wrapper/cef_scoped_library_loader_mac.mm @@ -2,8 +2,6 @@ // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. -#include "include/wrapper/cef_library_loader.h" - #include #include #include @@ -11,6 +9,8 @@ #include #include +#include "include/wrapper/cef_library_loader.h" + namespace { const char kFrameworkPath[] = @@ -46,7 +46,7 @@ std::string GetFrameworkPath(bool helper) { } // namespace -CefScopedLibraryLoader::CefScopedLibraryLoader() : loaded_(false) {} +CefScopedLibraryLoader::CefScopedLibraryLoader() = default; bool CefScopedLibraryLoader::Load(bool helper) { if (loaded_) { diff --git a/libcef_dll/wrapper/cef_scoped_library_loader_win.cc b/libcef_dll/wrapper/cef_scoped_library_loader_win.cc new file mode 100644 index 000000000..e044d346b --- /dev/null +++ b/libcef_dll/wrapper/cef_scoped_library_loader_win.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2025 The Chromium Embedded Framework 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 + +#include "include/base/cef_logging.h" +#include "include/wrapper/cef_certificate_util_win.h" +#include "include/wrapper/cef_library_loader.h" +#include "include/wrapper/cef_util_win.h" + +namespace { + +bool IsEqualIgnoringCase(const std::wstring& str1, const std::wstring& str2) { + return std::equal( + str1.begin(), str1.end(), str2.begin(), str2.end(), + [](wchar_t a, wchar_t b) { return std::tolower(a) == std::tolower(b); }); +} + +HMODULE Load(const std::wstring& dll_path, + const char* thumbprint, + bool allow_unsigned, + bool is_subprocess) { + if (!is_subprocess) { + // 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_path.data(), nullptr, + DONT_RESOLVE_DLL_REFERENCES | LOAD_WITH_ALTERED_SEARCH_PATH)) { + const auto& module_path = cef_util::GetModulePath(hModule); + if (!IsEqualIgnoringCase(module_path, dll_path)) { + LOG(FATAL) << "Found libcef.dll at unexpected path"; + } + + // Generate a FATAL error and crash if validation fails. + cef_certificate_util::ValidateCodeSigningAssert(dll_path, thumbprint, + allow_unsigned); + + FreeLibrary(hModule); + } else { + LOG(FATAL) << "Failed to load libcef.dll"; + } + } + + // Load normally. + if (HMODULE hModule = ::LoadLibraryEx(dll_path.data(), nullptr, + LOAD_WITH_ALTERED_SEARCH_PATH)) { + return hModule; + } + + LOG(FATAL) << "Failed to load libcef.dll"; + return nullptr; +} + +} // namespace + +CefScopedLibraryLoader::CefScopedLibraryLoader() = default; + +bool CefScopedLibraryLoader::LoadInMainAssert(const wchar_t* dll_path, + const char* thumbprint, + bool allow_unsigned) { + CHECK(!handle_); + handle_ = Load(dll_path, thumbprint, allow_unsigned, false); + return handle_ != nullptr; +} + +bool CefScopedLibraryLoader::LoadInSubProcessAssert() { + CHECK(!handle_); + + constexpr wchar_t kProcessTypeW[] = L"type"; + const auto& command_line = + cef_util::ParseCommandLineArgs(::GetCommandLineW()); + if (command_line.size() <= 1) { + return false; + } + if (cef_util::GetCommandLineValue(command_line, kProcessTypeW).empty()) { + return false; + } + + const auto& dll_path = + cef_util::GetCommandLineValue(command_line, switches::kLibcefPathW); + if (dll_path.empty()) { + // Default is libcef.dll in the same directory the executable and loaded by + // the delayload helper. + return true; + } + + handle_ = Load(dll_path, nullptr, true, true); + return handle_ != nullptr; +} + +CefScopedLibraryLoader::~CefScopedLibraryLoader() { + if (handle_) { + ::FreeLibrary(handle_); + } +} diff --git a/libcef_dll/wrapper/cef_util_win.cc b/libcef_dll/wrapper/cef_util_win.cc new file mode 100644 index 000000000..400f11a32 --- /dev/null +++ b/libcef_dll/wrapper/cef_util_win.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2025 The Chromium Embedded Framework 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 "include/wrapper/cef_util_win.h" + +#include + +#include +#include +#include +#include + +#if defined(CEF_BUILD_BOOTSTRAP) +#include "base/check_op.h" +#else +#include "include/base/cef_logging.h" +#endif + +namespace cef_util { + +namespace { + +void TrimWhitespace(std::wstring& str) { + // Trim leading whitespace + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](auto ch) { + return !std::iswspace(ch); + })); + + // Trim trailing whitespace + str.erase(std::find_if(str.rbegin(), str.rend(), + [](auto ch) { return !std::iswspace(ch); }) + .base(), + str.end()); +} + +} // namespace + +std::wstring GetExePath() { + HMODULE hModule = ::GetModuleHandle(nullptr); + CHECK(hModule); + return GetModulePath(hModule); +} + +std::wstring GetModulePath(HMODULE module) { + wchar_t buffer[MAX_PATH]; + DWORD length = ::GetModuleFileName(module, buffer, MAX_PATH); + CHECK_NE(length, 0U); + CHECK_LT(length, static_cast(MAX_PATH)); + return buffer; +} + +std::wstring GetLastErrorAsString() { + std::wstring error_message; + + DWORD error_message_id = ::GetLastError(); + if (error_message_id == 0) { + return error_message; + } + + LPWSTR message_buffer = NULL; + + DWORD size = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&message_buffer, 0, NULL); + + if (message_buffer) { + error_message = std::wstring(message_buffer, size); + LocalFree(message_buffer); + } + + return error_message; +} + +// Implementation based on CommandLine::ParseFromString. +std::vector ParseCommandLineArgs(const wchar_t* str) { + std::wstring command_line = str; + TrimWhitespace(command_line); + + int num_args = 0; + wchar_t** args = NULL; + // When calling CommandLineToArgvW, use the apiset if available. + // Doing so will bypass loading shell32.dll on Windows. + HMODULE downlevel_shell32_dll = + ::LoadLibraryEx(L"api-ms-win-downlevel-shell32-l1-1-0.dll", nullptr, + LOAD_LIBRARY_SEARCH_SYSTEM32); + if (downlevel_shell32_dll) { + auto command_line_to_argv_w_proc = + reinterpret_cast( + ::GetProcAddress(downlevel_shell32_dll, "CommandLineToArgvW")); + if (command_line_to_argv_w_proc) { + args = command_line_to_argv_w_proc(command_line.data(), &num_args); + } + } else { + // Since the apiset is not available, allow the delayload of shell32.dll + // to take place. + args = ::CommandLineToArgvW(command_line.data(), &num_args); + } + + std::vector result; + result.reserve(num_args); + + for (int i = 0; i < num_args; ++i) { + std::wstring arg = args[i]; + TrimWhitespace(arg); + if (!arg.empty()) { + result.push_back(arg); + } + } + + LocalFree(args); + + if (downlevel_shell32_dll) { + ::FreeLibrary(downlevel_shell32_dll); + } + + return result; +} + +std::wstring GetCommandLineValue(const std::vector& command_line, + const std::wstring& name) { + constexpr wchar_t kPrefix[] = L"--"; + constexpr wchar_t kSeparator[] = L"="; + constexpr wchar_t kQuoteChar = L'"'; + + const std::wstring& start = kPrefix + name + kSeparator; + for (const auto& arg : command_line) { + if (arg.find(start) == 0) { + auto value = arg.substr(start.length()); + if (value.length() > 2 && value[0] == kQuoteChar && + value[arg.length() - 1] == kQuoteChar) { + value = value.substr(1, value.length() - 2); + } + return value; + } + } + return std::wstring(); +} + +} // namespace cef_util diff --git a/tests/cefclient/cefclient_win.cc b/tests/cefclient/cefclient_win.cc index 1112341b1..6a1eb4528 100644 --- a/tests/cefclient/cefclient_win.cc +++ b/tests/cefclient/cefclient_win.cc @@ -4,10 +4,14 @@ #include +#include #include #include "include/cef_command_line.h" #include "include/cef_sandbox_win.h" +#include "include/wrapper/cef_certificate_util_win.h" +#include "include/wrapper/cef_library_loader.h" +#include "include/wrapper/cef_util_win.h" #include "tests/cefclient/browser/main_context_impl.h" #include "tests/cefclient/browser/main_message_loop_multithreaded_win.h" #include "tests/cefclient/browser/resource.h" @@ -16,6 +20,7 @@ #include "tests/shared/browser/client_app_browser.h" #include "tests/shared/browser/main_message_loop_external_pump.h" #include "tests/shared/browser/main_message_loop_std.h" +#include "tests/shared/browser/util_win.h" #include "tests/shared/common/client_app_other.h" #include "tests/shared/common/client_switches.h" #include "tests/shared/renderer/client_app_renderer.h" @@ -23,9 +28,101 @@ namespace client { namespace { +// Configure code signing requirements. For a code signing example see +// https://github.com/chromiumembedded/cef/issues/3824#issuecomment-2892139995 + +// TODO(client): Optionally require that the primary certificate match a +// specific thumbprint by setting this value to the SHA1 hash (e.g. 40 character +// upper-case hex-encoded value). If this valus is empty and |kAllowUnsigned| is +// false then any valid signature will be allowed. This is the "Thumbprint" +// output reported by some Windows PowerShell commands. It can also be retrieved +// directly with a PowerShell command like: > (Get-ChildItem +// Cert:\CurrentUser\My -CodeSigningCert)[0].Thumbprint +constexpr char kRequiredThumbprint[] = ""; + +// TODO(client): Optionally disallow unsigned binaries by setting this value to +// false. This value is disregarded if |kRequiredThumbprint| is specified. +constexpr bool kAllowUnsigned = true; + +// TODO(client): Optionally require that all binaries be signed with the same +// primary thumbprint. This value is ignored when |kRequiredThumbprint| is +// specified or if |kAllowUnsigned| is true. +constexpr bool kRequireMatchingThumbprints = false; + +static_assert(sizeof(kRequiredThumbprint) == 1 || + sizeof(kRequiredThumbprint) == + cef_certificate_util::kThumbprintLength + 1, + "invalid size for kRequiredThumbprint"); + +const char* RequiredThumbprint(std::string* exe_thumbprint) { + if constexpr (sizeof(kRequiredThumbprint) == + cef_certificate_util::kThumbprintLength + 1) { + return kRequiredThumbprint; + } + + if (!kAllowUnsigned && kRequireMatchingThumbprints && exe_thumbprint && + exe_thumbprint->length() == cef_certificate_util::kThumbprintLength) { + return exe_thumbprint->c_str(); + } + + return nullptr; +} + +bool VerifyCodeSigningAndLoad(CefScopedLibraryLoader& library_loader) { + // Enable early logging support (required before libcef is loaded). + // The *Assert() calls below will output a FATAL error and crash on failure. + cef::logging::ScopedEarlySupport scoped_logging({}); + + if (library_loader.LoadInSubProcessAssert()) { + // Running as a sub-process. We may be sandboxed. Nothing more to be done. + return true; + } + + std::string exe_thumbprint; + + // Check signatures for the already loaded executable. This may be the + // bootstrap, or the client executable if not using the bootstrap. + const std::wstring& exe_path = cef_util::GetExePath(); + cef_certificate_util::ThumbprintsInfo exe_info; + cef_certificate_util::ValidateCodeSigningAssert( + exe_path, RequiredThumbprint(nullptr), kAllowUnsigned, &exe_info); + if (exe_info.IsSignedAndValid()) { + exe_thumbprint = exe_info.valid_thumbprints[0]; + CHECK_EQ(cef_certificate_util::kThumbprintLength, exe_thumbprint.length()); + } + +#if defined(CEF_USE_BOOTSTRAP) + // Using a separate bootstrap executable that loaded a client DLL. Check + // signatures for the already loaded client DLL. + const std::wstring& client_dll_path = + cef_util::GetModulePath(client::GetCodeModuleHandle()); + cef_certificate_util::ValidateCodeSigningAssert( + client_dll_path, RequiredThumbprint(&exe_thumbprint), kAllowUnsigned); +#endif // defined(CEF_USE_BOOTSTRAP) + + // Require libcef.dll in the same directory as the executable. + auto sep_pos = exe_path.find_last_of(L"/\\"); + CHECK(sep_pos != std::wstring::npos); + const auto& libcef_dll_path = exe_path.substr(0, sep_pos + 1) + L"libcef.dll"; + + // Validate code signing requirements for libcef.dll before loading, and + // then load. + return library_loader.LoadInMainAssert(libcef_dll_path.c_str(), + RequiredThumbprint(&exe_thumbprint), + kAllowUnsigned); +} + int RunMain(HINSTANCE hInstance, int nCmdShow, void* sandbox_info) { CefMainArgs main_args(hInstance); + // Dynamically load the CEF library after code signing verification. + CefScopedLibraryLoader library_loader; + if (!VerifyCodeSigningAndLoad(library_loader)) { + return CEF_RESULT_CODE_KILLED; + } + + // The CEF library (libcef) is loaded at this point. + // Parse command-line arguments. CefRefPtr command_line = CefCommandLine::CreateCommandLine(); command_line->InitFromString(::GetCommandLineW()); diff --git a/tools/make_distrib.py b/tools/make_distrib.py index f9057d591..cff60c95f 100644 --- a/tools/make_distrib.py +++ b/tools/make_distrib.py @@ -1120,6 +1120,12 @@ elif platform == 'windows': 'include/', include_dir, options.quiet) transfer_gypi_files(cef_dir, cef_paths2['includes_win_capi'], \ 'include/', include_dir, options.quiet) + transfer_gypi_files(cef_dir, cef_paths2['includes_wrapper_win'], \ + 'include/', include_dir, options.quiet) + + # transfer libcef_dll_wrapper files + transfer_gypi_files(cef_dir, cef_paths2['libcef_dll_wrapper_sources_win'], \ + 'libcef_dll/', libcef_dll_dir, options.quiet) # transfer additional files, if any transfer_files(cef_dir, script_dir, os.path.join(script_dir, 'distrib', 'win'), \ @@ -1261,7 +1267,7 @@ elif platform == 'mac': # transfer libcef_dll_wrapper files transfer_gypi_files(cef_dir, cef_paths2['libcef_dll_wrapper_sources_mac'], \ - 'libcef_dll/', libcef_dll_dir, options.quiet) + 'libcef_dll/', libcef_dll_dir, options.quiet) # transfer additional files, if any transfer_files(cef_dir, script_dir, os.path.join(script_dir, 'distrib', 'mac'), \