High watch ✔︎ verified CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Accessibility
DescriptionUse after free in Accessibility
ComponentAccessibility
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable — factors set by independent review by AI
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.
Independent review by AIConfirmed
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker498883610
Fix commit52f2425c43f2 (chromium/src) +66/-77
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-02 (per vendor advisory)
Fix landed2026-05-26 (public git)
Disclosed2026-05-27
Patch gap1 day — fix public in source before the advisory

Background

`SequenceAffineComObjectRoot`
a COM object base used by Windows accessibility providers that is affine to one sequence (the UI thread), checking sequence affinity with DCHECK_CALLED_ON_VALID_SEQUENCE.
`IFastRundown`
a COM interface that signals the COM runtime to run down (release) references faster when an out-of-process client disappears; pre-fix it was implemented by the accessibility COM classes (ax_platform_node_textchildprovider_win.h:18, :22).
COM fast rundown
when IFastRundown is present, COM may bypass single-threaded-apartment marshaling and call Release() (and thus the destructor) directly on an RPC dispatch thread rather than the UI thread.
`AXTreeManagerMap`
a process-global, unsynchronized map of accessibility tree managers accessed during provider destruction. (described by reporter; not in shown source)

Root Cause Analysis

The accessibility COM provider classes derived from SequenceAffineComObjectRoot and additionally implemented IFastRundown, e.g. AXPlatformNodeTextChildProviderWin : public SequenceAffineComObjectRoot, public ITextChildProvider, public IFastRundown with COM_INTERFACE_ENTRY(IFastRundown) (ax_platform_node_textchildprovider_win.h:16-22), and the same pattern in AXPlatformNodeTextProviderWin (ax_platform_node_textprovider_win.h:18-25), plus AXPlatformNodeTextRangeProviderWin, AXPlatformNodeWin, and AXPlatformRelationWin (shown in the diff). These classes are designed to live and be destroyed on the UI thread, enforced only by a DCHECK_CALLED_ON_VALID_SEQUENCE that is compiled out of release builds (per reporter). Because IFastRundown lets COM bypass STA marshaling, a provider’s destructor can run on an RPC dispatch thread, so destruction-time work that touches the process-global, unsynchronized AXTreeManagerMap races with UI-thread mutations of that map. The invariant that these sequence-affine objects are only destroyed on their owning sequence was therefore broken by the presence of IFastRundown.

The fix is a revert that removes IFastRundown from the base-class list and the COM_INTERFACE_ENTRY(IFastRundown) from the COM map of all five classes (diff hunks across ax_platform_node_textchildprovider_win.h, ax_platform_node_textprovider_win.h, ax_platform_node_textrangeprovider_win.h, ax_platform_node_win.h, ax_platform_relation_win.h); the commit message states “IFastRundown is incompatible with SequenceAffineComObjectRoot, as it allows dtors to run off the UI thread.” With IFastRundown gone, COM reverts to STA marshaling so these objects are destroyed back on the UI thread, removing the off-thread destruction that enabled the race. The CreateIUnknown edits are an incidental cleanup, and the downstream map/observer corruption steps described by the reporter are (inferred) since that code is not shown here.

Key insight
Implementing IFastRundown on sequence-affine COM objects let COM destroy them off the UI thread, racing with UI-thread accessibility state; the fix removes IFastRundown so destruction returns to the owning thread.

Attack Path

  1. Hold a reference from an out-of-process client A UIA/accessibility client holds COM references to the Windows accessibility providers. (trigger per reporter)
  2. Terminate the client to trigger fast rundown When the client dies, COM, seeing IFastRundown, runs Release() and the destructor on an RPC dispatch thread instead of the UI thread (ax_platform_node_textprovider_win.h:20, pre-fix).
  3. Race against UI-thread map mutation A compromised renderer rapidly mutates accessibility state so the UI thread modifies AXTreeManagerMap while the RPC-thread destructor reads it, causing a use-after-free on the map’s backing storage. (inferred; map code not shown)
  4. Corruption primitive The attacker reclaims the freed storage to control pointers read during destruction, yielding memory corruption. (inferred)

Impact Assessment

A compromised renderer, combined with terminating an out-of-process accessibility client on Windows, could provoke destruction of sequence-affine COM objects off the UI thread and race with UI-thread access to a shared accessibility map, producing a use-after-free in the browser process. The advisory rates this high severity with no in-the-wild exploitation; escalation to a sandbox escape is (inferred).

Changed Functions

