[1] SetRawCookie authorization bypass enables cross-origin cookie writes
A cookie check that only asked whether the message agreed with itself.
High. The one check that survived the refactor compares two attacker-supplied fields against each other, so a compromised WebContent process writes cookies for whatever origin it names. No memory corruption is involved — the capability itself is the bug.
Cookie writes in WebKit cross a privilege boundary: the sandboxed renderer asks, and a more-trusted process that owns the actual cookie store decides. NetworkConnectionToWebProcess — the per-web-process receiver in the NetworkProcess that services cookie read and write messages — owns that decision for every message arriving on one WebContent connection. Of the fields a SetRawCookie message carries, only firstParty can be checked against something the NetworkProcess independently knows: an allow-list of first-party origins that the UI process populates as frames commit.
The angle: a compromised renderer can set a cookie for an origin it does not host, by naming a first party it legitimately owns and pointing both the cookie's URL and its domain at the victim.
The commit message identifies firstParty as the sole field in this message whose value the NetworkProcess can independently corroborate, and explains why the receiver check had been relaxed: InspectorPageAgent::setCookie broadcast a single cookie to every frame's document, so an inspected page containing a cross-origin iframe always produced at least one iteration whose document->cookieURL() and document->firstPartyForCookies() disagreed — a legitimate sender tripping a correct receiver check.
The pre-fix handler carried exactly one domain predicate:
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
// Pre-fix: the only domain check on the SetRawCookie path.
RegistrableDomain::uncheckedCreateFromHost(cookie.domain).matches(url)
Patch Details
The change has two halves, receiver and sender. On the receiver side, the single field-to-field predicate is replaced by two predicates that anchor each attacker-supplied field — cookie.domain and url — to firstParty, restoring, per the commit message, the pair that preceded 320304@main. firstParty is the field validateCookieAccess has already run through allowsFirstPartyForCookies against state the UI process populates via NetworkProcess::AddAllowedFirstPartyForCookies. On the sender side, InspectorPageAgent::setCookie stops broadcasting one cookie to every frame's document and skips the frames whose cookieURL() and firstPartyForCookies() do not agree, so the legitimate in-tree sender no longer needs the receiver to be permissive. A third IPC test case is added covering the exact field combination the two existing cases did not: cross-origin cookie.domain alone and cross-origin url alone were already tested; a matched cross-origin pair was not.
An IPC authorization check that compares two attacker-supplied fields to each other instead of anchoring them to the one field the receiver can independently corroborate.
Background
Where this lives. WebKit splits work across four process roles: WebContent runs untrusted page script and layout, the GPU process runs rendering and media, the Networking process owns sockets and the shared cookie store, and the UI process hosts the application and brokers policy. Cookies live in the Networking process precisely so a renderer compromise does not hand an attacker the jar directly.
Per-connection receivers.
Each WebContent process gets a NetworkConnectionToWebProcess in the NetworkProcess — one receiver object bound to one connection, servicing that renderer's cookie messages. Because the renderer on the other end may be fully compromised, every field in every message is untrusted input; the receiver's job is to decide which of those fields, if any, it can tie back to something it knows independently.
The first-party allow-list.
firstParty is the one field with a trusted counterpart. As frames commit, the UI process sends NetworkProcess::AddAllowedFirstPartyForCookies naming the first-party origins a given web process is entitled to act for. validateCookieAccess consults that state through allowsFirstPartyForCookies; a message naming a first party the process does not hold produces a non-Allow access, or CookieAccess::Terminate.
Registrable domains.
RegistrableDomain reduces a host to its public-suffix-plus-one form, so login.example.co.uk and www.example.co.uk compare equal. uncheckedCreateFromHost builds one from a bare host string without validating that the string came from anywhere in particular — which is the correct primitive when the host is trusted and the wrong one to build a security predicate on when it is not.
Analysis
The bug class is a confused deputy across a process boundary, expressed as a single-predicate authorization check. Both operands of RegistrableDomain::uncheckedCreateFromHost(cookie.domain).matches(url) arrive in the same message from the same untrusted sender. The predicate therefore asserts only that the message is internally consistent; it asserts nothing about whether the sender is authorized. Per the commit message, the two predicates that preceded 320304@main each anchored one of those fields to firstParty; collapsing them into one field-to-field comparison removed the only link between the message and any trusted reference value.
WebContent (compromised) NetworkProcess
──────────────────────── ──────────────
SetRawCookie { ───► validateCookieAccess(firstParty)
firstParty: attacker.test └─ UIProcess-populated allow-list OK
url: victim-bank.test
cookie.domain: victim-bank.test RegistrableDomain(cookie.domain)
} .matches(url) OK
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^
│ both operands from the message
└──► privileged cookie store write
The new IPC test encodes the forgery shape directly: firstParty: location.origin, url: 'http://victim-bank.test/', cookie.domain: 'victim-bank.test'. The attacker fills in three fields. firstParty names an origin the compromised process legitimately hosts, so validateCookieAccess returns Allow and does not terminate. url and cookie.domain both name the victim, so their registrable domains are equal — the forged pair agrees with itself — the lone predicate passes, and execution falls through to the privileged write.
Exploitability does not depend on any heap or timing condition; it depends only on holding a WebContent process, which is the standard post-renderer-compromise position, and on being able to emit one IPC message with three chosen strings. The primitive established is a cookie write against an arbitrary registrable domain with attacker-controlled httpOnly and secure flags — including cookies script running in the victim origin itself could not set through document.cookie. What that buys depends on the target site's session handling: session fixation where the site accepts a client-supplied session identifier, defeat of double-submit CSRF where the token lives in a cookie, forced-login manipulation where login state is keyed on one.
This vulnerability weakens the NetworkProcess's role as the arbiter of which renderer may act for which origin. The cookie store is shared across all origins in the session; an authorization check that only verifies self-consistency leaves that store writable by any process that can craft a message, which is exactly the boundary the four-process split exists to hold.
Audit directions
- Self-consistent IPC predicates. A receiver-side check whose operands all come from the same message verifies formatting, not authority. The dangerous shape is a comparison between two fields of one decoded struct, or between a struct field and a sibling parameter, with no third term the receiver can source independently. Start with the other cookie and storage messages on
NetworkConnectionToWebProcess, and with any handler that calls aRegistrableDomain::uncheckedCreateFromHostvariant on a string that arrived over IPC. Code-review tell: amatches(),isSameOrigin(), or==whose left and right operands are both reachable from the message parameter list. - Receiver checks relaxed to accommodate an in-tree sender. When a legitimate internal caller trips a correct receiver-side check, the safe fix is the sender; loosening the receiver silently re-permits every other sender on that connection. Audit the Web Inspector backend agents generally — they are in-tree senders that fan one protocol command out across many documents and frames — and look for receiver checks whose commit history shows them being widened rather than a sender being narrowed.
- Fields with and without a trusted counterpart. For each cross-process message that performs a privileged write, enumerate which fields the receiver can corroborate against UI-process-populated state and which it cannot, and confirm the authorization predicate is anchored to the former.
NetworkProcess::AddAllowedFirstPartyForCookiesis the anchor to trace backwards from in the cookie path; other subsystems have their own equivalents, and messages that touch none of them are the interesting ones.