Medium watch CVSS 9.6 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Browser
DescriptionUse after free in Browser
ComponentBrowser
Sub-componentWindows
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker520161438
Fix commit61d783000461 (chromium/src) +59/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-05 (per vendor advisory)
Fix landed2026-07-31 (public git)
Disclosed2026-09-08
Patch gap39 days — fix public in source before the advisory

Background

WinRT `PropertiesChanged` event
a Windows Runtime notification (registered here via add_PropertiesChanged on caption_statics2_) that fires when OS closed-caption styling changes, and which the system may deliver on an arbitrary thread.
`NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated()`
the call that walks the NativeTheme observer list and pushes a caption-style update to registered NativeThemeObservers, whose data lifecycle is owned by the sequence that registered the listener.
`base::SequencedTaskRunner`
a handle to a specific task sequence; HasCurrentDefault()/GetCurrentDefault() capture the current sequence so work can later be PostTasked back to it rather than run on whatever thread is executing.
Sequence affinity
the invariant that a given object (here the NativeTheme observer list and its observers) is only touched from one sequence; violating it produces data races and use-after-free.

Root Cause Analysis

Before the patch, the IEventHandler<IInspectable*> callback registered with add_PropertiesChanged invoked NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated() directly inside the handler body. Because a WinRT PropertiesChanged event “may be delivered on an arbitrary thread” (as the added comment states), this meant the NativeTheme observer list — and the observers it notifies — could be walked and dereferenced from a thread other than the one that owns that data’s lifecycle, violating the sequence-affinity invariant. When the notification runs on the wrong thread concurrently with the owning sequence tearing down or mutating those observer objects, the handler operates on freed or racing state, which is the use-after-free reported for the Browser process.

The fix captures the registering sequence’s runner via base::SequencedTaskRunner::GetCurrentDefault() (guarded by HasCurrentDefault()) into task_runner, and when present bounces the notification back with task_runner->PostTask(FROM_HERE, base::BindOnce([]{ ... NotifyOnCaptionStyleUpdated(); })) so the observer walk always runs on the sequence that registered the listener. Only when no sequenced runner is available does it fall back to the original inline call, preserving prior behavior in that case. The shipped regression test EventOnOtherThreadNotifiesOnRegisteringSequence confirms the intent by firing the event from a separate base::Thread and asserting the notification lands on the original (main) thread.

Key insight
The single mistake was running an observer-notification (NotifyOnCaptionStyleUpdated) directly on the arbitrary thread that delivered the WinRT event, instead of on the sequence that owns the observer data; the fix corrects it by capturing that sequence’s task_runner at registration time and PostTask-ing the notification back to it.

Attack Path

  1. Listener is registered on the Browser's owning sequence Chrome calls the listener-registration path (enclosing function name not shown in the hunk header; the commit message references RegisterClosedCaptionPropertiesChangedListener (inferred)), invoking caption_statics2_->add_PropertiesChanged with a callback that, pre-patch, calls NotifyOnCaptionStyleUpdated() inline.
  2. OS delivers the caption-change event on a foreign thread a closed-caption styling change causes the WinRT PropertiesChanged event to fire and be dispatched on an arbitrary system thread, not the registering sequence, as the added comment and the regression test’s separate base::Thread demonstrate.
  3. Cross-thread observer walk races the owning sequence the callback runs NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated() on that foreign thread while the owning sequence may be concurrently destroying or mutating the NativeTheme observer list, so the walk dereferences freed/racing observer state (the use-after-free). (inferred) — the exact freed object and the timing window are not shown in the diff, which only adds the thread bounce.
  4. Escalation controlled memory corruption of freed observer objects could be steered toward further compromise of the Browser process. (inferred) — no read/write primitive, sink, or code-execution step is demonstrated by the patch.

Impact Assessment

An attacker who can influence the timing of a closed-caption/native-theme property change relative to observer teardown could trigger a use-after-free in the Browser process on Windows, the higher-privileged process that hosts the NativeTheme observer list. The precondition is that the WinRT PropertiesChanged event be delivered on a thread other than the registering sequence (the normal system behavior for these events) while that sequence is mutating or destroying observers. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6. The advisory classifies this only as “Use after free in Browser” and does not state exploited-in-the-wild status or a severity rating in the provided metadata.

