Overview

Medium
Severity
6.3
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to a cross site scripting attack. Apple is aware of a report that this issue may have been actively exploited on Intel-based Mac systems.
ComponentWebKit
Sub-componentWebKit NetworkProcess
Bug ClassCross Origin
Tracker283095
Fix commit2815b4e29829 (WebKit/WebKit)
CWECWE-79 (Cross-site scripting)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L
CISA KEVListed
CreditedClément Lecigne and Benoît Sevens of Google's Threat Analysis Group
Disclosed2024-11-19

Background

`NetworkProcess::allowsFirstPartyForCookies`
NetworkProcess-side predicate that decides whether a given web process is authorized to use a given first party for cookie/resource operations, backed by a per-process allowed-set.
`m_allowedFirstPartiesForCookies`
per-web-process map from WebCore::ProcessIdentifier to the set of RegistrableDomains that process has been authorized to present as a first party.
`MESSAGE_CHECK` / `MESSAGE_CHECK_COMPLETION`
IPC guards that TERMINATE the offending web-process connection when their condition is false (a crash, not a leak).
`AllowCookieAccess`
new tri-state enum {Allow, Disallow, Terminate} that separates a legitimate quiet denial (Disallow) from a protocol violation that must terminate (Terminate).

Root Cause Analysis

Pre-patch NetworkProcess::allowsFirstPartyForCookies returned a plain bool, and both overloads contained escape-hatch conditions that returned true (cookie access ALLOWED) BEFORE consulting the per-process authorization set. The URL overload returned true whenever firstParty.isAboutBlank() or firstParty.isNull(), and the RegistrableDomain overload returned true whenever firstPartyDomain.isEmpty() — each short-circuiting ahead of the m_allowedFirstPartiesForCookies lookup and its set.contains(firstPartyDomain) decision. A web process could therefore obtain cookie access for a first party it was never authorized for simply by presenting such an edge-case first party. Crucially, this was NOT an inability to deny: a genuinely disallowed domain reached set.contains() == false and was already denied — pre-patch that false hit MESSAGE_CHECK and TERMINATED the connection, and a terminated request leaks nothing; the flaw was the ALLOW short-circuits sitting in front of the authorization lookup.

The fix converts the predicate to the tri-state AllowCookieAccess and REORDERS the logic so the per-process lookup runs FIRST; the about:blank/null/empty special cases now only downgrade a would-be Terminate to Disallow (denied quietly) and can never yield Allow. At the call sites the outcomes are split: MESSAGE_CHECK fires only on Terminate (a true protocol violation), while any non-Allow result returns empty/early.

Key insight
The bug was not an inability to deny — a disallowed domain was already denied by connection termination — but that the isAboutBlank()/isNull()/isEmpty() short-circuits returned Allow ahead of the per-process authorization lookup, letting a web process bypass first-party authorization by presenting such an edge-case first party.

Attack Path

  1. Craft an edge-case first party A malicious or compromised web process issues a cookie/resource IPC (e.g. scheduleResourceLoad, setCookiesFromDOM, getRawCookies) whose firstPartyForCookies is about:blank, null, or resolves to an empty RegistrableDomain.
  2. Skip authorization Pre-patch allowsFirstPartyForCookies returns true on that edge case before the m_allowedFirstPartiesForCookies / set.contains check ever runs, so the process is treated as authorized for a first party outside its allowed set.
  3. Obtain unauthorized cookie access The NetworkProcess proceeds to read/write cookies or load resources under a first party the process was never granted, defeating per-process site isolation.
  4. Cross-site scripting (inferred) The unauthorized cross-site cookie read/injection is leveraged into script execution in another origin, matching the XSS Apple reports.

Impact Assessment

Apple’s advisory states that processing maliciously crafted web content may lead to a cross-site scripting attack and reports the issue may have been actively exploited on Intel-based Mac systems (listed on CISA KEV); the engine rating is medium. This is an origin/isolation authorization bypass in the NetworkProcess first-party-for-cookies gate, not memory corruption. (inferred) The specific escalation from unauthorized cross-site cookie access to script execution in a victim origin is not shown by the diff.

Changed Functions

FunctionChangeNotes
if
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
modified
if
Source/WebKit/NetworkProcess/NetworkProcess.cpp
modified

