CVE-2026-76037
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
FilePathchrome/credential_provider/gaiacp/gcp_crash_reporter_client.h |
modified | |
GcpCrashReporterClientchrome/credential_provider/gaiacp/gcp_crash_reporter_client.h |
modified | |
GcpDllCrashReporterClientchrome/credential_provider/gaiacp/gcp_crash_reporting.cc |
modified |
Files Changed
chrome/credential_provider/gaiacp/gcp_crash_reporter_client.ccchrome/credential_provider/gaiacp/gcp_crash_reporter_client.hchrome/credential_provider/gaiacp/gcp_crash_reporting.ccchrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc
Patch
From 34c840263df6758e27bd5f15dc4954f322b7a15e Mon Sep 17 00:00:00 2001 From: Greg Thompson <[email protected]> Date: Tue, 04 Aug 2026 23:59:59 -0700 Subject: [PATCH] [GCPW] Put the crashpad database in GCPW's data directory This subdirectory of C:\ProgramData is already DACLd properly so that only SYSTEM and local admin may access it, so it's a suitable place for the crashpad database. Since Crashpad cannot function without a directory in which to work, this change gently refactors initialization: * The crash dump location is only computed once; and the leaf directory itself is no longer created by GCPW or its installer. Crashpad will create it as needed. * If the crash dump location's parent directory (GCPW's "data directory" within C:\ProgramData) cannot be created, crash initialization is skipped. * GCPW's CrashReporterClient no longer recomputes the crash dump location, but rather holds and returns whatever location it was giving at construction when asked. Fixed: 517612295 Change-Id: I502af034e238de8e74e4fe3c81672bf60845dbb6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8182731 Reviewed-by: Mark Mentovai <[email protected]> Commit-Queue: Greg Thompson <[email protected]> Auto-Submit: Greg Thompson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1673938} --- diff --git a/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.cc b/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.cc index 32305cf..2c97011 100644 --- a/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.cc +++ b/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.cc @@ -4,17 +4,22 @@ #include "chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h" +#include <utility> + #include "base/check.h" #include "base/file_version_info.h" -#include "base/files/file_path.h" #include "base/notreached.h" #include "base/strings/string_util.h" -#include "base/strings/utf_string_conversions.h" -#include "base/win/registry.h" #include "chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h" namespace credential_provider { +GcpCrashReporterClient::GcpCrashReporterClient( + base::FilePath crash_dump_location) + : crash_dump_location_(std::move(crash_dump_location)) { + CHECK(!crash_dump_location_.empty()); +} + GcpCrashReporterClient::~GcpCrashReporterClient() = default; base::FilePath GcpCrashReporterClient::GetPathForFileVersionInfo( @@ -59,10 +64,7 @@ } bool GcpCrashReporterClient::GetCrashDumpLocation(std::wstring* crash_dir) { - base::FilePath crash_directory_path = GetFolderForCrashDumps(); - if (crash_directory_path.empty()) - return false; - *crash_dir = crash_directory_path.value(); + *crash_dir = crash_dump_location_.value(); return true; } diff --git a/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h b/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h index dbf19874..bef95bf 100644 --- a/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h +++ b/chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h @@ -5,17 +5,14 @@ #ifndef CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_CRASH_REPORTER_CLIENT_H_ #define CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_CRASH_REPORTER_CLIENT_H_ +#include "base/files/file_path.h" #include "components/crash/core/app/crash_reporter_client.h" -namespace base { -class FilePath; -} - namespace credential_provider { class GcpCrashReporterClient : public crash_reporter::CrashReporterClient { public: - GcpCrashReporterClient() = default; + explicit GcpCrashReporterClient(base::FilePath crash_dump_location); GcpCrashReporterClient(const GcpCrashReporterClient&) = delete; GcpCrashReporterClient& operator=(const GcpCrashReporterClient&) = delete; @@ -38,6 +35,9 @@ protected: virtual base::FilePath GetPathForFileVersionInfo( const std::wstring& exe_path); + + private: + const base::FilePath crash_dump_location_; }; } // namespace credential_provider diff --git a/chrome/credential_provider/gaiacp/gcp_crash_reporting.cc b/chrome/credential_provider/gaiacp/gcp_crash_reporting.cc index 2f4bcf1..83a1eb1 100644 --- a/chrome/credential_provider/gaiacp/gcp_crash_reporting.cc +++ b/chrome/credential_provider/gaiacp/gcp_crash_reporting.cc @@ -4,6 +4,8 @@ #include "chrome/credential_provider/gaiacp/gcp_crash_reporting.h" +#include <utility> + #include "base/command_line.h" #include "base/debug/leak_annotations.h" #include "base/strings/utf_string_conversions.h" @@ -21,7 +23,8 @@ class GcpDllCrashReporterClient : public credential_provider::GcpCrashReporterClient { public: - GcpDllCrashReporterClient() = default; + explicit GcpDllCrashReporterClient(base::FilePath crash_dump_location) + : GcpCrashReporterClient(std::move(crash_dump_location)) {} ~GcpDllCrashReporterClient() override = default; protected: @@ -53,11 +56,18 @@ // to the crash service. Since the installer does not split its work between // a stub .exe and a main .dll, crash reporting can be configured in one place // right here. - // Create the crash client and install it (a la MainDllLoader::Launch). - GcpDllCrashReporterClient* crash_client = new GcpDllCrashReporterClient(); - ANNOTATE_LEAKING_OBJECT_PTR(crash_client); - InitializeGcpwCrashReporting(crash_client); + base::FilePath crash_dir = GetFolderForCrashDumps(); + if (crash_dir.empty()) { + // Crashpad cannot function without a directory in which to write. + return; + } + + // Create the crash client and install it (a la MainDllLoader::Launch). + GcpDllCrashReporterClient* crash_client = + new GcpDllCrashReporterClient(std::move(crash_dir)); + ANNOTATE_LEAKING_OBJECT_PTR(crash_client); + crash_reporter::SetCrashReporterClient(crash_client); base::CommandLine dll_main_cmd_line(base::CommandLine::NO_PROGRAM); diff --git a/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc b/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc index 44c31ff..2f6cf4cf 100644 --- a/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc +++ b/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc @@ -9,20 +9,18 @@ #include <string> #include "base/files/file_path.h" -#include "base/files/file_util.h" #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" #include "build/branding_buildflags.h" #include "chrome/credential_provider/common/gcp_strings.h" #include "chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h" +#include "chrome/credential_provider/gaiacp/gcp_utils.h" #include "chrome/credential_provider/gaiacp/logging.h" #include "components/crash/core/common/crash_key.h" #include "components/crash/core/common/crash_keys.h" namespace { -constexpr wchar_t kCrashpadDumpsFolder[] = L"GCPW Crashpad"; - #if BUILDFLAG(GOOGLE_CHROME_BRANDING) void SetCurrentVersionCrashKey() { static crash_reporter::CrashKeyString<32> version_key("current-version"); @@ -40,48 +38,19 @@ } #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) -// Returns the SYSTEM version of TEMP. We do this instead of GetTempPath so -// that both elevated and SYSTEM runs share the same directory. -base::FilePath GetSystemTempFolder() { - base::win::RegKey reg_key( - HKEY_LOCAL_MACHINE, - L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", - KEY_QUERY_VALUE); - - std::wstring temp_string; - if (reg_key.ReadValue(L"TEMP", &temp_string) != ERROR_SUCCESS) - return base::FilePath(); - - return base::FilePath(temp_string); -} - } // namespace
Original Bug Report
Potential Local Privilege Escalation in GCPW via Junction Hijacking in C:\Windows\TEMP
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential local privilege escalation vulnerability exists in Google Credential Provider for Windows (GCPW) because it roots its Crashpad database under C:\Windows\TEMP without performing NTFS junction validation. A low-privileged local user can pre-create this path as a junction pointing to an attacker-controlled or system-protected folder. When a SYSTEM process subsequently initializes the database or writes minidumps, it traverses the junction, resulting in arbitrary directory creation or potential credential leakage.
Affected files:
chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.ccthird_party/crashpad/crashpad/client/crash_report_database_win.ccchrome/credential_provider/setup/gcp_installer_crash_reporting.cc
Estimated timestamp from git blame: 2018-11-27
Description
A potential local privilege escalation (LPE) and information disclosure vulnerability exists in Google Credential Provider for Windows (GCPW) due to insecure initialization of its Crashpad database directory. GCPW resolves and initializes its Crashpad database path under the machine-wide temp directory (C:\Windows\TEMP) without verifying whether the resolved target folder contains an NTFS junction or reparse point.
On Windows, the default DACL for C:\Windows\TEMP grants BUILTIN\Users the permission to create files and subfolders. When a SYSTEM-privileged process (such as LogonUI.exe loading the GCPW credential provider DLL, or the GCPW background service) initializes crash reporting, it attempts to set up the Crashpad database under C:\Windows\TEMP\GCPW Crashpad. Since neither GCPW nor Crashpad performs reparse point (junction) validation, a local low-privileged user can hijack this creation process.
Technical Analysis
-
Path Derivation In
chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc,GetSystemTempFolderretrieves the machine’sTEMPfolder path by querying the registry:base::FilePath GetSystemTempFolder() { base::win::RegKey reg_key( HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", KEY_QUERY_VALUE); std::wstring temp_string; if (reg_key.ReadValue(L"TEMP", &temp_string) != ERROR_SUCCESS) return base::FilePath(); return base::FilePath(temp_string); }This resolves to
C:\Windows\TEMP. Next,GetFolderForCrashDumpsappends the hardcoded folder nameL"GCPW Crashpad":base::FilePath GetFolderForCrashDumps() { base::FilePath system_temp_dir = GetSystemTempFolder(); ... return system_temp_dir.Append(kCrashpadDumpsFolder); } -
Insecure Directory Initialization When
InitializeGcpwCrashReportingis called, it verifies the existence of the directory and attempts to create it:void InitializeGcpwCrashReporting(GcpCrashReporterClient* crash_client) { ... base::FilePath crash_dir = GetFolderForCrashDumps(); if (crash_dir.empty() || (!base::PathExists(crash_dir) && !base::CreateDirectory(crash_dir))) { ... } }The standard
base::PathExistsandbase::CreateDirectoryon Windows do not inspect whether the folder is an NTFS junction. If a junction exists atC:\Windows\TEMP\GCPW Crashpad, the calls succeed and proceed directly to opening and writing files within the junction target. -
Crashpad Database Initialization Within Crashpad’s Windows-specific database implementation (
third_party/crashpad/crashpad/client/crash_report_database_win.cc), the helper functionEnsureDirectorychecks for theFILE_ATTRIBUTE_DIRECTORYattribute but does not validate whetherFILE_ATTRIBUTE_REPARSE_POINTis set:bool EnsureDirectory(const base::FilePath& path) { DWORD fileattr = GetFileAttributes(path.value().c_str()); ... if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) == 0) { ... } return true; }As a result,
CrashReportDatabaseWin::Initializetraverses the junction to create subdirectories such asreportsandattachments, and createssettings.datunder the target directory with default security descriptors.
Potential Attack Steps
Please note: These are potential steps based on static analysis; our tooling does not currently have the capability to execute code or verify this dynamically with a live proof of concept.
- From a low-privilege user context, pre-create the junction pointing to an attacker-owned directory (to intercept minidumps) or a protected system directory:
mklink /J "C:\Windows\TEMP\GCPW Crashpad" "C:\Users\Public\attacker_folder" - Trigger GCPW initialization (e.g., by locking the workstation with
Win+Lor logging out, forcingLogonUI.exeto run the GCPW DLL as SYSTEM). - Notice that the SYSTEM process creates
reports\andattachments\subfolders, as well assettings.dat, inside the redirected target folder. - If a crash occurs in a process running under
SYSTEMthat uses this crash directory, the resulting minidump containing sensitive process memory (such as cleartext passwords, tokens, or LSA secrets) is written to the attacker-accessible folder, leading to complete local privilege escalation.
Suggested Fix
- Relocate the Database Directory: Move the GCPW Crashpad database to a secure, non-user-writable directory, such as a subdirectory of
C:\Windows\System32\config\systemprofile\AppData\Local\GCPW\instead of the sharedC:\Windows\TEMPfolder. - Reparse Point Validation: If the database must reside in a shared location, ensure that GCPW and Crashpad explicitly query the path attributes and refuse to initialize if any path component contains the
FILE_ATTRIBUTE_REPARSE_POINTattribute.
Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.