Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Google Updater
DescriptionInappropriate implementation in Google Updater
ComponentGoogle Updater
Bug ClassLogic Error
Tracker448113221
Fix commitd07f1a28289e (chromium/src) +292/-198
CISA KEVNot listed
CreditedJota Domingos
Disclosed2025-12-02

Changed Functions

FunctionChangeNotes
Settings
third_party/crashpad/crashpad/client/crash_report_database.h
modified
SettingsReader
third_party/crashpad/crashpad/client/crash_report_database.h
modified
CrashReportDatabase
third_party/crashpad/crashpad/client/crash_report_database.h
modified
CrashReportDatabaseGeneric
third_party/crashpad/crashpad/client/crash_report_database_generic.cc
modified
call_once
third_party/crashpad/crashpad/client/crash_report_database_generic.cc
modified
InitializeWithoutCreating
third_party/crashpad/crashpad/client/crash_report_database_generic.cc
modified
call_once
third_party/crashpad/crashpad/client/crash_report_database_mac.mm
modified

Files Changed

  • third_party/crashpad/README.chromium
  • third_party/crashpad/crashpad/client/crash_report_database.h
  • third_party/crashpad/crashpad/client/crash_report_database_generic.cc
  • third_party/crashpad/crashpad/client/crash_report_database_mac.mm
From d07f1a28289ee29a6f72e51d8d092db281a1de02 Mon Sep 17 00:00:00 2001
From: Joshua Pawlicki <waffles@chromium.org>
Date: Mon, 13 Oct 2025 12:26:04 -0700
Subject: [PATCH] Update Crashpad to b9d38f35eb99685dfd2c5156a5f629a486568f64

2060cc8f6c5d Rename third_party/linux/README.crashpad to .md
b9d38f35eb99 crashpad: Introduce a new SettingsReader type for read-only
             access

Bug: 448113221
Change-Id: I2bb399c30a99e39e80c640e4b815a7d31d48abe2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7036932
Commit-Queue: Joshua Pawlicki <waffles@chromium.org>
Auto-Submit: Joshua Pawlicki <waffles@chromium.org>
Commit-Queue: Mark Mentovai <mark@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1529062}
---

diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 312d438..6891738 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://chromium.googlesource.com/crashpad/crashpad
 Version: N/A
-Revision: ad8e3e906ca996977020a2cbf89668e737559f8f
+Revision: b9d38f35eb99685dfd2c5156a5f629a486568f64
 Update Mechanism: Manual
 License: Apache-2.0
 License File: crashpad/LICENSE
