Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in ServiceWorker
DescriptionInappropriate implementation in ServiceWorker
ComponentServiceWorker
Bug ClassLogic Error
Tracker497250399
Fix commitb01d62a11f3b (chromium/src) +0/-48
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
for
content/renderer/service_worker/embedded_worker_instance_client_impl.cc
modified

Files Changed

  • content/browser/service_worker/embedded_worker_instance.cc
  • content/public/browser/content_browser_client.cc
  • content/public/browser/content_browser_client.h
  • content/renderer/service_worker/embedded_worker_instance_client_impl.cc
  • third_party/blink/public/mojom/service_worker/embedded_worker.mojom
From b01d62a11f3b07e5cd754949abd04b1d879e56b4 Mon Sep 17 00:00:00 2001
From: Ari Chivukula <arichiv@chromium.org>
Date: Tue, 31 Mar 2026 07:38:24 -0700
Subject: [PATCH] Remove Blink runtime feature force-enable power from ServiceWorkers.

This change removes the
ContentBrowserClient::UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker
method and the associated forced_enabled_runtime_features field from
EmbeddedWorkerStartParams, which were used to enable specific Blink
runtime features for service workers in process-isolated renderers.

This field was no longer used, and is the potential source of a security
issue as a compromised service worker could enable sensitive features
like MojomJS depending on the future call tree.

Fixed: 497250399
Change-Id: I5a8a00e91bdba42abc9e25196fba5f67fec66250
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7715723
Auto-Submit: Ari Chivukula <arichiv@chromium.org>
Commit-Queue: Ari Chivukula <arichiv@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1607853}
---

diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc
index 8269d6f..13dc706 100644
--- a/content/browser/service_worker/embedded_worker_instance.cc
+++ b/content/browser/service_worker/embedded_worker_instance.cc
@@ -394,22 +394,6 @@
         params->devtools_worker_token.ToString());
   }
 
-  // To enable runtime features, the render process must be locked to the site.
-  // These features are highly privileged, so the renderer process with such
-  // features enabled shouldn't be used for other sites.
-  //
-  // WebUI schemes are process isolated already. To isolate other sites, the
-  // embedder can override ContentBrowserClient::ShouldLockProcessToSite().
-  if (rph->GetProcessLock().IsLockedToSite()) {
-    GetContentClient()
-        ->browser()
-        ->UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker(
-            context_->wrapper()->browser_context(), params->script_url,
-            params->forced_enabled_runtime_features);
-  }
-  CHECK(params->forced_enabled_runtime_features.empty() ||
-        rph->GetProcessLock().IsLockedToSite());
-
   // TODO(crbug.com/40584626): Support changes to blink::RendererPreferences
   // while the worker is running.
   DCHECK(context_->wrapper()->browser_context() ||
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 2e640bb..35e4074 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -499,11 +499,6 @@
   return true;
 }
 
-void ContentBrowserClient::UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker(
-    BrowserContext* context,
-    const GURL& script_url,
-    std::vector<std::string>& out_forced_enabled_runtime_features) {}
-
 bool ContentBrowserClient::AllowSharedWorker(
     const GURL& worker_url,
     const net::SiteForCookies& site_for_cookies,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index ba90f08..27a2f10 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -934,20 +934,6 @@
       const GURL& scope,
       BrowserContext* browser_context);
 
-  // Allows the embedder to enable process-wide blink features before starting a
-  // service worker. This is similar to
-  // `blink.mojom.CommitNavigationParams.force_enabled_origin_trials` but for
-  // RuntimeFeatures instead of Origin Trials.
-  //
-  // This method is only called when the process that will run the Service
-  // Worker is isolated. These features can be highly privileged, so the
-  // renderer process with such features enabled shouldn't be used for other
-  // sites.
-  virtual void UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker(
-      BrowserContext* context,
-      const GURL& script_url,
-      std::vector<std::string>& out_forced_enabled_runtime_features);
-
   // Allow the embedder to control if a Shared Worker can be connected from a
   // given tab.
   // This is called on the UI thread.
diff --git a/content/renderer/service_worker/embedded_worker_instance_client_impl.cc b/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
index b4a41b69..09b1643 100644
--- a/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
+++ b/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
@@ -98,10 +98,6 @@
 
   start_data->is_cross_origin_isolated = params->cross_origin_isolated;
 
-  for (const auto& feature : params->forced_enabled_runtime_features) {
-    blink::WebRuntimeFeatures::EnableFeatureFromString(feature, true);
-  }
-
   // `cache_storage` may be null if COEP is not enabled, we cannot bind
   // eagerly in that case.
   mojo::PendingRemote<blink::mojom::CacheStorage> cache_storage =
