12024828ce4fd3a6d4c7196503bfcd6e9372f3d5 [Site Isolation] Validate the target frame of cross-process postMessage and focus
Triage note: postMessageToRemote now takes the IPC::Connection and validates targetFrame->page()==this (MESSAGE_CHECK_BASE), that the source frame belongs to the sending process, and that the untrusted source origin matches sourceFrame->documentSecurityOriginData() (previously unsafeExtractWithoutValidation with no checks); remotePostMessage adds a targetFrame->page()==this check. Prevents a compromised renderer from spoofing a cross-origin message's source origin.
The bug at a glance
A compromised WebContent process could forge the source SecurityOrigin of a cross-process postMessage: before the patch WebPageProxy::postMessageToRemote extracted the untrusted source origin with unsafeExtractWithoutValidation and never checked who sent it, so the attacker renderer could deliver a MessageEvent to a victim cross-origin frame (in another process) bearing any event.origin it chose. This is a same-origin-policy / origin-confusion (spoofing) defect in the UIProcess IPC router, not memory corruption; its impact is bypassing origin-gated message handlers (e.g. a frame that trusts messages claiming to be from a parent site). High severity for a spoofing primitive is reasonable since postMessage origin is a core trust boundary, though it yields no direct memory-safety primitive by itself.
The classic IPC trust gap: the UIProcess accepted a renderer-supplied source origin verbatim (unsafeExtractWithoutValidation / UnvalidatedReason::NeedsReview) and routed a cross-process postMessage without confirming the sending process actually owns the claimed source frame.
Root cause
The invariant is that event.origin delivered to a target frame must reflect the real security origin of the frame that sent it, and that only the process hosting the source frame may speak for it. Pre-patch WebPageProxy::postMessageToRemote(source, untrustedSourceOrigin, target, untrustedTargetOrigin, …) did two unsafe extractions and immediately used the values. The sequence was: 1. take untrustedSourceOrigin via WTF::move(…).unsafeExtractWithoutValidation(NeedsReview) into sourceOrigin with no cross-check; 2. take untrustedTargetOrigin the same way; 3. route the message to the target frame using the attacker-chosen sourceOrigin as the message’s origin. A compromised renderer therefore picks any FrameIdentifier for source and any SecurityOriginData, so the victim frame observes a forged event.origin.
The patch adds the IPC::Connection& to the signature and rebuilds the checks. It resolves RefPtr sourceFrame = WebFrameProxy::webFrame(source) and RefPtr targetFrame = WebFrameProxy::webFrame(target); bails if either is missing or targetFrame has no page(); enforces MESSAGE_CHECK_BASE(targetFrame->page() == this, connection) so the message is handled on the WebPageProxy that mirrors the target frame’s page; then enforces that the sender owns the source frame via if (&sourceFrame->process() != WebProcessProxy::fromConnection(connection).ptr()) return;. Only after those gates does it extract sourceOrigin (now UnvalidatedReason::ValidatedElsewhere) and compare it to the authoritative expectedSourceOrigin = sourceFrame->documentSecurityOriginData(); a non-opaque mismatch is rejected via MESSAGE_CHECK_BASE(expectedSourceOrigin.isOpaque(), connection).
The companion WebPage::remotePostMessage (WebProcess side) adds targetFrame->page() != this to its early-return, closing the mirror of the same routing confusion inside the receiving renderer. The triage bugclass (Spoofing) matches the diff: the code added is pure authorization/validation, no lifetime or allocation change, so this is an origin-spoofing / IPC-authorization fix, not UAF.
Key code
Source origin now validated against the real source frame instead of trusted blindly
if (&sourceFrame->process() != WebProcessProxy::fromConnection(connection).ptr())
return;
auto sourceOrigin = WTF::move(untrustedSourceOrigin).unsafeExtractWithoutValidation(IPC::UnvalidatedReason::ValidatedElsewhere);
auto expectedSourceOrigin = sourceFrame->documentSecurityOriginData();
if (!sourceOrigin.isOpaque() && sourceOrigin != expectedSourceOrigin) {
MESSAGE_CHECK_BASE(expectedSourceOrigin.isOpaque(), connection);
return;
}
Fix approach
The fix threads IPC::Connection& into postMessageToRemote and converts a blind extraction into a gated one: (a) the target frame must belong to this WebPageProxy (MESSAGE_CHECK_BASE), (b) the sending process must own the claimed source frame (process identity check), and (c) the untrusted source origin must equal sourceFrame->documentSecurityOriginData() unless it is opaque. Opaque origins are deliberately still allowed because a CSP sandbox header can make a document opaque without the UIProcess knowing. Semantically the source origin is no longer taken on faith; it is authenticated against UIProcess-held frame state, and a lying renderer is either dropped or killed via MESSAGE_CHECK_BASE. A FIXME notes a residual edge: a frame whose cross-process parent changed the sandbox attribute may legitimately commit a real origin where the UIProcess still expects opaque.
Patch walkthrough
Source/WebKit/UIProcess/WebPageProxy.cpp— postMessageToRemote now takes IPC::Connection&, resolves source/target WebFrameProxy, enforces targetFrame->page()==this, verifies the sender owns the source frame, and validates the source origin against documentSecurityOriginData(); focusRemoteFrame likewise upgrades its ASSERT(page()==this) to a MESSAGE_CHECK_BASE and null-checks page().Source/WebKit/UIProcess/WebPageProxy.h— Declaration of postMessageToRemote updated to carry IPC::Connection& as its first parameter.Source/WebKit/WebProcess/WebPage/WebPage.cpp— remotePostMessage tightens its early-return from!targetFrameto!targetFrame || targetFrame->page() != this, rejecting messages routed to a frame not owned by this page.
Background
postMessage cross-process routing — With site isolation a frame and its cross-origin parent live in different WebContent processes; a postMessage between them is relayed through the UIProcess (WebPageProxy) which forwards it to the process hosting the target frame.
IPC::Untrusted / unsafeExtractWithoutValidation — A wrapper marking a value that crossed from a renderer and must be validated before use; unsafeExtractWithoutValidation unwraps it with an explicit reason tag and performs no checks, so the caller is responsible for authorization.
MESSAGE_CHECK_BASE — A WebKit IPC macro that, when its predicate fails, treats the message as malformed and terminates the sending WebProcess connection; used to enforce invariants a trustworthy renderer would never violate.
SecurityOriginData / opaque origin — The serializable identity of a document’s security origin; an opaque (unique) origin arises from sandboxing (e.g. a CSP sandbox header) and never matches another origin, which is why the fix explicitly permits opaque source origins.
WebFrameProxy / WebProcessProxy — UIProcess-side mirrors of a web frame and its hosting content process; sourceFrame->process() identifies which process legitimately owns a frame, and WebProcessProxy::fromConnection maps an IPC connection back to its process.
Vulnerability window
- Setup — Attacker compromises a WebContent process that hosts (or can name) some frame and establishes an IPC connection to the UIProcess.
- Trigger — Attacker sends a postMessageToRemote IPC naming a victim target frame, an arbitrary source FrameIdentifier, and a forged source SecurityOriginData.
- Pre-patch flaw — UIProcess extracts the origin without validation and forwards the message to the victim frame’s process with the forged origin.
- Delivery — Victim frame receives a MessageEvent whose event.origin is attacker-chosen, defeating origin-based message authentication.
- Post-patch — UIProcess rejects the message (or kills the connection) because the sender does not own the source frame or the origin does not match documentSecurityOriginData().
Proof of concept
No regression test ships in the diff. Conceptual trigger (inferred): from a compromised renderer, craft the IPC::PostMessageToRemote message with target = a cross-origin victim frame’s FrameIdentifier and untrustedSourceOrigin = ‘https://trusted.example’; a victim frame listening for message events gated on event.origin === ‘https://trusted.example’ would pre-patch accept the forged message. Requires a compromised WebContent process to forge the IPC, so the web path is (inferred) and not reachable from unprivileged script alone.
Exploitation
- Reach — Requires a compromised/controlled WebContent process able to emit the postMessageToRemote IPC (not reachable from sandboxed JS directly).
- Trigger — Send the IPC with a chosen source FrameIdentifier and a forged SecurityOriginData for the source origin.
- Primitive — Origin spoofing: deliver a MessageEvent to a victim cross-origin frame with a forged event.origin.
- Escalation (inferred) — If the victim page uses postMessage origin as an authorization gate (auth tokens, RPC bridges), the spoof can drive privileged in-page actions; it provides no memory-safety primitive on its own.
Detection & hunting
For defenders and detection engineers:
- postMessageToRemote invoked for a source frame not owned by the sending connection — Instrument the UIProcess handler to log when &sourceFrame->process() != sender process; such events are anomalous.
- Source origin mismatch against documentSecurityOriginData() — Alert on non-opaque sourceOrigin != expectedSourceOrigin, which the patch now rejects.
- MESSAGE_CHECK_BASE connection terminations around focusRemoteFrame/postMessageToRemote — A spike in process kills on these paths indicates a renderer probing the routing checks.
Remaining risk in the patch
- Opaque-origin desync acknowledged by the FIXME — The patch permits any opaque source origin and documents that a frame whose cross-process parent changed the sandbox attribute can commit a real origin where the UIProcess expects opaque, leaving a narrow origin-confusion window the comment flags but does not close.
- Blob URL lifetime FIXME still open on this path — A retained FIXME notes this message carries no blob URL handles, so unlike MessagePort/BroadcastChannel/ServiceWorker paths the network process takes no blob handles; an orthogonal blob-lifetime gap on this specific route remains unaddressed here.
Audit directions
- Other unsafeExtractWithoutValidation callers in UIProcess IPC handlers — Grep for unsafeExtractWithoutValidation / UnvalidatedReason::NeedsReview where a renderer-supplied origin or frame id is used without a subsequent ownership check.
- Cross-process frame-routing handlers missing targetFrame->page()==this — Review sibling handlers (focus, beforeunload dispatch, broadcastFocusedFrameToOtherProcesses) that take FrameIdentifiers for the same page-mirroring assumption.
- Sandbox/opaque-origin desync between parent and child processes — Investigate the FIXME path where a cross-process parent’s sandbox attribute change is not propagated to the child frame’s process.
- WebProcess-side remotePostMessage and peers — Confirm other WebProcess message receivers validate targetFrame->page()==this before acting.