[13] NamedSlotAssignment iterator-invalidation UAF
A predicate named hasAssignedNodes lazy-filled m_slots and rehashed it mid-iteration — the loop kept writing through freed Slot storage.
Rated High because the diff fixes a HashMap rehash-during-iteration UAF: a lazy-cache helper called from inside a
for (auto& slot : m_slots.values())loop reinserts intom_slots, invalidating the loop's value reference; subsequent stores likeslot->seenFirstElement = trueandWTF::move(slot->element)write through freed memory.
NamedSlotAssignment::resolveSlotsAfterSlotMutation iterated m_slots.values(). Inside the loop, hasAssignedNodes(shadowRoot, *slot) lazily called assignSlots(shadowRoot), which inserts new entries into m_slots for unseen slot names — every insertion can rehash and relocate value storage.
Source/WebCore/dom/SlotAssignment.cpp
Patch Details
assignSlots is hoisted to run once before the iteration (only when m_slotAssignmentsIsValid is false), so the map is in its final layout for the duration. The in-loop hasAssignedNodes call is replaced with a direct !slot->assignedNodes.isEmpty() field read that cannot mutate m_slots.
HashMap iterator invalidation by a helper that re-enters the container's mutation path during traversal.
Background
NamedSlotAssignment is the default slot-assignment strategy for shadow trees declared attachShadow({mode: 'open' | 'closed'}). It maintains m_slots, a HashMap<AtomString, std::unique_ptr<Slot>>. Each Slot records the active <slot> element, an oldElement, an assignedNodes list, and per-slot flags. m_slotAssignmentsIsValid is a dirty bit cleared when the cache may be stale. assignSlots(shadowRoot) walks host children and inserts m_slots entries for new slot names. HashMap insertion can rehash, invalidating iterators and references.
Analysis
The query-named helper (hasAssignedNodes) was actually mutation-shaped — it called assignSlots which inserts into m_slots. Any predicate that lazily fills a cache by mutating a container is a hazard when invoked during iteration of that same container.
Exploit shape (from the regression test): attach a shadow root with default named-slot assignment; add a slot element + matching host child to set m_slotAssignmentsIsValid = false; append many host children with fresh slot names not yet keys in m_slots; trigger resolveSlotsAfterSlotMutation (the PoC uses container.replaceChildren() on the shadow subtree). Inside the loop, the first hasAssignedNodes call invokes assignSlots, which calls HashMap::add repeatedly for each fresh name and triggers a rehash. The loop's slot reference and values()-iterator now point into freed table storage; the next field access (slot->seenFirstElement = true, then WTF::move(slot->element)) is a UAF on the relocated unique_ptr<Slot> storage. With heap grooming an attacker could attempt to reclaim the freed backing with attacker-shaped data, converting the UAF into a read/write of Slot-typed fields including RefPtr<HTMLSlotElement> element.
This vulnerability weakens memory safety in the WebContent renderer. The Shadow DOM spec assumes slot resolution can be invoked reentrantly from arbitrary DOM-mutation entry points without producing unsafe internal state.
Audit directions
- Lazy-cache predicates that mutate the container they appear to read. Grep WebCore for query-named helpers (
has*,is*,find*,contains*) that conditionally call a*assign*/*recalc*/*update*/*validate*routine, then check callers for invocations from withinfor (auto& x : container.values()). Start with otherSlotAssignmentmethods (addSlotElementByName,removeSlotElementByName,slotManualAssignmentDidChange). HashMap::addduring iteration of the same map. Grepfor (auto& .* : m_*.values())and check whether anything inside can reach.add(/.ensure(/.set(on the same map. Same hazard forHashSetandWeakHashMap.- Dirty-bit-driven lazy recomputation invoked from accessor-shaped helpers. Rename or hoist the rebuild to a single well-defined point. Start with shadow-tree and style-recalc paths.
- Bulk DOM mutation as fuzzing seed.
replaceChildren()exercises different paths from one-by-one removal. AuditContainerNode::replaceChildrencallers and slot-resolution entry points triggered by bulk removal.