diff --git a/third_party/blink/public/mojom/service_worker/embedded_worker.mojom b/third_party/blink/public/mojom/service_worker/embedded_worker.mojom
index dfc719e3..68a0ff7 100644
--- a/third_party/blink/public/mojom/service_worker/embedded_worker.mojom
+++ b/third_party/blink/public/mojom/service_worker/embedded_worker.mojom
@@ -91,15 +91,6 @@
   // Used to set up fetch requests.
   RendererPreferences renderer_preferences;
 
-  // List of blink runtime features (based on their names) to enable for this
-  // renderer process. To avoid exposing dangerous features to other sites, this
-  // is only populated if the renderer process is isolated.
-  //
-  // Ideally this would be part of content.mojom.Renderer, but
-  // EmbedderWorkerInstanceClient and content.mojom.Renderer use separate pipes
-  // so a ServiceWorker could be started before the features are enabled.
-  array<string> forced_enabled_runtime_features;
-
   // Used to talk to the service worker from the browser process.
   pending_receiver<ServiceWorker> service_worker_receiver;
 
Loading diff…

Original Bug Report

reported by vm...@google.com

ProtectedMemory bypass for MojoJS via runtime-enabled feature string iteration gadget

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A logic error in the generated code for runtime-enabled features allows an attacker with an arbitrary memory write primitive to bypass ProtectedMemory mitigations for MojoJS. By corrupting the forced_enabled_runtime_features vector during Service Worker startup, an attacker can invoke a generated string-to-setter mapping that legitimately unprotects and enables the flag. This exposes window.Mojo to the web context, bypassing context checks and facilitating a renderer sandbox escape.

Affected files:

  • third_party/blink/renderer/build/scripts/templates/runtime_enabled_features.cc.tmpl
  • content/renderer/service_worker/embedded_worker_instance_client_impl.cc
  • third_party/blink/renderer/platform/runtime_enabled_features.json5

Estimated timestamp from git blame: 2024-05-16

Background

ProtectedMemory is designed to prevent data-only attacks from modifying security-critical variables (like is_mojo_js_enabled_) by keeping them in read-only memory pages. They are only briefly made writable during legitimate updates via base::AutoWritableMemory.

Vulnerability Details

A structural flaw exists in how RuntimeEnabledFeaturesBase::SetFeatureEnabledFromString is generated by third_party/blink/renderer/build/scripts/templates/runtime_enabled_features.cc.tmpl.

The generator creates a static kFeatures[] array mapping feature string names to their respective setter functions. Crucially, it includes features marked with is_protected_feature: true. The generated setter for a protected feature (e.g., SetMojoJSEnabled) legitimately uses base::AutoWritableMemory to unprotect, modify, and re-protect the memory.

Because this setter is reachable via a string-indexed lookup, it creates a powerful Data-Oriented Programming (DOP) gadget. If an attacker can control a string passed to WebRuntimeFeatures::EnableFeatureFromString, they can safely toggle ProtectedMemory variables without causing a segmentation fault.

Potential Attack Path

Note: These are suggested steps based on static analysis; our tooling does not currently have the ability to run code to produce a live Proof-of-Concept.

  1. Obtain Primitives: An attacker exploits a typical renderer vulnerability (e.g., a V8 bug) to gain arbitrary memory read/write capabilities.
  2. Trigger IPC: The attacker triggers a Service Worker startup sequence, causing the browser to send an EmbeddedWorkerStartParamsPtr IPC to the renderer.
  3. Corrupt Heap Data: During deserialization, the params->forced_enabled_runtime_features std::vector<std::string> is allocated on the renderer’s heap. While the StartWorker task is queued, the attacker uses their write primitive to overwrite the vector’s backing store to point to a crafted string containing "MojoJS".
  4. Execute Gadget: EmbeddedWorkerInstanceClientImpl::StartWorker executes and iterates over the corrupted vector, calling blink::WebRuntimeFeatures::EnableFeatureFromString("MojoJS", true).
  5. Bypass ProtectedMemory: The gadget performs a binary search, finds "MojoJS", and invokes SetMojoJSEnabled(true). base::AutoWritableMemory safely unprotects the page, sets the global is_mojo_js_enabled_ flag to true, and re-protects it.
  6. Evade Context Checks: The attacker creates a new execution context (e.g., a new <iframe>). In Blink IDL, Mojo uses [ContextEnabled=MojoJS, RuntimeEnabled=MojoJS]. The generated bindings (v8_window.cc) use an OR condition for these attributes. Because the global RuntimeEnabled flag is now true, the check short-circuits, completely bypassing the strict ContextFeatureSettings::CrashIfMojoJSNotAllowed() mitigation.
  7. Sandbox Escape: window.Mojo is exposed to the attacker’s JavaScript, allowing them to bind privileged browser interfaces and escape the sandbox.

Suggested Fix

Modify third_party/blink/renderer/build/scripts/templates/runtime_enabled_features.cc.tmpl to explicitly exclude features marked with is_protected_feature: true from the kFeatures[] array used by SetFeatureEnabledFromString (and potentially IsFeatureEnabledFromString). Protected features should not be toggleable via dynamic string lookups.

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