Changed Functions

FunctionChangeNotes
if
ui/native_theme/native_theme_win.cc
modified

Files Changed

  • ui/native_theme/native_theme_win_unittest.cc
  • ui/native_theme/native_theme_win.cc

Audit Directions

  • WinRT/COM event handlers that touch sequence-affine state
    audit every add_* / Microsoft::WRL::Callback handler whose body directly touches singletons or observer lists (like NativeTheme::GetInstanceForWeb()), since these events may arrive on arbitrary threads and must PostTask back to the owning sequence.
  • OS-callback lambdas invoking observer notifications
    look for callbacks that call Notify*/observer-walk methods inline without capturing and hopping to a SequencedTaskRunner, the exact pattern fixed here.
  • `HasCurrentDefault` fallback paths
    review handlers registered where no sequenced runner exists (the else branch here still runs inline), to confirm those code paths cannot be reached on a foreign thread and are genuinely safe.
From 61d783000461a95d107ef90e6213f77d90fbc97e Mon Sep 17 00:00:00 2001
From: Ari Chivukula <[email protected]>
Date: Fri, 31 Jul 2026 12:09:57 -0700
Subject: [PATCH] Reland "[win] PropertiesChanged listener must use correct thread"

This reverts commit 15d710d92728f6ea8c56f966a2e6a97d8ec38180.

Reason for revert: Adding HasCurrentDefault first to unbreak unittests

