b4daba374467228a97720ab5f77fe4f4acbbefcb [M152] Add defensive copy in CollectFlattenedAssignedNodes
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
- Reachability — A page constructs shadow DOM with slots and triggers flattened-assignment collection on an outer slot.
- Trigger — During iteration, a nested slot-assignment recalculation clears the outer slot’s assigned_nodes_, freeing the backing store the loop reference points to.
- Primitive — Continued iteration over the dangling reference reads freed heap memory, a renderer use-after-free.
- 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
- Setup — Create shadow trees with slots whose assignment recomputation recurses into CollectFlattenedAssignedNodes (inferred).
- Trigger free — Cause a nested recalculation that clears the outer slot’s assigned_nodes_ mid-iteration, freeing the backing vector.
- Reclaim — Spray allocations to reoccupy the freed Oilpan backing with attacker-shaped data (inferred).
- 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.