CVE-2026-16424
Overview
Background
- `Surface` / `SurfaceManager`
- viz (GPU-process compositor) objects where each
Surfaceholds pending and activeCompositorFramedata andSurfaceManagerorchestrates committing frames across surfaces. - `SurfaceRange`
- a value describing a range of surface identifiers referenced by a frame; frames list them in
metadata.referenced_surfaces. - `referenced_surfaces`
- a
std::vector<SurfaceRange>stored inside a frame’s metadata, owned bypending_frame_data_oractive_frame_data_. - Re-entrancy
- here,
CommitFramesInRangeRecursivelycan synchronously activate a caller’s pending frame, which replacesactive_frame_data_and frees the vector the caller is currently iterating.
Root Cause Analysis
The surface commit path (the enclosing function name is not shown in the surface.cc hunk) iterated directly over pending_frame_data_->frame.metadata.referenced_surfaces and active_frame_data_->frame.metadata.referenced_surfaces, passing each SurfaceRange element by reference into SurfaceManager::CommitFramesInRangeRecursively(const SurfaceRange& range, ...). That recursive commit can synchronously activate a pending frame, which replaces active_frame_data_ (and can invalidate the pending vector), destroying the very std::vector<SurfaceRange> being iterated and thus dangling both the loop iterator and the const SurfaceRange& argument.
The patch fixes this two ways: Surface now copies each vector into a local const std::vector<SurfaceRange> referenced_surfaces = ... and iterates the copy, and SurfaceManager::CommitFramesInRangeRecursively now takes its range parameter by value instead of const SurfaceRange&. Together these ensure both the container being iterated and the range being operated on outlive any re-entrant activation that frees the original frame metadata, closing the use-after-free.
referenced_surfaces vector across a recursive call that can synchronously free that vector; the fix removes the aliasing by iterating a local copy of the vector and passing SurfaceRange by value.Attack Path
- Craft referencing frames
A compromised renderer submits
CompositorFrames whosemetadata.referenced_surfacesreference other surfaces, arranging a dependency graph that will drive recursive commits. - Enter recursive commit
The surface commit path iterates
referenced_surfacesand callsSurfaceManager::CommitFramesInRangeRecursivelyfor each range while still holding a reference into the frame-owned vector. - Force re-entrant activation
Inside the recursive commit, a pending frame is synchronously activated, replacing
active_frame_data_and freeing thereferenced_surfacesvector currently being iterated. - Use freed memory
Control returns to the loop, which continues iterating the freed vector (and dereferences the dangling
const SurfaceRange&), producing the use-after-free in the GPU process. - Corruption / escape (inferred) Reclaiming the freed vector storage could yield controlled corruption, but the diff does not demonstrate a specific primitive or sandbox escape.
Impact Assessment
(inferred). The precondition is the ability to drive the recursive commit path with attacker-controlled surface references.Changed Functions
| Function | Change | Notes |
|---|---|---|
forcomponents/viz/service/surfaces/surface.cc |
modified |
Files Changed
components/viz/service/surfaces/surface.cccomponents/viz/service/surfaces/surface_manager.cccomponents/viz/service/surfaces/surface_manager.h
Audit Directions
- Iterating owned containers across callbacksFlag loops over member-owned
std::vectors (frame/surface metadata) that call functions capable of synchronously mutating or freeing the owner during iteration. - Reference parameters into reentrant graph walksAudit recursive compositor/surface traversals that take
const T&arguments pointing into structures the recursion can replace; prefer by-value where re-entrancy can free the source. - Synchronous activation side effectsTrace all paths where commit/activation runs synchronously (
CommitFramesRecursively, activation callbacks) to find other callers still holding references intoactive_frame_data_/pending_frame_data_.
Patch
From d798f3bad2546fb2ec8d75c0fbdcfb866c4fb442 Mon Sep 17 00:00:00 2001 From: Saifuddin Hitawala <[email protected]> Date: Wed, 15 Jul 2026 12:34:32 -0700 Subject: [PATCH] [viz] Harden SurfaceManager recursive commit to avoid re-entrancy Harden SurfaceManager and Surface for committing frames recursively by passing SurfaceRange by value instead of ref and iterating over a copy of vectors so that we can avoid potential UAF through re-entrancy. Bug: 534858939 Change-Id: I2ec6ce6752eb540fa290dfd13f717d087e4115ca Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8096884 Reviewed-by: Vasiliy Telezhnikov <[email protected]> Commit-Queue: Saifuddin Hitawala <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662786} --- diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc index 0abab86..ae6755d 100644 --- a/components/viz/service/surfaces/surface.cc +++ b/components/viz/service/surfaces/surface.cc @@ -534,13 +534,19 @@ } if (HasPendingFrame()) { - for (auto& range : pending_frame_data_->frame.metadata.referenced_surfaces) + const std::vector<SurfaceRange> referenced_surfaces = + pending_frame_data_->frame.metadata.referenced_surfaces; + for (auto& range : referenced_surfaces) { surface_manager_->CommitFramesInRangeRecursively(range, predicate); + } } if (HasActiveFrame()) { - for (auto& range : active_frame_data_->frame.metadata.referenced_surfaces) + const std::vector<SurfaceRange> referenced_surfaces = + active_frame_data_->frame.metadata.referenced_surfaces; + for (auto& range : referenced_surfaces) { surface_manager_->CommitFramesInRangeRecursively(range, predicate); + } } // If we freed up some space in queue send ack for the last frame if it's diff --git a/components/viz/service/surfaces/surface_manager.cc b/components/viz/service/surfaces/surface_manager.cc index 43e7b1f..9d1c97c 100644 --- a/components/viz/service/surfaces/surface_manager.cc +++ b/components/viz/service/surfaces/surface_manager.cc @@ -757,7 +757,7 @@ } void SurfaceManager::CommitFramesInRangeRecursively( - const SurfaceRange& range, + SurfaceRange range, const CommitPredicate& predicate) { // Technically we need only latest active surface, but because activation will // happen during commit, it's impossible to predict which one will be active, diff --git a/components/viz/service/surfaces/surface_manager.h b/components/viz/service/surfaces/surface_manager.h index 117ce59..13c62e7 100644 --- a/components/viz/service/surfaces/surface_manager.h +++ b/components/viz/service/surfaces/surface_manager.h @@ -227,7 +227,11 @@ // surface processed calls `predicate` for each uncommitted frame from oldest // to newest. If predicate returns true, surface is committed. If not the // surface processing stops and we go to the next surface. - void CommitFramesInRangeRecursively(const SurfaceRange& range, + // |range| is passed by value because CommitFramesRecursively can + // synchronously activate a caller's pending frame, replacing + // active_frame_data_ and freeing the referenced_surfaces vector that the + // caller passed |range| out of. + void CommitFramesInRangeRecursively(SurfaceRange range, const CommitPredicate& predicate); private: