← WebKit — 2026-W36

a57966243b6cc373a187a7088f2eebeeeb316daf  The Networking process should validate download IDs coming from the WebContent process

severity high class AuthorizationError confidence 0.8 signal score 14.81 Source/WebKit/NetworkProcess patch-now AI review CONFIRMED
Primitive: any WebContent process could start/convert a download without UIProcess authorization or claim another process's download
Triage note: startDownload and convertMainResourceLoadToDownload now call consumeDownloadAuthorization, which checks m_authorizedDownloads (populated by the trusted UIProcess via AuthorizeDownloadForWebProcess) and MESSAGE_CHECKs the authorized process equals m_webProcessIdentifier. Adds a missing cross-process authorization gate on NetworkProcess download initiation.
Structural signals: smartptr_member_added (+6.47), null_guard (+2.59), uaf_lifetime (+4.75), msg_soft (+1.0)

The bug at a glance

The NetworkProcess entry points that start a download (NetworkConnectionToWebProcess::startDownload) or convert an in-flight main-resource load into a download (convertMainResourceLoadToDownload) had no check that the calling WebContent process was authorized by the trusted UIProcess to drive that DownloadID. Any WebContent process reaching its NetworkConnectionToWebProcess could therefore begin a download, or claim a DownloadID that logically belonged to another process. This is a cross-process authorization (sandbox-boundary) defect in the NetworkProcess, not memory corruption: the proven impact is unauthorized/hijacked download initiation. High severity is defensible because it is a missing trust gate between the untrusted WebContent process and the privileged NetworkProcess, but any escalation beyond download hijack is (inferred).

A classic ’trusted-UIProcess-decides, NetworkProcess-enforces’ split where the enforcement half was simply never written: the NetworkProcess accepted a download-start IPC on faith, so the authorization the UIProcess thought it was granting via policy decisions was never actually consumed.

Root cause

The violated invariant is that a download may only be started in the NetworkProcess by the WebContent process the UIProcess authorized for that specific DownloadID. Pre-patch there was no m_authorizedDownloads map and no consumer at all; startDownload() and convertMainResourceLoadToDownload() ran straight into their firstPartyForCookies MESSAGE_CHECK and proceeded.

The pre-patch sequence an attacker-controlled WebContent process could exploit: 1. the WebContent process sends StartDownload (or ConvertMainResourceLoadToDownload) with a DownloadID of its choosing over its NetworkConnectionToWebProcess IPC channel; 2. NetworkConnectionToWebProcess::startDownload executes with no check tying that DownloadID to an authorization record; 3. the only gate present is MESSAGE_CHECK on allowsFirstPartyForCookies for the request’s firstPartyForCookies, which governs cookie scope, not who may own the download; 4. the download begins, so a process can initiate a download the UIProcess never sanctioned, or race/claim a DownloadID intended for a different process.

The fix introduces m_authorizedDownloads (HashMap<DownloadID, WebCore::ProcessIdentifier>) in NetworkProcess, populated only by the trusted UIProcess through the new AuthorizeDownloadForWebProcess IPC (NetworkProcessProxy::authorizeDownloadForWebProcess). consumeDownloadAuthorization() looks up authorizedProcessForDownload(downloadID) and MESSAGE_CHECK_WITH_RETURN_VALUE(*authorizedProcess == m_webProcessIdentifier, false) — so a mismatch is treated as a malicious IPC. On teardown, removeNetworkConnectionToWebProcess now scrubs m_authorizedDownloads of every entry whose value is the departing process. The parallel WebPageProxy.cpp edits thread a processInitiatingNavigation argument into every receivedPolicyDecision call so the UIProcess records the correct initiating process when it authorizes the download; this is the authorization-producing half of the same change (the authorize call itself lives outside this diff) (inferred).

Key code

New cross-process authorization gate consumed on every download start

bool NetworkConnectionToWebProcess::consumeDownloadAuthorization(DownloadID downloadID)
{
    auto authorizedProcess = m_networkProcess->authorizedProcessForDownload(downloadID);
    if (!authorizedProcess) { /* RELEASE_LOG + */ return false; }
    MESSAGE_CHECK_WITH_RETURN_VALUE(*authorizedProcess == m_webProcessIdentifier, false);
    m_networkProcess->removeAuthorizedDownload(downloadID);
    return true;
}
// in startDownload / convertMainResourceLoadToDownload:
if (!consumeDownloadAuthorization(downloadID))
    return;

Fix approach

The patch adds an explicit per-DownloadID authorization ledger owned by the NetworkProcess and fed only by the trusted UIProcess (AuthorizeDownloadForWebProcess -> m_authorizedDownloads.set(downloadID, processIdentifier)). Both NetworkProcess-side download entry points now call consumeDownloadAuthorization() first, which (a) refuses unauthorized DownloadIDs, (b) MESSAGE_CHECKs that the recorded process equals the caller, and (c) removes the entry so an authorization is single-use. A connection closing purges its outstanding authorizations so a recycled ProcessIdentifier cannot inherit one. Semantically, download initiation is now a two-party handshake: the UIProcess must have authorized this exact process for this exact DownloadID before the NetworkProcess will act, collapsing the prior ‘any WebContent process may start any download’ surface to ‘only the UIProcess-designated owner.’

