Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Viz
DescriptionUse after free in Viz
ComponentViz
Bug ClassUAF
Tracker498259721
Fix commitc00f3a04a7f3 (chromium/src) +50/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
components/viz/service/layers/layer_context_impl_unittest.cc
modified

Files Changed

  • components/viz/service/layers/layer_context_impl.cc
  • components/viz/service/layers/layer_context_impl_unittest.cc
From c00f3a04a7f317448e1d1b545e77f8fb9223fdb7 Mon Sep 17 00:00:00 2001
From: Zhenyao Mo <zmo@chromium.org>
Date: Thu, 23 Apr 2026 17:14:14 -0700
Subject: [PATCH] [TreesInViz] set_needs_update_draw_properties() when update fails.

Otherwise, layer tree could be left in an inconsistent state.

TEST=viz_unittests

Bug: 498259721
Change-Id: Icac4d2cc81a32fd0275c49d2293e9f1ad1608476
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7791946
Auto-Submit: Zhenyao Mo <zmo@chromium.org>
Commit-Queue: Tzarial <zork@chromium.org>
Reviewed-by: Tzarial <zork@chromium.org>
Commit-Queue: Zhenyao Mo <zmo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1619855}
---

diff --git a/components/viz/service/layers/layer_context_impl.cc b/components/viz/service/layers/layer_context_impl.cc
index a806d042..af49f96 100644
--- a/components/viz/service/layers/layer_context_impl.cc
+++ b/components/viz/service/layers/layer_context_impl.cc
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/functional/callback_helpers.h"
 #include "base/memory/ptr_util.h"
 #include "base/notimplemented.h"
 #include "base/notreached.h"
@@ -1851,6 +1852,15 @@
   cc::LayerTreeImpl& layers = *host_impl_->active_tree();
   cc::PropertyTrees& property_trees = *layers.property_trees();
 
+  // Any update to the display tree requires a new draw properties update if
+  // validation fails and returns early, because we may have already mutated
+  // some state (like taking render surfaces or resizing trees).
+  base::ScopedClosureRunner cleanup(base::BindOnce(
+      [](cc::LayerTreeImpl* layers) {
+        layers->set_needs_update_draw_properties();
+      },
+      &layers));
+
   std::vector<std::unique_ptr<cc::RenderSurfaceImpl>> old_render_surfaces;
   property_trees.effect_tree_mutable().TakeRenderSurfaces(&old_render_surfaces);
 
@@ -2209,6 +2219,7 @@
     layers.MoveChangeTrackingToLayers();
   }
 
+  cleanup.ReplaceClosure(base::DoNothing());
   return base::ok();
 }
 
diff --git a/components/viz/service/layers/layer_context_impl_unittest.cc b/components/viz/service/layers/layer_context_impl_unittest.cc
index 22ee827c..4ee162f2 100644
--- a/components/viz/service/layers/layer_context_impl_unittest.cc
+++ b/components/viz/service/layers/layer_context_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "cc/layers/nine_patch_layer_impl.h"
 #include "cc/layers/nine_patch_thumb_scrollbar_layer_impl.h"
 #include "cc/layers/painted_scrollbar_layer_impl.h"
+#include "cc/layers/render_surface_impl.h"
 #include "cc/layers/solid_color_scrollbar_layer_impl.h"
 #include "cc/layers/surface_layer_impl.h"
 #include "cc/layers/texture_layer_impl.h"
@@ -4837,5 +4838,43 @@
   EXPECT_TRUE(result.has_value()) << (result.has_value() ? "" : result.error());
 }
 
