High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Paint
DescriptionUse after free in Paint
ComponentPaint
Bug ClassUAF
Tracker570675085
Fix commitbeee6bb7ce5b (chromium/src) +11/-4
CISA KEVNot listed
CreditedXinyang Ge
Disclosed2026-10-27

Background

`gradient_map_`
A Blink HashMap<client*, std::unique_ptr<GradientData>> caching built gradient data; like all WTF hash maps, insertion can rehash and reallocate the backing store, invalidating references into existing slots.
`GradientData`
Owned (std::unique_ptr) struct holding the built gradient and related paint data.
`BuildGradientData(reference_box)`
Resolves gradient attributes and constructs a new GradientData; it can re-enter layout/resource resolution and modify gradient_map_ (e.g. insert/evict entries for other clients).
`insert().stored_value->value`
WTF HashMap::insert returns an AddResult whose stored_value points into the map’s current backing store.

Root Cause Analysis

Pre-patch, LayoutSVGResourceGradient obtained a reference into the hash map and then built into it in one breath:

std::unique_ptr<GradientData>& gradient_data =
    gradient_map_.insert(&client, nullptr).stored_value->value;
if (!gradient_data)
  gradient_data = BuildGradientData(reference_box);

gradient_data is a reference (std::unique_ptr<GradientData>&) bound to the slot inside gradient_map_’s backing store. The subsequent BuildGradientData(reference_box) can modify gradient_map_ (resolving gradient attributes re-enters resource/layout code that can insert or evict map entries). Any such insertion can rehash the map and reallocate its backing store, freeing the memory the gradient_data reference points into. The assignment gradient_data = BuildGradientData(...) then writes the returned unique_ptr through the now-dangling reference, and later gradient_data->gradient reads through it — a use-after-free on the hash map’s freed backing store (a stale-reference write/read, matching the advisory’s “Use after free in Paint”).

The patch stops holding a reference across the build. It first looks up the existing entry without inserting (auto iter = gradient_map_.find(&client);), takes a raw GradientData* only to test presence, and if absent builds into a local auto new_gradient_data = BuildGradientData(reference_box); (owned by the stack), captures gradient_data = new_gradient_data.get(), CHECKs it, and only afterward inserts with gradient_map_.Set(&client, std::move(new_gradient_data)). Because the build completes before any map mutation and the result is held in a local, no reference into gradient_map_ is live across a reallocation; the later gradient_data->gradient dereference uses the stack-owned pointer (valid until Set moves it, after which the map owns the same heap GradientData, whose address is unchanged by the move).

Key insight
The code held a reference into a hash-map slot (insert(...).stored_value->value) and then called a builder that could reallocate the map, freeing that slot — assigning/reading through the stale reference is the UAF.

Attack Path

  1. Craft recursive/multi-client SVG gradients Author SVG whose gradient resource, when built, re-enters resolution for additional clients so that BuildGradientData causes further insertions into gradient_map_ (inferred reachability from the builder’s documented ability to modify the map).
  2. Force a paint/layout build Cause the gradient resource to be painted for a client with no cached GradientData, entering ensure/apply with the insert(...).stored_value->value reference bound.
  3. Reallocate mid-build Inside BuildGradientData, trigger enough map insertions to rehash gradient_map_, reallocating and freeing the backing store the reference points into.
  4. Stale-reference write The gradient_data = BuildGradientData(...) assignment writes a unique_ptr through the dangling reference.
  5. UAF The subsequent gradient_data->gradient read (and the freed slot) is a use-after-free in the renderer; with heap grooming of the freed hash-map store this is an exploitable primitive (reclaim/control inferred, not shown).

Impact Assessment

The defect is in the renderer process, reachable from untrusted web content that can render SVG gradients — no special privilege required. The proven primitive is a use-after-free (stale-reference write then read) on the freed gradient_map_ backing store; a unique_ptr being written through a dangling reference is especially dangerous (it can also drop/own freed memory). Reliability depends on the attacker forcing BuildGradientData to reallocate the map during the build. Escalation beyond a controlled crash to code execution is (inferred) and not shown by the diff; the advisory’s “Use after free” class matches the code.

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc
modified

Files Changed

  • third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc

Audit Directions

  • `insert(...).stored_value` held across calls
    Grep Blink for HashMap/HashSet insert(...).stored_value or AddResult references retained across any call that could mutate the same container; these are canonical rehash-UAF shapes.
  • Builder reentrancy
    Review BuildGradientData and sibling builders for the full set of map mutations they can trigger, to find other callers relying on a pre-insert reference.
From beee6bb7ce5bcda1cf8f825c217d3f49e6364635 Mon Sep 17 00:00:00 2001
From: Philip Rogers <pdr@chromium.org>
Date: Thu, 08 Oct 2026 01:45:06 -0700
Subject: [PATCH] Ensure gradient data pointers remain safe to use

This patch avoids holding a reference into `gradient_map_` across
`BuildGradientData()`, which can modify the gradient map. Instead,
look up the cached entry first and insert the newly built
`GradientData` into `gradient_map_` afterward.

Fixed: 570675085
Change-Id: I9c6acde97024951a34b7a39bb8b8e146e07911e9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8527725
Auto-Submit: Philip Rogers <pdr@chromium.org>
Reviewed-by: Fredrik Söderquist <fs@opera.com>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/main@{#1714266}
---
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc
index 028d430..2332d68 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc
@@ -23,7 +23,9 @@
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.h"
 
 #include <memory>
+#include <utility>
 
+#include "base/check.h"
 #include "third_party/blink/renderer/core/svg/gradient_attributes.h"
 #include "third_party/blink/renderer/core/svg/svg_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
@@ -152,10 +154,15 @@
   NOT_DESTROYED();
   ClearInvalidationMask();
 
-  std::unique_ptr<GradientData>& gradient_data =
-      gradient_map_.insert(&client, nullptr).stored_value->value;
-  if (!gradient_data)
-    gradient_data = BuildGradientData(reference_box);
+  auto iter = gradient_map_.find(&client);
+  GradientData* gradient_data =
+      (iter == gradient_map_.end() ? nullptr : iter->value.get());
+  if (!gradient_data) {
+    auto new_gradient_data = BuildGradientData(reference_box);
+    gradient_data = new_gradient_data.get();
+    CHECK(gradient_data);
+    gradient_map_.Set(&client, std::move(new_gradient_data));
+  }
 
   if (!gradient_data->gradient)
     return false;
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.