Patch walkthrough

  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp — Adds consumeDownloadAuthorization() and calls it at the top of startDownload() and convertMainResourceLoadToDownload(), returning early (and single-use-removing the entry) when the caller is not the authorized process.
  • Source/WebKit/NetworkProcess/NetworkProcess.cpp — Adds the m_authorizedDownloads store with authorizeDownloadForWebProcess()/authorizedProcessForDownload()/removeAuthorizedDownload(), and scrubs the map for a departing process in removeNetworkConnectionToWebProcess().
  • Source/WebKit/NetworkProcess/NetworkProcess.messages.in — Declares the new AuthorizeDownloadForWebProcess(processIdentifier, downloadID) -> () IPC so only the UIProcess can populate authorizations.
  • Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp — Implements authorizeDownloadForWebProcess() which sends the new message from the trusted UIProcess to the NetworkProcess.
  • Source/WebKit/UIProcess/WebPageProxy.cpp — Threads a processInitiatingNavigation argument through every receivedPolicyDecision() call path so the UIProcess attributes the download to the correct initiating process when authorizing it.

Background

NetworkProcess / WebContent process split — WebKit isolates untrusted web content in a sandboxed WebContent process; network and download work runs in the separate, more privileged NetworkProcess, reached only over validated IPC.

NetworkConnectionToWebProcess — The NetworkProcess-side object representing one WebContent process’s IPC connection; it carries m_webProcessIdentifier, the authenticated identity of the peer, used to attribute and gate requests.

MESSAGE_CHECK / MESSAGE_CHECK_WITH_RETURN_VALUE — WebKit IPC-hardening macros that treat a failed invariant as a malicious message, typically terminating the offending connection rather than continuing in an undefined state.

DownloadID — An opaque identifier for a download; because it is attacker-chosen in the IPC, it must be bound to an authorizing process to prevent one process initiating or claiming another’s download.

UIProcess as trust anchor — The UIProcess is the trusted coordinator that makes navigation/policy decisions; here it is the only party permitted to populate the NetworkProcess authorization ledger via AuthorizeDownloadForWebProcess.

Vulnerability window

  1. Setup — Attacker gains code execution in (or simply scripts) a WebContent process that holds a NetworkConnectionToWebProcess to the NetworkProcess.
  2. Trigger — The process sends StartDownload or ConvertMainResourceLoadToDownload with a chosen DownloadID and request.
  3. Pre-patch behavior — NetworkProcess starts the download with no authorization check, enabling unsanctioned downloads or claiming another process’s DownloadID.
  4. Post-patch behavior — consumeDownloadAuthorization() finds no matching authorization or a process mismatch and refuses, MESSAGE_CHECK terminating a forged claim.

Proof of concept

No regression test ships in the diff. Conceptual trigger (inferred): from a compromised WebContent process, send the NetworkProcess StartDownload/ConvertMainResourceLoadToDownload IPC with an arbitrary or another-process DownloadID; pre-patch the download proceeds, post-patch it is refused/terminated. The web-reachable path to inducing a download conversion is a navigation whose policy decision becomes a download; the forgery is the IPC-level DownloadID choice (inferred).

Exploitation

  1. Reach — Requires sending NetworkProcess IPC, i.e. a WebContent process (compromised, or abusing a legitimate download flow with a crafted DownloadID).
  2. Trigger — Invoke startDownload/convertMainResourceLoadToDownload with an unauthorized or cross-process DownloadID.
  3. Primitive — Unauthorized download initiation or hijacking of another process’s download; bounded by the surviving firstPartyForCookies cookie-scope check.
  4. Escalation (inferred) — Impact beyond download misattribution (e.g. writing attacker-chosen content to disk under another context, or confusing download UI/attribution) depends on downstream download handling not shown in this diff.

Detection & hunting

For defenders and detection engineers:

  • Download start with no authorization record —
  • Process-mismatch IPC termination —
  • Stale authorization cleanup —

Remaining risk in the patch

  • The authorize-side is outside this diff — The code that actually calls authorizeDownloadForWebProcess during a legitimate policy-to-download conversion is not shown here; correct and complete population of m_authorizedDownloads (every legitimate path, and only the right process) is unverified from the supplied diff.
  • ProcessIdentifier reuse — Authorization keys on WebCore::ProcessIdentifier; the cleanup on connection close mitigates reuse, but any path that authorizes before a process is fully associated could still misattribute (inferred).
  • FIXME runtime flag — The explicit ‘FIXME: add runtime feature flag in follow-up’ comment on the MESSAGE_CHECK suggests the gate may be behind rollout controls, so enforcement strength can vary by configuration.

Audit directions

  • Other NetworkProcess IPC entry points taking attacker-chosen identifiers —
  • Single-use authorization ledgers —
  • convertMainResourceLoadToDownload callers —

Before / after

Loading diff…