CVE-2026-8533
Overview
Files Changed
ui/accessibility/platform/browser_accessibility_manager.ccui/accessibility/platform/browser_accessibility_manager.h
Patch
From c52ae89ece584a8ee0bcb57eb703ae450592811d Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto <[email protected]> Date: Mon, 06 Apr 2026 01:10:32 -0700 Subject: [PATCH] Enable CHECK(!in_on_accessiblity_events) when BUILDFLAG(IS_WIN). Bug: 495247950 No-Try: true Change-Id: I3577464e3c716b3b2f48b58ca2c37ea63dc6c0a3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7707532 Reviewed-by: Mitsuru Oshima <[email protected]> Commit-Queue: Takashi Sakamoto <[email protected]> Cr-Commit-Position: refs/heads/main@{#1610239} --- diff --git a/ui/accessibility/platform/browser_accessibility_manager.cc b/ui/accessibility/platform/browser_accessibility_manager.cc index b8e5100..5650ded 100644 --- a/ui/accessibility/platform/browser_accessibility_manager.cc +++ b/ui/accessibility/platform/browser_accessibility_manager.cc @@ -470,9 +470,13 @@ // remove this exclusion. base::ScopedSafetyChecksExclusion scoped_unsafe; -#if DCHECK_IS_ON() +#if BUILDFLAG(IS_WIN) + CHECK(!in_on_accessibility_events_) + << "Should not re-enter OnAccessiblityEvents()"; +#endif // BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_WIN) || DCHECK_IS_ON() base::AutoReset<bool> auto_reset(&in_on_accessibility_events_, true); -#endif // DCHECK_IS_ON() +#endif // BUILDFLAG(IS_WIN) || DCHECK_IS_ON() // Update the cached device scale factor. if (!use_custom_device_scale_factor_for_testing_) diff --git a/ui/accessibility/platform/browser_accessibility_manager.h b/ui/accessibility/platform/browser_accessibility_manager.h index 5ce77548..4561ba3b 100644 --- a/ui/accessibility/platform/browser_accessibility_manager.h +++ b/ui/accessibility/platform/browser_accessibility_manager.h @@ -620,7 +620,7 @@ static bool never_suppress_or_delay_events_for_testing_; // For debug only: True when handling OnAccessibilityEvents. -#if DCHECK_IS_ON() +#if BUILDFLAG(IS_WIN) || DCHECK_IS_ON() bool in_on_accessibility_events_ = false; #endif // DCHECK_IS_ON()
Original Bug Report
Potential UAF in BrowserAccessibilityManager::OnAccessibilityEvents via nested message loop
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential heap use-after-free vulnerability exists in BrowserAccessibilityManager on Windows due to iterator invalidation during synchronous accessibility event firing. If a compromised renderer triggers a re-entrant IPC call during a COM nested message loop, the underlying event map is cleared, leaving the outer loop’s iterators pointing to freed memory. This freed memory is not protected by MiraclePtr, potentially allowing an attacker to escape the renderer sandbox.
Affected files:
ui/accessibility/platform/browser_accessibility_manager.ccui/accessibility/platform/browser_accessibility_manager_win.ccui/accessibility/ax_event_generator.h
Estimated timestamp from git blame: 2025-05-26
Description
A potential Heap-Use-After-Free (UAF) vulnerability exists in the browser process’s accessibility subsystem on Windows. Specifically, BrowserAccessibilityManager::OnAccessibilityEvents iterates over generated events using a standard library iterator that becomes invalidated if re-entry occurs. Re-entry can happen because firing accessibility events on Windows involves synchronous COM calls that can spin a nested message loop. A compromised renderer process can exploit this window to trigger a re-entrant IPC, clearing the underlying event map and leaving the outer loop’s iterators pointing to freed heap memory.
Technical Details
In ui/accessibility/platform/browser_accessibility_manager.cc, the OnAccessibilityEvents method processes generated events in a range-based for loop:
587: for (const auto& targeted_event : event_generator()) {
588: BrowserAccessibility* event_target = GetFromID(targeted_event.node_id);
...
599: if (focus && focus != event_target && focus->IsDescendantOf(event_target)) {
600: FireGeneratedEvent(targeted_event.event_params->event,
601: event_target->node());
602: } else {
603: deferred_events.push_back(targeted_event);
604: }
605: }
The event_generator() returns an AXEventGenerator object. Its begin() method returns an AXEventGenerator::Iterator, which internally maintains std::map::const_iterator and std::set::const_iterator pointing to the dynamically allocated nodes of the tree_events_ map.
On Windows, the virtual FireGeneratedEvent method is implemented in BrowserAccessibilityManagerWin::FireGeneratedEvent. Depending on the event type, this method synchronously calls Windows accessibility APIs such as ::NotifyWinEvent or UIA COM methods (e.g., ::UiaRaiseAutomationEvent).
If an Assistive Technology (AT) client is active, these COM calls can trigger a nested message loop on the browser’s UI thread (which is a Single-Threaded Apartment) to prevent cross-process deadlocks while waiting for the COM call to complete.
During this nested message loop, the UI thread continues to pump messages. If a compromised renderer sends a new blink.mojom.RenderAccessibilityHost::HandleAXEvents IPC, the task is dequeued and executed, leading to a re-entrant call to BrowserAccessibilityManager::OnAccessibilityEvents.
The re-entrant call processes its own events and subsequently reaches line 638:
638: event_generator().ClearEvents();
AXEventGenerator::ClearEvents() calls tree_events_.clear(). This destroys the std::map and explicitly frees all its internal red-black tree nodes (_Rb_tree_node) and associated std::set data.
When the re-entrant call and the nested message loop finish, the original Windows COM call returns, and execution unwinds back to the original for loop at line 587. The loop implicitly calls operator++() on its AXEventGenerator::Iterator. Because the iterator relies on raw std::map node pointers, it accesses the memory that was just freed, resulting in a use-after-free.
Mitigation Bypasses
- MiraclePtr (BackupRefPtr): The primary UAF occurs during the traversal of standard library iterators (
std::map::const_iterator). These iterators use raw pointers internally (e.g.,node->next), which are not protected by MiraclePtr. Furthermore, theTargetedEventstruct explicitly marks its reference asraw_ref<const EventParams, DanglingUntriaged>, opting out of BRP protection. - DCHECKs:
BrowserAccessibilityManagercontains a boolean flagin_on_accessibility_events_intended to catch re-entrancy, but it is strictly guarded by#if DCHECK_IS_ON(). It does not prevent re-entrancy in release builds.
Potential Attack Steps
(Note: The following steps describe a theoretical attack scenario, as the AI agent has not executed a live proof-of-concept).
- Prerequisite: The attacker compromises a renderer process. An assistive technology (like a screen reader) is active, enabling the accessibility tree and Windows COM event routing.
- Trigger IPC: The compromised renderer sends an
AXTreeUpdatevia theHandleAXEventsMojo IPC, crafting it so that a target node contains the currently focused node as a descendant (satisfying the condition at line 599). - Nested Message Loop: The browser UI thread enters
FireGeneratedEventand calls a synchronous Windows API (e.g.,::NotifyWinEvent), spinning up a nested message loop. - Re-entrant IPC: While the UI thread is in the nested loop, the renderer sends a second, distinct
HandleAXEventsIPC. - Free Memory: The nested loop dequeues the second IPC, re-enters
OnAccessibilityEvents, and eventually callsClearEvents(), freeing thestd::mapnodes used by the first invocation’s iterator. - Heap Spray / Reclaim: The attacker uses parallel WebWorkers or other IPC channels to spray the browser process heap, reclaiming the freed
_Rb_tree_nodememory with controlled data. - Exploitation: The original COM call returns, and the outer loop calls
operator++. The iterator traverses the attacker’s forged heap data, granting the attacker an arbitrary memory read/write primitive, which can be escalated to arbitrary code execution (Browser RCE / Sandbox Escape).
Evaluated with Chrome root at commit: 9760e6c70cd33a320713361f17c6dcca85648c0f
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. Please feel free to reach out to me if you have concerns or feedback.