← Chrome — 2026-W41

60126f0149fd9164c35ac0b4d077d8a3dac9b17c  [M153] Reinsert PatternData into pattern map after reconstruction

severity medium class UAF confidence 0.75 signal score 6.8 third_party/blink/renderer/core/ patch-now AI review CONFIRMED
Primitive: dangling unique_ptr<PatternData>& into pattern_map_ slot invalidated when the map is cleared during BuildPatternData -> UAF write
Triage note: Old code wrote paint_flags through std::unique_ptr<PatternData>& from pattern_map_.insert().stored_value->value after BuildPatternData could clear the map; fix uses find()+pointer and re-inserts via Set() (Fixed: 565742179).
Structural signals: security_assert (+3.0), area:third_party/blink/renderer/core/ (+2.5), surgical (+1.3)

The bug at a glance

A use-after-free write in the SVG layout/paint path: BuildPatternData can recursively re-enter pattern resolution and clear LayoutSVGResourcePattern::pattern_map_, invalidating a std::unique_ptr& reference obtained earlier from the same map; the code then writes through that dangling reference. SVG painting is fully web-reachable with attacker-controlled markup, and UAF writes to freed heap are strong exploitation primitives, so this rates high. It is a renderer-process memory-corruption bug shipped as a security cherry-pick to a stable branch.

LayoutSVGResourcePattern cached a reference INTO a hash-map slot (pattern_map_.insert(…).stored_value->value) and kept using it across a call to BuildPatternData — but building the pattern can trigger nested layout/resource resolution that clears or rehashes pattern_map_, freeing the slot the reference points at. The subsequent assignment (pattern_data = BuildPatternData(…)) and the following pattern_data->paint_flags = paint_flags then dereference freed memory.

Root cause

The pre-fix code bound std::unique_ptr& pattern_data to pattern_map_.insert(&client, nullptr).stored_value->value — a reference directly into a WTF::HashMap value slot. BuildPatternData(reference_box, paint_flags) can recursively resolve SVG resources and, as a side effect, clear or mutate pattern_map_ (the CL message states it ‘can be cleared while executing BuildPatternData’), which frees/relocates the map’s backing and leaves pattern_data dangling. The old code then did pattern_data = BuildPatternData(…) (assigning the returned unique_ptr through the stale reference) and pattern_data->paint_flags = paint_flags, i.e. a write through a dangling reference into freed storage. The fix stops holding a reference into the map across the rebuild: it looks up with pattern_map_.find(&client) into a raw PatternData* (nullptr if absent), and after BuildPatternData returns a fresh std::unique_ptr it re-inserts with pattern_map_.Set(&client, std::move(new_pattern_data)) — a lookup performed AFTER the map may have been mutated — then uses the raw pointer (CHECK(pattern_data)) taken from the just-built object rather than from a pre-held map slot. Because Set() re-resolves the bucket post-rebuild, no stale iterator/reference is dereferenced.

Key code

Re-insert freshly built PatternData via Set() after BuildPatternData, instead of writing through a held map-slot reference

auto iter = pattern_map_.find(&client);
PatternData* pattern_data =
    (iter == pattern_map_.end() ? nullptr : iter->value.get());
if (pattern_data && pattern_data->paint_flags != paint_flags) {
  pattern_data = nullptr;
}

if (!pattern_data) {
  auto new_pattern_data = BuildPatternData(reference_box, paint_flags);
  pattern_data = new_pattern_data.get();
  CHECK(pattern_data);
  pattern_map_.Set(&client, std::move(new_pattern_data));
  pattern_data->paint_flags = paint_flags;
}