+TEST_F(LayerContextImplTest, DoUpdateDisplayTreeEarlyReturnUAF) {
+  // 1. Initial valid update to set up the tree and populate
+  // render_surface_list_.
+  auto update = CreateDefaultUpdate();
+  // The secondary root effect node created in ResetTestState() already has
+  // kRoot reason.
+
+  auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
+  ASSERT_TRUE(result.has_value());
+
+  // Trigger a draw to populate render_surface_list_.
+  layer_context_impl_->host_impl()->active_tree()->UpdateDrawProperties(
+      /*update_tiles=*/true, /*update_image_animation_controller=*/true);
+
+  const auto& render_surface_list =
+      layer_context_impl_->host_impl()->active_tree()->GetRenderSurfaceList();
+  ASSERT_FALSE(render_surface_list.empty());
+
+  // 2. A failing update that returns early in DoUpdateDisplayTree after
+  // TakeRenderSurfaces.
+  auto update2 = CreateDefaultUpdate();
+  // We can make UpdateTransformTreeProperties fail by setting an invalid
+  // page_scale_factor.
+  update2->transform_tree_update = mojom::TransformTreeUpdate::New();
+  update2->transform_tree_update->page_scale_factor = -1.0f;
+
+  auto result2 = layer_context_impl_->DoUpdateDisplayTree(std::move(update2));
+  ASSERT_FALSE(result2.has_value());
+  EXPECT_EQ(result2.error(), "Invalid page_scale_factor");
+
+  // 3. Check if needs_update_draw_properties() is true.
+  // If it's false, we have a UAF potential because render_surface_list still
+  // has pointers to freed surfaces.
+  EXPECT_TRUE(layer_context_impl_->host_impl()
+                  ->active_tree()
+                  ->needs_update_draw_properties());
+}
+
 }  // namespace
 }  // namespace viz
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/viz/service/layers/layer_context_impl_unittest.cc b/components/viz/service/layers/layer_context_impl_unittest.cc
index 22ee827c..4ee162f2 100644
--- a/components/viz/service/layers/layer_context_impl_unittest.cc
+++ b/components/viz/service/layers/layer_context_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "cc/layers/nine_patch_layer_impl.h"
 #include "cc/layers/nine_patch_thumb_scrollbar_layer_impl.h"
 #include "cc/layers/painted_scrollbar_layer_impl.h"
+#include "cc/layers/render_surface_impl.h"
 #include "cc/layers/solid_color_scrollbar_layer_impl.h"
 #include "cc/layers/surface_layer_impl.h"
 #include "cc/layers/texture_layer_impl.h"
@@ -4837,5 +4838,43 @@
   EXPECT_TRUE(result.has_value()) << (result.has_value() ? "" : result.error());
 }
 
+TEST_F(LayerContextImplTest, DoUpdateDisplayTreeEarlyReturnUAF) {
+  // 1. Initial valid update to set up the tree and populate
+  // render_surface_list_.
+  auto update = CreateDefaultUpdate();
+  // The secondary root effect node created in ResetTestState() already has
+  // kRoot reason.
+
+  auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
+  ASSERT_TRUE(result.has_value());
+
+  // Trigger a draw to populate render_surface_list_.
+  layer_context_impl_->host_impl()->active_tree()->UpdateDrawProperties(
+      /*update_tiles=*/true, /*update_image_animation_controller=*/true);
+
+  const auto& render_surface_list =
+      layer_context_impl_->host_impl()->active_tree()->GetRenderSurfaceList();
+  ASSERT_FALSE(render_surface_list.empty());
+
+  // 2. A failing update that returns early in DoUpdateDisplayTree after
+  // TakeRenderSurfaces.
+  auto update2 = CreateDefaultUpdate();
+  // We can make UpdateTransformTreeProperties fail by setting an invalid
+  // page_scale_factor.
+  update2->transform_tree_update = mojom::TransformTreeUpdate::New();
+  update2->transform_tree_update->page_scale_factor = -1.0f;
+
+  auto result2 = layer_context_impl_->DoUpdateDisplayTree(std::move(update2));
+  ASSERT_FALSE(result2.has_value());
+  EXPECT_EQ(result2.error(), "Invalid page_scale_factor");
+
+  // 3. Check if needs_update_draw_properties() is true.
+  // If it's false, we have a UAF potential because render_surface_list still
+  // has pointers to freed surfaces.
+  EXPECT_TRUE(layer_context_impl_->host_impl()
+                  ->active_tree()
+                  ->needs_update_draw_properties());
+}
+
 }  // namespace
 }  // namespace viz
Loading diff…

Original Bug Report

reported by vm...@google.com