diff --git a/third_party/crashpad/crashpad/client/crash_report_database.h b/third_party/crashpad/crashpad/client/crash_report_database.h
index 2a8f98c..c24b4a62 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database.h
+++ b/third_party/crashpad/crashpad/client/crash_report_database.h
@@ -34,6 +34,7 @@
 namespace crashpad {
 
 class Settings;
+class SettingsReader;
 
 //! \brief An interface for managing a collection of crash report files and
 //!     metadata associated with the crash reports.
@@ -266,6 +267,16 @@
   static std::unique_ptr<CrashReportDatabase> InitializeWithoutCreating(
       const base::FilePath& path);
 
+  //! \brief Given a database path, return a read-only view of its settings.
+  //!
+  //! \param[in] path A path to the database. If the database does not exist, or
+  //!     the settings file does not exist, the returned reader will fail its
+  //!     read methods.
+  //!
+  //! \return A SettingsReader.
+  static std::unique_ptr<SettingsReader> GetSettingsReaderForDatabasePath(
+      const base::FilePath& path);
+
   //! \brief Returns the Settings object for this database.
   //!
   //! \return A weak pointer to the Settings object, which is owned by the
@@ -416,7 +427,7 @@
   virtual int CleanDatabase(time_t lockfile_ttl) { return 0; }
 
  protected:
-  CrashReportDatabase() {}
+  CrashReportDatabase() = default;
 
   //! \brief The path to the database passed to Initialize.
   //!
diff --git a/third_party/crashpad/crashpad/client/crash_report_database_generic.cc b/third_party/crashpad/crashpad/client/crash_report_database_generic.cc
index 617a074..c92922eb 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database_generic.cc
+++ b/third_party/crashpad/crashpad/client/crash_report_database_generic.cc
@@ -160,7 +160,7 @@
 
 class CrashReportDatabaseGeneric : public CrashReportDatabase {
  public:
-  CrashReportDatabaseGeneric();
+  explicit CrashReportDatabaseGeneric(const base::FilePath& path);
 
   CrashReportDatabaseGeneric(const CrashReportDatabaseGeneric&) = delete;
   CrashReportDatabaseGeneric& operator=(const CrashReportDatabaseGeneric&) =
@@ -168,7 +168,7 @@
 
   ~CrashReportDatabaseGeneric() override;
 
-  bool Initialize(const base::FilePath& path, bool may_create);
+  bool Initialize(bool may_create);
 
   // CrashReportDatabase:
   Settings* GetSettings() override;
@@ -260,26 +260,24 @@
   static bool WriteMetadata(const base::FilePath& path, const Report& report);
 
   Settings& SettingsInternal() {
-    std::call_once(settings_init_, [this]() {
-      settings_.Initialize(base_dir_.Append(kSettings));
-    });
+    std::call_once(settings_init_, [this]() { settings_.Initialize(); });
     return settings_;
   }
 
-  base::FilePath base_dir_;
+  const base::FilePath base_dir_;
   Settings settings_;
   std::once_flag settings_init_;
   InitializationStateDcheck initialized_;
 };
 
-CrashReportDatabaseGeneric::CrashReportDatabaseGeneric() = default;
+CrashReportDatabaseGeneric::CrashReportDatabaseGeneric(
+    const base::FilePath& path)
+    : base_dir_(path), settings_(path.Append(kSettings)) {}
 
 CrashReportDatabaseGeneric::~CrashReportDatabaseGeneric() = default;
 
-bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path,
-                                            bool may_create) {
+bool CrashReportDatabaseGeneric::Initialize(bool may_create) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
-  base_dir_ = path;
 
   if (!IsDirectory(base_dir_, true) &&
       !(may_create &&
@@ -303,20 +301,6 @@
   return true;
 }
 
-// static
-std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
-    const base::FilePath& path) {
-  auto database = std::make_unique<CrashReportDatabaseGeneric>();
-  return database->Initialize(path, true) ? std::move(database) : nullptr;
-}
-
-// static
-std::unique_ptr<CrashReportDatabase>
-CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) {
-  auto database = std::make_unique<CrashReportDatabaseGeneric>();
-  return database->Initialize(path, false) ? std::move(database) : nullptr;
-}
-
 base::FilePath CrashReportDatabaseGeneric::DatabasePath() {
   return base_dir_;
 }
@@ -944,4 +928,25 @@
          LoggingWriteFile(handle.get(), report.id.c_str(), report.id.size());
 }
 
+// static
+std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
+    const base::FilePath& path) {
+  auto database = std::make_unique<CrashReportDatabaseGeneric>(path);
+  return database->Initialize(true) ? std::move(database) : nullptr;
+}
+
+// static
+std::unique_ptr<CrashReportDatabase>
+CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) {
+  auto database = std::make_unique<CrashReportDatabaseGeneric>(path);
+  return database->Initialize(false) ? std::move(database) : nullptr;
+}
+
+// static
+std::unique_ptr<SettingsReader>
+CrashReportDatabase::GetSettingsReaderForDatabasePath(
+    const base::FilePath& path) {
+  return std::make_unique<SettingsReader>(path.Append(kSettings));
+}
+
 }  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/client/crash_report_database_mac.mm b/third_party/crashpad/crashpad/client/crash_report_database_mac.mm
index 9e4d257..6041434 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database_mac.mm
+++ b/third_party/crashpad/crashpad/client/crash_report_database_mac.mm
@@ -268,13 +268,11 @@
   void CleanOrphanedAttachments();
 
   Settings& SettingsInternal() {
-    std::call_once(settings_init_, [this]() {
-      settings_.Initialize(base_dir_.Append(kSettings));
-    });
+    std::call_once(settings_init_, [this]() { settings_.Initialize(); });
     return settings_;
   }
 
