High watch CVSS 9.6 chrome UAF 🔧 Commit mapped

Overview

High
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Sub-componentViz
Bug ClassUAF
Action tierwatch — corruption primitive but not attacker-reachable (local)
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.
Tracker534858939
Fix commitd798f3bad254 (chromium/src) +14/-4
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-07-14 (per vendor advisory)
Fix landed2026-07-15 (public git)
Disclosed2026-07-21
Patch gap6 days — fix public in source before the advisory

Background

`Surface` / `SurfaceManager`
viz (GPU-process compositor) objects where each Surface holds pending and active CompositorFrame data and SurfaceManager orchestrates 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 by pending_frame_data_ or active_frame_data_.
Re-entrancy
here, CommitFramesInRangeRecursively can synchronously activate a caller’s pending frame, which replaces active_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.

Key insight
The single mistake was iterating (and holding a reference into) a frame-owned 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

  1. Craft referencing frames A compromised renderer submits CompositorFrames whose metadata.referenced_surfaces reference other surfaces, arranging a dependency graph that will drive recursive commits.
  2. Enter recursive commit The surface commit path iterates referenced_surfaces and calls SurfaceManager::CommitFramesInRangeRecursively for each range while still holding a reference into the frame-owned vector.
  3. Force re-entrant activation Inside the recursive commit, a pending frame is synchronously activated, replacing active_frame_data_ and freeing the referenced_surfaces vector currently being iterated.
  4. 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.
  5. 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

A compromised or malicious renderer that can submit crafted compositor frames referencing other surfaces can trigger a re-entrant use-after-free inside the viz/GPU process during recursive frame commit. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6. Any escalation beyond the freed-vector dereference the patch guards against is (inferred). The precondition is the ability to drive the recursive commit path with attacker-controlled surface references.

Changed Functions

FunctionChangeNotes
for
components/viz/service/surfaces/surface.cc
modified

Files Changed

  • components/viz/service/surfaces/surface.cc
  • components/viz/service/surfaces/surface_manager.cc
  • components/viz/service/surfaces/surface_manager.h

Audit Directions

  • Iterating owned containers across callbacks
    Flag 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 walks
    Audit 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 effects
    Trace all paths where commit/activation runs synchronously (CommitFramesRecursively, activation callbacks) to find other callers still holding references into active_frame_data_/pending_frame_data_.
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:
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.