Patch walkthrough

  • third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc — The sole change. Before: std::unique_ptr& pattern_data = pattern_map_.insert(&client, nullptr).stored_value->value held a reference into the map slot, then pattern_data.reset()/pattern_data = BuildPatternData(…) wrote through it after BuildPatternData could have cleared the map — a UAF. After: the code does a find() into a raw PatternData* (null if missing), nulls it if paint_flags differ, and when rebuild is needed calls BuildPatternData into a local new_pattern_data unique_ptr, captures the raw pointer, CHECK(pattern_data) to reject a null build, and re-inserts with pattern_map_.Set(&client, std::move(new_pattern_data)). The key safety property: the map is written via Set() AFTER BuildPatternData returns (so any clear/rehash during the build is accounted for), and no reference/iterator into pattern_map_ is held across the build.

Background

LayoutSVGResourcePattern::pattern_map_ — A per-resource WTF::HashMap<client, std::unique_ptr> caching the resolved paint data (tile, transform, paint flags) for each client that references an SVG .

BuildPatternData — Constructs a PatternData by laying out/painting the pattern’s content tile. Because pattern content can itself reference resources (including re-entrant SVG resource resolution), it can trigger invalidation that clears pattern_map_ mid-build.

HashMap::insert stored_value vs find/Set — insert(…).stored_value->value returns a reference into the map’s backing store that is invalidated if the map is cleared or rehashed; holding it across a call that can mutate the map is unsafe. find() returning a raw element pointer plus a later Set() re-resolves the bucket and avoids a stale reference.

M153 branch — This instance is the M153 stable-branch cherry-pick (refs/branch-heads/8010, Bug: 567398487) of main-line commit dedbd3f (Fixed: 565742179); the identical source change also landed on M150 (commit a8a28009b6).

Vulnerability window

  1. Introduction — Latent in LayoutSVGResourcePattern: a reference into pattern_map_ was held across BuildPatternData, which can clear the map.
  2. Report — Tracked as Chromium issue 565742179 (Fixed), with branch-tracking bug 567398487 for the M153 merge.
  3. Main fix — Original fix dedbd3f9 by Stefan Zager, reviewed by Fredrik Söderquist, Cr-Commit-Position refs/heads/main@{#1706535}.
  4. M153 cherry-pick — Merged to M153 2026-10-05 via CL 8487766, Cr-Commit-Position refs/branch-heads/8010@{#1663} (Cr-Branched-From main@{#1681091}).

Proof of concept

The diff contains no test or PoC, so poc.available=false. The conceptual repro needs SVG markup where painting a causes BuildPatternData to re-enter and clear pattern_map_ (e.g. nested/self-referential resource references driving invalidation during the build). This is web-facing: rendering attacker-supplied SVG is enough, no permissions required. The precise invalidation chain that clears the map during BuildPatternData is not spelled out in the diff and is inferred from the commit message; a reliable trigger would require matching Blink’s SVG resource-invalidation ordering.

Exploitation

  1. Reach the sink (inferred) — Load SVG that fills an element with url(#pattern) so LayoutSVGResourcePattern::ResolveContextAndCreatePatternData runs and takes the pre-fix reference into pattern_map_.
  2. Clear the map mid-build (inferred) — Craft pattern content that, during BuildPatternData, triggers SVG resource invalidation clearing/rehashing pattern_map_, freeing the slot the held reference points to.
  3. UAF write (inferred) — The post-build assignment through the dangling std::unique_ptr& and pattern_data->paint_flags = paint_flags write into freed heap — a controlled-timing UAF write in the renderer.
  4. Heap grooming / escalation (inferred) — Reclaiming the freed PatternData slot with attacker-influenced allocations to turn the UAF write into a corrupted object is not shown by the patch and is inferred; no primitive is demonstrated in the diff.

Detection & hunting

For defenders and detection engineers:

  • ASAN/UAF crashes in LayoutSVGResourcePattern —
  • CHECK(pattern_data) abort —
  • Re-entrant pattern_map_ mutation during BuildPatternData —

Audit directions

  • References/iterators held across re-entrant builds —
  • insert().stored_value->value usage —
  • BuildPatternData side effects —

Before / after

Loading diff…