Chrome · Workers
CVE-2026-10996
Logic Error in Workers
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/BUILD.gn |
modified | |
GetStorageKeycontent/browser/devtools/dedicated_worker_devtools_agent_host.cc |
modified |
Files Changed
content/browser/BUILD.gncontent/browser/browser_interface_binders.cccontent/browser/compute_pressure/pressure_service_for_worker_unittest.cccontent/browser/devtools/dedicated_worker_devtools_agent_host.cccontent/browser/devtools/shared_worker_devtools_agent_host.cccontent/browser/direct_sockets/direct_sockets_service_impl.cccontent/browser/service_worker/service_worker_client.cc
Patch
From 606713645ace47728a8e2a56dd1fc6a8b44e13a7 Mon Sep 17 00:00:00 2001
From: Yoshisto Yanagisawa <yyanagisawa@chromium.org>
Date: Mon, 06 Apr 2026 08:23:23 -0700
Subject: [PATCH] Create opaque origins for data: URL workers behind a flag
When enabled, the kDataUrlWorkerOpaqueOrigin feature flag ensures that
dedicated and shared workers created from a data: URL have an opaque
origin (and thus an opaque StorageKey) rather than inheriting their
creator's origin, aligning with the HTML spec.
This CL implements the browser-process logic for this feature by:
1. Updating DedicatedWorkerHost to receive the appropriate StorageKey.
2. Generating an opaque StorageKey in DedicatedWorkerHostFactoryImpl
if the worker script is a data: URL and the flag is on.
3. Updating SharedWorkerInstance to use an opaque StorageKey if the
worker script is a data: URL and the flag is on.
4. Bypassing the CanAccessSharedWorkers() check in WebSharedWorkerImpl
for data: URL scripts when the feature is enabled, as opaque
origins do not normally allow access to shared workers.
Bug: 40051700
Change-Id: If8db773b61c1ff9e5ef00db6548f618b2e8f12c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7714382
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
Commit-Queue: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1610304}
---
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 1742ffc7..a560c5f 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2569,6 +2569,8 @@
"worker_host/worker_script_loader.h",
"worker_host/worker_script_loader_factory.cc",
"worker_host/worker_script_loader_factory.h",
+ "worker_host/worker_util.cc",
+ "worker_host/worker_util.h",
]
if (is_android) {
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 8b9989d..baa5bd1 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -420,8 +420,9 @@
auto* process_host =
static_cast<RenderProcessHostImpl*>(host->GetProcessHost());
CHECK(process_host);
- process_host->CreateNotificationService(
- rfh_id, creator_type, host->GetStorageKey(), std::move(receiver));
+ process_host->CreateNotificationService(rfh_id, creator_type,
+ host->GetWorkerStorageKey(),
+ std::move(receiver));
},
base::Unretained(host), rfh_id, creator_type);
}
@@ -505,7 +506,8 @@
auto* process_host =
static_cast<RenderProcessHostImpl*>(host->GetProcessHost());
if (process_host)
- (process_host->*method)(host->GetStorageKey(), std::move(receiver));
+ (process_host->*method)(host->GetWorkerStorageKey(),
+ std::move(receiver));
},
base::Unretained(host), method);
}
@@ -527,7 +529,7 @@
auto* process_host =
static_cast<RenderProcessHostImpl*>(host->GetProcessHost());
if (process_host)
- (process_host->*method)(host->GetStorageKey(), *host,
+ (process_host->*method)(host->GetWorkerStorageKey(), *host,
std::move(receiver));
},
base::Unretained(host), method);
@@ -1297,7 +1299,7 @@
// Dedicated workers
const url::Origin& GetContextForHost(DedicatedWorkerHost* host) {
- return host->GetStorageKey().origin();
+ return host->GetWorkerStorageKey().origin();
}
void PopulateDedicatedWorkerBinders(DedicatedWorkerHost* host,
@@ -1420,7 +1422,7 @@
auto* process_host = host->GetProcessHost();
GetContentClient()->browser()->BindTranslationManager(
process_host, process_host->GetBrowserContext(), host,
- host->GetStorageKey().origin(), std::move(receiver));
+ host->GetWorkerStorageKey().origin(), std::move(receiver));
},
base::Unretained(host)));
map->Add<language_detection::mojom::ContentLanguageDetectionDriver>(
@@ -1540,7 +1542,7 @@
auto* process_host = host->GetProcessHost();
GetContentClient()->browser()->BindTranslationManager(
process_host, process_host->GetBrowserContext(), host,
- host->GetStorageKey().origin(), std::move(receiver));
+ host->GetWorkerStorageKey().origin(), std::move(receiver));
},
base::Unretained(host)));
map->Add<language_detection::mojom::ContentLanguageDetectionDriver>(
diff --git a/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc b/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc
index 0c1b518..a2be8b8 100644
--- a/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc
+++ b/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc
@@ -18,6 +18,7 @@
#include "content/browser/worker_host/dedicated_worker_service_impl.h"
#include "content/browser/worker_host/shared_worker_host.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
+#include "content/browser/worker_host/worker_util.h"
#include "content/public/browser/shared_worker_instance.h"
#include "content/public/test/navigation_simulator.h"
#include "content/test/test_render_frame_host.h"
@@ -146,8 +147,8 @@
worker_host_ = std::make_unique<DedicatedWorkerHost>(
&worker_service_, blink::DedicatedWorkerToken(), rfh->GetProcess(),
rfh->GetGlobalId(), rfh->GetGlobalId(), rfh->GetStorageKey(),
- rfh->GetStorageKey().origin(), rfh->GetIsolationInfoForSubresources(),
- rfh->BuildClientSecurityState(),
+ rfh->GetStorageKey(), rfh->GetStorageKey().origin(),
+ rfh->GetIsolationInfoForSubresources(), rfh->BuildClientSecurityState(),
rfh->policy_container_host()->policies(),
/*creator_coep_reporter=*/nullptr,
/*network_restrictions_id=*/std::nullopt,
@@ -270,10 +271,15 @@
pressure_manager_.reset();
auto* rfh = contents()->GetPrimaryMainFrame();
+ blink::StorageKey worker_storage_key =
+ CalculateWorkerStorageKey(kWorkerUrl, rfh->GetStorageKey());
+ url::Origin renderer_origin =
+ CalculateWorkerRendererOrigin(kWorkerUrl, worker_storage_key);
+
SharedWorkerInstance instance(
kWorkerUrl, blink::mojom::ScriptType::kClassic,
network::mojom::CredentialsMode::kSameOrigin, "name",
- rfh->GetStorageKey(),
+ rfh->GetStorageKey(), worker_storage_key, renderer_origin,
blink::mojom::SharedWorkerCreationContextType::kSecure,
rfh->GetStorageKey().IsFirstPartyContext()
? blink::mojom::SharedWorkerSameSiteCookies::kAll
diff --git a/content/browser/devtools/dedicated_worker_devtools_agent_host.cc b/content/browser/devtools/dedicated_worker_devtools_agent_host.cc
index 811b327..f911bffa 100644
--- a/content/browser/devtools/dedicated_worker_devtools_agent_host.cc
+++ b/content/browser/devtools/dedicated_worker_devtools_agent_host.cc
@@ -50,7 +50,7 @@
std::optional<blink::StorageKey>
DedicatedWorkerDevToolsAgentHost::GetStorageKey() {
DedicatedWorkerHost* const host = GetDedicatedWorkerHost();
- return host ? std::make_optional(host->GetStorageKey()) : std::nullopt;
+ return host ? std::make_optional(host->GetWorkerStorageKey()) : std::nullopt;
}
std::string DedicatedWorkerDevToolsAgentHost::GetType() {
diff --git a/content/browser/devtools/shared_worker_devtools_agent_host.cc b/content/browser/devtools/shared_worker_devtools_agent_host.cc
index 28e3430..bbf038c 100644
--- a/content/browser/devtools/shared_worker_devtools_agent_host.cc
+++ b/content/browser/devtools/shared_worker_devtools_agent_host.cc
@@ -81,7 +81,7 @@
}
blink::StorageKey SharedWorkerDevToolsAgentHost::GetStorageKey() const {
- return instance_.storage_key();
+ return instance_.worker_storage_key();
}
bool SharedWorkerDevToolsAgentHost::Activate() {
@@ -125,7 +125,7 @@
bool SharedWorkerDevToolsAgentHost::Matches(SharedWorkerHost* worker_host) {
return instance_.Matches(worker_host->instance().url(),
worker_host->instance().name(),
- worker_host->instance().storage_key(),
+ worker_host->instance().creator_storage_key(),
worker_host->instance().same_site_cookies());
}
diff --git a/content/browser/direct_sockets/direct_sockets_service_impl.cc b/content/browser/direct_sockets/direct_sockets_service_impl.cc
index 151c5546..f605ce0 100644
--- a/content/browser/direct_sockets/direct_sockets_service_impl.cc
+++ b/content/browser/direct_sockets/direct_sockets_service_impl.cc
@@ -294,7 +294,10 @@
/*access_allowed=*/shared_worker &&
ArePermissionTypesAllowedForWorker(
shared_worker->GetProcessHost(),
- shared_worker->instance().storage_key().origin(),
+ // Use the worker's own origin for permission checks. This
+ // ensures that data: URL workers, which have opaque
+ // origins, are denied sensitive permissions.
+ shared_worker->instance().worker_storage_key().origin(),
std::move(required_permissions)));
} else {
std::move(callback)
diff --git a/content/browser/service_worker/service_worker_client.cc b/content/browser/service_worker/service_worker_client.cc
index 5da2325..58523e5f 100644
--- a/content/browser/service_worker/service_worker_client.cc
+++ b/content/browser/service_worker/service_worker_client.cc
@@ -608,7 +608,7 @@
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc b/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc
index 0c1b518..a2be8b8 100644
--- a/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc
+++ b/content/browser/compute_pressure/pressure_service_for_worker_unittest.cc
@@ -18,6 +18,7 @@
#include "content/browser/worker_host/dedicated_worker_service_impl.h"
#include "content/browser/worker_host/shared_worker_host.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
+#include "content/browser/worker_host/worker_util.h"
#include "content/public/browser/shared_worker_instance.h"
#include "content/public/test/navigation_simulator.h"
#include "content/test/test_render_frame_host.h"
@@ -146,8 +147,8 @@
worker_host_ = std::make_unique<DedicatedWorkerHost>(
&worker_service_, blink::DedicatedWorkerToken(), rfh->GetProcess(),
rfh->GetGlobalId(), rfh->GetGlobalId(), rfh->GetStorageKey(),
- rfh->GetStorageKey().origin(), rfh->GetIsolationInfoForSubresources(),
- rfh->BuildClientSecurityState(),
+ rfh->GetStorageKey(), rfh->GetStorageKey().origin(),
+ rfh->GetIsolationInfoForSubresources(), rfh->BuildClientSecurityState(),
rfh->policy_container_host()->policies(),
/*creator_coep_reporter=*/nullptr,
/*network_restrictions_id=*/std::nullopt,
@@ -270,10 +271,15 @@
pressure_manager_.reset();
auto* rfh = contents()->GetPrimaryMainFrame();
+ blink::StorageKey worker_storage_key =
+ CalculateWorkerStorageKey(kWorkerUrl, rfh->GetStorageKey());
+ url::Origin renderer_origin =
+ CalculateWorkerRendererOrigin(kWorkerUrl, worker_storage_key);
+
SharedWorkerInstance instance(
kWorkerUrl, blink::mojom::ScriptType::kClassic,
network::mojom::CredentialsMode::kSameOrigin, "name",
- rfh->GetStorageKey(),
+ rfh->GetStorageKey(), worker_storage_key, renderer_origin,
blink::mojom::SharedWorkerCreationContextType::kSecure,
rfh->GetStorageKey().IsFirstPartyContext()
? blink::mojom::SharedWorkerSameSiteCookies::kAll
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index 231dde9..7a718e3 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -24,6 +24,7 @@
#include "content/browser/worker_host/shared_worker_connector_impl.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
#include "content/browser/worker_host/worker_script_fetcher.h"
+#include "content/browser/worker_host/worker_util.h"
#include "content/public/browser/shared_worker_instance.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
@@ -84,10 +85,16 @@
base::WeakPtr<SharedWorkerHost> CreateHostWithExtendedLifetime(
bool extended_lifetime) {
+ blink::StorageKey creator_storage_key =
+ blink::StorageKey::CreateFirstParty(url::Origin::Create(kWorkerUrl));
+ blink::StorageKey worker_storage_key =
+ CalculateWorkerStorageKey(kWorkerUrl, creator_storage_key);
+ url::Origin renderer_origin =
+ CalculateWorkerRendererOrigin(kWorkerUrl, worker_storage_key);
SharedWorkerInstance instance(
kWorkerUrl, blink::mojom::ScriptType::kClassic,
network::mojom::CredentialsMode::kSameOrigin, "name",
- blink::StorageKey::CreateFirstParty(url::Origin::Create(kWorkerUrl)),
+ creator_storage_key, worker_storage_key, renderer_origin,
blink::mojom::SharedWorkerCreationContextType::kSecure,
blink::mojom::SharedWorkerSameSiteCookies::kAll, extended_lifetime);
auto host = std::make_unique<SharedWorkerHost>(
@@ -391,7 +398,7 @@
network::mojom::URLLoaderFactoryParamsPtr params =
host->CreateNetworkFactoryParamsForSubresources();
- EXPECT_EQ(host->GetStorageKey().origin(),
+ EXPECT_EQ(host->GetWorkerStorageKey().origin(),
params->isolation_info.frame_origin());
EXPECT_FALSE(params->isolation_info.nonce().has_value());
}
@@ -399,11 +406,17 @@
TEST_F(SharedWorkerHostTest,
CreateNetworkFactoryParamsForSubresourcesWithNonce) {
base::UnguessableToken nonce = base::UnguessableToken::Create();
+ blink::StorageKey creator_storage_key = blink::StorageKey::CreateWithNonce(
+ url::Origin::Create(kWorkerUrl), nonce);
+ blink::StorageKey worker_storage_key =
+ CalculateWorkerStorageKey(kWorkerUrl, creator_storage_key);
+ url::Origin renderer_origin =
+ CalculateWorkerRendererOrigin(kWorkerUrl, worker_storage_key);
+
SharedWorkerInstance instance(
kWorkerUrl, blink::mojom::ScriptType::kClassic,
- network::mojom::CredentialsMode::kSameOrigin, "name",
- blink::StorageKey::CreateWithNonce(url::Origin::Create(kWorkerUrl),
- nonce),
+ network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+ worker_storage_key, renderer_origin,
blink::mojom::SharedWorkerCreationContextType::kSecure,
blink::mojom::SharedWorkerSameSiteCookies::kNone,
/*extended_lifetime=*/false);
@@ -443,10 +456,17 @@
TEST_F(SharedWorkerHostTestWithLNAEnabled,
CreateNetworkFactoryParamsForSubresources) {
+ blink::StorageKey creator_storage_key =
+ blink::StorageKey::CreateFirstParty(url::Origin::Create(kWorkerUrl));
+ blink::StorageKey worker_storage_key =
+ CalculateWorkerStorageKey(kWorkerUrl, creator_storage_key);
+ url::Origin renderer_origin =
+ CalculateWorkerRendererOrigin(kWorkerUrl, worker_storage_key);
+
SharedWorkerInstance instance(
kWorkerUrl, blink::mojom::ScriptType::kClassic,
- network::mojom::CredentialsMode::kSameOrigin, "name",
- blink::StorageKey::CreateFirstParty(url::Origin::Create(kWorkerUrl)),
+ network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+ worker_storage_key, renderer_origin,
blink::mojom::SharedWorkerCreationContextType::kSecure,
blink::mojom::SharedWorkerSameSiteCookies::kAll,
/*extended_lifetime=*/false);
diff --git a/content/browser/worker_host/shared_worker_instance_unittest.cc b/content/browser/worker_host/shared_worker_instance_unittest.cc
index 9d319a0..59c34fc 100644
--- a/content/browser/worker_host/shared_worker_instance_unittest.cc
+++ b/content/browser/worker_host/shared_worker_instance_unittest.cc
@@ -8,9 +8,12 @@
#include <string>
#include <string_view>
+#include "base/feature_list.h"
#include "base/strings/utf_string_conversions.h"
+#include "content/browser/worker_host/worker_util.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
@@ -25,9 +28,14 @@
SharedWorkerInstance CreateInstance(const GURL& script_url,
const std::string& name,
const blink::StorageKey& storage_key) {
+ blink::StorageKey worker_storage_key =
+ CalculateWorkerStorageKey(script_url, storage_key);
+ url::Origin renderer_origin =
+ CalculateWorkerRendererOrigin(script_url, worker_storage_key);
return SharedWorkerInstance(
script_url, blink::mojom::ScriptType::kClassic,
network::mojom::CredentialsMode::kSameOrigin, name, storage_key,
+ worker_storage_key, renderer_origin,
blink::mojom::SharedWorkerCreationContextType::kNonsecure,
storage_key.IsFirstPartyContext()
? blink::mojom::SharedWorkerSameSiteCookies::kAll
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/broadcastchannel/opaque-origin-expected.txt b/third_party/blink/web_tests/external/wpt/webmessaging/broadcastchannel/opaque-origin-expected.txt
deleted file mode 100644
index a1d3fec..0000000
--- a/third_party/blink/web_tests/external/wpt/webmessaging/broadcastchannel/opaque-origin-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] BroadcastChannel messages from data URL dedicated workers should be self-contained
- promise_test: Unhandled rejection with value: "Received message from an opaque origin"
-[FAIL] BroadcastChannel messages from data URL shared workers should be self-contained
- promise_test: Unhandled rejection with value: "Received message from an opaque origin"
-Harness: the test ran to completion.
-
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page