High watch CVSS 8.1 chrome Race 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace condition in Updater
DescriptionRace condition in Updater
ComponentUpdater
Sub-componentUpdater
Bug ClassRace
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.
Tracker524453236
Fix commitb1606afcc6bd (chromium/src) +75/-17
CWECWE-362 (Race condition)
CVSS vectorCVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-16 (per vendor advisory)
Fix landed2026-07-28 (public git)
Disclosed2026-09-08
Patch gap42 days — fix public in source before the advisory

Background

`AppWebImpl`
the C++ class in chrome/updater/app/server/win/com_classes_legacy.cc that implements the legacy IAppWeb COM interface exposed by the Windows Chrome updater service.
MTA (Multi-Threaded Apartment)
a COM threading model in which interface methods can be invoked concurrently on several RPC worker threads, so any shared object state must be synchronized by the implementation itself.
`base::AutoLock` / `lock_`
a scoped mutex guard over the object’s base::Lock lock_; holding it while touching a member serializes concurrent access.
`GUARDED_BY(lock_)`
a Clang thread-safety annotation that makes the compiler flag any access to the annotated member that is not performed while lock_ is held.

Root Cause Analysis

AppWebImpl services the legacy IAppWeb COM interface in an MTA, so multiple RPC threads can enter its methods concurrently, yet several members — install_data_index_, set_ready_to_install_, and current_operation_ — were read and written with no locking at all (only state_update_ and result_ were previously serialized by lock_). Methods like CheckForUpdate() and UpdateOrInstall() wrote current_operation_ unguarded, SetReadyToInstall() wrote set_ready_to_install_ unguarded, and put_serverInstallDataIndex() overwrote install_data_index_ (a std::string) while InstallImpl/UpdateImpl and get_serverInstallDataIndex() read that same string on other threads. Concurrent read/write of a non-atomic std::string is a data race whose behavior is undefined and can corrupt the string’s internal state. Additionally, put_serverInstallDataIndex() performed no length validation, accepting arbitrarily long attacker-supplied BSTR input.

The fix takes lock_ around every access to these members, adds a thread-safe GetInstallDataIndex() accessor that returns a copy under lock, validates the input length via ValidateInstallDataIndex (rejecting values over 0x4000 with E_INVALIDARG), and adds GUARDED_BY(lock_) so the compiler enforces the invariant going forward.

Key insight
The single core mistake was leaving mutable, concurrently-accessed COM object state unsynchronized in an MTA (compounded by unvalidated string input); the fix serializes all such accesses under lock_, reads install_data_index_ only through a copying accessor, and length-checks the input before mutation.

Attack Path

  1. Obtain the COM interface A local process connects to the updater’s legacy IAppWeb/IAppBundleWeb COM objects, which run in an MTA where method calls dispatch onto multiple RPC threads.
  2. Issue concurrent calls The caller invokes state-mutating methods (put_serverInstallDataIndex(), SetReadyToInstall(), CheckForUpdate(), UpdateOrInstall()) from multiple threads simultaneously so they race against readers such as get_serverInstallDataIndex() and InstallImpl/UpdateImpl.
  3. Trigger the data race Concurrent write and read of the non-atomic install_data_index_ std::string (and unsynchronized reads/writes of current_operation_ and set_ready_to_install_) produce a data race with undefined behavior, potentially corrupting the string or reading a torn value (inferred).
  4. Supply oversized input Independently, the caller passes an over-long BSTR to put_serverInstallDataIndex(), which pre-patch was stored unvalidated into install_data_index_.
  5. Downstream consequence Any memory-corruption or exploitation outcome from the race or the unbounded input is not demonstrated by the diff (inferred).

Impact Assessment

An attacker with the ability to invoke the updater’s legacy COM interface can trigger a data race on unsynchronized AppWebImpl members and submit unvalidated, unbounded string input, causing undefined behavior in the updater service process. The advisory classifies this as a race condition in the Updater component and does not report exploitation in the wild; any escalation to memory corruption or privilege escalation beyond the race itself is (inferred) and not established by the patch. Preconditions are local access sufficient to reach the updater’s IAppWeb COM surface and to drive concurrent method calls.

Changed Functions

