CVE-2026-6311
Overview
Files Changed
ui/accessibility/platform/ax_platform_node_win.cc
Patch
From db2a3f8ac9a6c1fa8e8a26c5d34072814f14c6a0 Mon Sep 17 00:00:00 2001 From: Benjamin Beaudry <[email protected]> Date: Fri, 03 Apr 2026 08:57:27 -0700 Subject: [PATCH] [M147][a11y] Fix uninitialized memory in get_columnHeaderCells/get_rowHeaderCells Both get_columnHeaderCells and get_rowHeaderCells allocate a COM array via CoTaskMemAlloc, but report the requested array size (column_header_ids.size()) to the caller instead of the number of successfully populated elements (index). If a node lookup fails mid-iteration, the trailing array slots contain uninitialized memory that the COM marshaller will treat as valid IUnknown* pointers. This CL fixes both methods to return the actual populated count (index), matching the pattern already used by get_targets. (cherry picked from commit c81f01b469c4eae60e312ebe8b1b691d454609e9) Fixed: 498201025 Change-Id: I596745388199d61eef8261fe0ae6e1d3e773f240 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7717598 Reviewed-by: Kevin Babbitt <[email protected]> Commit-Queue: Kevin Babbitt <[email protected]> Auto-Submit: Benjamin Beaudry <[email protected]> Commit-Queue: Benjamin Beaudry <[email protected]> Cr-Original-Commit-Position: refs/heads/main@{#1607938} Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7729125 Cr-Commit-Position: refs/branch-heads/7727@{#2183} Cr-Branched-From: ce01102937348db7b88c8a4257ee4b3ac702eb1a-refs/heads/main@{#1596535} --- diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc index e8c6a971..3685384d 100644 --- a/ui/accessibility/platform/ax_platform_node_win.cc +++ b/ui/accessibility/platform/ax_platform_node_win.cc @@ -4274,7 +4274,7 @@ } } - *n_column_header_cells = static_cast<LONG>(column_header_ids.size()); + *n_column_header_cells = static_cast<LONG>(index); return S_OK; } @@ -4330,7 +4330,7 @@ } } - *n_row_header_cells = static_cast<LONG>(row_header_ids.size()); + *n_row_header_cells = static_cast<LONG>(index); return S_OK; }
Original Bug Report
Potential uninitialized memory use in AXPlatformNodeWin header cell getters
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: The AXPlatformNodeWin::get_columnHeaderCells and get_rowHeaderCells methods allocate an array via CoTaskMemAlloc but may leave elements uninitialized if node lookups fail. The methods incorrectly return the requested array size rather than the successfully populated count, causing the COM marshaller to process uninitialized memory. A compromised renderer could potentially exploit this to escape the sandbox by grooming the OLE heap.
Affected files:
ui/accessibility/platform/ax_platform_node_win.cc
Estimated timestamp from git blame: 2022-02-08
Summary
A potential uninitialized memory vulnerability exists in ui/accessibility/platform/ax_platform_node_win.cc within the AXPlatformNodeWin::get_columnHeaderCells and AXPlatformNodeWin::get_rowHeaderCells methods. If an accessibility node lookup fails during array population, the uninitialized memory allocated by ::CoTaskMemAlloc is returned to the COM caller because the methods return the requested array size instead of the number of successfully populated elements.
Technical Details
Both vulnerable methods follow the exact same pattern. For instance, in get_columnHeaderCells:
- The method retrieves a list of node IDs (
column_header_ids). - It allocates memory using
::CoTaskMemAlloc(column_header_ids.size() * sizeof(IUnknown*)). Crucially,CoTaskMemAllocdoes not zero-initialize the memory. - It iterates over the IDs, looks up the corresponding wrapper, and if successful, calls
QueryInterfaceand increments anindexcounter. - If
GetDelegate()->GetFromNodeID(node_id)returnsnullptr(e.g., due to a missing wrapper from reparenting edge cases), the loop skips the element and does not incrementindex. - The method concludes by setting
*n_column_header_cells = static_cast<LONG>(column_header_ids.size());instead of using theindexvariable.
If any node lookup fails, the resulting array will contain uninitialized bytes at the end, but the output count (*n_column_header_cells) tells the COM marshaller that the entire array is populated with valid IUnknown* pointers.
Potential Exploitation Steps
Note: These are suggested steps based on static analysis. Our tooling agent does not currently have the capability to run code to produce a working Proof of Concept.
An attacker with a compromised renderer process could potentially achieve a sandbox escape (RCE in the browser process) through the following sequence:
- Heap Grooming: The attacker sends
AXTreeUpdateIPC messages containing many nodes with large string attributes (likekNameorkDescription). These are allocated asBSTRobjects in the browser process, which use the same Windows OLE/COM heap asCoTaskMemAlloc. This allows the attacker to fill the heap with fake COM object pointers and fake vtables. - State Corruption: The attacker sends a crafted
AXTreeUpdatedesigned to trigger a reparenting edge case (e.g., exploiting crbug.com/40833630). This creates a state where anAXNodeexists logically in the tree but lacks a corresponding platform wrapper in theid_wrapper_map_. - Table Construction: The attacker constructs a table where the column headers reference the ID of the corrupted node alongside valid node IDs.
- Trigger: An external out-of-process accessibility client (like a screen reader) is induced to query
get_columnHeaderCellson the target table. - Vtable Dispatch: The browser returns the array containing uninitialized, attacker-controlled OLE heap memory. The Windows NDR (Network Data Representation) marshaller iterates over the array based on the inflated count and calls
QueryInterfaceandReleaseon the garbage pointers. This indirect call dereferences the attacker’s fake vtable, hijacking control flow.
Note that MiraclePtr does not protect against this issue, as the uninitialized bytes are processed directly by the OS-level COM marshaller, bypassing PartitionAlloc.
Suggested Fix
Update both get_columnHeaderCells and get_rowHeaderCells to set the output count to the number of successfully processed nodes (index) rather than the requested size.
// ui/accessibility/platform/ax_platform_node_win.cc
// In AXPlatformNodeWin::get_columnHeaderCells (approx line 4288)
- *n_column_header_cells = static_cast<LONG>(column_header_ids.size());
+ *n_column_header_cells = static_cast<LONG>(index);
// In AXPlatformNodeWin::get_rowHeaderCells (approx line 4344)
- *n_row_header_cells = static_cast<LONG>(row_header_ids.size());
+ *n_row_header_cells = static_cast<LONG>(index);
This correctly handles cases where nodes fail to resolve, identical to the pattern already safely used in AXPlatformNodeWin::get_targets.
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.