[5] Frame-tree ancestor-walk consolidation (`FrameAncestorIterator`)
Low — no exploitable defect is identified here. This is a semantics-preserving consolidation of 24 hand-rolled ancestor walks; the value is forward-looking, and the one thing worth carrying away is where type-filtered walks fail open on RemoteFrame ancestors.
WebCore's frame tree is the per-process view of the browsing-context hierarchy: in-process frames are LocalFrame, out-of-process frames appear as RemoteFrame placeholders. Many security decisions walk a frame's ancestors — CSP frame-ancestors, X-Frame-Options, Document::canAccessAncestor, secure-context inheritance, WebAuthn relying-party scoping. Before this change each walk was re-implemented by hand, independently deciding its start node, null handling, and how to treat a non-LocalFrame ancestor.
The angle: for audit value — a RemoteFrame ancestor skipped by a type-filtered policy walk contributes no origin and therefore cannot cause a deny, so a walk that skips rather than fails on remote ancestors is the fail-open direction under site isolation.
The diff introduces FrameAncestorIterator/FrameAncestorRange and converts 24 ancestor walks to them; the commit does not carry a message detailing a specific motivating defect.
Source/WebCore/page/Frame.h
Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp
Patch Details
The diff adds FrameAncestorIterator/FrameAncestorRange and converts 24 hand-rolled ancestor walks — including the cross-origin policy sites above, the loader, focus/fullscreen/user-gesture plumbing, accessibility, and two WebProcess callers. FrameAncestorRange holds an owning RefPtr<Frame> m_first so a temporary range passed into std::ranges::any_of keeps its start frame alive, and the iterator's RefPtr<FrameType> m_current replaces the raw Frame* loop variables in FrameLoader::allAncestorsAreComplete/started, the same two functions that lose their NODELETE annotation in FrameLoader.h.
Fail-open-by-omission: a type-filtered ancestor walk silently skips out-of-process ancestors in security-sensitive traversals.
Background
Local vs. remote frames. Under site isolation, a cross-origin subframe lives in another process and appears locally as a RemoteFrame placeholder carrying no in-process origin; same-site frames are LocalFrame.
Why the walks matter. Several ancestor walks are the cross-origin policy decision: CSP frame-ancestors, XFO, canAccessAncestor, secure-context inheritance, WebAuthn RP scoping.
firstOfType() semantics. The new primitive's firstOfType() encodes skip as the default for ancestors of the wrong type. For rendering/accessibility walks, skipping is correct.
Analysis
No memory-safety or logic defect is identified here. Comparing each hunk against its predecessor, the start node and the skip-vs-fail treatment of non-LocalFrame ancestors are identical before and after at all 24 sites, so this reads as a semantics-preserving consolidation of security-sensitive traversal code; no commit message accompanies the change to indicate otherwise.
The mechanism worth naming anyway: for a policy walk under site isolation, skipping a RemoteFrame ancestor is fail-open — it lives in another process, contributes no origin, and cannot cause a deny. The diff shows both shapes side by side. checkFrameAncestors uses ancestorFrames<LocalFrame>(frame) and skips remote ancestors. A second checkFrameAncestors(..., const Vector<Ref<SecurityOrigin>>& ancestorOrigins) overload evaluates the same directive against an explicitly supplied ancestor-origin list — whether that path covers the remote ancestors the type-filtered walk skips is the open audit question, and determining which callers use which form is the first step. shouldInterruptLoadForXFrameOptions instead uses the untyped ancestorFrames(...) plus an explicit !localFrame -> return true, the fail-closed shape.
Audit directions
- Type-filtered ancestor walks in policy code. Any security decision using
ancestorFrames<LocalFrame>(...)must have a separate path that accounts for remote ancestors (the origin-list overload, or a fail-closed default). Audit CSPframe-ancestors, XFO,canAccessAncestor, and WebAuthn RP scoping for whether aRemoteFrameancestor can make the walk under-count. In code review,ancestorFrames<LocalFrame>inside a function that decides allow/deny is the tell — contrast with the!localFrame -> return truefail-closed pattern. - Owning-range lifetime under
std::rangesadaptors.FrameAncestorRange's owningm_firstexists to keep a temporary alive acrossstd::ranges::any_of; confirm new callers don't strip that ownership when composing range adaptors.