← All issues

[4] FrameLoader inherits empty registrable domain for about:blank popups

Severity: Medium | Component: WebCore FrameLoader | d6c7e00

Rated Medium because the diff fixes a reliable third-party-cookie-blocking bypass reachable from any first-party page via window.open('about:blank'); impact is privacy/tracking, not memory safety.

When an about:blank popup made cross-origin requests, its firstPartyForCookies was set to about:blank, which has an empty registrable domain. thirdPartyCookieBlockingDecisionForRequest() returns ThirdPartyCookieBlockingDecision::None for empty domains, bypassing third-party cookie blocking. FrameLoader::updateFirstPartyForCookies() should inherit the opener's firstPartyForCookies when the main frame URL is an owner-inherited document.

Source/WebCore/loader/FrameLoader.cpp

void FrameLoader::updateFirstPartyForCookies()
{
- if (RefPtr page = m_frame->page())
- setFirstPartyForCookies(page->mainFrameURL());
+ RefPtr page = m_frame->page();
+ if (!page)
+ return;
+
+ auto firstPartyForCookies = page->mainFrameURL();
+ if (SecurityPolicy::shouldInheritSecurityOriginFromOwner(firstPartyForCookies)) {
+ if (RefPtr opener = dynamicDowncast<LocalFrame>(page->mainFrame().opener())) {
+ if (RefPtr openerDocument = opener->document())
+ firstPartyForCookies = openerDocument->firstPartyForCookies();
+ }
+ }
+
+ setFirstPartyForCookies(firstPartyForCookies);
}

LayoutTests/http/tests/resourceLoadStatistics/third-party-cookie-blocking-about-blank-popup.html

+ case "#step2":
+ var popup = window.open("about:blank");
+ popup.eval(
+ "fetch('" + thirdPartyOrigin + "/cookies/resources/echo-cookies.py', { credentials: 'include' })" + ...
+ );

updateFirstPartyForCookies() previously set the frame's firstPartyForCookies to page->mainFrameURL() unconditionally. The fix adds a check: if the main frame URL is one that should inherit its security origin from its owner (via SecurityPolicy::shouldInheritSecurityOriginFromOwner()), it walks to page->mainFrame().opener() (downcast to LocalFrame), retrieves the opener document's firstPartyForCookies(), and uses that instead.

Failure to mirror opener/parent inheritance into the first-party-for-cookies field when the document URL is an owner-inherited scheme like about:blank.

firstPartyForCookies is a per-frame URL recorded on outgoing requests; the network layer uses its registrable domain (eTLD+1) as the "first party" when deciding whether a cookie destination is first- or third-party. For about:blank and other opaque schemes, the registrable domain is the empty string. WebKit's third-party cookie blocking (Intelligent Tracking Prevention) suppresses cookies on cross-site subresource requests when the destination has been classified as a tracker; the decision is gated on thirdPartyCookieBlockingDecisionForRequest(). Per HTML spec, an about:blank document inherits its security origin from the browsing context that created it; SecurityPolicy::shouldInheritSecurityOriginFromOwner(url) returns true for the set of URL schemes (about:blank, about:srcdoc, data: etc.) that follow this rule.

When a page opened an about:blank popup, the popup's main frame URL was literally about:blank. updateFirstPartyForCookies() then set firstPartyForCookies on the popup's frame to that URL. When the popup subsequently issued a cross-origin request, the network layer computed the registrable domain of about:blank — empty. thirdPartyCookieBlockingDecisionForRequest() treats an empty registrable domain as None, i.e. "do not block", because there is no meaningful first party to compare against.

🔒

Detailed analysis of the cookie-policy boundary that this opener-inheritance gap weakens, and the realistic conditions under which it can be triggered from web content

Subscribe to read more

🔒

Multiple reusable audit patterns identified across WebKit's first-party / origin-inheritance machinery, with concrete grep starting points and a site-isolation caveat worth examining

Subscribe to read more