← All reports

[Site Isolation] Authorize cookie IPC against the sites a web process hosts

Site isolation gave iframes their embedder as first party, and its cookies too.

Component: WebKit Site Isolation | a5cb75b

Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp

+bool NetworkConnectionToWebProcess::hostsDomain(const RegistrableDomain& domain) const
+{
+ // A URL without a registrable domain, such as a file: URL, has no site to check.
+ return !m_hostedDomains || domain.isEmpty() || m_hostedDomains->contains(domain);
+}
...
+ // firstParty is not enough: under site isolation, a cross-site iframe's process is allowed its embedder as a
+ // first party, so it could otherwise name the embedder's URL and get the embedder's own cookies.
+ if (allowUnhostedURL == AllowUnhostedURL::No && !hostsDomain(RegistrableDomain { url })) {
+ CONNECTION_RELEASE_LOG_ERROR(IPC, "%" PUBLIC_LOG_STRING ": Rejecting cookie access for a site this process does not host", messageName.characters());
+ return CookieAccess::Disallow;
+ }

Source/WebKit/UIProcess/WebProcessProxy.cpp

+std::optional<HashSet<RegistrableDomain>> WebProcessProxy::hostedDomains() const
+{
+ if (m_hostedSiteTracking != HostedSiteTracking::Recorded)
+ return std::nullopt;
+ auto domains = m_sharedProcessDomains;
+ for (auto& site : m_committedSites) {
+ if (!site.isEmpty())
+ domains.add(site.domain());
+ }
+ return domains;
+}

Under site isolation, cross-site iframes run in separate WebContent processes, and the network process is the sole holder of the cookie store. It authorized cookie IPC (cookiesForDOM, setCookiesFromDOM, GetRawCookies, SetRawCookie, DeleteCookie, domCookiesForHost and others) by checking the firstParty a renderer supplied — but site isolation deliberately grants an out-of-process iframe its embedder's domain as an allowed first party, and cookies are selected by url, which was never checked.

The UI process now sends each process's hosted-domain set (WebProcessProxy::hostedDomains) to the network process, and validateCookieAccess requires the request url's registrable domain to be in it. cookieRequestHeaderFieldValueDigest opts out via AllowUnhostedURL::Yes, and domCookiesForHost now replies std::optional so WebCookieCache doesn't cache a denial as an empty jar. The check stays permissive (std::nullopt) with site isolation off, before a process is used for site isolation, in single-process mode, for web archives, and for remote-worker processes.

Before:
  iframe proc (evil.com) --IPC(firstParty=embedder, url=embedder)--> NetworkProcess
       validate(firstParty) OK --> returns embedder's cookies (HttpOnly too)

After:
  UIProcess: hostedDomains() --setHostedDomains--> NetworkProcess
  iframe proc --IPC(firstParty=embedder, url=embedder)--> validate(firstParty) OK
       hostsDomain(RegistrableDomain{url})?
         nullopt set | empty domain | contains()  --> allow
         otherwise                                --> Disallow (domCookiesForHost: nullopt)

A compromised cross-site iframe process could previously name its embedder's URL and read or write that site's cookies, HttpOnly included, over IPC. This is a new security boundary on a high-value target, built as an allowlist with several deliberate fail-open paths.