Files Changed

  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
  • Source/WebKit/NetworkProcess/NetworkProcess.cpp
  • Source/WebKit/NetworkProcess/NetworkProcess.h
  • Source/WebKit/NetworkProcess/NetworkSession.cpp
  • Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp
  • Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerConnection.cpp
  • Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm

Audit Directions

  • Allow-before-authorize short-circuits
    grep for predicates that return true/allow on edge-case inputs (isAboutBlank/isNull/isEmpty/inherited-origin) placed BEFORE the real per-principal authorization lookup; reorder so authorization runs first and special cases can only relax termination, never grant.
  • bool gate consumed only by `MESSAGE_CHECK`
    find authorization predicates returning bool where “deny” and “terminate” are conflated; a would-be denial that only crashes masks call sites that should quietly refuse (and hides where an allow-short-circuit can leak).
  • Edge-case first parties across IPC
    audit every cookie/resource entry point that accepts a firstPartyForCookies from the web process to confirm a non-Allow result denies without granting or leaking cross-site cookie data.
diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
index 3ce50b762262..1dd61fa378fb 100644
--- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
@@ -503,7 +503,7 @@ void NetworkConnectionToWebProcess::didReceiveInvalidMessage(IPC::Connection&, I
 
 void NetworkConnectionToWebProcess::createSocketChannel(const ResourceRequest& request, const String& protocol, WebSocketIdentifier identifier, WebPageProxyIdentifier webPageProxyID, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, const ClientOrigin& clientOrigin, bool hadMainFrameMainResourcePrivateRelayed, bool allowPrivacyProxy, OptionSet<AdvancedPrivacyProtections> advancedPrivacyProtections, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, WebCore::StoredCredentialsPolicy storedCredentialsPolicy)
 {
-    MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()));
+    MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()) != NetworkProcess::AllowCookieAccess::Terminate);
 
     ASSERT(!m_networkSocketChannels.contains(identifier));
     if (auto channel = NetworkSocketChannel::create(*this, m_sessionID, request, protocol, identifier, webPageProxyID, frameID, pageID, clientOrigin, hadMainFrameMainResourcePrivateRelayed, allowPrivacyProxy, advancedPrivacyProtections, shouldRelaxThirdPartyCookieBlocking, storedCredentialsPolicy))
@@ -553,11 +553,11 @@ RefPtr<ServiceWorkerFetchTask> NetworkConnectionToWebProcess::createFetchTask(Ne
 
 void NetworkConnectionToWebProcess::scheduleResourceLoad(NetworkResourceLoadParameters&& loadParameters, std::optional<NetworkResourceLoadIdentifier> existingLoaderToResume)
 {
-    bool hasCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, loadParameters.request.firstPartyForCookies());
-    if (UNLIKELY(!hasCookieAccess))
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, loadParameters.request.firstPartyForCookies());
+    if (UNLIKELY(allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow))
         RELEASE_LOG_ERROR(Loading, "scheduleResourceLoad: Web process does not have cookie access to url %" SENSITIVE_LOG_STRING " for request %" SENSITIVE_LOG_STRING, loadParameters.request.firstPartyForCookies().string().utf8().data(), loadParameters.request.url().string().utf8().data());
 
-    MESSAGE_CHECK(hasCookieAccess);
+    MESSAGE_CHECK(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate);
 
     CONNECTION_RELEASE_LOG(Loading, "scheduleResourceLoad: (parentPID=%d, pageProxyID=%" PRIu64 ", webPageID=%" PRIu64 ", frameID=%" PRIu64 ", resourceID=%" PRIu64 ", existingLoaderToResume=%" PRIu64 ")", loadParameters.parentPID, loadParameters.webPageProxyID.toUInt64(), loadParameters.webPageID ? loadParameters.webPageID->toUInt64() : 0, loadParameters.webFrameID.object().toUInt64(), loadParameters.identifier.toUInt64(), valueOrDefault(existingLoaderToResume).toUInt64());
 
