cd11ac52db [Site Isolation] NetworkProcess::m_allowedFirstPartiesForCookies not restored for subframe processes after crash
Triage note: Cross-origin cookie isolation state lost on NP relaunch for subframe processes.
Contents
The bug at a glance
Under Site Isolation, a NetworkProcess crash left cross-origin subframe web processes unable to prove first-party-for-cookies authorization, so their subsequent cookie/fetch IPCs failed allowsFirstPartyForCookies MESSAGE_CHECKs and the subframe web processes were killed. This is a functional/availability and security-model consistency defect: legitimate cross-origin iframes break after a network-process relaunch, and the recovery path was reconstructing the allow-list only from main-frame state. Because MESSAGE_CHECK failures terminate the offending web process, the bug is a reliable cross-origin-subframe denial of service and a sign the cookie-authorization state machine was not crash-durable. It is not a memory-corruption bug, hence high-but-not-critical.
The subtlety is where the authoritative copy of ‘which first parties this web process may claim for cookies’ lived. It was reconstructed on network-process (re)launch from WebProcessProxy::allowedFirstPartiesForCookies(), which only walked pages’ legacy main-frame process and current URL – so cross-origin subframe processes’ grants evaporated across a crash. The fix moves the source of truth onto each WebProcessProxy (which survives NetworkProcess crashes) and re-sends it per-connection.
Root cause
In the NetworkProcess, cookie access is gated per web process by m_allowedFirstPartiesForCookies, a map from web-process identifier to (LoadedWebArchive, set-of-RegistrableDomain). Cookie-bearing IPCs are validated against this map via allowsFirstPartyForCookies MESSAGE_CHECKs; a failed check is treated as a misbehaving/compromised web process and terminates it.
Before this patch the map was seeded at NetworkProcess startup inside initializeNetworkProcess from NetworkProcessCreationParameters::allowedFirstPartiesForCookies, and that vector was built by NetworkProcessProxy::sendCreationParametersToNewProcess calling the static WebProcessProxy::allowedFirstPartiesForCookies(). That static method iterated globalPages() and, for each page, appended only (page->legacyMainFrameProcess().coreProcessIdentifier(), RegistrableDomain(page->currentURL())). Under Site Isolation a page’s cross-origin subframes run in separate web processes whose identifiers and first-party domains are absent from that enumeration. So when the NetworkProcess crashed and a fresh one launched, its allow-list contained only main-frame processes. A cross-origin subframe process that then issued a cookie/fetch IPC failed the MESSAGE_CHECK and was killed.
The fix relocates the durable state to WebProcessProxy, which outlives NetworkProcess crashes. WebProcessProxy gains a member std::pair<LoadedWebArchive, HashSet<RegistrableDomain>> m_allowedFirstPartiesForCookies and an addAllowedFirstPartyForCookies(domain, loadedWebArchive) mutator; the old static allowedFirstPartiesForCookies() enumeration is deleted and replaced by allowedFirstPartiesForCookiesData() returning the stored pair. NetworkProcessProxy::addAllowedFirstPartyForCookies now also records the grant into the owning WebProcessProxy, so every grant accumulates on the process object.
Delivery moves from the one-shot creation parameters to a per-connection channel: NetworkProcessConnectionParameters gains loadedWebArchive and a HashSet<RegistrableDomain> allowedFirstPartiesForCookies (plus serialization entries and a LoadedWebArchive.h / RegistrableDomain.h include). In getNetworkProcessConnection the proxy copies webProcessProxy.allowedFirstPartiesForCookiesData() into these parameters. On the NetworkProcess side, createNetworkConnectionToWebProcess now rebuilds m_allowedFirstPartiesForCookies for the reconnecting identifier using ensure(…) with the incoming pair, OR-ing LoadedWebArchive::Yes and add()-ing each domain. Because every web process re-establishes its NetworkProcessConnection after a crash, each one now re-supplies its own complete first-party set – subframe processes included – so the authorization map is fully reconstructed and the MESSAGE_CHECKs pass. The stale NetworkProcessCreationParameters::allowedFirstPartiesForCookies field and its initializeNetworkProcess seeding loop are removed as dead.
Key code
NetworkProcess rebuilds the per-process cookie allow-list from each reconnecting web process (NetworkProcess.cpp)
auto& [currentLoadedWebArchive, currentDomains] = m_allowedFirstPartiesForCookies.ensure(identifier, [&] {
return std::make_pair(parameters.loadedWebArchive, HashSet<RegistrableDomain> { });
}).iterator->value;
if (parameters.loadedWebArchive == LoadedWebArchive::Yes)
currentLoadedWebArchive = LoadedWebArchive::Yes;
for (auto& domain : parameters.allowedFirstPartiesForCookies)
currentDomains.add(domain);
Patch walkthrough
Source/WebKit/UIProcess/WebProcessProxy.cpp / .h— Replaces the static allowedFirstPartiesForCookies() (which only sampled each page’s main-frame process + currentURL) with a per-instance m_allowedFirstPartiesForCookies pair and addAllowedFirstPartyForCookies()/allowedFirstPartiesForCookiesData() accessors, making each web process the durable owner of its cookie grants.Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp— sendCreationParametersToNewProcess drops the old allowedFirstPartiesForCookies seeding; getNetworkProcessConnection now copies the process’s stored pair into the per-connection parameters; addAllowedFirstPartyForCookies also forwards the grant to the WebProcessProxy so it persists across NetworkProcess crashes.Source/WebKit/NetworkProcess/NetworkProcess.cpp— initializeNetworkProcess no longer seeds the map from creation parameters; createNetworkConnectionToWebProcess rebuilds m_allowedFirstPartiesForCookies per identifier from the connection parameters (ensure + OR LoadedWebArchive + add domains); adds a RELEASE_LOG when a connection is removed.Source/WebKit/Shared/NetworkProcessConnectionParameters.h / .serialization.in— Adds loadedWebArchive and HashSet<RegistrableDomain> allowedFirstPartiesForCookies to the per-connection parameters (with LoadedWebArchive.h and WebCore/RegistrableDomain.h includes) so each reconnecting web process carries its own cookie grants to the new NetworkProcess.Source/WebKit/NetworkProcess/NetworkProcessCreationParameters.h / .serialization.in— Removes the now-dead allowedFirstPartiesForCookies vector from the one-shot creation parameters, since authorization is delivered per-connection instead.LayoutTests/http/tests/security/cross-origin-iframe-fetch-after-crash*— New regression test: a cross-origin iframe fetches successfully, the test terminates the NetworkProcess, waits for relaunch, and asserts the cross-origin iframe can still fetch afterward (previously the subframe process would be killed by the failed MESSAGE_CHECK).
Background
Site Isolation and subframe processes — With Site Isolation, cross-origin iframes are hosted in separate web processes from the page’s main frame. This means the security-relevant state for a single tab is spread across multiple WebProcessProxy objects, and any network-side authorization keyed by web-process identifier must account for every one of them, not just the main-frame process.
allowedFirstPartiesForCookies / MESSAGE_CHECK — The NetworkProcess keeps m_allowedFirstPartiesForCookies mapping each web-process identifier to the registrable domains it is permitted to name as the cookie first party. Cookie/fetch IPCs are validated with allowsFirstPartyForCookies MESSAGE_CHECKs. A MESSAGE_CHECK failure is deliberately fatal: it terminates the sending web process on the assumption it is compromised or buggy, which is exactly why an incorrectly-empty allow-list turns into a subframe-process kill.
LoadedWebArchive — A per-process flag indicating the process has loaded a web archive, which relaxes first-party-for-cookies checks (archives can legitimately reference many origins). It is tracked alongside the domain set and must be OR-ed across grants, so the patch carries it through the per-connection parameters and merges it (Yes wins) when rebuilding the map.
Creation vs connection parameters — NetworkProcessCreationParameters are sent once when a NetworkProcess is launched; NetworkProcessConnectionParameters are sent each time a web process establishes (or re-establishes) its connection. Moving the cookie allow-list from the former to the latter is the crux of the fix: connection setup happens again after a crash and is driven by each individual web process, so every process re-supplies its own grants.
Crash durability of authorization state — A NetworkProcess is disposable and restarts on crash, losing all in-memory state including m_allowedFirstPartiesForCookies. The UI-process WebProcessProxy objects persist across such restarts, making them the correct durable home for grants that must be replayed to a fresh NetworkProcess. The bug was that the replay source only reflected main-frame state.
Vulnerability window
- Baseline — NetworkProcess seeds m_allowedFirstPartiesForCookies from creation parameters built by WebProcessProxy::allowedFirstPartiesForCookies(), which samples only each page’s legacy main-frame process and currentURL.
- Site Isolation added — Cross-origin subframes move into separate web processes whose cookie grants are not captured by the main-frame-only enumeration.
- Crash — The NetworkProcess crashes; a fresh one launches with an allow-list containing only main-frame processes’ domains.
- Subframe kill — A surviving cross-origin subframe process issues a cookie/fetch IPC, fails the allowsFirstPartyForCookies MESSAGE_CHECK, and is terminated – the observed breakage (bug 314951, rdar://177247707).
- Fix — Grants are stored durably per WebProcessProxy and re-sent via NetworkProcessConnectionParameters on reconnect; NetworkProcess rebuilds its map per identifier from each connecting process.
- Regression test — cross-origin-iframe-fetch-after-crash.html asserts a cross-origin iframe still fetches after a forced NetworkProcess termination and relaunch.
Proof of concept
The added LayoutTest embeds a cross-origin (localhost:8000 vs 127.0.0.1) iframe that fetches once successfully, then the top frame calls testRunner.terminateNetworkProcess(), waits for relaunch, and asks the iframe to fetch again. Pre-patch the subframe process is killed by the failed allowsFirstPartyForCookies MESSAGE_CHECK after relaunch; post-patch the fetch succeeds because the process re-supplies its own first-party grants on reconnection.
window.addEventListener("message", async event => {
if (event.data === "pre-crash-ok") {
log("Cross-origin iframe fetch succeeded before network process crash.");
if (window.testRunner)
testRunner.terminateNetworkProcess();
await waitForNetworkProcessToRelaunch();
document.querySelector("iframe").contentWindow.postMessage("fetch-after-crash", "*");
return;
}
if (event.data === "post-crash-ok") {
log("PASS: Cross-origin iframe fetch succeeded after network process crash.");
} else {
log("FAIL: " + event.data);
}
if (window.testRunner)
testRunner.notifyDone();
});
Exploitation
- Trigger — Cause or wait for a NetworkProcess crash while a cross-origin subframe process is alive. NetworkProcess crashes are reachable/inducible via other NetworkProcess bugs or resource exhaustion; testRunner.terminateNetworkProcess() models it directly.
- Effect (availability) — After relaunch, the subframe process’s first cookie/fetch IPC fails the MESSAGE_CHECK and the subframe web process is terminated – a reliable cross-origin-subframe denial of service that breaks embedded content until reload.
- Security-model angle — The bug is a gap in crash-durable authorization state rather than a spoofing/bypass primitive; there is no evidence the missing entries let a process gain cookies it should not have – the failure is over-restrictive (kill), not permissive. So exploitation value is DoS and process-lifecycle disruption, not cross-origin cookie theft.
- Post-fix — Each web process now re-declares its own first-party set on reconnect, so the map is fully rebuilt and the MESSAGE_CHECKs pass; the DoS window is closed.
Detection & hunting
For defenders and SOC / detection engineers:
- Subframe process termination after NetworkProcess relaunch — Correlate NetworkProcess crash/relaunch events with immediately-following web-process terminations attributed to allowsFirstPartyForCookies MESSAGE_CHECK failures; a tight temporal pairing is the fingerprint of this bug (or an attempt to exploit the DoS).
- IPC MESSAGE_CHECK kill logs — Monitor for clusters of MESSAGE_CHECK-triggered process kills referencing first-party-for-cookies validation, especially for processes hosting cross-origin iframes, and the new RELEASE_LOG(Process, …removeNetworkConnectionToWebProcess…) entries around connection churn.
- Repeated NetworkProcess terminations — An attacker seeking to weaponize the DoS would repeatedly induce NetworkProcess crashes; alert on abnormal NetworkProcess restart frequency per session.
Audit directions
- Other NetworkProcess state seeded from main-frame-only enumerations — Search UIProcess for other per-web-process authorization state (file-path allow-lists, storage access, etc.) reconstructed via page->legacyMainFrameProcess() enumerations that would similarly miss Site-Isolation subframe processes across a crash.
- Creation- vs connection-parameter placement — Review which security grants are delivered via one-shot NetworkProcessCreationParameters and confirm they are re-established per NetworkProcessConnection; any that are not are candidates for the same crash-durability gap.
- Merge semantics of grants — Verify the ensure/OR/add merge in createNetworkConnectionToWebProcess cannot drop or over-grant domains under repeated reconnects, and that LoadedWebArchive::Yes is never silently downgraded.
- MESSAGE_CHECK fatality vs recoverable state — Audit MESSAGE_CHECKs that gate on state which can be legitimately lost (e.g., across peer-process crashes); a fatal check over recoverable authorization is a DoS pattern and should be re-derivable rather than assumed present.