CVE-2025-43240
Overview
Background
- DownloadProxy
- The UIProcess object representing an in-flight download, whose originating frame info is surfaced to clients (e.g. WKDownload.originatingFrame).
- originatingFrameInfo
- Metadata describing the frame (URL, security origin, webView) credited with initiating a navigation or download.
- targetItem
- A back/forward-list item that a navigation is traversing to; its presence marks the navigation as history traversal rather than a fresh content-initiated load.
- isRequestFromClientOrUserInput
- A navigation flag indicating the request originated from the embedding client or direct user input rather than from executing web content.
- PolicyAction::Download
- The navigation policy outcome telling WebKit to turn a navigation/response into a download instead of rendering it.
Root Cause Analysis
WebPageProxy::receivedPolicyDecision() and receivedNavigationResponsePolicyDecision() run in the UIProcess and, when the policy action is PolicyAction::Download, construct a DownloadProxy via ProcessPool::createDownloadProxy(). The last argument to that call is the originating frame info, which becomes the download’s originatingFrame / associated origin exposed to the client (e.g. WKDownload.originatingFrame). Before the patch, both paths unconditionally passed navigation->originatingFrameInfo() (or the navigationAction’s originatingFrameInfoData) as the originating frame. The invariant being violated is that a download’s originating frame/origin should reflect the actual frame that initiated the resource load, and only when that association is meaningful. For navigations that are back/forward list traversals (navigation->targetItem() is set) or that came from client/user input rather than from web content (navigation->isRequestFromClientOrUserInput()), there is no legitimate web-content frame origin to attribute the download to — yet the old code still stamped the download with the previous/opener frame’s originatingFrameInfo, incorrectly associating the download with an origin (e.g. the prior page’s host) that did not actually initiate it.
The fix adds a condition: when navigation exists and it is either a targetItem (go-back/forward) navigation or a client/user-input-initiated request, the download proxy is created with std::nullopt for the originating frame info, so no misleading origin is attached (the client sees an empty host / about:blank-like frame). Otherwise the original originatingFrameInfo is preserved. The added API tests (OriginatingFrameHostWhenDownloadComesFromGoBackNavigation and …FromClientInputNavigation, plus the isClientOrUserInitiated branch in the opener test) assert exactly this: for go-back and client-initiated downloads, download.originatingFrame.securityOrigin.host is empty, while genuinely frame-initiated downloads still equal the opener main frame. This restores correct, non-spoofable origin attribution for downloads.
Attack Path
- Set up navigation state The attacker gets the victim to load attacker or third-party pages so that a back/forward list entry or a client/user-input navigation can later resolve to a download.
- Trigger a download via a non-content-initiated navigation A go-back navigation (targetItem) or a client/user-input request results in a response the client turns into a download (PolicyAction::Download).
- Reach the vulnerable proxy creation receivedPolicyDecision or receivedNavigationResponsePolicyDecision creates a DownloadProxy and, pre-patch, stamps it with navigation->originatingFrameInfo() from an unrelated/prior frame.
- Cause origin misattribution The download is now associated with an originating frame/origin that did not initiate it, so download.originatingFrame reports the wrong (attacker-influenced or victim) host.
- Exploit downstream origin-based decisions Any client logic or security prompt that trusts the download’s originating origin (attribution UI, per-origin download policies, provenance checks) can be misled by the spoofed association.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
WebPageProxy::receivedPolicyDecisionSource/WebKit/UIProcess/WebPageProxy.cpp |
modified | For Download actions, now passes std::nullopt as originating frame info when the navigation is a targetItem (back/forward) or isRequestFromClientOrUserInput, otherwise keeps the original originatingFrameInfo. |
WebPageProxy::receivedNavigationResponsePolicyDecisionSource/WebKit/UIProcess/WebPageProxy.cpp |
modified | Same conditional fix applied to the navigation-response download path: nullopt originating frame for back/forward or client/user-input navigations. |
WKDownload OriginatingFrame testsTools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm |
modified | Adds a local frameInfoShouldBeEqual block and isClientOrUserInitiated flag, plus two new tests asserting that go-back and client-input downloads report an empty originatingFrame host while genuinely frame-initiated downloads still match the opener frame. |
Files Changed
Source/WebKit/UIProcess/WebPageProxy.cppTools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm
Audit Directions
- Every createDownloadProxy call siteGrep for createDownloadProxy across UIProcess and verify each passes originating frame info only when a genuine content frame initiated it; look for callers still using originatingFrameInfo unconditionally.
- Other consumers of originatingFrameInfo for provenanceSearch for originatingFrameInfo / originatingFrameInfoData and downloadOriginatingPage to find other features (e.g. app-initiated state, cross-origin checks) that may inherit an unrelated frame’s origin during back/forward or client-input navigations.
- targetItem / isRequestFromClientOrUserInput gating parityGrep for navigation->targetItem() and isRequestFromClientOrUserInput() to see where these distinctions matter and audit paths that credit an origin without checking them, especially security-relevant attribution.
- Origin association on other navigation-derived objectsLook at how UserMediaPermission, WebsiteDataStore attribution, and referrer/opener plumbing choose an origin for navigations lacking a real initiating frame, applying the same nullopt-when-no-real-origin reasoning.