CVE-2026-118307
Overview
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 builtgradientand related paint data. - `BuildGradientData(reference_box)`
- Resolves gradient attributes and constructs a new
GradientData; it can re-enter layout/resource resolution and modifygradient_map_(e.g. insert/evict entries for other clients). - `insert().stored_value->value`
- WTF
HashMap::insertreturns anAddResultwhosestored_valuepoints 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).
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
- Craft recursive/multi-client SVG gradients
Author SVG whose gradient resource, when built, re-enters resolution for additional clients so that
BuildGradientDatacauses further insertions intogradient_map_(inferred reachability from the builder’s documented ability to modify the map). - Force a paint/layout build
Cause the gradient resource to be painted for a client with no cached
GradientData, enteringensure/applywith theinsert(...).stored_value->valuereference bound. - Reallocate mid-build
Inside
BuildGradientData, trigger enough map insertions to rehashgradient_map_, reallocating and freeing the backing store the reference points into. - Stale-reference write
The
gradient_data = BuildGradientData(...)assignment writes aunique_ptrthrough the dangling reference. - UAF
The subsequent
gradient_data->gradientread (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
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
| Function | Change | Notes |
|---|---|---|
ifthird_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 callsGrep Blink for
HashMap/HashSetinsert(...).stored_valueorAddResultreferences retained across any call that could mutate the same container; these are canonical rehash-UAF shapes. - Builder reentrancyReview
BuildGradientDataand sibling builders for the full set of map mutations they can trigger, to find other callers relying on a pre-insert reference.
Patch
From beee6bb7ce5bcda1cf8f825c217d3f49e6364635 Mon Sep 17 00:00:00 2001 From: Philip Rogers <[email protected]> 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 <[email protected]> Reviewed-by: Fredrik Söderquist <[email protected]> Commit-Queue: Fredrik Söderquist <[email protected]> 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;