@@ -786,7 +786,10 @@ void NetworkConnectionToWebProcess::registerURLSchemesAsCORSEnabled(Vector<Strin
 
 void NetworkConnectionToWebProcess::cookiesForDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, FrameIdentifier frameID, PageIdentifier pageID, IncludeSecureCookies includeSecureCookies, ApplyTrackingPrevention applyTrackingPrevention, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, CompletionHandler<void(String cookieString, bool secureCookiesAccessed)>&& completionHandler)
 {
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty), completionHandler({ }, false));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler({ }, false));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler({ }, false);
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -803,7 +806,10 @@ void NetworkConnectionToWebProcess::cookiesForDOM(const URL& firstParty, const S
 
 void NetworkConnectionToWebProcess::setCookiesFromDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, WebCore::FrameIdentifier frameID, PageIdentifier pageID, ApplyTrackingPrevention applyTrackingPrevention, const String& cookieString, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking)
 {
-    MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate);
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return;
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -824,7 +830,10 @@ void NetworkConnectionToWebProcess::cookiesEnabledSync(const URL& firstParty, co
 
 void NetworkConnectionToWebProcess::cookiesEnabled(const URL& firstParty, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, CompletionHandler<void(bool)>&& completionHandler)
 {
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty), completionHandler(false));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler(false));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler(false);
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession) {
@@ -838,7 +847,10 @@ void NetworkConnectionToWebProcess::cookiesEnabled(const URL& firstParty, const
 
 void NetworkConnectionToWebProcess::cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ApplyTrackingPrevention applyTrackingPrevention, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, CompletionHandler<void(String, bool)>&& completionHandler)
 {
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty), completionHandler({ }, false));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler({ }, false));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler({ }, false);
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -849,7 +861,10 @@ void NetworkConnectionToWebProcess::cookieRequestHeaderFieldValue(const URL& fir
 
 void NetworkConnectionToWebProcess::getRawCookies(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<FrameIdentifier> frameID, std::optional<PageIdentifier> pageID, ApplyTrackingPrevention applyTrackingPrevention, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, CompletionHandler<void(Vector<WebCore::Cookie>&&)>&& completionHandler)
 {
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty), completionHandler({ }));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler({ }));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler({ });
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -878,7 +893,10 @@ void NetworkConnectionToWebProcess::deleteCookie(const URL& url, const String& c
 
 void NetworkConnectionToWebProcess::cookiesForDOMAsync(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<WebCore::FrameIdentifier> frameID, std::optional<WebCore::PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ApplyTrackingPrevention applyTrackingPrevention, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, WebCore::CookieStoreGetOptions&& options, CompletionHandler<void(std::optional<Vector<WebCore::Cookie>>&&)>&& completionHandler)
 {
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty), completionHandler(std::nullopt));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler(std::nullopt));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler(std::nullopt);
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -895,7 +913,10 @@ void NetworkConnectionToWebProcess::cookiesForDOMAsync(const URL& firstParty, co
 
 void NetworkConnectionToWebProcess::setCookieFromDOMAsync(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, std::optional<WebCore::FrameIdentifier> frameID, std::optional<WebCore::PageIdentifier> pageID, ApplyTrackingPrevention applyTrackingPrevention, WebCore::Cookie&& cookie, ShouldRelaxThirdPartyCookieBlocking shouldRelaxThirdPartyCookieBlocking, CompletionHandler<void(bool)>&& completionHandler)
 {
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty), completionHandler(false));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, firstParty);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler(false));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler(false);
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -915,7 +936,10 @@ void NetworkConnectionToWebProcess::domCookiesForHost(const URL& url, Completion
 {
     auto host = url.host().toString();
     MESSAGE_CHECK_COMPLETION(HashSet<String>::isValidValue(host), completionHandler({ }));
-    MESSAGE_CHECK_COMPLETION(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, url), completionHandler({ }));
+    auto allowCookieAccess = m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, url);
+    MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler({ }));
+    if (allowCookieAccess != NetworkProcess::AllowCookieAccess::Allow)
+        return completionHandler({ });
 
     auto* networkStorageSession = storageSession();
     if (!networkStorageSession)
