High watch CVSS 8.4 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactLink following in CredentialProvider
DescriptionLink following in CredentialProvider
ComponentCredentialProvider
Sub-componentGCPW
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker517612295
Fix commit34c840263df6 (chromium/src) +50/-83
CWECWE-59
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-08-05 (public git)
Disclosed2026-08-18
Patch gap13 days — fix public in source before the advisory

Background

GCPW
Google Credential Provider for Windows (chrome/credential_provider/), an optional Windows component, usually deployed by an enterprise, that lets users sign in to Windows with Google accounts. Its DLL runs as SYSTEM, and its installer runs elevated.
Crashpad database directory
the folder where Crashpad keeps crash reports, metadata and dumps for the monitored process. Here it is returned to Crashpad by GcpCrashReporterClient::GetCrashDumpLocation.
System `TEMP`
the machine-wide temp directory read from HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\TEMP (typically C:\Windows\Temp). Unlike GCPW’s data directory under C:\ProgramData, which the commit message says is DACL’d to SYSTEM and local admins only, ordinary local users can generally create entries in it (inferred).
Link following
a privileged process works on a path inside a directory that a lower-privileged user can influence, so the user can pre-create the target or replace it with a junction or symlink and redirect the privileged writes.

Root Cause Analysis

Before the patch, GetFolderForCrashDumps() built the Crashpad database path as GetSystemTempFolder() + L"GCPW Crashpad", which is a fixed, predictable name inside the system-wide temp directory. InitializeGcpwCrashReporting() then called base::PathExists(crash_dir) and created the directory only if it was missing, and it treated ERROR_ALREADY_EXISTS as benign. If an unprivileged local user had already created GCPW Crashpad, or a junction or link with that name, the elevated GCPW DLL and installer simply used it. Crashpad, running with their privileges, then created and wrote database files and dumps through an attacker-controlled path, which is the “link following” in the advisory.

The fix moves the database to GetDataDirectory() + "Crashpad", inside GCPW’s C:\ProgramData data directory, which only SYSTEM and admins can write. It removes GetSystemTempFolder, kCrashpadDumpsFolder and InitializeGcpwCrashReporting. It also computes the path once, hands it to the new GcpCrashReporterClient(base::FilePath) constructor, and skips crash-reporting setup entirely when the path is empty.

Key insight
A privileged component placed its crash database at a predictable name in a directory that unprivileged users can write, and accepted whatever was already there. The fix moves the database under a directory whose ACL already shuts out non-admin users, so a lower-privileged user can no longer pre-create or redirect that path.

Attack Path

  1. Local foothold The attacker has an unprivileged account on a Windows machine with GCPW installed. Web content cannot reach this bug.
  2. Pre-plant the path Before GCPW crash reporting initializes, the attacker creates GCPW Crashpad in the system temp directory, or a junction or link with that name pointing to a location the attacker chooses (inferred, based on the advisory’s “link following” and the removed PathExists/CreateDirectory logic).
  3. Privileged initialization The GCPW DLL (as SYSTEM) or the elevated installer calls the pre-patch GetFolderForCrashDumps()/InitializeGcpwCrashReporting(), accepts the existing entry, and gives that path to Crashpad through GetCrashDumpLocation.
  4. Redirected privileged writes Crashpad creates its database files and writes crash dumps through the attacker-controlled path with elevated privileges. This could allow file creation or overwrite in protected locations, or let the attacker read dumps of a SYSTEM process (inferred).

Impact Assessment

A local, unprivileged user could make GCPW’s SYSTEM/elevated processes create or write files through a path the user controls. This could plausibly be turned into local privilege escalation, or into disclosure of sensitive crash-dump memory from the credential provider (inferred). The bug affects only Windows systems where the optional GCPW component is installed, and it needs local code execution as another user. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.4. The metadata gives no exploited-in-the-wild status or severity.

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
GcpCrashReporterClient
chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h
modified

Files Changed

  • chrome/credential_provider/gaiacp/gcp_crash_reporting.cc
  • chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.cc
  • chrome/credential_provider/setup/gcp_installer_crash_reporting.cc
  • chrome/credential_provider/gaiacp/gcp_crash_reporter_client.h
  • chrome/credential_provider/gaiacp/gcp_crash_reporter_client.cc
  • chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h