-  base::FilePath base_dir_;
+  const base::FilePath base_dir_;
   Settings settings_;
   std::once_flag settings_init_;
   bool xattr_new_names_;
@@ -284,7 +282,7 @@
 CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
     : CrashReportDatabase(),
       base_dir_(path),
-      settings_(),
+      settings_(path.Append(kSettings)),
       settings_init_(),
       xattr_new_names_(false),
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/crashpad/crashpad/client/settings_test.cc b/third_party/crashpad/crashpad/client/settings_test.cc
index c955116..9410314 100644
--- a/third_party/crashpad/crashpad/client/settings_test.cc
+++ b/third_party/crashpad/crashpad/client/settings_test.cc
@@ -26,14 +26,15 @@
 
 class SettingsTest : public testing::Test {
  public:
-  SettingsTest() = default;
+  SettingsTest()
+      : temp_dir_(),
+        settings_path_(temp_dir_.path().Append(FILE_PATH_LITERAL("settings"))),
+        settings_(settings_path_) {}
 
   SettingsTest(const SettingsTest&) = delete;
   SettingsTest& operator=(const SettingsTest&) = delete;
 
-  base::FilePath settings_path() {
-    return temp_dir_.path().Append(FILE_PATH_LITERAL("settings"));
-  }
+  base::FilePath settings_path() { return settings_path_; }
 
   Settings* settings() { return &settings_; }
 
@@ -51,12 +52,11 @@
 
  protected:
   // testing::Test:
-  void SetUp() override {
-    ASSERT_TRUE(settings()->Initialize(settings_path()));
-  }
+  void SetUp() override { ASSERT_TRUE(settings()->Initialize()); }
 
  private:
-  ScopedTempDir temp_dir_;
+  const ScopedTempDir temp_dir_;
+  const base::FilePath settings_path_;
   Settings settings_;
 };
 
@@ -65,8 +65,8 @@
   EXPECT_TRUE(settings()->GetClientID(&client_id));
   EXPECT_NE(client_id, UUID());
 
-  Settings local_settings;
-  EXPECT_TRUE(local_settings.Initialize(settings_path()));
+  Settings local_settings(settings_path());
+  EXPECT_TRUE(local_settings.Initialize());
   UUID actual;
   EXPECT_TRUE(local_settings.GetClientID(&actual));
   EXPECT_EQ(actual, client_id);
@@ -82,8 +82,8 @@
   EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled));
   EXPECT_TRUE(enabled);
 
-  Settings local_settings;
-  EXPECT_TRUE(local_settings.Initialize(settings_path()));
+  Settings local_settings(settings_path());
+  EXPECT_TRUE(local_settings.Initialize());
   enabled = false;
   EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled));
   EXPECT_TRUE(enabled);
@@ -108,21 +108,43 @@
   EXPECT_TRUE(settings()->GetLastUploadAttemptTime(&actual));
   EXPECT_EQ(actual, expected);
 
-  Settings local_settings;
-  EXPECT_TRUE(local_settings.Initialize(settings_path()));
+  Settings local_settings(settings_path());
+  EXPECT_TRUE(local_settings.Initialize());
   actual = -1;
   EXPECT_TRUE(local_settings.GetLastUploadAttemptTime(&actual));
   EXPECT_EQ(actual, expected);
 }
 
