CVE-2026-7984
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/renderer/accessibility/read_anything/read_anything_app_model.cc |
modified |
Files Changed
chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
Patch
From b30d69ffbdea747bd420ac064c2bbbd7ac4a1951 Mon Sep 17 00:00:00 2001
From: Lauren Winston <lwinston@google.com>
Date: Mon, 06 Apr 2026 11:25:54 -0700
Subject: [PATCH] Return early and destroy the tree if Unserialize returns false.
This means that there is invalid state and the tree should
be destroyed.
Fixed: 498277368
Change-Id: Ie9c4045a87453fc23d1ae226a1d1e69f638a2e6b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7728849
Reviewed-by: Eitan Goldberger <eitang@google.com>
Commit-Queue: Lauren Winston <lwinston@google.com>
Cr-Commit-Position: refs/heads/main@{#1610371}
---
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
index 1ae4917..6475ce9 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
@@ -573,7 +573,14 @@
VLOG(1) << "Unserializing an update with a known tree ID: "
<< update.tree_data.tree_id;
}
- tree->Unserialize(update);
+ // If tree->Unserialize returns false, there is invalid state and the tree
+ // should be destroyed.
+ const bool unserialized = tree->Unserialize(update);
+ DUMP_WILL_BE_CHECK(unserialized);
+ if (!unserialized) {
+ OnAXTreeDestroyed(tree_id);
+ return;
+ }
}
// Set URL info if it hasn't already been set.
Original Bug Report
Potential Use-After-Free in Read Anything WebUI via unchecked AXTree::Unserialize
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: ReadAnythingAppModel::UnserializeUpdates ignores the return value of AXTree::Unserialize. A compromised renderer can send malformed accessibility updates with node ID 0 to force an ID collision, resulting in a dangling pointer being stored in the tree, bypassing MiraclePtr.
Affected files:
chrome/renderer/accessibility/read_anything/read_anything_app_model.ccui/accessibility/ax_tree.cc
Estimated timestamp from git blame: 2025-06-25
Summary
A potential Use-After-Free (UAF) vulnerability exists in the Read Anything WebUI renderer. ReadAnythingAppModel::UnserializeUpdates fails to check the return value of ui::AXTree::Unserialize(). A compromised content renderer can exploit this by sending a sequence of malformed AXTreeUpdate messages containing the invalid node ID 0 (ui::kInvalidAXNodeID). This triggers an ID collision in AXTree::CreateNode, causing it to return a raw pointer to a node that is immediately destroyed. The dangling pointer is stored in the tree, leading to a UAF that could be leveraged for Remote Code Execution (RCE) in the privileged WebUI process.
Technical Details
- Unchecked Return Value: In
ReadAnythingAppModel::UnserializeUpdates, the return value oftree->Unserialize(update)is ignored. If an update fails mid-way, theAXTreeis left in a partially updated, inconsistent state. - Invalid ID Bypass: An attacker sends an update specifying a child with
id: 0. InAXTree::CreateNode(0),SANITIZER_CHECK(id != kInvalidAXNodeID)is used. In official release builds,SANITIZER_CHECKis a no-op, allowing node ID 0 to be processed and inserted intoid_map_[0]. - ID Collision and Dangling Pointer: The attacker sends a second update referencing
id: 0.AXTree::GetFromId(0)has a hardcoded fast-path that returnsnullptrforkInvalidAXNodeID, bypassing theid_map_. Thus, the tree believes node 0 does not exist and callsCreateNode(0)again. - Inside
CreateNode(0), a newAXNodeis allocated viastd::make_unique. It attempts to insert it usingid_map_.try_emplace(0, std::move(node)). Since0is already in the map from the first update,try_emplacefails. Under C++17, theunique_ptrretains ownership. CreateNodeextracts the raw pointer (node.get()) and returns it. As the function exits, theunique_ptrgoes out of scope, immediately freeing the newly allocatedAXNode.- MiraclePtr Bypass: The returned dangling raw pointer is then appended to the parent’s children vector, which stores
raw_ptr<AXNode>. Because the node was completely freed before theraw_ptrwas constructed, the BackupRefPtr (BRP) refcount was 0 at the time of free. In release builds where BRP cookie checks are disabled, creating araw_ptrto this freed memory simply increments the refcount in the freed slot’s metadata, successfully creating araw_ptrto attacker-reclaimable memory without crashing. - The attacker can then use heap spraying to reclaim this memory with a controlled payload. A third
AXTreeUpdatecan trigger operations (likeDestroySubtree) on this dangling pointer, leading to arbitrary memory read/write or control-flow hijacking.
Suggested Attacker Steps
(Note: These are potential steps based on code analysis; our tooling agent cannot execute code to verify a working exploit)
- Compromise a content renderer process.
- Wait for or trick the user into opening the “Read Anything” side panel for the compromised page.
- Send Update 1:
{root_id: 5, nodes: [{id: 5, child_ids: [0]}]}to the WebUI renderer. This populatesid_map_[0]. The update fails later, but the state persists. - Send Update 2:
{nodes: [{id: 5, child_ids: [0]}]}. This triggersCreateNode(0)again, which failstry_emplace, frees the node, and stores the dangling pointer in node 5’s children. - Spray the WebUI renderer’s heap to overwrite the freed
AXNodeslot with a counterfeit object. - Send Update 3:
{nodes: [{id: 5, child_ids: []}]}. This removes node 5’s children, invokingDestroySubtreeon the counterfeit object, triggering the UAF and executing the payload.
Proposed Fix
- Check Return Values: In
ReadAnythingAppModel::UnserializeUpdates, check the return value oftree->Unserialize(). If it fails, explicitly destroy or reset theAXTreeto prevent using a corrupted state. - Prevent Dangling Pointers: In
AXTree::CreateNode,CHECKthattry_emplacesucceeds (CHECK(inserted);is currently aDCHECK). If it fails, do not return the raw pointer of the localunique_ptr. - Strict ID Validation: Upgrade
SANITIZER_CHECK(id != kInvalidAXNodeID)inAXTree::CreateNodeand other critical paths to a fatalCHECKin all builds to completely prevent the ingestion ofkInvalidAXNodeID.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results from 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.