Audit Directions

  • Privileged code writing under shared temp dirs
    Look for other elevated or SYSTEM Chrome components (updater, elevation service, installer, enterprise helpers) that build fixed-name paths under C:\Windows\Temp, %ProgramData% subtrees without explicit DACLs, or other world-writable locations, and use them with PathExists/CreateDirectory.
  • "Already exists" treated as success
    Flag patterns where ERROR_ALREADY_EXISTS or a positive PathExists result is accepted without checking the owner, the DACL, or whether the path is a reparse point.
  • Crash and log databases of privileged processes
    Audit every CrashReporterClient::GetCrashDumpLocation implementation and log-file location for processes that run above the user’s integrity level, and confirm the directory is created with a restrictive ACL and cannot be redirected.
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
 
 namespace credential_provider {
 
-void InitializeGcpwCrashReporting(GcpCrashReporterClient* crash_client) {
-  crash_reporter::SetCrashReporterClient(crash_client);
-
-  base::FilePath crash_dir = GetFolderForCrashDumps();
-
-  if (crash_dir.empty() ||
-      (!base::PathExists(crash_dir) && !base::CreateDirectory(crash_dir))) {
-    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
-    if (hr != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) {
-      LOGFN(ERROR) << "Failed to create directory for crash dumps: "
-                   << crash_dir << " hr=" << putHR(hr);
-    }
-  }
-}
-
 base::FilePath GetFolderForCrashDumps() {
-  base::FilePath system_temp_dir = GetSystemTempFolder();
-  if (system_temp_dir.empty())
-    // Failed to get a temp dir, something's gone wrong.
-    return system_temp_dir;
-  return system_temp_dir.Append(kCrashpadDumpsFolder);
-}
+  base::FilePath data_dir = GetDataDirectory();
+  if (data_dir.empty()) {
+    return data_dir;
+  }
 
+  // Crashpad will create the directory as-needed.
+  return data_dir.Append(FILE_PATH_LITERAL("Crashpad"));
+}
 
 void SetCommonCrashKeys(const base::CommandLine& command_line) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h b/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h
index 10949fc..006682b 100644
--- a/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h
+++ b/chrome/credential_provider/gaiacp/gcp_crash_reporting_utils.h
@@ -12,30 +12,9 @@
 
 namespace credential_provider {
 
-class GcpCrashReporterClient;
-
-// Helper function used to prepare the database used by crashpad.
-// Since the GCPW dll is run under the SYSTEM account the crash reports will be
-// stored in a system level temp folder (see comments for the function
-// GetSystemTempFolder in gcp_crash_reporting_utils.cc for how the system temp
-// folder is retrieved). The crashpad folder will be under
-// "<Temp folder>\GCPW Crashpad".
-// After this function call, the caller can request to start an embedded crash
-// handler process to listen for crashes on the calling process. When a crash
-// occurs on this process, the handler will request that a crash dump be written
-// in the crashpad folder and then attempt to upload the crashes to a Crash
-// server (if enabled and certain throttling conditions are satisfied).
-// Since every DLL_ATTACH event for this dll will try to start crash handling
-// there can be multiple entry points to this function via different calls
-// to DLL entry points that are exposed. In order to not start one handler per
-// entry point that is called, the command line that starts the entry point
-// (e.g. the execution of "rundll32.exe gaia1_0.dll,<entry_point>") should also
-// provide a "process-type" switch so that these child entry points can re-use
-// the same crashpad handler process to report crashes.
-void InitializeGcpwCrashReporting(GcpCrashReporterClient* crash_client);
-
-// Returns the system level temp folder in which GCPW can create a crash
-// database.
+// Returns the folder in which GCPW can create a crash database
+// ("<DataDirectory>\Crashpad"; see `GetDataDirectory()` in gcp_utils.h), or an
+// empty path in case of error.
 base::FilePath GetFolderForCrashDumps();
 
 // No-op in Chromium builds.
diff --git a/chrome/credential_provider/setup/gcp_installer_crash_reporting.cc b/chrome/credential_provider/setup/gcp_installer_crash_reporting.cc
index 6c4a38aa..dcb57d37 100644
--- a/chrome/credential_provider/setup/gcp_installer_crash_reporting.cc
+++ b/chrome/credential_provider/setup/gcp_installer_crash_reporting.cc
@@ -5,6 +5,7 @@
 #include "chrome/credential_provider/setup/gcp_installer_crash_reporting.h"
 
 #include <string>
+#include <utility>
 
 #include "base/command_line.h"
 #include "base/debug/leak_annotations.h"
@@ -31,10 +32,16 @@
   // a stub .exe and a main .dll, crash reporting can be configured in one place
   // right here.
 
-  GcpCrashReporterClient* crash_client = new GcpCrashReporterClient();
-  ANNOTATE_LEAKING_OBJECT_PTR(crash_client);
+  base::FilePath crash_dir = GetFolderForCrashDumps();
+  if (crash_dir.empty()) {
+    // Crashpad cannot function without a directory in which to write.
+    return;
+  }
 
-  InitializeGcpwCrashReporting(crash_client);
+  GcpCrashReporterClient* crash_client =
+      new GcpCrashReporterClient(std::move(crash_dir));
+  ANNOTATE_LEAKING_OBJECT_PTR(crash_client);
+  crash_reporter::SetCrashReporterClient(crash_client);
 
   crash_reporter::InitializeCrashpadWithEmbeddedHandler(true, "GCPW Installer",
                                                         "", base::FilePath());
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