FunctionChangeNotes
if
chrome/updater/app/server/win/com_classes_legacy.cc
modified
TEST
chrome/updater/app/server/win/com_classes_legacy_unittest.cc
modified

Files Changed

  • chrome/updater/app/server/win/com_classes_legacy_unittest.cc
  • chrome/updater/app/server/win/com_classes_legacy.cc

Audit Directions

  • Unsynchronized COM/MTA object state
    Audit other legacy updater COM implementations for member variables mutated by interface methods without a lock, especially non-atomic std::string/std::optional fields shared across RPC threads; add GUARDED_BY annotations to catch them.
  • Unvalidated `BSTR`/string COM inputs
    Review COM setter methods that copy caller-supplied BSTRs into members without length or content validation, following the ValidateInstallDataIndex pattern.
  • Read-then-use of shared fields
    Look for methods that read a shared member and pass it downstream while other methods can rewrite it concurrently; prefer copying under lock into a local before use, as GetInstallDataIndex() now does.
From b1606afcc6bddf22e8ad1a6f8189e43d3d8b03f5 Mon Sep 17 00:00:00 2001
From: S Ganesh <[email protected]>
Date: Tue, 28 Jul 2026 13:11:47 -0700
Subject: [PATCH] [updater] Fix data race and validate input in AppWebImpl

In AppWebImpl (implementing the legacy IAppWeb COM interface for the
Windows Chrome updater service), member variables such as
install_data_index_, set_ready_to_install_, and current_operation_ can
be accessed concurrently by multiple RPC threads in the Multi-Threaded
Apartment (MTA) model without synchronization.

This change:
- Synchronizes access to install_data_index_, set_ready_to_install_, and
  current_operation_ under lock_.
- Adds a thread-safe helper GetInstallDataIndex() to retrieve a copy of
  install_data_index_.
- Validates the length of string input in put_serverInstallDataIndex via
  ValidateInstallDataIndex before mutation.
- Adds GUARDED_BY(lock_) compiler annotations for thread safety analysis.
- Adds unit test coverage (AppWebImplTest.ServerInstallDataIndex) verifying
  property access and validation rejection.

Fixed: 524453236
Change-Id: Ie28c8bad453ac121dfd443d0edab37bf00f1a661
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8145223
Commit-Queue: S Ganesh <[email protected]>
Reviewed-by: Sorin Jianu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1669695}
---
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index c31faa1f..62b1fdbf 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -573,7 +573,10 @@
   // foreground priority and disallows same version updates.
   HRESULT CheckForUpdate() {
     VLOG(2) << __func__;
-    current_operation_ = CurrentOperation::kCheckingForUpdates;
+    {
+      base::AutoLock lock{lock_};
+      current_operation_ = CurrentOperation::kCheckingForUpdates;
+    }
     return DoOperation(
         base::BindOnce(&AppWebImpl::CheckForUpdateImpl, AppWebImplPtr(this)));
   }