FunctionChangeNotes
if
ui/accessibility/platform/ax_platform_node_textchildprovider_win.cc
modified
COMPONENT_EXPORT
ui/accessibility/platform/ax_platform_node_textchildprovider_win.h
modified
if
ui/accessibility/platform/ax_platform_node_textprovider_win.cc
modified
COMPONENT_EXPORT
ui/accessibility/platform/ax_platform_node_textprovider_win.h
modified
COMPONENT_EXPORT
ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
modified

Files Changed

  • chrome/browser/accessibility/leaked_node_detection_browsertest.cc
  • ui/accessibility/platform/ax_platform_node_textchildprovider_win.cc
  • ui/accessibility/platform/ax_platform_node_textchildprovider_win.h
  • ui/accessibility/platform/ax_platform_node_textprovider_win.cc
  • ui/accessibility/platform/ax_platform_node_textprovider_win.h
  • ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h

Audit Directions

  • Thread-affinity vs. COM rundown interfaces
    Never combine IFastRundown (or any interface permitting off-apartment release) with objects whose lifetime is pinned to a single sequence; destruction must honor the affinity.
  • Release-stripped affinity checks
    A DCHECK_CALLED_ON_VALID_SEQUENCE guarding a destructor gives no protection in release builds; enforce with CHECK or by design when the violation is a memory-safety issue.
  • Unsynchronized process-global containers
    Audit any process-global map/container reachable from both UI-thread mutations and background-thread callbacks for the possibility of concurrent access during rehash/resize.
From 52f2425c43f2ca0091b7b28f5aadb2cdffff25a8 Mon Sep 17 00:00:00 2001
From: Greg Thompson <[email protected]>
Date: Tue, 26 May 2026 08:48:26 -0700
Subject: [PATCH] [M148] Revert "Reland "[A11yPerformance] Enable fast rundown for accessibility objects""

