Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in ServiceWorker
DescriptionInappropriate implementation in ServiceWorker
ComponentServiceWorker
Bug ClassLogic Error
Tracker497437113
Fix commitf96ee4aeefc7 (chromium/src) +16/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Files Changed

  • content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc
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.");
+    }
   }
 }
 
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • content/browser/service_worker/service_worker_main_resource_loader.cc
  • content/browser/service_worker/service_worker_fetch_dispatcher.cc
  • content/browser/service_worker/service_worker_client.cc
  • content/browser/loader/navigation_url_loader_impl.cc
  • services/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::kBrowserProcessId
  • is_trusted = true
  • is_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:

  1. First Call: The renderer calls CreateLoaderAndStart with valid, unused Mojo endpoints. mojo::FusePipes succeeds, and is_data_pipe_fused_ is set to true.
  2. Second Call: The renderer calls CreateLoaderAndStart again with a maliciously crafted ResourceRequest (e.g., targeting a sensitive cross-origin URL, spoofing request_initiator, and injecting trusted_params). Because is_data_pipe_fused_ is now true, the code enters the else branch and blindly forwards the attacker’s request to the highly privileged fallback_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.

  1. An attacker registers a Service Worker using the Static Router API with a race-network-and-fetch-handler source (or relies on ServiceWorkerAutoPreload).
  2. The attacker compromises the renderer process hosting the Service Worker (e.g., via a V8 vulnerability).
  3. A navigation occurs that triggers the race request, causing the browser process to send a DispatchFetchEvent IPC containing the race_network_request_loader_factory Mojo remote.
  4. From the compromised renderer, the attacker intercepts this remote.
  5. To keep the factory alive in the browser process, the attacker’s script intentionally delays or never finishes the fetch event.
  6. The attacker creates valid, unused Mojo pipes and calls CreateLoaderAndStart on the remote. This flips the internal is_data_pipe_fused_ state to true in the browser process.
  7. The attacker crafts a malicious ResourceRequest targeting a cross-origin URL (e.g., https://mail.google.com), sets the request_initiator to match the target, and injects trusted_params with appropriate isolation_info.
  8. The attacker calls CreateLoaderAndStart again on the same remote with this payload. The browser process forwards it to the Network Service using its trusted factory.
  9. 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.

View on issue tracker
Links in the report