CVE-2026-13844
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/updater/app/app_server_win.cc |
modified |
Files Changed
chrome/updater/app/app_server_win.ccchrome/updater/app/app_server_win.h
Patch
From ef27618f9ceea2e9e4d83a419d50b9705ef0c082 Mon Sep 17 00:00:00 2001 From: Noah Rose Ledesma <[email protected]> Date: Wed, 27 May 2026 10:20:06 -0700 Subject: [PATCH] Synchronize access to `on_service_stopping_` Access to the `on_service_stopping_` callback in AppServerWin occurs on COM RPC threads. It's already known that `Stop` can be invoked multiple times; given that the updater uses MTA, we ought to be careful about concurrent access too. Bug: 516926115 Change-Id: I9080f5d89ba195a01a5a14ed84e44f0c6a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7879166 Reviewed-by: Sorin Jianu <[email protected]> Reviewed-by: S Ganesh <[email protected]> Commit-Queue: Noah Rose Ledesma <[email protected]> Cr-Commit-Position: refs/heads/main@{#1637052} --- diff --git a/chrome/updater/app/app_server_win.cc b/chrome/updater/app/app_server_win.cc index 325e1d5..4a02886 100644 --- a/chrome/updater/app/app_server_win.cc +++ b/chrome/updater/app/app_server_win.cc @@ -27,6 +27,7 @@ #include "base/notreached.h" #include "base/strings/strcat.h" #include "base/strings/utf_string_conversions.h" +#include "base/synchronization/lock.h" #include "base/task/sequenced_task_runner.h" #include "base/types/expected_macros.h" #include "base/win/atl.h" @@ -266,8 +267,13 @@ // service process. // It is possible for `Stop` to be called multiple times, so check for a // valid `on_service_stopping_` callback before calling `Run`. - if (on_service_stopping_) { - std::move(on_service_stopping_).Run(); + base::OnceClosure on_service_stopping; + { + base::AutoLock lock(on_service_stopping_lock_); + on_service_stopping = std::move(on_service_stopping_); + } + if (on_service_stopping) { + std::move(on_service_stopping).Run(); } } UnregisterClassObjects(); @@ -283,8 +289,12 @@ } HRESULT AppServerWin::RunCOMServer(base::OnceClosure on_service_stopping) { - on_service_stopping_ = std::move(on_service_stopping); + { + base::AutoLock lock(on_service_stopping_lock_); + on_service_stopping_ = std::move(on_service_stopping); + } absl::Cleanup reset_on_service_stopping = [&] { + base::AutoLock lock(on_service_stopping_lock_); on_service_stopping_.Reset(); }; return Run(); diff --git a/chrome/updater/app/app_server_win.h b/chrome/updater/app/app_server_win.h index d76d060ec..a5a7d0a7 100644 --- a/chrome/updater/app/app_server_win.h +++ b/chrome/updater/app/app_server_win.h @@ -11,7 +11,9 @@ #include "base/functional/callback.h" #include "base/memory/scoped_refptr.h" +#include "base/synchronization/lock.h" #include "base/task/sequenced_task_runner.h" +#include "base/thread_annotations.h" #include "chrome/updater/app/app_server.h" #include "chrome/updater/update_service.h" #include "chrome/updater/update_service_internal.h" @@ -110,7 +112,9 @@ std::unique_ptr<UpdateServiceInternalStub> active_duty_internal_stub_; std::unique_ptr<UpdateServiceStub> active_duty_stub_; - base::OnceClosure on_service_stopping_; + // Guards `on_service_stopping_`, which is accessed on COM RPC threads. + base::Lock on_service_stopping_lock_; + base::OnceClosure on_service_stopping_ GUARDED_BY(on_service_stopping_lock_); }; // Returns the singleton AppServerWin instance.
Original Bug Report
Potential Race Condition in AppServerWin::Stop() Leads to Use-After-Free in GoogleUpdater
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 race condition in GoogleUpdater’s AppServerWin::Stop() method on Windows allows unsynchronized concurrent execution of the on_service_stopping_ callback. This can lead to a double-release of the underlying BindState, causing a potential Use-After-Free (UAF) vulnerability. If successfully exploited, a local non-admin user could potentially achieve local privilege escalation to SYSTEM.
Affected files:
chrome/updater/app/app_server_win.ccchrome/updater/app/app_server_win.h
Estimated timestamp from git blame: 2025-12-11
Summary
A potential race condition exists in the Chromium updater’s AppServerWin::Stop() implementation due to unsynchronized access to the on_service_stopping_ callback. This callback is executed when the system-level COM server is shutting down. Because the callback is invoked and moved without thread synchronization, concurrent execution across multiple threads (such as COM RPC threads and the Service Control Manager thread) can cause a double-release of the underlying BindStateBase, potentially leading to a Use-After-Free (UAF) vulnerability.
Root Cause Analysis
In chrome/updater/app/app_server_win.cc, AppServerWin::CreateWRLModule() registers AppServerWin::Stop as the WRL Module release notifier:
void AppServerWin::CreateWRLModule() {
Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::Create(
this, &AppServerWin::Stop);
}
AppServerWin::Stop() can also be called via the SCM (Service Control Manager) dispatcher thread when the service is stopped by the OS.
The Stop() method is implemented as follows:
void AppServerWin::Stop() {
VLOG(2) << __func__ << ": COM server is shutting down.";
if (IsSystemInstall(updater_scope())) {
if (on_service_stopping_) {
std::move(on_service_stopping_).Run();
}
}
UnregisterClassObjects();
...
}
If Thread A (e.g., an SCM stop thread) and Thread B (e.g., a COM RPC thread executing due to a transition to zero objects) call Stop() concurrently, the following sequence of events can occur:
- Both threads evaluate the
if (on_service_stopping_)check as true concurrently before either can clear or move the member variable. - Both threads proceed to execute
std::move(on_service_stopping_).Run(). - In
base/functional/callback.h,OnceCallback::Run() &&moves the internal state holder:internal::BindStateHolder holder = std::move(holder_); - This invokes the move constructor of
scoped_refptr<BindStateBase>inbase/memory/scoped_refptr.h:scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) { r.ptr_ = nullptr; } - Because this move operation is non-atomic, both threads can copy the same raw pointer address
r.ptr_into their respective localholdervariables before either thread assignsnullptrto the source pointer. - Both threads successfully execute the check
CHECK(!is_null())and invoke the callback. - When the local
holdervariables on both threads go out of scope, their destructors invokeRelease(ptr_)on the sameBindStateBasepointer. - The first destructor decrements the reference count to 0, deleting the
BindStateobject. The second destructor attempts to release the already-deleted pointer, causing a Use-After-Free.
Because scoped_refptr::ptr_ is annotated with the RAW_PTR_EXCLUSION macro, this smart pointer is not protected by MiraclePtr/BackupRefPtr.
Potential Exploitation Path
Note: These are suggested and potential steps, as our analysis is static and we have not executed a live proof-of-concept.
- An unprivileged local user logged into an interactive session (who is explicitly granted COM execute permissions via the
InteractiveSID) activates the updater’s COM classes. - The attacker triggers rapid reference additions and releases (or coordinates a COM release with an SCM service stop request) to trigger concurrent invocations of
AppServerWin::Stop()on separate threads. - If the race condition is successfully triggered, the underlying
BindStateis double-released. - By reclaiming the freed heap memory of the
BindStatebefore the second thread’s destructor runs, the attacker could overwrite thedestructor_function pointer. - When the second thread executes
destructor_(this), control flow is redirected, potentially allowing arbitrary code execution in the context of the service (NT AUTHORITY\SYSTEM). This would result in Local Privilege Escalation (LPE).
Suggested Fix
Protect the access to on_service_stopping_ with a mutex or base::Lock to perform a synchronized move-and-invoke, similar to the pattern utilized in ModuleReleaseHelper within chrome/windows_services/service_program/process_wrl_module.cc:
void AppServerWin::Stop() {
VLOG(2) << __func__ << ": COM server is shutting down.";
base::OnceClosure on_stopping;
{
base::AutoLock lock(lock_);
on_stopping = std::move(on_service_stopping_);
}
if (on_stopping) {
std::move(on_stopping).Run();
}
UnregisterClassObjects();
...
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.