High watch CVSS 3.1 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in ServiceWorker
DescriptionInappropriate implementation in ServiceWorker
ComponentServiceWorker
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497437113
Fix commitf96ee4aeefc7 (chromium/src) +16/-4
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-29 (per vendor advisory)
Fix landed2026-04-02 (public git)
Disclosed2026-05-05
Patch gap33 days — fix public in source before the advisory

Background

`ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory`
a browser-side URLLoaderFactory used by the RaceNetworkRequest ServiceWorker feature to forward a single already-started network response’s data pipe to the renderer.
RaceNetworkRequest
a ServiceWorker optimization that starts a direct network fetch in parallel with the service worker fetch handler and delivers whichever response is ready first.
`is_data_pipe_fused_`
a boolean flag set once the factory has spliced (“fused”) its forwarded response data pipe into the client, marking the factory as already consumed.
`ReportBadMessage`
a Mojo receiver mechanism that marks an incoming IPC as malformed and terminates the calling (renderer) process.

Root Cause Analysis

The factory is designed for exactly one CreateLoaderAndStart call: the first call fuses the forwarded data pipe and sets is_data_pipe_fused_ = true. In the pre-patch code, a second CreateLoaderAndStart (the else branch, reached when is_data_pipe_fused_ is already true) silently fell back to fallback_factory_->CreateLoaderAndStart(...), quietly starting a brand-new request instead of treating the reuse as an error. Because a legitimate renderer never issues a second call on this single-use factory, that fallback path could only be reached by a compromised or buggy renderer, and honoring it let the renderer drive unexpected loader state through a factory that had already been consumed.

The fix replaces the fallback with receiver_.ReportBadMessage(...) (guarded by the default-enabled kill-switch feature kKillSwitchForRaceNetworkRequestMultipleCreateLoaderAndStartCalls), so a duplicate call now terminates the renderer rather than being serviced. This restores the single-use invariant by making illegal reuse fatal instead of silently tolerated.

Key insight
The single mistake was treating a duplicate CreateLoaderAndStart on a one-shot factory as a recoverable condition (fall back and start a new request) rather than as an IPC-contract violation; the fix converts that impossible-for-a-good-renderer state into a bad-message kill.

Attack Path

  1. Establish a RaceNetworkRequest loader A page controlled by a service worker triggers a navigation/subresource fetch that instantiates a ServiceWorkerForwardedRaceNetworkRequestURLLoaderFactory in the browser process.
  2. Consume the factory once The first CreateLoaderAndStart fuses the forwarded data pipe and sets is_data_pipe_fused_ = true, leaving the factory in its post-use state.
  3. Reuse the factory A compromised renderer sends a second CreateLoaderAndStart on the same factory receiver, reaching the else branch that a legitimate renderer never hits.
  4. Trigger unexpected browser-side loader behavior Pre-patch, the browser served this by calling fallback_factory_->CreateLoaderAndStart(...), starting an unintended request through an already-consumed factory (the concrete downstream corruption or confusion this enables is not shown in the diff) (inferred).

Impact Assessment

An attacker with a compromised renderer could invoke browser-side loader logic that was never meant to run twice, driving the RaceNetworkRequest factory into an unexpected state; the advisory classes this as a LogicError / inappropriate implementation in ServiceWorker, not a demonstrated memory-corruption primitive. Preconditions are a service worker controlling the document and a renderer able to issue a second CreateLoaderAndStart IPC. No exploited-in-the-wild status is stated, and any escalation beyond the mishandled reuse is (inferred).

Files Changed

  • content/common/service_worker/forwarded_race_network_request_url_loader_factory.cc

Audit Directions

  • Single-use IPC objects with silent fallback
    Audit other Mojo-exposed factories/loaders that maintain a “consumed”/“fused” flag but tolerate re-entry by re-doing work instead of reporting a bad message.
  • `else`-branch permissiveness on renderer-driven state
    Look for browser-side handlers that treat a should-be-impossible renderer state as recoverable (fallback, retry, no-op) rather than as a protocol violation.
  • RaceNetworkRequest / forwarding loaders
    Review the broader RaceNetworkRequest and ServiceWorker loader plumbing for other places where forwarded pipes or receivers can be re-driven after their one intended use.
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