← Chrome — 2026-W32

b4daba374467228a97720ab5f77fe4f4acbbefcb  [M152] Add defensive copy in CollectFlattenedAssignedNodes

severity low class UAF CVSS 8.8 signal score 4.8 third_party/blink/renderer/core/ patch-now AI review CONFIRMED
Structural signals: msg_soft (+1.0), area:third_party/blink/renderer/core/ (+2.5), surgical (+1.3)

The bug at a glance

The commit converts an iteration over a reference into a defensive copy to stop the backing HeapVector from being freed mid-loop during nested slot-assignment recalculation; the message explicitly describes a use-after-free validated against a POC, in web-reachable Blink DOM slot code.

Web content that drives shadow-DOM slot assignment can, through a nested recalculation, clear the outer slot’s assigned_nodes_ while CollectFlattenedAssignedNodes is iterating it by reference, giving an attacker-reachable renderer heap use-after-free usable as an exploitation primitive.

Root cause

CollectFlattenedAssignedNodes bound a const reference to slot.AssignedNodes() (the slot’s assigned_nodes_ member) and iterated it while synchronously recursing into slot-assignment recalculation. A nested recalculation can clear the outer slot’s assigned nodes, freeing/reallocating the HeapVector backing store that the reference and in-flight iterators still point at, so continued iteration reads freed memory. The fix changes the binding from a reference to a by-value HeapVector<Member> copy, so the loop iterates over a stable local snapshot whose backing survives any mutation of the original during the recursion.

Key code

Reference -> defensive copy to survive mid-loop mutation

-  const HeapVector<Member<Node>>& assigned_nodes = slot.AssignedNodes();
+  // Copy the array - code inside the loop can modify assigned nodes.
+  const HeapVector<Member<Node>> assigned_nodes = slot.AssignedNodes();

Patch walkthrough

  • third_party/blink/renderer/core/html/html_slot_element.cc — Drops the ‘&’ so assigned_nodes is a value copy of slot.AssignedNodes() rather than an alias to the live assigned_nodes_ member. This guarantees the vector backing referenced by the loop remains valid even if nested slot-assignment recalculation clears or reallocates the original during iteration, eliminating the use-after-free.

Background

HTMLSlotElement / assigned_nodes_ — A in shadow DOM tracks the light-DOM nodes assigned to it in the assigned_nodes_ HeapVector.

CollectFlattenedAssignedNodes — A helper that builds the flattened assignment, iterating assigned nodes and recursing into nested slots’ assignments.

Slot assignment recalculation — DOM mutations can synchronously trigger recalculation that mutates a slot’s assigned_nodes_, invalidating references taken before the recursion.

Oilpan HeapVector — Blink’s garbage-collected vector whose backing store can be reallocated/freed when the vector is cleared or regrown, invalidating prior references and iterators.

Vulnerability window

  1. Reachability — A page constructs shadow DOM with slots and triggers flattened-assignment collection on an outer slot.
  2. Trigger — During iteration, a nested slot-assignment recalculation clears the outer slot’s assigned_nodes_, freeing the backing store the loop reference points to.
  3. Primitive — Continued iteration over the dangling reference reads freed heap memory, a renderer use-after-free.
  4. Escalation — Reallocating the freed backing with controlled Member values could yield type confusion or controlled pointer dereference for renderer RCE (inferred).

Proof of concept

Conceptually, build nested shadow-DOM slots and, from within the slot-assignment recalculation path reached during CollectFlattenedAssignedNodes, perform a DOM mutation that clears the outer slot’s assigned nodes so the ongoing iteration dereferences the freed HeapVector backing. The commit notes a POC existed in the bug but no committable repro was found; no exploit code is provided here.

Exploitation

  1. Setup — Create shadow trees with slots whose assignment recomputation recurses into CollectFlattenedAssignedNodes (inferred).
  2. Trigger free — Cause a nested recalculation that clears the outer slot’s assigned_nodes_ mid-iteration, freeing the backing vector.
  3. Reclaim — Spray allocations to reoccupy the freed Oilpan backing with attacker-shaped data (inferred).
  4. Exploit — Use the stale Member reads for info leak or control-flow corruption in the renderer (inferred).

Detection & hunting

For defenders and detection engineers:

  • weggli for ‘const HeapVector<…>& x = obj.SomeNodes();’ followed by a loop that can recurse/mutate — Flag references bound to live GC-vector members iterated across code that can mutate that member.
  • grep for AssignedNodes()/assigned_nodes_ used by reference in blink/renderer/core/html and dom — Audit all callers that hold a reference to a slot’s assigned nodes across potential recalculation.
  • ASAN/Oilpan UAF reports in HTMLSlotElement::CollectFlattenedAssignedNodes — Hunt renderer crash dumps for use-after-free within flattened-assignment collection.

Audit directions

  • Slot/assignment helpers in third_party/blink/renderer/core/html/html_slot_element.cc and dom/flat_tree traversal — Look for other loops iterating a slot’s live node vectors by reference while recursing into recalculation.
  • General Blink pattern: iterating a HeapVector member by reference across reentrant mutation — Search for references/iterators into GC-backed containers held across callbacks or recalculation that can clear/regrow them.

Before / after

Loading diff…