Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Accessibility
DescriptionUse after free in Accessibility
ComponentAccessibility
Bug ClassUAF
Tracker498883610
Fix commit52f2425c43f2 (chromium/src) +66/-77
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

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
From 52f2425c43f2ca0091b7b28f5aadb2cdffff25a8 Mon Sep 17 00:00:00 2001
From: Greg Thompson <grt@chromium.org>
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 <grt@chromium.org>
> > > Reviewed-by: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> > > Commit-Queue: Greg Thompson <grt@chromium.org>
> > > 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 <grt@chromium.org>
> > Reviewed-by: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> > Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> > 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 <grt@chromium.org>
> Reviewed-by: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
> 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 <benjamin.beaudry@microsoft.com>
Reviewed-by: Greg Thompson <grt@chromium.org>
Auto-Submit: chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
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 vm...@google.com

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