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 <[email protected]> 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 <[email protected]> Reviewed-by: Hidehiko Abe <[email protected]> Commit-Queue: Yoshisato Yanagisawa <[email protected]> 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