High watch CVSS 8.3 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in Accessibility
DescriptionUninitialized Use in Accessibility
ComponentAccessibility
Sub-componentA11y
Bug ClassUninitialized Memory
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker498201025
Fix commitdb2a3f8ac9a6 (chromium/src) +2/-2
CWECWE-457 (Use of uninitialized)
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-31 (per vendor advisory)
Fix landed2026-04-03 (public git)
Disclosed2026-04-15
Patch gap12 days — fix public in source before the advisory

Background

IAccessible2 table API
the Windows accessibility interface through which assistive-technology clients query a table’s header cells via methods like get_columnHeaderCells and get_rowHeaderCells.
`CoTaskMemAlloc`
the COM allocator used to hand an array of IUnknown* pointers back to an out-of-process client; the callee reports both the buffer and an element count.
COM marshaller
the RPC layer that, given a pointer array and a count, AddRefs and serializes each element across the process boundary, dereferencing every slot the count claims is valid.
Populated-count invariant
the reported element count must equal the number of slots actually written, never the number allocated.

Root Cause Analysis

get_columnHeaderCells and get_rowHeaderCells allocate an output array with CoTaskMemAlloc sized to column_header_ids.size() (respectively row_header_ids.size()), then loop over the header ids resolving each to an accessibility node and writing the corresponding IAccessible2* into the array, incrementing a local index only on success. When a node lookup fails partway through, fewer than size() slots are written, but the code set *n_column_header_cells = static_cast<LONG>(column_header_ids.size()) — the requested size, not the populated index. The trailing, never-written slots hold whatever uninitialized heap bytes CoTaskMemAlloc returned, and the COM marshaller treats each as a valid IUnknown*, calling AddRef and dereferencing it.

The fix reports static_cast<LONG>(index) from both methods, so the marshaller only ever touches slots that were actually populated — the same pattern already used by get_targets.

Key insight
The single mistake is reporting the allocated size instead of the populated count, so uninitialized array slots are exposed as live COM interface pointers; returning index bounds the count to the elements that were actually written.

Attack Path

  1. Enable accessibility a client (screen reader, UIA/IA2 bridge, or a page that turns on accessibility) causes Chromium’s browser process to service IAccessibleTable header-cell queries.
  2. Craft a table with unresolvable headers the attacker page builds an accessible table whose header-id list contains entries that fail node lookup mid-iteration, so index < size().
  3. Invoke the header query the client calls get_columnHeaderCells/get_rowHeaderCells; the method returns the oversized count with uninitialized trailing slots.
  4. Marshaller dereferences garbage the COM marshaller AddRefs each claimed element, dereferencing uninitialized heap memory as an IUnknown*.
  5. Corruption or disclosure depending on the stale bytes, this yields a crash, an information leak, or a controlled pointer dereference in the browser process.

Impact Assessment

An attacker gains an uninitialized-memory use in the privileged browser process, where accessibility runs, turning stale heap contents into IUnknown* dereferences that can crash, leak memory, or be steered toward a controlled call. Preconditions are that accessibility is active and the page can present a table whose header lookups fail mid-iteration; no renderer compromise is required, but the payoff depends on grooming the freshly allocated heap slots.

Files Changed

  • ui/accessibility/platform/ax_platform_node_win.cc

Audit Directions

  • Allocated-size vs written-count
    audit every COM/array-returning method that allocates to a collection’s .size() but fills conditionally in a loop; the reported count must be the write index.
  • Failible lookups inside fill loops
    flag loops that skip elements on lookup failure yet size the output to the input list, leaving uninitialized tail slots.
  • `CoTaskMemAlloc` out-params
    review all accessibility interfaces returning IUnknown* arrays (headers, relations, selections) against the get_targets populated-count pattern.
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;
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

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:

  1. The method retrieves a list of node IDs (column_header_ids).
  2. It allocates memory using ::CoTaskMemAlloc(column_header_ids.size() * sizeof(IUnknown*)). Crucially, CoTaskMemAlloc does not zero-initialize the memory.
  3. It iterates over the IDs, looks up the corresponding wrapper, and if successful, calls QueryInterface and increments an index counter.
  4. If GetDelegate()->GetFromNodeID(node_id) returns nullptr (e.g., due to a missing wrapper from reparenting edge cases), the loop skips the element and does not increment index.
  5. The method concludes by setting *n_column_header_cells = static_cast<LONG>(column_header_ids.size()); instead of using the index variable.

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:

  1. Heap Grooming: The attacker sends AXTreeUpdate IPC messages containing many nodes with large string attributes (like kName or kDescription). These are allocated as BSTR objects in the browser process, which use the same Windows OLE/COM heap as CoTaskMemAlloc. This allows the attacker to fill the heap with fake COM object pointers and fake vtables.
  2. State Corruption: The attacker sends a crafted AXTreeUpdate designed to trigger a reparenting edge case (e.g., exploiting crbug.com/40833630). This creates a state where an AXNode exists logically in the tree but lacks a corresponding platform wrapper in the id_wrapper_map_.
  3. Table Construction: The attacker constructs a table where the column headers reference the ID of the corrupted node alongside valid node IDs.
  4. Trigger: An external out-of-process accessibility client (like a screen reader) is induced to query get_columnHeaderCells on the target table.
  5. 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 QueryInterface and Release on 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.

View on issue tracker
Links in the report