CVE-2026-112697
Overview
Background
- `CSSStyleSheet::forEachStyleScope`
- Iterates the tree scopes that have adopted a given
CSSStyleSheetand invokes a caller-supplied callback (apply) on each one’sStyle::Scope. - `m_adoptingTreeScopes`
- The collection (a weak/hash set of tree scopes) stored on a
CSSStyleSheetrecording whichTreeScopes currently adopt this stylesheet (e.g. viadocument.adoptedStyleSheets/ shadow-root adopted stylesheets). - `Ref` / `copyToVectorOf`
Ref<T>is a non-null ref-counted smart pointer;copyToVectorOf<Ref<ContainerNode>>(collection)materializes a snapshotVectorof strong references over the collection’s current members.- WebKit lifetime model
- Lifetime is managed with
Ref/RefPtr/WeakPtr(not Oilpan). Iterating a container directly exposes the caller to iterator/backing-store invalidation if the callback mutates that container.
Root Cause Analysis
CSSStyleSheet::forEachStyleScope (Source/WebCore/css/CSSStyleSheet.cpp) iterated the live member collection directly: for (Ref treeScope : m_adoptingTreeScopes) apply(protect(styleScopeFor(treeScope)));. Although Ref treeScope holds a strong reference to each element it visits, the loop still iterates over m_adoptingTreeScopes’s own backing storage. The callback apply(protect(styleScopeFor(treeScope))) runs arbitrary style-engine work that can re-enter stylesheet machinery and mutate m_adoptingTreeScopes — adding or removing adopting tree scopes — while the loop is mid-iteration. Mutating the collection reallocates/rehashes its backing store, invalidating the in-progress iterator, so the loop then reads from freed hash-table storage: a use-after-free.
The patch snapshots the collection before iterating: for (const auto& treeScope : copyToVectorOf<Ref<ContainerNode>>(m_adoptingTreeScopes)) apply(protect(styleScopeFor(treeScope)));. copyToVectorOf<Ref<ContainerNode>> builds a standalone Vector of Refs — both decoupling iteration from the mutable member container (no dangling iterator if the set changes) and keeping each referenced tree scope alive for the duration of the loop. Apple’s advisory labels this a use-after-free leading to arbitrary code execution; the one-line diff is consistent with that class: the fix is the canonical “iterate a copy, not the live container” remedy for iterator-invalidation UAF.
m_adoptingTreeScopes container while its own callback could mutate that container, so a reentrant add/remove invalidated the iterator and the loop read from freed backing storage.Attack Path
- Adopt a stylesheet widely
Script creates a
CSSStyleSheetand adopts it into multiple tree scopes (document + shadow roots) viaadoptedStyleSheets, populatingm_adoptingTreeScopeswith several entries. - Trigger `forEachStyleScope` Cause a style operation that walks the stylesheet’s adopting scopes (e.g. mutating the stylesheet’s rules, which must propagate to every adopting scope).
- Reenter from the callback
During the per-scope
applycallback, run script/DOM work (inferred: via a reachable mutation hook) that adds or removes an adopted stylesheet, mutatingm_adoptingTreeScopesand reallocating its backing store. - Dangling iterator The outer loop continues from an invalidated iterator pointing into freed hash-table memory.
- Groom and reuse Attacker arranges a controlled allocation to occupy the freed storage.
- UAF primitive Continued iteration dereferences attacker-influenced freed memory as tree-scope entries, yielding a use-after-free usable toward type confusion / code execution (escalation inferred; the diff proves the invalidation, not the full exploit).
Impact Assessment
Files Changed
Source/WebCore/css/CSSStyleSheet.cpp
Audit Directions
- Iterate-a-copy patternGrep WebCore for
for (Ref ... : m_...)/for (auto& ... : m_...)loops whose body calls out to callbacks or style/DOM code that could mutate the same member collection; these are candidate iterator-invalidation UAFs. - Adopted-stylesheet lifecycleAudit other methods touching
m_adoptingTreeScopes(registration/unregistration, notification walks) for reentrancy during mutation. - Style `Scope` notification walksReview other
forEach/notify loops in the style engine that invoke scope callbacks while holding iterators into live containers.
Patch
diff --git a/Source/WebCore/css/CSSStyleSheet.cpp b/Source/WebCore/css/CSSStyleSheet.cpp
index 4a58423df7b8..3d4d2f2f6cb1 100644
--- a/Source/WebCore/css/CSSStyleSheet.cpp
+++ b/Source/WebCore/css/CSSStyleSheet.cpp
@@ -274,7 +274,8 @@ void CSSStyleSheet::forEachStyleScope(NOESCAPE const Function<void(Style::Scope&
apply(*scope);
return;
}
- for (Ref treeScope : m_adoptingTreeScopes)
+
+ for (const auto& treeScope : copyToVectorOf<Ref<ContainerNode>>(m_adoptingTreeScopes))
apply(protect(styleScopeFor(treeScope)));
}