Original change's description:
> Revert "[win] PropertiesChanged listener must use correct thread"
>
> This reverts commit f24ddfe94420b50854c20a68018b4276c1112492.
>
> Reason for revert:
> LUCI Bisection has identified this change as the cause of a test failure. See the analysis: https://ci.chromium.org/ui/p/chromium/bisection/test-analysis/b/5920246637527040
>
> Sample build with failed test: https://ci.chromium.org/b/8674831598765593553
> Affected test(s):
> [://chrome/test\:browser_tests!gtest::AXTreeDistillerTest#DistillsWebPage/simple_page](https://ci.chromium.org/ui/test/chromium/:%2F%2Fchrome%2Ftest%5C:browser_tests%21gtest::AXTreeDistillerTest%23DistillsWebPage%2Fsimple_page?q=VHash%3A553c1560028db846)
> [://chrome/test\:browser_tests!gtest::ChromeContentSettingsAgentDelegateBrowserTest#PluginsTemporarilyAllowed](https://ci.chromium.org/ui/test/chromium/:%2F%2Fchrome%2Ftest%5C:browser_tests%21gtest::ChromeContentSettingsAgentDelegateBrowserTest%23PluginsTemporarilyAllowed?q=VHash%3A553c1560028db846)
> [://chrome/test\:browser_tests!gtest::ChromeRenderFrameObserverTest#LoadTimesAndCsiDefinedUnconditionally](https://ci.chromium.org/ui/test/chromium/:%2F%2Fchrome%2Ftest%5C:browser_tests%21gtest::ChromeRenderFrameObserverTest%23LoadTimesAndCsiDefinedUnconditionally?q=VHash%3A553c1560028db846)
> [://chrome/test\:browser_tests!gtest::PasswordAutofillAgentTest#AcceptingSuggestionDoesntRewriteUsername](https://ci.chromium.org/ui/test/chromium/:%2F%2Fchrome%2Ftest%5C:browser_tests%21gtest::PasswordAutofillAgentTest%23AcceptingSuggestionDoesntRewriteUsername?q=VHash%3A553c1560028db846)
> [://chrome/test\:browser_tests!gtest::PasswordAutofillAgentTest#PreviewField](https://ci.chromium.org/ui/test/chromium/:%2F%2Fchrome%2Ftest%5C:browser_tests%21gtest::PasswordAutofillAgentTest%23PreviewField?q=VHash%3A553c1560028db846)
> and 59 more ...
>
> If this is a false positive, please report it at http://b.corp.google.com/createIssue?component=1199205&description=Analysis%3A+https%3A%2F%2Fci.chromium.org%2Fui%2Fp%2Fchromium%2Fbisection%2Ftest-analysis%2Fb%2F5920246637527040&format=PLAIN&priority=P3&title=Wrongly+blamed+https%3A%2F%2Fchromium-review.googlesource.com%2Fc%2Fchromium%2Fsrc%2F%2B%2F8163303&type=BUG
>
> Original change's description:
> > [win] PropertiesChanged listener must use correct thread
> >
> > RegisterClosedCaptionPropertiesChangedListener must resolve changes on
> > the thread that manages the data lifecycle of the relevant data.
> >
> > Fixed: 520161438
> > Change-Id: I57f3f04159214d3ed4cb7c35dca58366cdf37d44
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8163303
> > Auto-Submit: Ari Chivukula <[email protected]>
> > Reviewed-by: Elly <[email protected]>
> > Commit-Queue: Elly <[email protected]>
> > Cr-Commit-Position: refs/heads/main@{#1671131}
> >
>
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Change-Id: Iec9b72d42906d9f87816a68d85157d55f070964d
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8175977
> Commit-Queue: Shunya Shishido <[email protected]>
> Bot-Commit: [email protected] <[email protected]>
> Owners-Override: Shunya Shishido <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#1671582}

Fixed: 520161438
Change-Id: I0a7ae2daf9b8db1827207d7b90c6ce14c1c3c393
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8182567
Reviewed-by: ccameron chromium <[email protected]>
Auto-Submit: Ari Chivukula <[email protected]>
Commit-Queue: Ari Chivukula <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1672006}
---
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index e1229fc0..e380266 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -23,11 +23,14 @@
 #include "base/containers/fixed_flat_map.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/span.h"
+#include "base/functional/bind.h"
+#include "base/location.h"
 #include "base/no_destructor.h"
 #include "base/notreached.h"
 #include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/cstring_view.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/win/core_winrt_util.h"
 #include "base/win/hstring_reference.h"
 #include "base/win/scoped_gdi_object.h"
@@ -1133,10 +1136,24 @@
   }
 
   EventRegistrationToken token;
+  scoped_refptr<base::SequencedTaskRunner> task_runner;
+  if (base::SequencedTaskRunner::HasCurrentDefault()) {
+    task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+  }
   hr = caption_statics2_->add_PropertiesChanged(
       Microsoft::WRL::Callback<ABI::Windows::Foundation::IEventHandler<
-          IInspectable*>>([](IInspectable*, IInspectable*) -> HRESULT {
-        NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated();
+          IInspectable*>>([task_runner](IInspectable*,
+                                        IInspectable*) -> HRESULT {
+        // The event may be delivered on an arbitrary thread; bounce the
+        // notification back to the sequence that registered the listener.
+        if (task_runner) {
+          task_runner->PostTask(
+              FROM_HERE, base::BindOnce([] {
+                NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated();
+              }));
+        } else {
+          NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated();
+        }
         return S_OK;
       }).Get(),
       &token);
diff --git a/ui/native_theme/native_theme_win_unittest.cc b/ui/native_theme/native_theme_win_unittest.cc
index 0caf26a..33c887a5 100644
--- a/ui/native_theme/native_theme_win_unittest.cc
+++ b/ui/native_theme/native_theme_win_unittest.cc
@@ -13,7 +13,9 @@
 #include <wrl/implements.h>
 
 #include "base/scoped_observation.h"
+#include "base/test/bind.h"
 #include "base/test/task_environment.h"
+#include "base/threading/thread.h"
 #include "base/win/scoped_com_initializer.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -181,6 +183,7 @@
 
   // Simulate a caption style change from the OS.
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
+  base::RunLoop().RunUntilIdle();
 }
 
 // Verifies that multiple PropertiesChanged events each trigger
@@ -199,6 +202,43 @@
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
+  base::RunLoop().RunUntilIdle();
+}
+
+// Verifies that when the WinRT PropertiesChanged event is delivered on a
+// different thread, OnCaptionStyleUpdated is still dispatched to observers on
+// the sequence that registered the listener.
+TEST_F(NativeThemeWinCaptionTest,
+       EventOnOtherThreadNotifiesOnRegisteringSequence) {
+  TestNativeThemeWin theme;
+  ASSERT_TRUE(fake_statics().has_handler());
+
+  const base::PlatformThreadId main_thread_id =
+      base::PlatformThread::CurrentId();
+  base::PlatformThreadId notify_thread_id;
+
+  MockCaptionObserver observer;
+  EXPECT_CALL(observer, OnCaptionStyleUpdated()).WillOnce([&] {
+    notify_thread_id = base::PlatformThread::CurrentId();
+  });
+  base::ScopedObservation<NativeTheme, NativeThemeObserver> observation(
+      &observer);
+  observation.Observe(NativeTheme::GetInstanceForWeb());
+
+  // Invoke the registered handler from a different thread, as the system may
+  // do for WinRT events.
+  base::Thread other_thread("CaptionEventThread");
+  ASSERT_TRUE(other_thread.Start());
+  base::RunLoop run_loop;
+  other_thread.task_runner()->PostTaskAndReply(
+      FROM_HERE, base::BindLambdaForTesting([&] {
+        EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
+      }),
+      run_loop.QuitClosure());
+  run_loop.Run();
+  other_thread.Stop();
+
+  EXPECT_EQ(main_thread_id, notify_thread_id);
 }
 
 }  // namespace
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/native_theme/native_theme_win_unittest.cc b/ui/native_theme/native_theme_win_unittest.cc
index 0caf26a..33c887a5 100644
--- a/ui/native_theme/native_theme_win_unittest.cc
+++ b/ui/native_theme/native_theme_win_unittest.cc
@@ -13,7 +13,9 @@
 #include <wrl/implements.h>
 
 #include "base/scoped_observation.h"
+#include "base/test/bind.h"
 #include "base/test/task_environment.h"
+#include "base/threading/thread.h"
 #include "base/win/scoped_com_initializer.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -181,6 +183,7 @@
 
   // Simulate a caption style change from the OS.
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
+  base::RunLoop().RunUntilIdle();
 }
 
 // Verifies that multiple PropertiesChanged events each trigger
@@ -199,6 +202,43 @@
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
   EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
+  base::RunLoop().RunUntilIdle();
+}
+
+// Verifies that when the WinRT PropertiesChanged event is delivered on a
+// different thread, OnCaptionStyleUpdated is still dispatched to observers on
+// the sequence that registered the listener.
+TEST_F(NativeThemeWinCaptionTest,
+       EventOnOtherThreadNotifiesOnRegisteringSequence) {
+  TestNativeThemeWin theme;
+  ASSERT_TRUE(fake_statics().has_handler());
+
+  const base::PlatformThreadId main_thread_id =
+      base::PlatformThread::CurrentId();
+  base::PlatformThreadId notify_thread_id;
+
+  MockCaptionObserver observer;
+  EXPECT_CALL(observer, OnCaptionStyleUpdated()).WillOnce([&] {
+    notify_thread_id = base::PlatformThread::CurrentId();
+  });
+  base::ScopedObservation<NativeTheme, NativeThemeObserver> observation(
+      &observer);
+  observation.Observe(NativeTheme::GetInstanceForWeb());
+
+  // Invoke the registered handler from a different thread, as the system may
+  // do for WinRT events.
+  base::Thread other_thread("CaptionEventThread");
+  ASSERT_TRUE(other_thread.Start());
+  base::RunLoop run_loop;
+  other_thread.task_runner()->PostTaskAndReply(
+      FROM_HERE, base::BindLambdaForTesting([&] {
+        EXPECT_HRESULT_SUCCEEDED(fake_statics().SimulatePropertiesChanged());
+      }),
+      run_loop.QuitClosure());
+  run_loop.Run();
+  other_thread.Stop();
+
+  EXPECT_EQ(main_thread_id, notify_thread_id);
 }
 
 }  // namespace
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in NativeThemeWin via off-thread WinRT PropertiesChanged callback

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 browser process of Chromium due to a thread-safety issue with the WinRT ClosedCaptionProperties.PropertiesChanged event handler. The event callback runs on a system Multi-Threaded Apartment (MTA) thread and calls NotifyOnCaptionStyleUpdated directly without dispatching back to the UI thread. This creates a race condition with the UI thread mutating the non-thread-safe base::ObserverList, potentially leading to memory corruption or browser process code execution.