+TEST_F(SettingsTest, ReadOnly) {
+  {  // A SettingsReader can read an existing setting...
+    SettingsReader reader(settings_path());
+    bool enabled = true;
+    // Default value is false.
+    EXPECT_TRUE(reader.GetUploadsEnabled(&enabled));
+    EXPECT_FALSE(enabled);
+
+    settings()->SetUploadsEnabled(true);
+    EXPECT_TRUE(reader.GetUploadsEnabled(&enabled));
+    EXPECT_TRUE(enabled);
+  }
+
+  {  // ...but not one that doesn't exist.
+    base::FilePath bad_path =
+        settings_path().DirName().Append(FILE_PATH_LITERAL("does_not_exist"));
+    SettingsReader reader(bad_path);
+    bool enabled = true;
+    EXPECT_FALSE(reader.GetUploadsEnabled(&enabled));
+  }
+}
+
 // The following tests write a corrupt settings file and test the recovery
 // operation.
 
 TEST_F(SettingsTest, BadFileOnInitialize) {
   InitializeBadFile();
 
-  Settings settings;
-  EXPECT_TRUE(settings.Initialize(settings_path()));
+  Settings settings(settings_path());
+  EXPECT_TRUE(settings.Initialize());
 }
 
 TEST_F(SettingsTest, BadFileOnGet) {
@@ -132,8 +154,8 @@
   EXPECT_TRUE(settings()->GetClientID(&client_id));
   EXPECT_NE(client_id, UUID());
 
-  Settings local_settings;
-  EXPECT_TRUE(local_settings.Initialize(settings_path()));
+  Settings local_settings(settings_path());
+  EXPECT_TRUE(local_settings.Initialize());
   UUID actual;
   EXPECT_TRUE(local_settings.GetClientID(&actual));
   EXPECT_EQ(actual, client_id);
@@ -162,8 +184,8 @@
       << ErrnoMessage("unlink");
 #endif
 
-  Settings local_settings;
-  EXPECT_TRUE(local_settings.Initialize(settings_path()));
+  Settings local_settings(settings_path());
+  EXPECT_TRUE(local_settings.Initialize());
   UUID new_client_id;
   EXPECT_TRUE(local_settings.GetClientID(&new_client_id));
   EXPECT_NE(new_client_id, client_id);
Loading diff…

Original Bug Report

reported by jo...@gmail.com

LPE - Arbitrary File Write in Google Chrome Enterprise (MacOS): The GoogleUpdater, which is executed by root, follows symlinks when writing the file settings.dat in the user folder

Summary: LPE - Arbitrary File Write in Google Chrome Enterprise (MacOS): The GoogleUpdater, which is executed by root, follows symlinks when writing the file settings.dat in the user folder

Program: Google VRP

Vulnerability type: Privilege Escalation

Details

Vulnerability Description

When accessing chrome://settings/help in Google Chrome Enterprise - Version 140.0.7339.214 (Official Build) (x86_64) for MacOS, the GoogleUpdater.app ("/Library/Application Support/Google/GoogleUpdater/Current/GoogleUpdater.app/Contents/Helpers/launcher") is executed as root.

The launcher executes the following command:

  • /Library/Application\ Support/Google/GoogleUpdater/Current/GoogleUpdater.app/Contents/MacOS/GoogleUpdater –server –service=update –system

Which will execute the following command:

  • /Library/Application Support/Google/GoogleUpdater/142.0.7416.0/GoogleUpdater.app/Contents/MacOS/GoogleUpdater –crash-handler –system –database=/Library/Application Support/Google/GoogleUpdater/142.0.7416.0/Crashpad –url=https://clients2.google.com/cr/report –annotation=prod=Update4 –annotation=ver=142.0.7416.0 –handshake-fd=5

This will trigger a file read and write of:

  • /Users/<User>/Library/Application Support/Google/Chrome/Crashpad/settings.dat

The user has full permissions on the folder Crashpad and the file settings.dat. This allows the user to create a symbolic link in the folder which will be followed by the GoogleUpdater application when writing the file settings.dat.

This allows a non-privileged user to obtain a arbitrary file write. However, I did not found a way to control the content of the file.

Attack Preconditions

To exploit this issue, an attacker must have access as non-privileged user to the machine.

Reproduction Steps / POC

To exploit this issue, perform the following steps:

  1. Open Google Chrome Enterpise
  2. Remove the file /Users/<User>/Library/Application Support/Google/Chrome/Crashpad/settings.dat if it exists
  3. Create the symlink: ln -sf /tmp/arbitrary_write /Users/$USER/Library/Application\ Support/Google/Chrome/Crashpad/settings.dat
  4. Go to Help -> About Google Chrome
  5. Verify that the file /tmp/arbitrary_write was created by root

Attack scenario

Every user in the system independently of their privileges is able to exploit this issue, since the GoogleUpdater will go through every user folder at /Users.

This allows a non-privileged user to overwrite any file in the filesystem. However, I did not found a way to control the content of the settings.dat file, which means that in the end the impact will depend on the file that the user overwrites.

View on issue tracker