High watch 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 — ub-class only — no demonstrated corruption path
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.
Tracker495247950
Fix commitc52ae89ece58 (chromium/src) +7/-3
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-03-23 (per vendor advisory)
Fix landed2026-04-06 (public git)
Disclosed2026-05-12
Patch gap36 days — fix public in source before the advisory

Background

`BrowserAccessibilityManager`
the browser-process object that holds the cached accessibility tree for a frame and applies incoming accessibility updates/events from the renderer.
`OnAccessibilityEvents`
the method that processes a batch of accessibility events/tree mutations; re-entering it while a prior invocation is still on the stack can mutate or free tree nodes the outer call still holds.
`in_on_accessibility_events_`
a boolean re-entrancy sentinel, set via base::AutoReset<bool> for the duration of event handling; before the patch it existed only under DCHECK_IS_ON().
`DCHECK_IS_ON()` vs `BUILDFLAG(IS_WIN)`
DCHECK_IS_ON() is true only in debug/dcheck builds; BUILDFLAG(IS_WIN) is true in all Windows builds including release/stable.

Root Cause Analysis

Processing a batch of accessibility events in OnAccessibilityEvents can, on Windows, synchronously trigger platform accessibility callbacks that re-enter the manager and run OnAccessibilityEvents again while the outer call is still iterating; the outer frame then continues to use tree nodes the nested call has mutated or destroyed, which is the use-after-free. The re-entrancy sentinel in_on_accessibility_events_ and its base::AutoReset guard already existed, but only compiled under DCHECK_IS_ON(), so shipping (release) Windows builds carried no re-entrancy detection at all.

The patch adds an always-on CHECK(!in_on_accessibility_events_) under BUILDFLAG(IS_WIN) at method entry and compiles the sentinel and its AutoReset whenever BUILDFLAG(IS_WIN) || DCHECK_IS_ON(). This converts an undefined-behavior re-entrant UAF on stable Windows into a deterministic, safe abort before the nested processing can corrupt state. It is a detect-and-crash mitigation that tightens enforcement rather than a redesign that removes the re-entrancy itself.

Key insight
The re-entrancy guard that prevents reentering OnAccessibilityEvents was debug-only (DCHECK_IS_ON()), leaving stable Windows unprotected; the fix enables an always-on CHECK plus the sentinel on BUILDFLAG(IS_WIN), so a re-entrant call aborts safely instead of proceeding into a use-after-free.

Attack Path

  1. Deliver crafted accessibility updates A compromised or malicious renderer sends a batch of accessibility events that the browser processes in OnAccessibilityEvents (inferred: renderer control of the event stream).
  2. Provoke synchronous re-entry On Windows, handling those events invokes platform accessibility APIs that synchronously call back into the manager, re-entering OnAccessibilityEvents while the first invocation is still active.
  3. Use freed/mutated nodes The nested invocation mutates or frees accessibility tree nodes that the suspended outer invocation continues to reference, producing the use-after-free described by the advisory.
  4. Post-patch behavior With the change, the entry CHECK(!in_on_accessibility_events_) fails on the nested call and aborts the process before the dangling access, turning the UAF into a controlled crash on Windows.

Impact Assessment

The advisory classifies this as a use after free in Accessibility, rated high. Exploitation requires driving the browser-process accessibility pipeline into re-entrant OnAccessibilityEvents processing on Windows (reachable from renderer-supplied accessibility events), and a successful UAF would occur in the browser process; the patch only adds a detect-and-abort guard, so any escalation from the freed-memory access to code execution is not demonstrated by the diff and is (inferred). No exploited-in-the-wild status is stated in the provided advisory.

Files Changed

  • ui/accessibility/platform/browser_accessibility_manager.cc
  • ui/accessibility/platform/browser_accessibility_manager.h

Audit Directions

  • Debug-only re-entrancy/state guards
    Grep for sentinels and AutoReset/CHECK logic gated solely on DCHECK_IS_ON(); any that defend against re-entrant mutation of shared/owned state leave release builds unprotected.
  • Synchronous platform callbacks during tree mutation
    Audit browser-process code (accessibility, a11y platform integration) that calls OS APIs while iterating or mutating a cached tree, since those APIs can synchronously re-enter and invalidate iterators/nodes.
  • Renderer-driven event batches
    Review handlers that process batched, renderer-supplied events for assumptions of non-re-entrancy and for node lifetime across callbacks within a single batch.
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()
 
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • ui/accessibility/platform/browser_accessibility_manager_win.cc
  • ui/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

  1. 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, the TargetedEvent struct explicitly marks its reference as raw_ref<const EventParams, DanglingUntriaged>, opting out of BRP protection.
  2. DCHECKs: BrowserAccessibilityManager contains a boolean flag in_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).

  1. 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.
  2. Trigger IPC: The compromised renderer sends an AXTreeUpdate via the HandleAXEvents Mojo IPC, crafting it so that a target node contains the currently focused node as a descendant (satisfying the condition at line 599).
  3. Nested Message Loop: The browser UI thread enters FireGeneratedEvent and calls a synchronous Windows API (e.g., ::NotifyWinEvent), spinning up a nested message loop.
  4. Re-entrant IPC: While the UI thread is in the nested loop, the renderer sends a second, distinct HandleAXEvents IPC.
  5. Free Memory: The nested loop dequeues the second IPC, re-enters OnAccessibilityEvents, and eventually calls ClearEvents(), freeing the std::map nodes used by the first invocation’s iterator.
  6. Heap Spray / Reclaim: The attacker uses parallel WebWorkers or other IPC channels to spray the browser process heap, reclaiming the freed _Rb_tree_node memory with controlled data.
  7. 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.

View on issue tracker