CVE-2026-9905
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/accessibility/platform/ax_platform_node_textchildprovider_win.cc |
modified | |
COMPONENT_EXPORTui/accessibility/platform/ax_platform_node_textchildprovider_win.h |
modified | |
ifui/accessibility/platform/ax_platform_node_textprovider_win.cc |
modified | |
COMPONENT_EXPORTui/accessibility/platform/ax_platform_node_textprovider_win.h |
modified | |
COMPONENT_EXPORTui/accessibility/platform/ax_platform_node_textrangeprovider_win.h |
modified |
Files Changed
chrome/browser/accessibility/leaked_node_detection_browsertest.ccui/accessibility/platform/ax_platform_node_textchildprovider_win.ccui/accessibility/platform/ax_platform_node_textchildprovider_win.hui/accessibility/platform/ax_platform_node_textprovider_win.ccui/accessibility/platform/ax_platform_node_textprovider_win.hui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
Patch
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"))
Regression Test / PoC
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()));
Original Bug Report
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.ccui/accessibility/ax_tree_manager_map.hui/accessibility/ax_tree_manager_map.ccui/accessibility/platform/sequence_affine_com_object_root_win.hui/accessibility/ax_tree_manager.ccui/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:
SetStart(null)setsstart_toAXTreeIDUnknown().SetEnd(null)evaluates whether to callRemoveObserver(end_->tree_id()). The checkend_->tree_id() != start_->tree_id()evaluates totruebecausestart_was just nulled. This erroneously triggersRemoveObserverfor theend_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.
- Compromise a renderer process.
- Execute a loop rapidly creating and destroying iframes to trigger UI thread map mutations and rehashes.
- Spray the browser heap to reclaim freed
absl::flat_hash_mapbacking arrays with fakeraw_ptr<AXTreeManager>entries. - Induce a crash in an out-of-process UIA client to trigger COM fast rundown on the RPC thread.
- The RPC thread reads the attacker’s fake pointer and accesses a fake
AXTree. - Use the
ObserverList::RemoveObserverprimitive to corrupt a function pointer and execute arbitrary shellcode.
Suggested Fix
- Remove IFastRundown: The safest fix is to remove
IFastRundownfromAXPlatformNodeTextRangeProviderWin. If an object is strictly tied to an STA (UI thread), it must be cleaned up on that thread, even if it delays rundown. - Enforce Sequence Checks: Upgrade the
DCHECKinSequenceAffineComObjectRootto aCHECKto ensure COM objects are never destroyed off-sequence in release builds. - Thread-Safe Map: If cross-thread access to
AXTreeManagerMapis truly required, synchronize access using anabsl::Mutexor 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.