@@ -1440,7 +1464,8 @@ void NetworkConnectionToWebProcess::establishSWContextConnection(WebPageProxyIde
 {
     auto* session = networkSession();
     if (auto* swServer = session ? session->swServer() : nullptr) {
-        MESSAGE_CHECK_COMPLETION(session->networkProcess().allowsFirstPartyForCookies(webProcessIdentifier(), registrableDomain), completionHandler());
+        auto allowCookieAccess = session->networkProcess().allowsFirstPartyForCookies(webProcessIdentifier(), registrableDomain);
+        MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler());
         m_swContextConnection = makeUnique<WebSWServerToContextConnection>(*this, webPageProxyID, WTFMove(registrableDomain), serviceWorkerPageIdentifier, *swServer);
     }
     completionHandler();
diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.cpp b/Source/WebKit/NetworkProcess/NetworkProcess.cpp
index eb014c672222..1f3dcced7272 100644
--- a/Source/WebKit/NetworkProcess/NetworkProcess.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkProcess.cpp
@@ -458,48 +458,49 @@ void NetworkProcess::webProcessWillLoadWebArchive(WebCore::ProcessIdentifier pro
     }).iterator->value.first = LoadedWebArchive::Yes;
 }
 
-bool NetworkProcess::allowsFirstPartyForCookies(WebCore::ProcessIdentifier processIdentifier, const URL& firstParty)
+auto NetworkProcess::allowsFirstPartyForCookies(WebCore::ProcessIdentifier processIdentifier, const URL& firstParty) -> AllowCookieAccess
 {
-    // FIXME: This should probably not be necessary. If about:blank is the first party for cookies,
-    // we should set it to be the inherited origin then remove this exception.
-    if (firstParty.isAboutBlank())
-        return true;
+    auto allowCookieAccess = allowsFirstPartyForCookies(processIdentifier, RegistrableDomain { firstParty });
+    if (allowCookieAccess == NetworkProcess::AllowCookieAccess::Terminate) {
+        // FIXME: This should probably not be necessary. If about:blank is the first party for cookies,
+        // we should set it to be the inherited origin then remove this exception.
+        if (firstParty.isAboutBlank())
+            return AllowCookieAccess::Disallow;
 
-    if (firstParty.isNull())
-        return true; // FIXME: This shouldn't be allowed.
+        if (firstParty.isNull())
+            return AllowCookieAccess::Disallow; // FIXME: This shouldn't be allowed.
+    }
 
-    return allowsFirstPartyForCookies(processIdentifier, RegistrableDomain { firstParty });
+    return allowCookieAccess;
 }
 
-bool NetworkProcess::allowsFirstPartyForCookies(WebCore::ProcessIdentifier processIdentifier, const RegistrableDomain& firstPartyDomain)
+auto NetworkProcess::allowsFirstPartyForCookies(WebCore::ProcessIdentifier processIdentifier, const RegistrableDomain& firstPartyDomain) -> AllowCookieAccess
 {
     // FIXME: This shouldn't be needed but it is hit sometimes at least with PDFs.
-    if (firstPartyDomain.isEmpty())
-        return true;
-
+    auto terminateOrDisallow = firstPartyDomain.isEmpty() ? AllowCookieAccess::Disallow : AllowCookieAccess::Terminate;
     if (!decltype(m_allowedFirstPartiesForCookies)::isValidKey(processIdentifier)) {
         ASSERT_NOT_REACHED();
-        return false;
+        return terminateOrDisallow;
     }
 
     auto iterator = m_allowedFirstPartiesForCookies.find(processIdentifier);
     if (iterator == m_allowedFirstPartiesForCookies.end()) {
         ASSERT_NOT_REACHED();
-        return false;
+        return terminateOrDisallow;
     }
 
     if (iterator->value.first == LoadedWebArchive::Yes)
-        return true;
+        return AllowCookieAccess::Allow;
 
     auto& set = iterator->value.second;
     if (!std::remove_reference_t<decltype(set)>::isValidValue(firstPartyDomain)) {
         ASSERT_NOT_REACHED();
-        return false;
+        return terminateOrDisallow;
     }
 
     auto result = set.contains(firstPartyDomain);
-    ASSERT(result);
-    return result;
+    ASSERT(result || terminateOrDisallow == AllowCookieAccess::Disallow);
+    return result ? AllowCookieAccess::Allow : terminateOrDisallow;
 }
 
 void NetworkProcess::addStorageSession(PAL::SessionID sessionID, const WebsiteDataStoreParameters& parameters)
diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.h b/Source/WebKit/NetworkProcess/NetworkProcess.h
index 59f9e97d610e..6a158d4371a4 100644
--- a/Source/WebKit/NetworkProcess/NetworkProcess.h
+++ b/Source/WebKit/NetworkProcess/NetworkProcess.h
@@ -417,8 +417,9 @@ public:
     void deleteWebsiteDataForOrigin(PAL::SessionID, OptionSet<WebsiteDataType>, const WebCore::ClientOrigin&, CompletionHandler<void()>&&);
     void deleteWebsiteDataForOrigins(PAL::SessionID, OptionSet<WebsiteDataType>, const Vector<WebCore::SecurityOriginData>& origins, const Vector<String>& cookieHostNames, const Vector<String>& HSTSCacheHostnames, const Vector<RegistrableDomain>&, CompletionHandler<void()>&&);
 
-    bool allowsFirstPartyForCookies(WebCore::ProcessIdentifier, const URL&);
-    bool allowsFirstPartyForCookies(WebCore::ProcessIdentifier, const RegistrableDomain&);
+    enum class AllowCookieAccess : uint8_t { Disallow, Allow, Terminate };
+    AllowCookieAccess allowsFirstPartyForCookies(WebCore::ProcessIdentifier, const URL&);
+    AllowCookieAccess allowsFirstPartyForCookies(WebCore::ProcessIdentifier, const RegistrableDomain&);
     void addAllowedFirstPartyForCookies(WebCore::ProcessIdentifier, WebCore::RegistrableDomain&&, LoadedWebArchive, CompletionHandler<void()>&&);
     void webProcessWillLoadWebArchive(WebCore::ProcessIdentifier);
 