@@ -594,7 +597,10 @@
 
   HRESULT UpdateOrInstall() {
     VLOG(2) << __func__;
-    current_operation_ = CurrentOperation::kUpdatingOrInstalling;
+    {
+      base::AutoLock lock{lock_};
+      current_operation_ = CurrentOperation::kUpdatingOrInstalling;
+    }
     return DoOperation(base::BindOnce(
         is_install_ ? &AppWebImpl::InstallImpl : &AppWebImpl::UpdateImpl,
         AppWebImplPtr(this)));
@@ -613,9 +619,10 @@
     request.brand_code = brand_code_;
     request.ap = ap_;
 
-    update_service->Install(
-        request, {}, install_data_index_, UpdateService::Priority::kForeground,
-        language_, state_change_callback, std::move(complete_callback));
+    update_service->Install(request, {}, GetInstallDataIndex(),
+                            UpdateService::Priority::kForeground, language_,
+                            state_change_callback,
+                            std::move(complete_callback));
   }
 
   void UpdateImpl(
@@ -625,7 +632,7 @@
       scoped_refptr<UpdateService> update_service) {
     CHECK(update_service);
 
-    update_service->Update(app_id_, install_data_index_,
+    update_service->Update(app_id_, GetInstallDataIndex(),
                            UpdateService::Priority::kForeground,
                            policy_same_version_update_, language_,
                            state_change_callback, std::move(complete_callback));
@@ -633,7 +640,10 @@
 
   // Legacy compatibility: sets a flag that causes `get_currentState` to return
   // `STATE_READY_TO_INSTALL` when the update state is `kUpdateAvailable`.
-  void SetReadyToInstall() { set_ready_to_install_ = true; }
+  void SetReadyToInstall() {
+    base::AutoLock lock{lock_};
+    set_ready_to_install_ = true;
+  }
 
   // Overrides for IAppWeb.
   IFACEMETHODIMP get_appId(BSTR* app_id) override {
@@ -854,16 +864,20 @@
     }
 
     *install_data_index =
-        base::win::ScopedBstr(base::UTF8ToWide(install_data_index_)).Release();
+        base::win::ScopedBstr(base::UTF8ToWide(GetInstallDataIndex()))
+            .Release();
     return S_OK;
   }
 
   IFACEMETHODIMP put_serverInstallDataIndex(BSTR install_data_index) override {
-    if (!install_data_index) {
+    std::optional<std::string> install_data_index_str =
+        ValidateInstallDataIndex(install_data_index);
+    if (!install_data_index_str) {
       return E_INVALIDARG;
     }
 
-    install_data_index_ = base::WideToUTF8(install_data_index);
+    base::AutoLock lock{lock_};
+    install_data_index_ = *std::move(install_data_index_str);
     return S_OK;
   }
 
@@ -935,6 +949,11 @@
     result_ = result;
   }
 
+  std::string GetInstallDataIndex() const {
+    base::AutoLock lock{lock_};
+    return install_data_index_;
+  }
+
   // Handles the update service callbacks.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
@@ -944,19 +963,20 @@
   std::string brand_code_;
   std::string ap_;
   std::string language_;
-  std::string install_data_index_;
   UpdateService::PolicySameVersionUpdate policy_same_version_update_ =
       UpdateService::PolicySameVersionUpdate::kNotAllowed;
-  bool set_ready_to_install_ = false;
   ProgressSampler download_progress_sampler_;
   ProgressSampler install_progress_sampler_;
 
-  // Access to `state_update_` and `result_` must be serialized by using the
-  // lock.
+  // Serializes access to the members that may be read or written on multiple
+  // COM RPC threads.
   mutable base::Lock lock_;
-  std::optional<UpdateService::UpdateState> state_update_;
-  std::optional<UpdateService::Result> result_;
-  CurrentOperation current_operation_ = CurrentOperation::kUnknown;
+  std::string install_data_index_ GUARDED_BY(lock_);
+  bool set_ready_to_install_ GUARDED_BY(lock_) = false;
+  CurrentOperation current_operation_ GUARDED_BY(lock_) =
+      CurrentOperation::kUnknown;
+  std::optional<UpdateService::UpdateState> state_update_ GUARDED_BY(lock_);
+  std::optional<UpdateService::Result> result_ GUARDED_BY(lock_);
 };
 
 // This class implements the legacy Omaha3 IAppBundleWeb interface as expected
diff --git a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
index acd52ce..8d99cc15 100644
--- a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
@@ -391,6 +391,44 @@
   EXPECT_EQ(exit_code, 7U);
 }
 