Original change's description:
> Revert "Reland "[A11yPerformance] Enable fast rundown for accessibility objects""
>
> This reverts commit 638a472eef8aefbfb4963cab584dfb49a3d45c7e.
>
> Reason for revert: IFastRundown is incompatible with SequenceAffineComObjectRoot, as it allows dtors to run off the UI thread.
>
> Original change's description:
> > Reland "[A11yPerformance] Enable fast rundown for accessibility objects"
> >
> > This is a reland of commit 723fef8a40d4e3a3c32a3d065ae230fea3bb77ee.
> > LeakedNodeDetectionBrowsertest.TerminateClient is disabled due to
> > failures on some bots.
> >
> > Original change's description:
> > > [A11yPerformance] Enable fast rundown for accessibility objects
> > >
> > > Chrome vends accessibility objects to an assistive technology tool via
> > > COM interface pointers. COM machinery is responsible for releasing
> > > outstanding references held by an out-of-process client in the event
> > > that said client terminates. This "rundown" processing may take on the
> > > order of minutes.
> > >
> > > In this CL, we add IFastRundown to the set of interfaces implemented by
> > > our COM classes. This is a signal to COM that we would like rundown to
> > > happen more quickly. Local testing shows that rundown happens after
> > > approximately ten seconds rather than six minutes.
> > >
> > > BYPASS_RECITATION_REASON=False positive; only indentation is changing.
> > >
> > > AX-Relnotes: n/a.
> > > Bug: 443107137
> > > Change-Id: Ifacacc3e69fa34a296be513a71528529fe40095c
> > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6917710
> > > Auto-Submit: Greg Thompson <[email protected]>
> > > Reviewed-by: Benjamin Beaudry <[email protected]>
> > > Commit-Queue: Greg Thompson <[email protected]>
> > > Cr-Commit-Position: refs/heads/main@{#1514777}
> >
> > BYPASS_RECITATION_REASON=False positive; only indentation is changing.
> >
> > AX-Relnotes: n/a.
> > Bug: 443107137
> > Change-Id: Id45749d9f73ce4e327bc73e6c7b89c00fa50eb3f
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6943297
> > Auto-Submit: Greg Thompson <[email protected]>
> > Reviewed-by: Benjamin Beaudry <[email protected]>
> > Commit-Queue: Benjamin Beaudry <[email protected]>
> > Cr-Commit-Position: refs/heads/main@{#1518866}
>
> Bug: 443107137
> Fixed: 498883610
> Change-Id: I33ad27037b9dabdf7366b4f4393e172ac3cce8e6
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7785869
> Auto-Submit: Greg Thompson <[email protected]>
> Reviewed-by: Benjamin Beaudry <[email protected]>
> Commit-Queue: Benjamin Beaudry <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#1618915}

(cherry picked from commit 1f09265853ad1fb13ab57f0c573eb32bf87bc05e)

Bug: 514923707,443107137,498883610
Change-Id: I33ad27037b9dabdf7366b4f4393e172ac3cce8e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7876281
Reviewed-by: Benjamin Beaudry <[email protected]>
Reviewed-by: Greg Thompson <[email protected]>
Auto-Submit: chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
Commit-Queue: Benjamin Beaudry <[email protected]>
Cr-Commit-Position: refs/branch-heads/7778@{#3756}
Cr-Branched-From: 77f495ee216d4c3cc784d33658bad4778c0680ee-refs/heads/main@{#1610480}
---

diff --git a/chrome/browser/accessibility/leaked_node_detection_browsertest.cc b/chrome/browser/accessibility/leaked_node_detection_browsertest.cc
index 9a196a8..e3f5dc0 100644
--- a/chrome/browser/accessibility/leaked_node_detection_browsertest.cc
+++ b/chrome/browser/accessibility/leaked_node_detection_browsertest.cc
@@ -70,14 +70,21 @@
   WaitForNodeCounts({0U, 0U, 0U, 0U});
 }
 
-// TODO(crbug.com/443107137): Disabled due to flaky failures. It seems that not
-// all machines perform rundown quckily upon disappearance of the UiaClient
-// despite all classes implementing IFastRundown. Run this test with
-// --test-launcher-timeout=3600000 --ui-test-action-max-timeout=3600000
-// --ui-test-action-timeout=3600000 --single-process-tests to set the various
-// test timeouts longer than the approx six minute slow rundown time.
+// Disabled due to flaky failures. It seems that not all machines perform
+// rundown quckily upon disappearance of the client.
 IN_PROC_BROWSER_TEST_P(LeakedNodeDetectionBrowsertest,
                        DISABLED_TerminateClient) {
+  if (client_api() == ClientApi::kIAccessible2) {
+    // When MSAA is in use, it can take six minutes for the final references on
+    // the stubs issued by the accessible process to be released after
+    // termination of the client. This is far greater than the normal test
+    // timeout. Run this test with --test-launcher-timeout=3600000
+    // --ui-test-action-max-timeout=3600000 --ui-test-action-timeout=3600000 to
+    // see that it eventually passes.
+    GTEST_SKIP() << "Termination of an IA2 client with normal "
+                    "test timeouts causes leaks";
+  }
+
   // Initialize the UI Automation client; giving it this window.
   ASSERT_HRESULT_SUCCEEDED(InitializeClient(browser()));
 
diff --git a/ui/accessibility/platform/ax_platform_node_textchildprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textchildprovider_win.cc
index 97d8fe4..8278da8 100644
--- a/ui/accessibility/platform/ax_platform_node_textchildprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textchildprovider_win.cc
@@ -53,12 +53,8 @@
     IUnknown** unknown) {
   Microsoft::WRL::ComPtr<AXPlatformNodeTextChildProviderWin>
       text_child_provider(Create(owner));
-  if (!text_child_provider) {
-    *unknown = nullptr;
-    return;
-  }
-  ITextChildProvider* provider_ptr = text_child_provider.Detach();
-  *unknown = provider_ptr;
+  if (text_child_provider)
+    *unknown = text_child_provider.Detach();
 }
 
 HRESULT AXPlatformNodeTextChildProviderWin::get_TextContainer(
diff --git a/ui/accessibility/platform/ax_platform_node_textchildprovider_win.h b/ui/accessibility/platform/ax_platform_node_textchildprovider_win.h
index 78f8d9e..dbc628e 100644
--- a/ui/accessibility/platform/ax_platform_node_textchildprovider_win.h
+++ b/ui/accessibility/platform/ax_platform_node_textchildprovider_win.h
@@ -14,12 +14,10 @@
 namespace ui {
 class COMPONENT_EXPORT(AX_PLATFORM) AXPlatformNodeTextChildProviderWin
     : public SequenceAffineComObjectRoot,
-      public ITextChildProvider,
-      public IFastRundown {
+      public ITextChildProvider {
  public:
   BEGIN_COM_MAP(AXPlatformNodeTextChildProviderWin)
   COM_INTERFACE_ENTRY(ITextChildProvider)
-  COM_INTERFACE_ENTRY(IFastRundown)
   END_COM_MAP()
 
   AXPlatformNodeTextChildProviderWin();
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
index 7849e7bb..86c66f52 100644
--- a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
@@ -47,12 +47,8 @@
                                                    IUnknown** unknown) {
   Microsoft::WRL::ComPtr<AXPlatformNodeTextProviderWin> text_provider(
       Create(owner));
-  if (!text_provider) {
-    *unknown = nullptr;
-    return;
-  }
-  ITextEditProvider* provider_ptr = text_provider.Detach();
-  *unknown = provider_ptr;
+  if (text_provider)
+    *unknown = text_provider.Detach();
 }
 
 //
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win.h b/ui/accessibility/platform/ax_platform_node_textprovider_win.h
index 0c78e94..c57e6ebe 100644
--- a/ui/accessibility/platform/ax_platform_node_textprovider_win.h
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win.h
@@ -13,16 +13,14 @@
 
 namespace ui {
 
-class COMPONENT_EXPORT(AX_PLATFORM)
-    __declspec(uuid("3e1c192b-4348-45ac-8eb6-4b58eeb3dcca"))
-    AXPlatformNodeTextProviderWin : public SequenceAffineComObjectRoot,
-                                    public ITextEditProvider,
-                                    public IFastRundown {
+class COMPONENT_EXPORT(AX_PLATFORM) __declspec(
+    uuid("3e1c192b-4348-45ac-8eb6-4b58eeb3dcca")) AXPlatformNodeTextProviderWin
+    : public SequenceAffineComObjectRoot,
+      public ITextEditProvider {
  public:
   BEGIN_COM_MAP(AXPlatformNodeTextProviderWin)
   COM_INTERFACE_ENTRY(ITextProvider)
   COM_INTERFACE_ENTRY(ITextEditProvider)
-  COM_INTERFACE_ENTRY(IFastRundown)
   COM_INTERFACE_ENTRY(AXPlatformNodeTextProviderWin)
   END_COM_MAP()
 
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
index b5769710..291e40d7 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -19,15 +19,13 @@
 #include "ui/accessibility/platform/sequence_affine_com_object_root_win.h"
 
 namespace ui {
-class COMPONENT_EXPORT(AX_PLATFORM)
-    __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/accessibility/leaked_node_detection_browsertest.cc b/chrome/browser/accessibility/leaked_node_detection_browsertest.cc
index 9a196a8..e3f5dc0 100644
--- a/chrome/browser/accessibility/leaked_node_detection_browsertest.cc
+++ b/chrome/browser/accessibility/leaked_node_detection_browsertest.cc
@@ -70,14 +70,21 @@
   WaitForNodeCounts({0U, 0U, 0U, 0U});
 }
 
-// TODO(crbug.com/443107137): Disabled due to flaky failures. It seems that not
-// all machines perform rundown quckily upon disappearance of the UiaClient
-// despite all classes implementing IFastRundown. Run this test with
-// --test-launcher-timeout=3600000 --ui-test-action-max-timeout=3600000
-// --ui-test-action-timeout=3600000 --single-process-tests to set the various
-// test timeouts longer than the approx six minute slow rundown time.
+// Disabled due to flaky failures. It seems that not all machines perform
+// rundown quckily upon disappearance of the client.
 IN_PROC_BROWSER_TEST_P(LeakedNodeDetectionBrowsertest,
                        DISABLED_TerminateClient) {
+  if (client_api() == ClientApi::kIAccessible2) {
+    // When MSAA is in use, it can take six minutes for the final references on
+    // the stubs issued by the accessible process to be released after
+    // termination of the client. This is far greater than the normal test
+    // timeout. Run this test with --test-launcher-timeout=3600000
+    // --ui-test-action-max-timeout=3600000 --ui-test-action-timeout=3600000 to
+    // see that it eventually passes.
+    GTEST_SKIP() << "Termination of an IA2 client with normal "
+                    "test timeouts causes leaks";
+  }
+
   // Initialize the UI Automation client; giving it this window.
   ASSERT_HRESULT_SUCCEEDED(InitializeClient(browser()));
Loading diff…

Original Bug Report

reported by [email protected]

Potential Sandbox Escape via UAF in AXTreeManagerMap due to IFastRundown 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 without the security team.

Overview: A race condition exists between COM’s IFastRundown mechanism on an RPC thread and UI thread mutations to the process-global AXTreeManagerMap. This leads to a Use-After-Free (UAF) on the map’s backing array, bypassing MiraclePtr (BRP) and allowing a compromised renderer to achieve arbitrary memory corruption and potential sandbox escape.

Affected files:

  • ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
  • ui/accessibility/ax_tree_manager_map.h
  • ui/accessibility/ax_tree_manager_map.cc
  • ui/accessibility/platform/sequence_affine_com_object_root_win.h
  • ui/accessibility/ax_tree_manager.cc
  • ui/accessibility/ax_tree_manager.h

Estimated timestamp from git blame: 2023-06-23

Background

AXPlatformNodeTextRangeProviderWin is a COM class used to provide text range information to UI Automation (UIA) clients on Windows. It inherits from SequenceAffineComObjectRoot and CComSingleThreadModel, indicating it is designed to run on the UI thread (an STA). To reduce latency during out-of-process client crashes, it implements IFastRundown.

The Vulnerability

When an out-of-process UIA client terminates abruptly, the COM runtime initiates a fast rundown. Because IFastRundown is implemented, COM bypasses STA marshaling and calls Release() directly on an RPC dispatch thread.

SequenceAffineComObjectRoot::InternalRelease() asserts it is on the correct sequence via DCHECK_CALLED_ON_VALID_SEQUENCE. However, because DCHECKs are removed in release builds, the sequence violation is ignored, and the object is destroyed on the RPC thread.

During destruction, ~TextRangeEndpoints() sets its start and end positions to null sequentially:

  1. SetStart(null) sets start_ to AXTreeIDUnknown().
  2. SetEnd(null) evaluates whether to call RemoveObserver(end_->tree_id()). The check end_->tree_id() != start_->tree_id() evaluates to true because start_ was just nulled. This erroneously triggers RemoveObserver for the end_ tree ID on the RPC thread.

RemoveObserver calls AXTreeManager::FromID(), which accesses the process-global, unsynchronized AXTreeManagerMap (an absl::flat_hash_map<AXTreeID, raw_ptr<AXTreeManager>>).

The Race Condition and BRP Bypass

Concurrently, a compromised renderer can rapidly create and destroy <iframe> elements, forcing the browser to insert and remove entries in the AXTreeManagerMap on the UI thread. This causes the absl::flat_hash_map to frequently rehash, allocating new backing slot arrays and freeing old ones.

If the RPC thread executes map_.find() while the UI thread is rehashing the map, the RPC thread will read from a freed slot array.

Crucially, MiraclePtr (BRP) does not prevent exploitation here. The UAF occurs on the container’s backing array (which holds the raw_ptr bytes), not the AXTreeManager object itself. An attacker can reclaim the freed slot array and directly overwrite the 8 bytes of the raw_ptr, bypassing BRP quarantine entirely.

Exploitation Primitive

The attacker controls the pointer returned by GetManager. The code proceeds to call ax_tree_manager->ax_tree()->RemoveObserver(this); using the fake AXTreeManager. This invokes ObserverList::RemoveObserver on an attacker-controlled fake AXTree object.

By carefully crafting the fake ObserverList memory structure (e.g., controlling observers_ vector pointers and the live_iterators_ linked list), the attacker can force ObserverList to execute it->MarkForRemoval() or observers_.erase(it) on arbitrary addresses. This provides powerful arbitrary decrement, arbitrary null-write, or arbitrary memory move primitives, which can be leveraged to hijack control flow and escape the sandbox.

Potential Attacker Steps

Note: These steps are theoretical, as our tooling agent cannot execute code.

  1. Compromise a renderer process.
  2. Execute a loop rapidly creating and destroying iframes to trigger UI thread map mutations and rehashes.
  3. Spray the browser heap to reclaim freed absl::flat_hash_map backing arrays with fake raw_ptr<AXTreeManager> entries.
  4. Induce a crash in an out-of-process UIA client to trigger COM fast rundown on the RPC thread.
  5. The RPC thread reads the attacker’s fake pointer and accesses a fake AXTree.
  6. Use the ObserverList::RemoveObserver primitive to corrupt a function pointer and execute arbitrary shellcode.

Suggested Fix

  1. Remove IFastRundown: The safest fix is to remove IFastRundown from AXPlatformNodeTextRangeProviderWin. If an object is strictly tied to an STA (UI thread), it must be cleaned up on that thread, even if it delays rundown.
  2. Enforce Sequence Checks: Upgrade the DCHECK in SequenceAffineComObjectRoot to a CHECK to ensure COM objects are never destroyed off-sequence in release builds.
  3. Thread-Safe Map: If cross-thread access to AXTreeManagerMap is truly required, synchronize access using an absl::Mutex or base locking mechanism.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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