Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactLink following in CredentialProvider
DescriptionLink following in CredentialProvider
ComponentCredentialProvider
Bug ClassLogic Error
Tracker517612295
Fix commit34c840263df6 (chromium/src) +50/-83
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-18

Changed Functions

FunctionChangeNotes
FilePath
chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h
modified
GcpCrashReporterClient
chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h
modified
GcpDllCrashReporterClient
chrome/credential_provider/gaiacp/gcp_crash_reporting.cc
modified

Files Changed

  • chrome/credential_provider/gaiacp/gcp_crash_reporter_client.cc
  • chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h
  • chrome/credential_provider/gaiacp/gcp_crash_reporting.cc
  • chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc
From 34c840263df6758e27bd5f15dc4954f322b7a15e Mon Sep 17 00:00:00 2001
From: Greg Thompson <grt@chromium.org>
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 <mark@chromium.org>
Commit-Queue: Greg Thompson <grt@chromium.org>
Auto-Submit: Greg Thompson <grt@chromium.org>
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
 
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • third_party/crashpad/crashpad/client/crash_report_database_win.cc
  • chrome/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

  1. Path Derivation In chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc, GetSystemTempFolder retrieves the machine’s TEMP folder 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, GetFolderForCrashDumps appends the hardcoded folder name L"GCPW Crashpad":

    base::FilePath GetFolderForCrashDumps() {
      base::FilePath system_temp_dir = GetSystemTempFolder();
      ...
      return system_temp_dir.Append(kCrashpadDumpsFolder);
    }
    
  2. Insecure Directory Initialization When InitializeGcpwCrashReporting is 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::PathExists and base::CreateDirectory on Windows do not inspect whether the folder is an NTFS junction. If a junction exists at C:\Windows\TEMP\GCPW Crashpad, the calls succeed and proceed directly to opening and writing files within the junction target.

  3. Crashpad Database Initialization Within Crashpad’s Windows-specific database implementation (third_party/crashpad/crashpad/client/crash_report_database_win.cc), the helper function EnsureDirectory checks for the FILE_ATTRIBUTE_DIRECTORY attribute but does not validate whether FILE_ATTRIBUTE_REPARSE_POINT is 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::Initialize traverses the junction to create subdirectories such as reports and attachments, and creates settings.dat under 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.

  1. 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"
    
  2. Trigger GCPW initialization (e.g., by locking the workstation with Win+L or logging out, forcing LogonUI.exe to run the GCPW DLL as SYSTEM).
  3. Notice that the SYSTEM process creates reports\ and attachments\ subfolders, as well as settings.dat, inside the redirected target folder.
  4. If a crash occurs in a process running under SYSTEM that 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

  1. 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 shared C:\Windows\TEMP folder.
  2. 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_POINT attribute.

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.

View on issue tracker