← All issues

[13] NamedSlotAssignment iterator-invalidation UAF

Severity: High | Component: WebCore Shadow DOM | 20beac1

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 into m_slots, invalidating the loop's value reference; subsequent stores like slot->seenFirstElement = true and WTF::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

+ if (!m_slotAssignmentsIsValid)
+ assignSlots(shadowRoot);
+
for (auto& slot : m_slots.values()) {
if (slot->seenFirstElement)
continue;
...
slot->seenFirstElement = true;
ASSERT(slot->element);
- if (hasAssignedNodes(shadowRoot, *slot))
+ if (!slot->assignedNodes.isEmpty())
slot->oldElement = WTF::move(slot->element);

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.

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.

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.

🔒

How a query-shaped helper turned a routine slot-resolution loop into a renderer memory-safety bug, and what an attacker would need to escalate beyond a crash.

Subscribe to read more

🔒

Several reusable audit patterns spanning shadow DOM and other WebCore subsystems, with concrete grep targets for finding the same anti-pattern elsewhere.

Subscribe to read more