67f6fef1e489ce1f328a371ac204d123da72128a [M154] Reinsert PatternData into pattern map after reconstruction
Triage note: Bound std::unique_ptr<PatternData>& to pattern_map_.insert().stored_value->value, then BuildPatternData() can clear pattern_map_, invalidating the reference before writing through it. Fix re-inserts via pattern_map_.Set(). M154 cherry-pick; SVG patterns web-reachable.
The bug at a glance
Use-after-free in Blink SVG paint-server layout, reachable from any page that renders an SVG . A dangling reference into a LayoutSVGResourcePattern::pattern_map_ hash-table slot was written through after BuildPatternData re-entrantly cleared/rehashed the map. UAF-write in the renderer is a strong exploitation primitive; High. M154 release-branch cherry-pick.
The bug is an iterator/reference-invalidation UAF: pattern_map_.insert(&client, nullptr).stored_value->value handed out a std::unique_ptr& into a hash-table slot, and BuildPatternData() can re-enter and clear or rehash pattern_map_, invalidating that reference; the subsequent assignment and pattern_data->paint_flags write then landed in a stale/freed slot. M154 branch (refs/branch-heads/8037) cherry-pick of main fix dedbd3f9dee8.
Root cause
LayoutSVGResourcePattern previously bound std::unique_ptr& pattern_data to pattern_map_.insert(&client, nullptr).stored_value->value, a reference directly into a WTF::HashMap slot, then called BuildPatternData(reference_box, paint_flags) and assigned the result back through that reference. BuildPatternData can re-enter layout/paint and cause pattern_map_ to be cleared or rehashed, which invalidates the earlier AddResult and its stored_value reference; writing through the stale reference is a use-after-free write into a freed or moved hash-table bucket. The fix does a pattern_map_.find(&client) up front and takes a raw PatternData* (iter->value.get() or nullptr), builds the new data into a local new_pattern_data, CHECK()s it, and only then re-inserts with pattern_map_.Set(&client, std::move(new_pattern_data)) after reconstruction completes, so no reference spans the re-entrant mutation.
Key code
layout_svg_resource_pattern.cc: don’t hold a map-slot reference across BuildPatternData; Set() afterward
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— Replaces the std::unique_ptr& bound to pattern_map_.insert(…).stored_value->value with a find() + raw PatternData* snapshot. BuildPatternData’s result is held in a local, CHECK()d non-null, and re-inserted via pattern_map_.Set() only after reconstruction, so no reference or iterator survives a re-entrant clear/rehash. M154 branch cherry-pick of main commit dedbd3f9dee8 (Fixed: 565742179, branch bug 567398861).
Background
LayoutSVGResourcePattern / pattern_map_ — Blink layout object for an SVG paint server. pattern_map_ is a per-resource HashMap from client layout object to std::unique_ptr.
PatternData — Cached pattern paint state (including paint_flags) built by BuildPatternData and stored in pattern_map_.
WTF::HashMap AddResult / stored_value — insert() returns an AddResult whose stored_value points into the bucket. The reference/iterator is invalidated if the map is mutated (cleared, rehashed) before use.
BuildPatternData re-entrancy — Constructing pattern data can run further layout/paint that invalidates SVG resources and clears pattern_map_, so any reference held across it may dangle.
Vulnerability window
- Pre-patch — Pattern resolution held a unique_ptr& into a pattern_map_ slot across BuildPatternData, which could clear the map and invalidate that slot -> UAF write.
- Main fix 2026-09-29 — Lands dedbd3f9dee8 on main (CL 8464440), Fixed: 565742179.
- M154 cherry-pick — Cherry-picked to refs/branch-heads/8037 as 67f6fef1e4 (CL 8483415, branch bug 567398861).
Proof of concept
No test or PoC ships in this cherry-pick diff. Conceptually (inferred): render an SVG that fills an element with a url(#pattern) paint server and arrange for pattern reconstruction to re-enter layout/paint that invalidates the pattern resource, so pattern_map_ is cleared while a reference into it is live; the stale reference is then written. Web-facing reachability is direct: SVG painting is triggered by ordinary markup/CSS/script.
Exploitation
- Trigger — A page paints an element using an SVG paint server, and causes pattern reconstruction to re-enter and clear/rehash pattern_map_ during BuildPatternData (inferred).
- Free / invalidate — The hash-table clear or rehash frees/moves the bucket the held unique_ptr& referenced, leaving a dangling reference (inferred).
- UAF write — Assigning the new unique_ptr and writing pattern_data->paint_flags through the stale reference corrupts freed/reallocated heap; heap grooming could turn this into a controlled write primitive (inferred).
Detection & hunting
For defenders and detection engineers:
- Reference/iterator into a WTF::HashMap held across a call that can mutate the map — Flag T& x = map.insert(…).stored_value->value (or an iterator) kept live across a non-trivial call; the fixed pattern re-looks-up or Set()s after the call returns.
- AddResult.stored_value used after a potentially re-entrant callee — Any use of stored_value following a build/layout/paint helper is suspect; re-entrant SVG resource invalidation is a known cause of pattern_map_ clears.
- ASan heap-use-after-free WRITE in layout/svg paint-server code — Watch for UAF writes in LayoutSVGResourcePattern / SVG resource containers during pattern painting.
Audit directions
- Sibling SVG paint servers — Review LayoutSVGResourceGradient and other LayoutSVGResourceContainer subclasses for the same insert().stored_value->value-held-across-build pattern.
- HashMap AddResult lifetime across callbacks — Grep Blink for .insert(…).stored_value held across function calls that can mutate the same map.
- Re-entrant resource invalidation — Audit paths where building/painting one resource can invalidate the containing resource’s own cache mid-operation.