e404fdbb247d1a58b5e8e6ccd85d653e2528a674 [M150] Add defensive copy in CollectFlattenedAssignedNodes
The bug at a glance
A renderer-process use-after-free of a HeapVector backing store during slot assignment is reachable from ordinary web content via shadow DOM / slots, and the commit message confirms a working POC existed; the only mitigating factor is no committable repro and the need to drive a nested recalculation.
A page using shadow DOM with elements can force a nested slot-assignment recalculation that clears the outer slot’s assigned nodes mid-iteration, freeing the vector being iterated and giving an attacker a UAF read/write primitive in the renderer.
Root cause
CollectFlattenedAssignedNodes bound a const reference to slot.AssignedNodes() (the slot’s assigned_nodes_ HeapVector) and iterated over it. Inside the loop, flattened-assignment collection can synchronously recurse into slot-assignment recalculation, which can clear the outer slot’s assigned nodes and free/reallocate the backing vector while the reference-based iteration is still live, producing a use-after-free. The fix changes the reference to a by-value copy of the HeapVector so the iterated backing store stays valid for the whole loop regardless of nested mutation.
Key code
Reference-to-backing-store replaced with a defensive by-value copy
- 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— CollectFlattenedAssignedNodes now takes a by-value copy of slot.AssignedNodes() instead of a reference, so a nested slot-assignment recalculation that clears the outer slot’s assigned nodes (freeing the original backing vector) cannot invalidate the vector being iterated, eliminating the use-after-free.
Background
HTMLSlotElement / — A shadow-DOM element that projects (assigns) light-DOM nodes into a shadow tree; its AssignedNodes() returns the HeapVector of currently assigned nodes.
Flattened assigned nodes — CollectFlattenedAssignedNodes expands nested slots to compute the flattened list of nodes a slot ultimately renders, recursing through child slot assignments.
HeapVector backing store — An Oilpan-managed contiguous array; clearing/reassigning assigned_nodes_ can free or reallocate the backing memory that an outstanding reference still points at.
Vulnerability window
- Reachability — Reachable from ordinary web content in a stock build via shadow DOM with elements.
- Trigger — Script manipulates slot assignments so that collecting the outer slot’s flattened nodes synchronously recurses into a recalculation that clears the outer slot’s assigned nodes.
- Primitive — The nested clear frees/reallocates the HeapVector backing store while the outer loop still iterates by reference, yielding a use-after-free read (and potential write) in the renderer.
- Escalation — Reclaiming the freed backing allocation with controlled objects could upgrade the UAF toward type confusion / arbitrary read-write in the renderer (inferred).
Proof of concept
Conceptually: build a shadow tree with slots so that computing an outer slot’s flattened assigned nodes triggers a synchronous slot-assignment recalculation (e.g. via mutation during the walk) that clears the outer slot’s assigned_nodes_; the in-flight reference-based loop then reads freed memory. The bug report had a local POC but no committable repro; no exploit code is provided here.
Exploitation
- Setup — Construct shadow roots and nested assignments that recurse during flattened-node collection (inferred).
- Trigger — Cause a nested recalculation to clear the outer slot’s assigned nodes mid-iteration, freeing the backing vector (inferred).
- Groom — Spray replacement HeapVector-sized allocations to control the reclaimed memory read/written by the dangling iteration (inferred).
Detection & hunting
For defenders and detection engineers:
- weggli:
const HeapVector<...>& x = obj.SomeNodes();followed by a loop that can call back into code mutating the same collection — Hunt reference bindings to Blink collection getters that are iterated while synchronous DOM recalculation can run. - grep for AssignedNodes()/assigned_nodes_ used by reference in iteration loops — Review slot/shadow-DOM iteration sites for reentrancy that can clear the collection.
Audit directions
- third_party/blink/renderer/core/html/html_slot_element.cc and shadow-DOM assignment code — Check every iteration over AssignedNodes()/assigned nodes for reentrant mutation during the loop.
- Blink layout/style/DOM loops binding references to member HeapVectors — Look for
const HeapVector<...>&bindings iterated while the loop body can synchronously trigger recalculation or script that mutates that vector.