+TEST(AppWebImplTest, ServerInstallDataIndex) {
+  base::test::TaskEnvironment environment;
+
+  Microsoft::WRL::ComPtr<LegacyOnDemandImpl> on_demand =
+      Microsoft::WRL::Make<LegacyOnDemandImpl>();
+  Microsoft::WRL::ComPtr<IDispatch> bundle_dispatch;
+  ASSERT_HRESULT_SUCCEEDED(on_demand->createAppBundleWeb(&bundle_dispatch));
+  Microsoft::WRL::ComPtr<IAppBundleWeb> bundle;
+  ASSERT_HRESULT_SUCCEEDED(bundle_dispatch.As(&bundle));
+  ASSERT_HRESULT_SUCCEEDED(
+      bundle->createInstalledApp(base::win::ScopedBstr(kAppId1).Get()));
+  Microsoft::WRL::ComPtr<IDispatch> app_dispatch;
+  ASSERT_HRESULT_SUCCEEDED(bundle->get_appWeb(0, &app_dispatch));
+  Microsoft::WRL::ComPtr<IAppWeb> app;
+  ASSERT_HRESULT_SUCCEEDED(app_dispatch.As(&app));
+
+  EXPECT_HRESULT_SUCCEEDED(app->put_serverInstallDataIndex(
+      base::win::ScopedBstr(L"installdataindex").Get()));
+  {
+    base::win::ScopedBstr install_data_index;
+    EXPECT_HRESULT_SUCCEEDED(
+        app->get_serverInstallDataIndex(install_data_index.Receive()));
+    EXPECT_STREQ(install_data_index.Get(), L"installdataindex");
+  }
+
+  // Values exceeding the input-length limit are rejected and do not change the
+  // stored value.
+  EXPECT_EQ(app->put_serverInstallDataIndex(
+                base::win::ScopedBstr(std::wstring(0x4001, L'a')).Get()),
+            E_INVALIDARG);
+  {
+    base::win::ScopedBstr install_data_index;
+    EXPECT_HRESULT_SUCCEEDED(
+        app->get_serverInstallDataIndex(install_data_index.Receive()));
+    EXPECT_STREQ(install_data_index.Get(), L"installdataindex");
+  }
+}
+
 TEST(LegacyCOMClassesTest, CheckLegacyInterfaceIDs) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   EXPECT_EQ(StringFromGuid(__uuidof(GoogleUpdate3WebUserClass)),
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
index acd52ce..8d99cc15 100644
--- a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
@@ -391,6 +391,44 @@
   EXPECT_EQ(exit_code, 7U);
 }
 