diff --git a/Source/WebKit/NetworkProcess/NetworkSession.cpp b/Source/WebKit/NetworkProcess/NetworkSession.cpp
index f57164fc5d35..25c4a6d325b9 100644
--- a/Source/WebKit/NetworkProcess/NetworkSession.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkSession.cpp
@@ -783,7 +783,7 @@ void NetworkSession::appBoundDomains(CompletionHandler<void(HashSet<WebCore::Reg
 
 void NetworkSession::addAllowedFirstPartyForCookies(WebCore::ProcessIdentifier webProcessIdentifier, std::optional<WebCore::ProcessIdentifier> requestingProcessIdentifier, WebCore::RegistrableDomain&& firstPartyForCookies)
 {
-    if (requestingProcessIdentifier && (requestingProcessIdentifier != webProcessIdentifier) && !m_networkProcess->allowsFirstPartyForCookies(requestingProcessIdentifier.value(), firstPartyForCookies)) {
+    if (requestingProcessIdentifier && (requestingProcessIdentifier != webProcessIdentifier) && m_networkProcess->allowsFirstPartyForCookies(requestingProcessIdentifier.value(), firstPartyForCookies) != NetworkProcess::AllowCookieAccess::Allow) {
         ASSERT_NOT_REACHED();
         return;
     }
diff --git a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp
index 5afab4decdf4..0d8d1738de44 100644
--- a/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp
+++ b/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp
@@ -352,7 +352,7 @@ void WebSWServerConnection::postMessageToServiceWorker(ServiceWorkerIdentifier d
 
 void WebSWServerConnection::scheduleJobInServer(ServiceWorkerJobData&& jobData)
 {
-    MESSAGE_CHECK(networkProcess().allowsFirstPartyForCookies(identifier(), WebCore::RegistrableDomain::uncheckedCreateFromHost(jobData.topOrigin.host())));
+    MESSAGE_CHECK(networkProcess().allowsFirstPartyForCookies(identifier(), WebCore::RegistrableDomain::uncheckedCreateFromHost(jobData.topOrigin.host())) != NetworkProcess::AllowCookieAccess::Terminate);
 
     ASSERT(!jobData.scopeURL.isNull());
     if (jobData.scopeURL.isNull()) {
diff --git a/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerConnection.cpp b/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerConnection.cpp
index 444642c70d44..1a35cf6d4105 100644
--- a/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerConnection.cpp
+++ b/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServerConnection.cpp
@@ -89,7 +89,7 @@ NetworkSession* WebSharedWorkerServerConnection::session()
 
 void WebSharedWorkerServerConnection::requestSharedWorker(WebCore::SharedWorkerKey&& sharedWorkerKey, WebCore::SharedWorkerObjectIdentifier sharedWorkerObjectIdentifier, WebCore::TransferredMessagePort&& port, WebCore::WorkerOptions&& workerOptions)
 {
-    MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, WebCore::RegistrableDomain::uncheckedCreateFromHost(sharedWorkerKey.origin.topOrigin.host())));
+    MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, WebCore::RegistrableDomain::uncheckedCreateFromHost(sharedWorkerKey.origin.topOrigin.host())) != NetworkProcess::AllowCookieAccess::Terminate);
     MESSAGE_CHECK(sharedWorkerObjectIdentifier.processIdentifier() == m_webProcessIdentifier);
     MESSAGE_CHECK(sharedWorkerKey.name == workerOptions.name);
     CONNECTION_RELEASE_LOG("requestSharedWorker: sharedWorkerObjectIdentifier=%" PUBLIC_LOG_STRING, sharedWorkerObjectIdentifier.toString().utf8().data());
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
index 77ae695483e9..f61602dc70e8 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
@@ -376,6 +376,39 @@ TEST(IPCTestingAPI, DecodesReplyArgumentsForAsyncMessage)
     EXPECT_STREQ([alertMessage UTF8String], "[{\"type\":\"bool\",\"value\":false}]");
 }
 
+TEST(IPCTestingAPI, EmptyFirstPartyForCookiesCookieRequestHeaderFieldValue)
+{
+    RetainPtr webView = createWebViewWithIPCTestingAPI();
+    RetainPtr delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>document.cookie='a=b';</script>" baseURL:[NSURL URLWithString:@"https://webkit.org/"]];
+    auto sendMessage = @"const connection = IPC.connectionForProcessTarget('Networking');"
+        "const result = connection.sendSyncMessage("
+        "    0,"
+        "    IPC.messages.NetworkConnectionToWebProcess_CookieRequestHeaderFieldValue.name,"
+        "    1000,"
+        "    ["
+        "        {type: 'String', value: null},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'String', value: location.href},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'FrameID', value: IPC.frameID},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint64_t', value: IPC.pageID},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint8_t', value: 0},"
+        "        {type: 'uint8_t', value: 1},"
+        "    ]"
+        ");";
+    [webView evaluateJavaScript:sendMessage completionHandler:nil];
+    while (![webView objectByEvaluatingJavaScript:@"result"])
+        TestWebKitAPI::Util::spinRunLoop();
+    EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"result.arguments[0].value"] UTF8String], "<null>");
+}
+
 TEST(IPCTestingAPI, DescribesArguments)
 {
     auto webView = createWebViewWithIPCTestingAPI();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
index 77ae695483e9..f61602dc70e8 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IPCTestingAPI.mm
@@ -376,6 +376,39 @@ TEST(IPCTestingAPI, DecodesReplyArgumentsForAsyncMessage)
     EXPECT_STREQ([alertMessage UTF8String], "[{\"type\":\"bool\",\"value\":false}]");
 }
 
+TEST(IPCTestingAPI, EmptyFirstPartyForCookiesCookieRequestHeaderFieldValue)
+{
+    RetainPtr webView = createWebViewWithIPCTestingAPI();
+    RetainPtr delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>document.cookie='a=b';</script>" baseURL:[NSURL URLWithString:@"https://webkit.org/"]];
+    auto sendMessage = @"const connection = IPC.connectionForProcessTarget('Networking');"
+        "const result = connection.sendSyncMessage("
+        "    0,"
+        "    IPC.messages.NetworkConnectionToWebProcess_CookieRequestHeaderFieldValue.name,"
+        "    1000,"
+        "    ["
+        "        {type: 'String', value: null},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'String', value: location.href},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'FrameID', value: IPC.frameID},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint64_t', value: IPC.pageID},"
+        "        {type: 'uint8_t', value: 1},"
+        "        {type: 'uint8_t', value: 0},"
+        "        {type: 'uint8_t', value: 1},"
+        "    ]"
+        ");";
+    [webView evaluateJavaScript:sendMessage completionHandler:nil];
+    while (![webView objectByEvaluatingJavaScript:@"result"])
+        TestWebKitAPI::Util::spinRunLoop();
+    EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"result.arguments[0].value"] UTF8String], "<null>");
+}
+
 TEST(IPCTestingAPI, DescribesArguments)
 {
     auto webView = createWebViewWithIPCTestingAPI();
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.