Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Tracing
DescriptionUse after free in Tracing
ComponentTracing
Bug ClassUAF
Tracker520426287
Fix commit858ee83ea6e4 (chromium/src) +40/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
chrome/windows_services/elevated_tracing_service/process_watcher.cc
modified
ThreadDelegate
chrome/windows_services/elevated_tracing_service/process_watcher.h
modified

Files Changed

  • chrome/windows_services/elevated_tracing_service/process_watcher.cc
  • chrome/windows_services/elevated_tracing_service/process_watcher.h
  • chrome/windows_services/elevated_tracing_service/session_registry.cc
From 858ee83ea6e4a0ae9c05bd536e23eb9c5c1cac4e Mon Sep 17 00:00:00 2001
From: Greg Thompson <grt@chromium.org>
Date: Wed, 10 Jun 2026 09:02:22 -0700
Subject: [PATCH] [etw tracing] Fix shutdown race

Destruction of a SystemTracingSession by its last ref being released
could race with detection of client process termination by
ProcessWatcher. To avoid a data race, ProcessWatcher's destructor must
wait for its watch task to complete before allowing destruction to
complete. This ensures that the SystemTracingSession instance that owns
the watcher (by way of a ScopedSession) is not destroyed while
SessionRegistry::OnClientTerminated is handling termination of the
client.

Fixed: 520426287
Change-Id: Ia6d27379ec566ee1ff7e2454fd9fc9c1810f2ec7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7918051
Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org>
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644684}
---

diff --git a/chrome/windows_services/elevated_tracing_service/process_watcher.cc b/chrome/windows_services/elevated_tracing_service/process_watcher.cc
index 38d2dea0..0e35b9ac 100644
--- a/chrome/windows_services/elevated_tracing_service/process_watcher.cc
+++ b/chrome/windows_services/elevated_tracing_service/process_watcher.cc
@@ -7,6 +7,7 @@
 #include <windows.h>
 
 #include <array>
+#include <functional>
 #include <utility>
 
 #include "base/check.h"
@@ -27,11 +28,17 @@
 
 // Waits for either the watched process to terminate or for the shutdown event
 // to be signaled. In the former case, the `on_terminated` closure is
-// run before exiting.
+// run before exiting. `completed_event` is signaled when the task is complete.
 void WatchInThreadPool(base::Process process,
                        base::OnceClosure on_terminated,
                        HANDLE startup_event,
-                       base::win::ScopedHandle shutdown_event) {
+                       base::win::ScopedHandle shutdown_event,
+                       base::win::ScopedHandle completed_event,
+                       base::PlatformThreadId& watch_thread_id) {
+  // Give the ID of this thread to the ProcessWatcher so that it can avoid
+  // waiting on itself during destruction.
+  watch_thread_id = base::PlatformThread::CurrentId();
+
   // Signal that the task is ready to watch.
   ::SetEvent(std::exchange(startup_event, nullptr));
 
@@ -48,6 +55,8 @@
   if (result == WAIT_OBJECT_0) {
     std::move(on_terminated).Run();
   }  // else the shutdown event was signaled.
+
+  ::SetEvent(completed_event.get());
 }
 
 }  // namespace
@@ -66,13 +75,22 @@
                           /*dwDesiredAccess=*/0,
                           /*bInheritHandle=*/FALSE, DUPLICATE_SAME_ACCESS));
 
+  // Prepare a dup of the completed event for the task to signal.
+  HANDLE completed_event = nullptr;
+  CHECK(::DuplicateHandle(::GetCurrentProcess(), completed_event_.handle(),
+                          ::GetCurrentProcess(), &completed_event,
+                          /*dwDesiredAccess=*/0,
+                          /*bInheritHandle=*/FALSE, DUPLICATE_SAME_ACCESS));
+
   base::ThreadPool::CreateTaskRunner(
       {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()})
       ->PostTask(
           FROM_HERE,
           base::BindOnce(&WatchInThreadPool, std::move(process),
                          std::move(on_terminated), startup_event.handle(),
-                         base::win::ScopedHandle(shutdown_event)));
+                         base::win::ScopedHandle(shutdown_event),
+                         base::win::ScopedHandle(completed_event),
+                         std::ref(watch_thread_id_)));
 
   // Wait for the watch task to signal that it is ready.
   startup_event.Wait();
@@ -81,6 +99,13 @@
 ProcessWatcher::~ProcessWatcher() {
   // Signal that the watch task should exit if it is still watching the process.
   shutdown_event_.Signal();
+
+  // Wait for the watch task to complete before continuing with destruction,
+  // unless destruction is taking place on the watch thread itself. This can
+  // happen if client termination synchronously triggers destruction via COM.
+  if (base::PlatformThread::CurrentId() != watch_thread_id_) {
+    completed_event_.Wait();
+  }
 }
 
 }  // namespace elevated_tracing_service
diff --git a/chrome/windows_services/elevated_tracing_service/process_watcher.h b/chrome/windows_services/elevated_tracing_service/process_watcher.h
index d31cfa4..49ee70f 100644
--- a/chrome/windows_services/elevated_tracing_service/process_watcher.h
+++ b/chrome/windows_services/elevated_tracing_service/process_watcher.h
@@ -8,6 +8,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/process/process.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
 
 namespace elevated_tracing_service {
 
@@ -22,10 +23,14 @@
   ~ProcessWatcher();
 
  private:
-  class ThreadDelegate;
-
   // An event that is signaled at destruction to cancel the watch.
   base::WaitableEvent shutdown_event_;
+
+  // An event that is signaled when the watch task has completed.
+  base::WaitableEvent completed_event_;
+
+  // The TID of the thread servicing the watch task.
+  base::PlatformThreadId watch_thread_id_ = base::kInvalidThreadId;
 };
 
 }  // namespace elevated_tracing_service