Affected files:

  • ui/native_theme/native_theme_win.cc
  • ui/native_theme/native_theme.cc

Estimated timestamp from git blame: 2026-04-22

Description

A potential Use-After-Free (UAF) vulnerability exists in the Windows browser process of Chromium. The issue lies in NativeThemeWin::RegisterClosedCaptionPropertiesChangedListener() (ui/native_theme/native_theme_win.cc:1119-1149), which registers a WinRT event handler whose body runs directly on the calling thread of the WinRT event source—typically a system MTA threadpool thread—and immediately calls NotifyOnCaptionStyleUpdated() on the Web theme instance:

// ui/native_theme/native_theme_win.cc:1135-1142
EventRegistrationToken token;
hr = caption_statics2_->add_PropertiesChanged(
    Microsoft::WRL::Callback<ABI::Windows::Foundation::IEventHandler<
        IInspectable*>>([](IInspectable*, IInspectable*) -> HRESULT {
      NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated();
      return S_OK;
    }).Get(),
    &token);

In a production release build, the sequence checker inside NotifyOnCaptionStyleUpdated() is compiled out:

// ui/native_theme/native_theme.cc:293-300
void NativeTheme::NotifyOnCaptionStyleUpdated() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Stripped in release builds

  native_theme_observers_.Notify(&NativeThemeObserver::OnCaptionStyleUpdated);
}