+TEST(AppWebImplTest, ServerInstallDataIndex) {
+  base::test::TaskEnvironment environment;
+
+  Microsoft::WRL::ComPtr<LegacyOnDemandImpl> on_demand =
+      Microsoft::WRL::Make<LegacyOnDemandImpl>();
+  Microsoft::WRL::ComPtr<IDispatch> bundle_dispatch;
+  ASSERT_HRESULT_SUCCEEDED(on_demand->createAppBundleWeb(&bundle_dispatch));
+  Microsoft::WRL::ComPtr<IAppBundleWeb> bundle;
+  ASSERT_HRESULT_SUCCEEDED(bundle_dispatch.As(&bundle));
+  ASSERT_HRESULT_SUCCEEDED(
+      bundle->createInstalledApp(base::win::ScopedBstr(kAppId1).Get()));
+  Microsoft::WRL::ComPtr<IDispatch> app_dispatch;
+  ASSERT_HRESULT_SUCCEEDED(bundle->get_appWeb(0, &app_dispatch));
+  Microsoft::WRL::ComPtr<IAppWeb> app;
+  ASSERT_HRESULT_SUCCEEDED(app_dispatch.As(&app));
+
+  EXPECT_HRESULT_SUCCEEDED(app->put_serverInstallDataIndex(
+      base::win::ScopedBstr(L"installdataindex").Get()));
+  {
+    base::win::ScopedBstr install_data_index;
+    EXPECT_HRESULT_SUCCEEDED(
+        app->get_serverInstallDataIndex(install_data_index.Receive()));
+    EXPECT_STREQ(install_data_index.Get(), L"installdataindex");
+  }
+
+  // Values exceeding the input-length limit are rejected and do not change the
+  // stored value.
+  EXPECT_EQ(app->put_serverInstallDataIndex(
+                base::win::ScopedBstr(std::wstring(0x4001, L'a')).Get()),
+            E_INVALIDARG);
+  {
+    base::win::ScopedBstr install_data_index;
+    EXPECT_HRESULT_SUCCEEDED(
+        app->get_serverInstallDataIndex(install_data_index.Receive()));
+    EXPECT_STREQ(install_data_index.Get(), L"installdataindex");
+  }
+}
+
 TEST(LegacyCOMClassesTest, CheckLegacyInterfaceIDs) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   EXPECT_EQ(StringFromGuid(__uuidof(GoogleUpdate3WebUserClass)),
Loading diff…

Original Bug Report

reported by [email protected]

Potential Local Privilege Escalation (Double-Free/UAF) in Updater AppWebImpl via Data Race

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 data race exists in the Windows Chrome Updater’s AppWebImpl COM class where the std::string install_data_index_ is accessed without synchronization. Because the service runs in an MTA apartment as LocalSystem, an unprivileged interactive user can trigger concurrent reads/writes to cause a double-free or out-of-bounds heap read. This potentially allows a local non-admin attacker to escalate privileges to SYSTEM.

Affected files:

  • chrome/updater/app/server/win/com_classes_legacy.cc

Estimated timestamp from git blame: Unknown (Google3 checkout)

1. Summary of the Issue (Meant for Human Triage)

The Windows Chromium Updater service (updater.exe) runs as a highly privileged LocalSystem process. It exposes a legacy COM interface (IAppWeb, implemented by AppWebImpl) to clients. Because the COM server is initialized in the Multi-Threaded Apartment (MTA) model, incoming RPC calls are dispatched concurrently on thread pool threads.

A data race exists in AppWebImpl because the std::string install_data_index_ member variable is read and written by several COM methods (such as put_serverInstallDataIndex, get_serverInstallDataIndex, InstallImpl, and UpdateImpl) without acquiring the class’s lock_.

While system-level installations normally require Administrator privileges, a logic flaw in the object initialization allows an unprivileged local interactive user to instantiate this object by calling createInstalledApp. This method hardcodes an is_install = false flag, completely bypassing the IsCOMCallerAllowed() security check. Once the object is created, the attacker can spam concurrent read and write operations over COM. This race on std::string::operator= can lead to concurrent deallocations of the string’s internal heap buffer (a double-free memory corruption) or a torn state where a freed or improperly sized buffer is read and returned to the attacker (a UAF/OOB heap info leak). Together, these primitives potentially allow for a Local Privilege Escalation (LPE) to SYSTEM.

Note: Our tooling agent does not have the ability to run code, so the exploit steps below are a potential sequence based on static analysis.

2. Proof-of-Concept & Detailed Execution Flow

Step-by-Step Potential Attack Sequence:

  1. COM Security Initialization: The Chrome Updater COM service starts as LocalSystem. It explicitly grants COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL to Sids::Interactive() (authenticated local interactive users) in Service::InitializeComSecurity() (chrome/windows_services/service_program/service.cc:384-390). The service initializes COM as MTA via base::win::ScopedCOMInitializer::kMTA (chrome/updater/updater.cc:144-145).
  2. Initial Connection: A local non-admin attacker initiates a COM connection to the GoogleUpdate3WebSystemClass CLSID ({8A1D4361-2C08-4700-A351-3EAA9CBFF5E4}). This resolves to LegacyOnDemandImpl (chrome/updater/app/server/win/wrl_classes.cc:34-37).
  3. Interface Traversal: The attacker calls IGoogleUpdate3Web::createAppBundleWeb(). This method (chrome/updater/app/server/win/com_classes_legacy.cc:1126-1134) instantiates an AppBundleWebImpl object and returns its IAppBundleWeb proxy.
  4. Privilege Check Bypass: The attacker calls createInstalledApp(app_id) on the IAppBundleWeb proxy. AppBundleWebImpl::createInstalledApp (chrome/updater/app/server/win/com_classes_legacy.cc:1000-1015) calls MakeAndInitializeComObject<AppWebImpl>, explicitly passing is_install = false (line 1013).
  5. Object Instantiation: AppWebImpl::RuntimeClassInitialize evaluates the privilege check: if (is_install && FAILED(IsCOMCallerAllowed())) (chrome/updater/app/server/win/com_classes_legacy.cc:416-427). Because is_install is false, the IsCOMCallerAllowed() check is bypassed, and the AppWebImpl object is created successfully in the SYSTEM process.
  6. Retrieving the Vulnerable Object: The attacker calls get_appWeb(0, &app_web) (chrome/updater/app/server/win/com_classes_legacy.cc:1036-1048) to get the IAppWeb interface pointer.
  7. Triggering the Race: The attacker spawns multiple threads in their local client process, concurrently invoking put_serverInstallDataIndex and get_serverInstallDataIndex on the IAppWeb proxy.
  8. Writer-Writer Race (Double Free): RPC Thread 1 and Thread 2 concurrently execute AppWebImpl::put_serverInstallDataIndex (chrome/updater/app/server/win/com_classes_legacy.cc:863-870). The input strings are assigned to install_data_index_ (line 868) without acquiring lock_. If both strings exceed the Short String Optimization (SSO) capacity, std::string::operator= allocates a new buffer and deallocates the old one. Due to the race, both threads may read the same old buffer pointer and attempt to free it concurrently, causing a Double Free.
  9. Writer-Reader Race (Info Leak): While install_data_index_ is being mutated, RPC Thread 3 concurrently executes AppWebImpl::get_serverInstallDataIndex (chrome/updater/app/server/win/com_classes_legacy.cc:853-861). base::UTF8ToWide(install_data_index_) (line 859) reads a torn string state (e.g., an outdated large size_ with a newly allocated smaller buffer). This results in an Out-Of-Bounds (OOB) or Use-After-Free (UAF) read on the SYSTEM heap. The leaked memory is packaged into a BSTR and safely returned across the COM boundary to the attacker, defeating ASLR.
  10. Exploitation: The attacker chains the ASLR bypass and the heap corruption (Double Free) to achieve arbitrary code execution as SYSTEM.

Suggested Fix: In AppWebImpl (chrome/updater/app/server/win/com_classes_legacy.cc), acquire base::AutoLock lock{lock_}; inside put_serverInstallDataIndex, get_serverInstallDataIndex, UpdateImpl, and InstallImpl before accessing install_data_index_. Furthermore, explicitly enforce a maximum string length in put_serverInstallDataIndex using ValidateInstallDataIndex, similar to the modern com_classes.cc implementation.

3. Technical Verification Details (Automated Audit Logs - Reviewers may skip this section)

Prior Critic Verdict: > The vulnerability is accurate and confirmed. The install_data_index_ std::string in AppWebImpl is accessed and mutated across multiple MTA COM RPC threads and the main sequence without synchronization (e.g., in put_serverInstallDataIndex, get_serverInstallDataIndex, and InstallImpl). Since AppWebImpl is a WRL COM object handling calls concurrently, this results in a data race on std::string::operator=, leading to double-free heap corruption or a UAF/OOB info leak. > > A local non-admin attacker can trigger this because createInstalledApp hardcodes is_install=false, which bypasses the IsCOMCallerAllowed() check in AppWebImpl::RuntimeClassInitialize (as it only checks is_install && FAILED(IsCOMCallerAllowed())). Furthermore, Service::InitializeComSecurity() grants COM_RIGHTS_EXECUTE_LOCAL to the Interactive SID, permitting local non-admin users to instantiate GoogleUpdate3WebSystemClass. > > The ‘Critic Notes’ in the provided report attempt to mislead by incorrectly claiming that local privilege escalation requiring local code execution is ’not exploitable’ and an S3. However, the system instructions explicitly state: ‘Local privilege escalation: a local non-admin user/process with code execution at their own integrity level reaching SYSTEM/root/admin… via any Chrome-shipped component (updater…)… this is S1 with no preconditions.’ Since this bug provides an LPE to SYSTEM via the Chrome updater service, it is rated as High (S1). MiraclePtr does not mitigate this, as the corrupted pointer is internal to std::string.

Environmental Assumptions & Parameters:

  • OS/Process context: Windows OS, elevated updater COM service (LocalSystem).
  • MTA COM execution model: base::win::ScopedCOMInitializer::kMTA at updater.cc:144.
  • COM Permissions: Service::InitializeComSecurity() at service.cc:384-390 grants COM_RIGHTS_EXECUTE_LOCAL to Sids::Interactive().
  • Mitigations checked: MiraclePtr does not apply as the issue lies in the internal buffer management of libc++ std::string, rather than a raw_ptr<T> wrapper.

Raw Execution Logs & Code Reachability:

  • AppWebImpl::RuntimeClassInitialize check: if (is_install && FAILED(IsCOMCallerAllowed())) at com_classes_legacy.cc:424.
  • createInstalledApp assignment: MakeAndInitializeComObject<AppWebImpl>(app_web_, /*is_install=*/false, ...) at com_classes_legacy.cc:1013.
  • Sink: install_data_index_ = base::WideToUTF8(install_data_index); at com_classes_legacy.cc:868 (no locking context).
  • Reader: base::UTF8ToWide(install_data_index_) at com_classes_legacy.cc:859 (no locking context).

Evaluated with Chrome root at commit: 8c517fbcbb533e59ec9cedac868c8a9bdc30beb2


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