874b112868 [Site Isolation] Storage access isn't revoked when an iframe navigates itself cross-origin
Triage note: Cross-origin storage-access isolation bypass; granted access persisted after a same-frame cross-origin navigation.
Contents
The bug at a glance
This is a cross-origin storage-access isolation bypass specific to Site Isolation: storage access granted to an iframe survived a self-initiated cross-origin navigation when a redirect bounced the load back to the original process. Persisting unpartitioned-storage access across an origin boundary violates the Storage Access API’s own limitation and can enable cross-site tracking/data access, a privacy/same-origin-policy weakening rather than memory corruption – consistent with a medium rating. Exploitation requires the target frame to already hold a storage-access grant.
The existing revocation on cross-origin document change happened in the destination process via WebLocalFrameLoaderClient::dispatchWillChangeDocument, but under Site Isolation a cross-origin navigation commits in a new process that never held the grant, and a redirect back to the origin’s process then sees an unchanged current URL and skips revocation. The fix moves revocation earlier – to dispatchDecidePolicyForNavigationAction in the source process at navigation start – so a frame navigating itself cross-origin loses storage access before any redirect can preserve it.
Root cause
Storage access under the Storage Access API is a grant given to a nested (iframe) document, e.g. after a user gesture via requestStorageAccess(). Per the spec the grant must last only until the nested document navigates across an origin boundary. WebKit historically enforced revocation in WebLocalFrameLoaderClient::dispatchWillChangeDocument: when the document is about to change and the new origin differs from the old, it calls removeStorageAccess().
Site Isolation changes the process model: a cross-origin navigation does not stay in the same web process, it commits in the process assigned to the destination site. dispatchWillChangeDocument still runs and still performs its origin check, but it now runs in the destination process, which never held the grant. So removeStorageAccess() there is a no-op – the grant it wants to drop lives in a different process.
The bug becomes observable with a redirect. The commit’s worked example: a localhost iframe (holding storage access) navigates to 127.0.0.1, a cross-origin site. Site Isolation spins up a new 127.0.0.1 process; dispatchWillChangeDocument there fails the cross-origin check and calls removeStorageAccess(), but that process never had access, so nothing changes. The 127.0.0.1 endpoint then redirects back to localhost, and the navigation is re-hosted in the original localhost process. There, dispatchWillChangeDocument compares the document’s current URL (still localhost) with the new URL (localhost) and, seeing them same-origin, does not revoke. Net result: the original localhost process retains storage access even though the iframe underwent a cross-origin navigation.
The fix relocates the enforcement to the source process at navigation start, in WebLocalFrameLoaderClient::dispatchDecidePolicyForNavigationAction. The rewritten guard first checks the navigation requester. If another frame initiated the navigation (requester frameID differs from this frame’s, and the requester’s Site differs from the frame’s Site), it removes storage access as before. The new branch handles the frame navigating itself: when *requestor->frameID == m_frame->frameID(), it checks redirectResponse.isNull() (i.e. this is the initial navigation, not a redirect continuation) and whether the frame’s current origin is not same-origin as the request URL, and if so calls removeStorageAccess(). Because this severs access in the source process the moment a same-frame cross-origin navigation begins, no downstream redirect – including one that returns to the original process – can preserve the grant. The layout test request-and-grant-access-then-navigate-cross-site-should-not-have-access.https.html is removed from the Site Isolation failure expectations, confirming it now passes with isolation enabled.
Key code
Revoke storage access at navigation start for a self cross-origin navigation (WebLocalFrameLoaderClient.cpp)
if (auto requestor = navigationAction.requester(); requestor && requestor->frameID) {
// another frame initiated navigation
if (*requestor->frameID != m_frame->frameID() && Site(requestor->url) != Site(m_frame->url()))
removeStorageAccess();
// this frame navigated itself
else if (*requestor->frameID == m_frame->frameID()) {
if (redirectResponse.isNull() && !SecurityOrigin::create(m_frame->url())->isSameOriginAs(SecurityOrigin::create(request.url())))
removeStorageAccess();
}
}
Patch walkthrough
Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp— dispatchDecidePolicyForNavigationAction’s requester check is restructured. The condition now binds requestor and requires requestor->frameID up front. If another frame initiated the navigation (different frameID and different Site), removeStorageAccess() is called as before. A new else-if handles the frame navigating itself (same frameID): when redirectResponse.isNull() (initial navigation) and the frame’s current origin is not same-origin as request.url() (SecurityOrigin::create(m_frame->url()) vs SecurityOrigin::create(request.url())), it calls removeStorageAccess(). This revokes at the source process at navigation start, independent of any later redirect.LayoutTests/platform/mac-site-isolation/TestExpectations— Removes request-and-grant-access-then-navigate-cross-site-should-not-have-access.https.html from the list of expected failures under Site Isolation, since the fix makes it pass with isolation enabled.
Background
Storage Access API grant — A per-nested-document permission that lets an embedded cross-site iframe use unpartitioned cookies/storage, typically after requestStorageAccess() and a user gesture. The spec explicitly limits the grant to last only until the nested document navigates across an origin boundary, which is the invariant this bug violated.
Site Isolation process model — With Site Isolation, frames of different sites run in different web processes, and a cross-origin navigation commits in the destination site’s process. This means per-process state such as a storage-access grant does not automatically transfer, so revocation logic keyed to the committing document can run in the wrong process.
dispatchWillChangeDocument — The pre-existing revocation point: invoked as a document is about to change, it did the cross-origin comparison and called removeStorageAccess(). Under Site Isolation it runs in the destination process (which lacks the grant) and, after a redirect back to the origin process, sees an unchanged current URL and skips revocation.
dispatchDecidePolicyForNavigationAction — The policy hook consulted in the source process when a navigation begins, before commit and before any redirect resolves. Moving revocation here makes the grant severed at the origin, so redirects cannot resurrect it.
redirectResponse.isNull() — Distinguishes the initial navigation decision from a policy decision for a redirect continuation. The self-navigation branch requires it to be null so revocation triggers on the frame’s own initial cross-origin navigation rather than on redirect legs, matching the intent to cut access at the source.
Vulnerability window
- Baseline behavior — Storage access was revoked on cross-origin document change in dispatchWillChangeDocument, correct in the single-process model.
- Site Isolation regression — With isolation, cross-origin navigations commit in a new process lacking the grant, so the destination-process revocation is a no-op and does not touch the origin process’s grant.
- Redirect preserves access — A redirect back to the original site re-uses the origin process, where the current-URL check sees no origin change and skips revocation, leaving the grant alive across the cross-origin trip.
- Discovery — Filed as bug 316853 / rdar://179286611; identified via the failing storageAccess navigate-cross-site test under Site Isolation.
- Fix — Committed as 315222@main: revoke at the source in dispatchDecidePolicyForNavigationAction when a frame navigates itself cross-origin, and drop the test from the failure expectations.
Triggering
No exploit PoC is added; the patch only edits TestExpectations to un-skip an existing WPT-style test. Trigger: with Site Isolation enabled, an iframe obtains storage access (e.g. via requestStorageAccess under a user gesture), then navigates itself to a cross-origin URL that redirects back to the original origin (the localhost -> 127.0.0.1 -> localhost example in the commit). On an unpatched build the iframe still holds storage access after the round trip; the un-skipped test request-and-grant-access-then-navigate-cross-site-should-not-have-access.https.html observes that retained access.
Exploitation
- Obtain a grant — An embedded cross-site iframe acquires storage access through the Storage Access API, typically gated on a user gesture.
- Self-navigate cross-origin via redirect — The iframe navigates itself to a cross-origin endpoint that immediately redirects back to its original origin, exploiting Site Isolation’s process reuse so the origin process’s grant is never revoked.
- Retain unpartitioned access — After the round trip the frame continues to use unpartitioned cookies/storage across the origin boundary, enabling cross-site data access or tracking that the spec intends to forbid.
- Note — This is a policy/isolation bypass, not memory corruption; impact is privacy/cross-origin data exposure and it presupposes a legitimately obtained initial grant.
Detection & hunting
For defenders and SOC / detection engineers:
- Storage access surviving cross-origin navigation —
- Revocation running in a process without the grant —
- WPT storage-access navigation tests under isolation —
Audit directions
- All per-process security-state revocation —
- Requester-based navigation checks —
- Redirect handling —
- Origin vs Site comparisons —