← All issues

[2] Cross-Process Page Identity Confusion in didPostMessage

Severity: High | Component: WebKit UIProcess | 1ad0d2a

Rated High because the diff exposes a UI-process IPC that resolves a global WebPageProxyIdentifier and dispatches user-script messages into the target page without confirming sender ownership; a compromised renderer thereby injects payloads into another renderer's WKScriptMessageHandler.

WebProcessProxy::didPostMessage() resolved a WebPageProxyIdentifier via the global WebPageProxy::fromIdentifier() and acted on the result without verifying that the page was hosted in the sending WebProcess. Under site isolation, the identifier is a process-agnostic handle; a compromised renderer could supply any page's identifier and have its forged postMessage payload delivered as if it had come from that page.

Source/WebKit/UIProcess/WebProcessProxy.cpp

RefPtr page = WebPageProxy::fromIdentifier(pageID);
if (!page)
return completionHandler(makeUnexpected(String()));
+ MESSAGE_CHECK_COMPLETION(isAssociatedWithPage(pageID), completionHandler(makeUnexpected(String())));
RefPtr controller = WebUserContentControllerProxy::get(identifier);
+ for (Ref remotePage : m_remotePages) {
+ if (remotePage->page() && remotePage->page()->identifier() == pageID)
+ return true;
+ }
+ if (m_pagesPendingClose.contains(pageID))
+ return true;

A MESSAGE_CHECK_COMPLETION(isAssociatedWithPage(pageID), ...) is added immediately after the global lookup. isAssociatedWithPage is extended to consult m_remotePages (cross-origin iframe processes under site isolation) and a new m_pagesPendingClose counted set, populated by a new sendPageCloseMessage helper that wraps every Messages::WebPage::Close send. Call sites in ProvisionalPageProxy, RemotePageProxy, SuspendedPageProxy, and WebPageProxy are migrated to the helper.

Missing sender-authorization on a UI-process IPC handler that resolves a cross-process object handle via a global registry without checking it belongs to the sending process.

WebPageProxy is the UI-process object representing a logical tab; under site isolation, multiple WebContent processes may attach to one page (a main-frame process plus per-site RemotePageProxy for cross-origin iframes). WebProcessProxy tracks which pages each renderer hosts via m_pageMap, m_provisionalPages, m_remotePages, and m_suspendedPages. WebPageProxyIdentifier is a globally unique handle resolved by WebPageProxy::fromIdentifier() regardless of asker. didPostMessage is the UI-process handler for window.webkit.messageHandlers.<name>.postMessage(...) from injected user scripts. MESSAGE_CHECK aborts the message and terminates the sending WebContent on failure.

The handler accepted a renderer-supplied WebPageProxyIdentifier, resolved it globally, then dispatched the payload through the resolved page's WebUserContentControllerProxy with no ownership check. A compromised WebContent process could forge WebProcessProxy::DidPostMessage carrying a victim page's identifier and a serialized JS payload; the UI process would deliver it as if it originated from that page.

🔒

Explores how a cross-process IPC identifier can be weaponized against the UI process's per-renderer ownership boundaries, and what the realistic primitive looks like for a compromised WebContent process under site isolation.

Subscribe to read more

🔒

Four reusable audit patterns identified for confused-deputy bugs in UI-process IPC handlers, including specific helper predicates and lifecycle windows to investigate.

Subscribe to read more