The member native_theme_observers_ is a non-thread-safe base::ObserverList<> (ui/native_theme/native_theme.h:598-602), backed by a std::vector of observers (base/observer_list.h:510). The class documentation explicitly states: > “ObserverList is not thread-compatible. Iterating on the same ObserverList simultaneously in different threads is not safe.”

Because this list is mutated on the browser’s UI thread (e.g., when a page rapidly attaches/detaches iframe frames or opens/closes sub-windows, driving WebContentsImpl construction and destruction which registers/unregisters observers), an MTA thread iterating the list via the WinRT callback concurrently with a UI thread reallocating the underlying std::vector triggers a Use-After-Free (UAF).

Potential Attack/Trigger Path

Note: These are potential, suggested steps to trigger the race condition, as our tooling agent doesn’t yet have the ability to run code.

  1. User Interaction: The user is social-engineered to alter accessibility options (e.g., Settings → Accessibility → Captions) while visiting a malicious page.
  2. Page-Driven Observer Churn: The page continuously opens and closes frames or sub-windows, forcing the browser process UI thread to register/deregister WebContentsImpl instances to/from the NativeTheme::GetInstanceForWeb() observer list. This results in frequent memory reallocations of the underlying std::vector.
  3. MTA Thread Notification: The Windows OS raises the PropertiesChanged event, running the registered callback on an MTA thread.
  4. Race Condition and UAF: The MTA thread processes the callback and indexes into the freed/reallocated observer list buffer. It reads a corrupted CheckedObserverAdapter and performs a virtual call to OnCaptionStyleUpdated() on an attacker-influenced pointer, potentially leading to control flow hijacking / Remote Code Execution (RCE) in the browser process.

Suggested Fix

To fix this thread-safety issue, the WinRT callback must hop back to the correct sequence using a task runner before performing any observer notification. This can be securely achieved by wrapping the callback using base::BindPostTask or a sequence task runner.

For instance, capture the current SequencedTaskRunner and post a task:

auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
hr = caption_statics2_->add_PropertiesChanged(
    Microsoft::WRL::Callback<ABI::Windows::Foundation::IEventHandler<
        IInspectable*>>([task_runner](IInspectable*, IInspectable*) -> HRESULT {
      task_runner->PostTask(
          FROM_HERE,
          base::BindOnce([]() {
            NativeTheme::GetInstanceForWeb()->NotifyOnCaptionStyleUpdated();
          }));
      return S_OK;
    }).Get(),
    &token);

Evaluated with Chrome root at commit: d8b226a3be7c9c1ac9240c09e14698866c82e4ac


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