← All issues

[10] Use WeakHashSet::forEach to iterate over set where the iterator might be invalidated

Severity: Medium | Component: WebCore PageGroup | 42beed9

Rated Medium because the observable effect is iterator invalidation reachable from script-driven re-entrancy in caption-preference fan-out, the analyst's confidence is 0.85, and escalation beyond a renderer crash depends on freed-slot reclamation timing — useful as one stepping-stone primitive in a chain.

Source/WebCore/page/PageGroup.cpp

void PageGroup::captionPreferencesChanged()
{
- for (Ref page : m_pages)
- page->captionPreferencesChanged();
+ m_pages.forEach([](auto& page) {
+ page.captionPreferencesChanged();
+ });
BackForwardCache::singleton().markPagesForCaptionPreferencesChanged();
}

PageGroup::captionPreferencesChanged() switches from a range-based for (Ref page : m_pages) over its WeakHashSet<Page> member to m_pages.forEach([](auto& page) { ... }). The lambda calls page.captionPreferencesChanged() on each Page. A SaferCPPExpectations file is also updated, likely removing this site from a known-bad-pattern allowlist.

Iterator invalidation in a weak collection traversal that re-enters script-observable code capable of mutating the same collection.

WeakHashSet<T> holds weak references to T; entries whose referent is destroyed become empty and are eventually cleaned up. Iterating with a range-based for materialises a Ref per live entry but the iterator walks the underlying hash table. WeakHashSet::forEach is a member helper that strongifies live entries up front so add/remove/cleanup of weak slots during the callback does not corrupt the in-flight walk. PageGroup::captionPreferencesChanged notifies each Page that user caption preferences changed; that notification can reach HTMLMediaElement/TextTrackList listeners and trigger synchronous JS-observable work.

The per-element callback can synchronously re-enter script — caption/track lifecycle, media element notifications, possibly synchronous event dispatch — and that re-entry can mutate m_pages: a Page can be destroyed (collapsing its weak entry), or callback-driven allocation can cause the WeakHashSet to amortize-remove dead entries during a rehash. Either invalidates the range-based-for iterator.

🔒

The re-entrancy and lifetime implications of weak-collection iteration during script-observable callbacks are explored, along with the practical reach of the resulting primitive.

Subscribe to read more

🔒

Multiple reusable audit patterns identified for iterator-invalidation bugs across WebCore, including a concrete worklist source for variant discovery.

Subscribe to read more