diff --git a/chrome/windows_services/elevated_tracing_service/session_registry.cc b/chrome/windows_services/elevated_tracing_service/session_registry.cc
index f9610391..bd30ed0 100644
--- a/chrome/windows_services/elevated_tracing_service/session_registry.cc
+++ b/chrome/windows_services/elevated_tracing_service/session_registry.cc
@@ -6,6 +6,8 @@
 
 #include <objbase.h>
 
+#include <wrl/client.h>
+
 #include <utility>
 
 #include "base/check_deref.h"
@@ -108,6 +110,9 @@
   // still holds the session's IUnknown pointer (meaning that the ScopedSession
   // has yet to be destroyed), tell COM to force a disconnect.
   if (IUnknown* unknown = core->release(); unknown != nullptr) {
+    // Keep a strong reference locally to prevent the object from being
+    // synchronously destroyed (and deallocated) during CoDisconnectObject.
+    Microsoft::WRL::ComPtr<IUnknown> keep_alive(unknown);
     ::CoDisconnectObject(unknown, /*dwReserved=*/0);
 
     // This task is handling client termination before the session is destroyed,
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in elevated_tracing_service due to SessionRegistry and ProcessWatcher 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 potential Use-After-Free (UAF) vulnerability exists in the Windows-only elevated tracing service due to a race condition between COM session destruction and client termination. A thread-pool worker thread can invoke CoDisconnectObject on a dangling pointer to a freed SystemTracingSession object. If exploited, this could allow a local interactive user to escalate privileges to SYSTEM.

Affected files:

  • chrome/windows_services/elevated_tracing_service/session_registry.cc
  • chrome/windows_services/elevated_tracing_service/process_watcher.cc
  • chrome/windows_services/elevated_tracing_service/system_tracing_session.cc

Estimated timestamp from git blame: 2024-10-09

Description

A potential Use-After-Free (UAF) vulnerability has been identified in the Windows-only elevated_tracing_service within Chromium. The issue lies in how the SessionRegistry coordinates clean session destruction with client process termination.

SessionRegistry::OnClientTerminated (session_registry.cc:106-118) is invoked on a ThreadPool thread when a client process terminates. It calls ::CoDisconnectObject on a raw IUnknown* pointer retrieved from SessionCore via an atomic exchange unknown_.exchange(nullptr) (session_registry.cc:35). However, SessionCore holds this pointer as a raw, weak reference without having called AddRef (session_registry.cc:31), meaning the lifetime of the SystemTracingSession is independent of SessionCore.

Concurrently, if COM drops the last reference to SystemTracingSession (due to client process exit or rundown), the SystemTracingSession destructor runs on an arbitrary MTA RPC thread. The destructor triggers ~ScopedSession, which runs OnSessionDestroyed (session_registry.cc:47).

If the ThreadPool thread wins the atomic exchange, OnSessionDestroyed on the RPC thread retrieves nullptr and returns early without blocking (session_registry.cc:98). The RPC thread then proceeds to destroy ProcessWatcher (session_registry.cc:54). ~ProcessWatcher signals the shutdown event but does not wait/join for the background ThreadPool thread task to finish (process_watcher.cc:81-84). Consequently, the RPC thread deallocates SystemTracingSession immediately. When the preempted ThreadPool thread resumes, it calls CoDisconnectObject(unknown) on the now-freed pointer, initiating a virtual function call on a dangling pointer.

Because cet_shadow_stack (Intel CET) is disabled for this executable target (BUILD.gn:22-23), reclaiming the deallocated session’s heap slot could allow an attacker to hijack control flow.

Potential Attack Steps

  1. A local low-privilege interactive user calls CoCreateInstance to activate CLSID_SystemTracingSession in the service.
  2. The client calls AcceptInvitation to register the active session and start the background process-monitoring task (ProcessWatcher).
  3. The attacker terminates their client process to trigger both:
    • The kernel process-handle signal (running OnClientTerminated on a ThreadPool thread).
    • The COM fast-rundown stub Release (destroying the session on an RPC thread).
  4. A race is triggered: if the ThreadPool thread exchanges the pointer and gets preempted before invoking CoDisconnectObject, the RPC thread will delete the session unhindered.
  5. When the ThreadPool thread resumes, it performs a virtual call (QueryInterface) on the freed SystemTracingSession object.
  6. Grooming/spraying the heap from a secondary COM connection to reclaim the deallocated block could lead to arbitrary code execution as NT AUTHORITY\SYSTEM.

Note: These are suggested potential steps; our tooling does not currently have the capability to execute code or run a working Proof of Concept (PoC).

Suggested Fix

To remediate this race, ~ProcessWatcher must block and wait for the posted background ThreadPool task to terminate before allowing destruction of the outer class to proceed, or SessionCore should extend the lifetime of the SystemTracingSession object by managing strong references (Microsoft::WRL::ComPtr) rather than un-refcounted raw pointers.

Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf


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