CVE-2026-7909
Overview
Files Changed
content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc
Patch
From f96ee4aeefc76c420782298b64f41e6dfa1f1db2 Mon Sep 17 00:00:00 2001
From: Shunya Shishido <sisidovski@chromium.org>
Date: Thu, 02 Apr 2026 08:23:44 -0700
Subject: [PATCH] Report bad message on reuse of ForwardedRaceNetworkRequestURLLoaderFactory
The ForwardedRaceNetworkRequestURLLoaderFactory is intended for a single
use. If CreateLoaderAndStart is called after the data pipe has already
been fused, it indicates an unexpected state, so report a bad message
instead of falling back.
Bug: 497437113
Change-Id: I2867a9359a6764065fe95e0b1f7d68fc80cf0d05
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7723101
Commit-Queue: Shunya Shishido <sisidovski@chromium.org>
Reviewed-by: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1609228}
---
diff --git a/content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc b/content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc
index a64f9b5..cfd03bec 100644
--- a/content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc
+++ b/content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc
@@ -4,8 +4,16 @@
#include "content/common/service_worker/forwarded_race_network_request_url_loader_factory.h"
+#include "base/feature_list.h"
+
namespace content {
+namespace {
+// Kill switch for multiple CreateLoaderAndStart calls.
+BASE_FEATURE(kKillSwitchForRaceNetworkRequestMultipleCreateLoaderAndStartCalls,
+ base::FEATURE_ENABLED_BY_DEFAULT);
+} // namespace
+
ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory::
ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory(
mojo::PendingReceiver<network::mojom::URLLoaderClient> client_receiver,
@@ -34,10 +42,14 @@
CHECK(result) << resource_request.url;
is_data_pipe_fused_ = true;
} else {
- // If already fused, create a new URLLoader and start the new request.
- fallback_factory_->CreateLoaderAndStart(
- std::move(receiver), request_id, options, resource_request,
- std::move(client), traffic_annotation);
+ // A legitimate renderer will never hit this branch.
+ // If we are here, the renderer is compromised or severely buggy.
+ if (base::FeatureList::IsEnabled(
+ kKillSwitchForRaceNetworkRequestMultipleCreateLoaderAndStartCalls)) {
+ receiver_.ReportBadMessage(
+ "ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory: "
+ "CreateLoaderAndStart called multiple times.");
+ }
}
}
Original Bug Report
Site Isolation bypass via ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential vulnerability in ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory allows a compromised Service Worker renderer to bypass Site Isolation. By calling CreateLoaderAndStart twice on a provided Mojo pipe, the renderer can forward arbitrary requests to a trusted browser-process URLLoaderFactory, enabling cross-origin data theft.
Affected files:
content/common/service_worker/forwarded_race_network_request_url_loader_factory.cccontent/browser/service_worker/service_worker_main_resource_loader.cccontent/browser/service_worker/service_worker_fetch_dispatcher.cccontent/browser/service_worker/service_worker_client.cccontent/browser/loader/navigation_url_loader_impl.ccservices/network/cors/cors_url_loader_factory.cc
Estimated timestamp from git blame: 2025-12-17
Summary
A potential vulnerability exists in the Service Worker ‘Race Network Request’ implementation where a highly trusted URLLoaderFactory is exposed to the renderer process with insufficient validation. A compromised Service Worker renderer can abuse this factory’s state machine to make arbitrary network requests with browser-process privileges, effectively bypassing Site Isolation, Opaque Response Blocking (ORB), and the request_initiator origin lock.
Vulnerability Details
When a Race Network Request is initiated for a main resource, ServiceWorkerMainResourceLoader::StartRaceNetworkRequest (in content/browser/service_worker/service_worker_main_resource_loader.cc) creates a URLLoaderFactory intended for the browser process. This factory is created via NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient and is granted elevated privileges:
process_id=network::mojom::kBrowserProcessIdis_trusted=trueis_orb_enabled=false
This privileged factory is passed as the fallback_factory_ to a new instance of ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory. A Mojo PendingRemote to this wrapper factory is then sent to the untrusted Service Worker renderer via DispatchFetchEventParams.race_network_request_loader_factory.
In content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc, the CreateLoaderAndStart method has a logic flaw:
void ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory::CreateLoaderAndStart(
/* ... */
const network::ResourceRequest& resource_request,
/* ... */) {
if (!is_data_pipe_fused_) {
bool result = mojo::FusePipes(std::move(client_receiver_), std::move(client));
CHECK(result) << resource_request.url;
result = mojo::FusePipes(std::move(receiver), std::move(loader_));
CHECK(result) << resource_request.url;
is_data_pipe_fused_ = true;
} else {
fallback_factory_->CreateLoaderAndStart(
std::move(receiver), request_id, options, resource_request,
std::move(client), traffic_annotation);
}
}
A compromised renderer can call this method twice:
- First Call: The renderer calls
CreateLoaderAndStartwith valid, unused Mojo endpoints.mojo::FusePipessucceeds, andis_data_pipe_fused_is set totrue. - Second Call: The renderer calls
CreateLoaderAndStartagain with a maliciously craftedResourceRequest(e.g., targeting a sensitive cross-origin URL, spoofingrequest_initiator, and injectingtrusted_params). Becauseis_data_pipe_fused_is nowtrue, the code enters theelsebranch and blindly forwards the attacker’s request to the highly privilegedfallback_factory_.
Because the fallback_factory_ is trusted by the Network Service, it skips standard renderer security checks. It accepts the spoofed request_initiator without killing the renderer, processes the injected trusted_params (attaching the victim’s cross-origin cookies), and disables ORB. The renderer can then read the raw cross-origin response, resulting in a full Site Isolation bypass.
Potential Reproduction Steps
Note: These are suggested steps based on code analysis; we do not have a working Proof of Concept yet.
- An attacker registers a Service Worker using the Static Router API with a
race-network-and-fetch-handlersource (or relies onServiceWorkerAutoPreload). - The attacker compromises the renderer process hosting the Service Worker (e.g., via a V8 vulnerability).
- A navigation occurs that triggers the race request, causing the browser process to send a
DispatchFetchEventIPC containing therace_network_request_loader_factoryMojo remote. - From the compromised renderer, the attacker intercepts this remote.
- To keep the factory alive in the browser process, the attacker’s script intentionally delays or never finishes the
fetchevent. - The attacker creates valid, unused Mojo pipes and calls
CreateLoaderAndStarton the remote. This flips the internalis_data_pipe_fused_state totruein the browser process. - The attacker crafts a malicious
ResourceRequesttargeting a cross-origin URL (e.g.,https://mail.google.com), sets therequest_initiatorto match the target, and injectstrusted_paramswith appropriateisolation_info. - The attacker calls
CreateLoaderAndStartagain on the same remote with this payload. The browser process forwards it to the Network Service using its trusted factory. - The Network Service fulfills the request, attaches the victim’s cookies, and streams the cross-origin response back to the compromised renderer, completing the data theft.
Suggested Fix
The ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory should strictly enforce that it only handles a single request, or it must validate the incoming ResourceRequest.
A potential fix is to disconnect the receiver and destroy the factory immediately after the first CreateLoaderAndStart call if the intention is only to fuse pipes. If the fallback_factory_ must be used for subsequent calls (e.g., for redirects), the wrapper must validate that the incoming ResourceRequest perfectly matches the original request parameters (URL, initiator, etc.) and strictly reject or strip trusted_params coming from the untrusted renderer.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.