[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);
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.
Aaaaaaa Aaaaa Aaaaa Aaa Aaaaaaaaaa Aaaaaa Aaaaaa a Aaaaaa Aaaa Aaaa Aaaaaaa Aaaaaaaaaa Aaaaaaaaaaa Aaa a Aaaa Aaaaaaa a Aaaaaaaa Aaaa Aaaaa Aa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaaa Aaaaaa Aaaa Aaaa Aaaaaaaa Aaaa Aaaaa Aaaaaa Aaaaa Aaa Aaa Aaaa Aa Aaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaaaa Aaaaaaaaa Aaaaaa Aaa Aaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaaaaa Aaaaaaaaaaaaaa Aaaaa Aaaaa Aaaaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaa Aaaaa Aaaa Aaa Aaaaaaaa a Aaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaa Aaaaa Aaaaa Aaaaaaaa Aaa Aaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aa a Aaa Aa Aaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaaaaaa Aa Aaaaaaaa Aaaaa Aaaaaaa Aa Aaaaaaa Aaa Aaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaa Aaaaa Aaaaaaaaaa Aaa Aaa Aaaa a Aaaaaaaaaa Aa Aaaaaaaaaaaa Aaaaaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aa Aaa Aaaaaaaaaa Aaaaaaaaa Aaa Aaaaaa Aaa Aaaa Aaaaaaa Aaaa Aaaaaaaaaa Aaa Aa Aaaaaaa Aaaaaaaaaaa Aaaa Aaaaaaaaa Aaaaaaaaaaaa Aaaaa Aaaaaa Aaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaa
🔒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
Audit directions
a Aaaaaaaaaaaa Aaaaaaaaaa Aaaa Aaaaaa Aaa Aaaaaaaaa Aaaa Aaaaaa Aa Aaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaaaaa Aaaa Aaaaaaaaaaaaa Aaaa a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaa Aaaaa Aaaaaaa Aaa Aaaaaaaaaaa Aaaa Aaaaaa Aaaa Aaaaaa a a Aaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaa Aaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaaa Aa Aaa Aaaa Aaaaaa Aaaa Aaaa Aaaaaa Aa a Aaaaaaaaaaaaaa Aaa Aaaaa Aaaaaaa Aaaaaaaa Aaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaa Aaaa Aaaa Aaaaaa Aaa Aaaaaaaaa Aaa Aaaaaaaaaaaaaa
a Aaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aa Aaaaa Aaa Aaaaaaa Aa a Aaaaaa Aaaaaaaaaaaa Aaaaaa Aaaaa Aaaa Aaaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaa
a Aaaaaa Aaa Aaaaaaaa Aa Aaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaa Aaaaa Aaaa Aaaaaaaaaa Aaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaa Aa Aaaa Aaaaaaaa
🔒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