UAF in GPU process via LayerContextImpl::DoUpdateDisplayTree aborted update

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A Use-After-Free (UAF) vulnerability exists in the GPU process when kTreesInViz is enabled. LayerContextImpl resizes property trees, freeing RenderSurfaceImpl objects, but can abort early on validation errors without updating needs_update_draw_properties_, leaving dangling pointers in LayerTreeImpl’s render surface list.

Affected files:

  • components/viz/service/layers/layer_context_impl.cc
  • cc/trees/damage_tracker.cc
  • cc/trees/layer_tree_impl.cc
  • cc/trees/property_tree.cc

Estimated timestamp from git blame: 2025-06-04

Summary

A potential Use-After-Free (UAF) vulnerability has been identified in the GPU process within the LayerContextImpl component, affecting the kTreesInViz code path. The root cause is that DoUpdateDisplayTree performs irreversible state mutations—specifically shrinking property trees and freeing RenderSurfaceImpl objects—before completing the validation of the display tree update.

If validation fails subsequently (e.g., due to an invalid page_scale_factor), the update is aborted early. This early return bypasses the logic that sets needs_update_draw_properties_ = true. Consequently, LayerTreeImpl is left in a “torn” state where its render_surface_list_ (which uses RAW_PTR_EXCLUSION) contains raw pointers to the now-freed RenderSurfaceImpl heap allocations. A subsequent draw operation will dereference these dangling pointers.

Potential Exploitation Steps

Note: These are suggested steps based on static analysis. Our tooling agent does not yet have the ability to run code to provide a working proof of concept.

  1. Initial Setup: A compromised Renderer process establishes a valid mojom::LayerContext connection to the GPU process.
  2. Clear Draw Flag: The Renderer sends a valid mojom::LayerTreeUpdate with frame_has_damage = true. This forces a successful draw cycle, which sets LayerTreeImpl::needs_update_draw_properties_ to false and populates render_surface_list_ with valid pointers.
  3. Trigger the Free: The Renderer crafts a malicious LayerTreeUpdate where update->num_effect_nodes is smaller than the current size. DoUpdateDisplayTree calls ResizePropertyTree, which physically removes effect nodes and frees their associated RenderSurfaceImpl objects.
  4. Heap Grooming: The attacker includes a large array in update->surface_ranges. The allocation of the base::flat_set backing vector for these ranges lands in the recently freed RenderSurfaceImpl heap slots. The attacker fills this array with crafted bytes representing a fake RenderSurfaceImpl object (including a forged damage_tracker_ pointer).
  5. Trigger the Abort: The attacker sets update->page_scale_factor to NaN. Validation fails at layer_context_impl.cc:1976, causing an early return. needs_update_draw_properties_ remains false.
  6. Trigger the Draw: The early return calls ReportBadMessage, which queues an asynchronous Mojo teardown task. Before it runs, the attacker sends SetNeedsBeginFrame(true) via the CompositorFrameSink interface on the same pipe. This schedules a local BeginFrame task on the Viz thread.
  7. The UAF: The BeginFrame task executes first, calling LayerTreeHostImpl::PrepareToDraw. Because needs_update_draw_properties_ is false, it skips recalculating the draw properties and keeps the stale render_surface_list_.
  8. RCE Primitives: PrepareToDraw calls DamageTracker::UpdateDamageTracking, which iterates the stale list and executes render_surface->damage_tracker()->PrepareForUpdate(). Using the attacker’s forged damage_tracker_ pointer, PrepareForUpdate() executes with a controlled this context. This provides the attacker with an arbitrary 32-bit increment primitive (mailbox_id_++) and an arbitrary vector clear primitive (contributing_surfaces_.clear()), which can be chained to achieve Remote Code Execution (RCE) in the GPU process.

Suggested Fix

Ensure that the needs_update_draw_properties_ flag is set to true (or the render_surface_list_ is explicitly cleared) whenever property trees are physically mutated, regardless of whether the overall DoUpdateDisplayTree function succeeds or fails.

For example, ResizePropertyTree could accept the LayerTreeImpl reference and call set_needs_update_draw_properties() internally if it returns true. Alternatively, DoUpdateDisplayTree could use a base::ScopedClosureRunner to ensure state cleanup on early returns.

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