← WebKit — 2026-W41

9804a95e09676c514912067b0b77f85c690b7dd5  [WebKit] Let the WebProcess mint the NavigationIdentifier of the navigations it starts

severity medium class Spoofing confidence 0.5 signal score 8.47 Source/WebCore/loader
Primitive: A web process reports a NavigationIdentifier minted by another process, causing cross-process navigation-ID confusion
Triage note: NavigationIdentifier becomes ProcessQualified<ObjectIdentifier>, and WebPageProxy::decidePolicyForNavigationAction only reuses a web-process-supplied navigationID when navigationID->processIdentifier()==process->coreProcessIdentifier() (navigationIDFromWebProcess). This hardens against a compromised web process spoofing a process-local navigation ID; most of the diff is mechanical .toUInt64()->object().toUInt64() logging plumbing, so impact is an identifier-confusion bug rather than clear memory corruption.
Structural signals: smartptr_member_added (+6.47), area:Source/WebCore/loader (+2.0)

Commit message

[WebKit] Let the WebProcess mint the NavigationIdentifier of the navigations it starts
https://bugs.webkit.org/show_bug.cgi?id=326415
rdar://189226143

Reviewed by Alex Christensen.

API::Navigation objects in WebNavigationState are released when the WebProcess DocumentLoader that
owns them is detached and sends DidDestroyNavigation. A navigation started by the WebProcess only
received its identifier in the navigation policy reply, so whenever that reply did not reach the
DocumentLoader, the UIProcess kept the navigation until the WebProcess was no longer associated with
the page. This happened when the policy check was cancelled before the reply arrived, when the
decision was Download, for the policy check of an initial empty document load, and for a
same-document navigation in a document that had no identifier.

Make NavigationIdentifier a ProcessQualified identifier, like BackForwardItemIdentifier, so that
identifiers minted in different processes cannot collide. PolicyChecker now mints one for the
DocumentLoader it checks when that loader has none, before the navigation is handed to the client,
and WebPageProxy::decidePolicyForNavigationAction() creates the API::Navigation with that identifier
when the sending process minted it. The DocumentLoader then always has the identifier the UIProcess
tracks the navigation under, and reports it when it is detached.

The pending API request used to be cleared for any policy check that carried an identifier, which
meant a navigation the UIProcess had started or already knew. Since a WebProcess-initiated
navigation now always carries one, it is cleared for a navigation the UIProcess already knows.

The Web Inspector loader identifier and the WebDriver BiDi navigation identifier now include the
process that minted the identifier, since the object part alone is no longer unique.

Tests: WKNavigation.LeakCheckPolicyCheckCancelledBeforeReply
       WKNavigation.LeakCheckInitialEmptyDocumentLoadFromScript
       WKNavigation.LeakCheckNavigationActionBecomesDownload

* Source/WebCore/inspector/InspectorIdentifierRegistry.cpp:
(Inspector::BackendIdentifierRegistry::loaderId):
* Source/WebCore/loader/FrameLoader.cpp:
(WebCore::FrameLoader::clearProvisionalLoad):
* Source/WebCore/loader/NavigationIdentifier.h:
* Source/WebCore/loader/PolicyChecker.cpp:
(WebCore::PolicyChecker::checkNavigationPolicy):
* Source/WebKit/Scripts/webkit/messages.py:
(serialized_identifiers):
(types_that_cannot_be_forward_declared):
(headers_for_type):
* Source/WebKit/Scripts/webkit/tests/MessageArgumentDescriptions.cpp:
(IPC::serializedIdentifiers):
* Source/WebKit/Shared/ProcessQualified.serialization.in:
* Source/WebKit/UIProcess/API/APINavigation.cpp:
(API::Navigation::Navigation):
* Source/WebKit/UIProcess/API/APINavigation.h:
(API::Navigation::create):
* Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp:
(WebKit::navigationIDToProtocolString):
* Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm:
(WebKit::WebPageProxy::beginSafeBrowsingCheck):
* Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp:
(WebKit::NetworkProcessProxy::considerProcessSwapForNavigationResponse):
* Source/WebKit/UIProcess/ProvisionalPageProxy.cpp:
* Source/WebKit/UIProcess/WebFrameProxy.h:
* Source/WebKit/UIProcess/WebNavigationState.cpp:
(WebKit::WebNavigationState::createLoadRequestNavigation):
(WebKit::WebNavigationState::createBackForwardNavigation):
* Source/WebKit/UIProcess/WebNavigationState.h:
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::receivedNavigationActionPolicyDecision):
(WebKit::WebPageProxy::continueNavigationInNewProcess):
(WebKit::WebPageProxy::didStartProvisionalLoadForFrameShared):
(WebKit::WebPageProxy::didReceiveServerRedirectForProvisionalLoadForFrameShared):
(WebKit::WebPageProxy::didCommitLoadForFrame):
(WebKit::WebPageProxy::didFinishLoadForFrame):
(WebKit::WebPageProxy::decidePolicyForNavigationAction):
(WebKit::WebPageProxy::decidePolicyForResponseShared):
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::loadRequest):
(WebKit::WebPage::loadData):
(WebKit::WebPage::goToBackForwardItem):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Tools/TestWebKitAPI/Tests/WebKit/WKWebView/Navigation.mm:
(waitForNavigationToBeDestroyed):
(loadAndWaitForPath):
(TestWebKitAPI::TEST(WKNavigation, LeakCheckPolicyCheckCancelledBeforeReply)):
(TestWebKitAPI::TEST(WKNavigation, LeakCheckInitialEmptyDocumentLoadFromScript)):
(TestWebKitAPI::TEST(WKNavigation, LeakCheckNavigationActionBecomesDownload)):

Canonical link: https://commits.webkit.org/323024@main

Before / after

Loading diff…