Low chrome UAF 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Workers
DescriptionUse after free in Workers
ComponentWorkers
Bug ClassUAF
Tracker497408124
Fix commitecb2d9f92d3f (chromium/src) +172/-16
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
storage/browser/blob/blob_url_registry.cc
modified
if
storage/browser/blob/blob_url_store_impl.cc
modified
TEST_F
content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
modified
if
content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
modified
TEST_F
content/browser/worker_host/shared_worker_host_unittest.cc
modified
TEST_P
storage/browser/blob/blob_url_registry_unittest.cc
modified

Files Changed

  • storage/browser/blob/blob_url_registry.cc
  • content/browser/worker_host/shared_worker_host.cc
  • storage/browser/blob/blob_url_registry_unittest.cc
  • content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
  • storage/browser/blob/blob_url_registry.h
  • content/browser/worker_host/shared_worker_host_unittest.cc
  • content/browser/worker_host/dedicated_worker_host.cc
  • storage/browser/blob/blob_url_store_impl.cc
From ecb2d9f92d3f1db479c963524aab18b078bc8995 Mon Sep 17 00:00:00 2001
From: Minoru Chikamune <[email protected]>
Date: Sat, 19 Sep 2026 05:23:15 -0700
Subject: [PATCH] Improve origin check and mapping validation in BlobUrlRegistry and worker hosts

Ensure worker hosts only pass ALLOW_OPAQUE_ORIGIN_STORAGE_KEY_MISMATCH
when the worker origin is opaque. In addition, prevent duplicate
mappings in BlobUrlRegistry and clarify cross-origin status handling in
BlobURLStoreImpl.

TAG=agy
CONV=3ae70192-2c92-4bb2-88d1-8cf469c4a0dc

Fixed: 497408124
Change-Id: Ib2b78fb1103d1a36d77848971fd971c7d5b7be3b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8255371
Commit-Queue: Minoru Chikamune <[email protected]>
Reviewed-by: Rakina Zata Amni <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1701383}
---
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index 441374f..434dbf5 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -972,10 +972,12 @@
           weak_factory_.GetWeakPtr()),
       !(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
           GetProcessHost()->GetBrowserContext())),
-      base::FeatureList::IsEnabled(blink::features::kDataUrlWorkerOpaqueOrigin)
-          ? storage::BlobURLValidityCheckBehavior::DEFAULT
-          : storage::BlobURLValidityCheckBehavior::
-                ALLOW_OPAQUE_ORIGIN_STORAGE_KEY_MISMATCH);
+      (!base::FeatureList::IsEnabled(
+           blink::features::kDataUrlWorkerOpaqueOrigin) &&
+       renderer_origin_.opaque())
+          ? storage::BlobURLValidityCheckBehavior::
+                ALLOW_OPAQUE_ORIGIN_STORAGE_KEY_MISMATCH
+          : storage::BlobURLValidityCheckBehavior::DEFAULT);
 }
 
 void DedicatedWorkerHost::CreateCodeCacheHost(
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index 98708424..aec63a1b9 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -780,8 +780,13 @@
 
   auto* storage_partition_impl = GetStoragePartitionImpl();
 
+  // Pass `instance().renderer_origin()` rather than
+  // `instance().worker_storage_key().origin()` because when
+  // `kDataUrlWorkerOpaqueOrigin` is disabled, a data: URL shared worker's
+  // `worker_storage_key_.origin()` remains non-opaque while its renderer-side
+  // origin is opaque. We must use the origin actually used by the renderer.
   storage_partition_impl->GetBlobUrlRegistry()->AddReceiver(
-      GetWorkerStorageKey(), instance().worker_storage_key().origin(),
+      GetWorkerStorageKey(), instance().renderer_origin(),
       GetProcessHost()->GetDeprecatedID(), std::move(receiver),
       /*context_type_for_debugging=*/"Shared Worker",
       base::BindRepeating(
@@ -796,10 +801,12 @@
       base::BindRepeating([]() -> bool { return false; }),
       !(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
           GetProcessHost()->GetBrowserContext())),
-      base::FeatureList::IsEnabled(blink::features::kDataUrlWorkerOpaqueOrigin)
-          ? storage::BlobURLValidityCheckBehavior::DEFAULT
-          : storage::BlobURLValidityCheckBehavior::
-                ALLOW_OPAQUE_ORIGIN_STORAGE_KEY_MISMATCH);
+      (!base::FeatureList::IsEnabled(
+           blink::features::kDataUrlWorkerOpaqueOrigin) &&
+       instance().renderer_origin().opaque())
+          ? storage::BlobURLValidityCheckBehavior::
+                ALLOW_OPAQUE_ORIGIN_STORAGE_KEY_MISMATCH
+          : storage::BlobURLValidityCheckBehavior::DEFAULT);
 }
 
 void SharedWorkerHost::CreateBucketManagerHost(
diff --git a/storage/browser/blob/blob_url_registry.cc b/storage/browser/blob/blob_url_registry.cc
index b5bd1aa..c3ebb81 100644
--- a/storage/browser/blob/blob_url_registry.cc
+++ b/storage/browser/blob/blob_url_registry.cc
@@ -96,8 +96,8 @@
     int render_process_host_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!BlobUrlUtils::UrlHasFragment(blob_url));
-  if (IsUrlMapped(blob_url, storage_key) ==
-      BlobUrlRegistry::MappingStatus::kIsMapped) {
+  if (IsUrlMapped(blob_url, storage_key) !=
+      BlobUrlRegistry::MappingStatus::kNotMappedOther) {
     return false;
   }
   BlobUrlData data;
@@ -176,7 +176,7 @@
     // A fallback_ check isn't needed because a given Blob URL will either be
     // registered in this BlobUrlRegistry or registered in the fallback
     // BlobUrlRegistry but not both.
-    return BlobUrlRegistry::MappingStatus::kNotMappedOther;
+    return BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin;
   }
   if (fallback_) {
     return fallback_->IsUrlMapped(blob_url, storage_key);
diff --git a/storage/browser/blob/blob_url_registry.h b/storage/browser/blob/blob_url_registry.h
index 37b78a5..e0273ae7 100644
--- a/storage/browser/blob/blob_url_registry.h
+++ b/storage/browser/blob/blob_url_registry.h
@@ -42,6 +42,9 @@
     // This refers to either a first-party or different third-party context
     // attempting to access a Blob URL created in a third-party context.
     kNotMappedCrossPartitionSameOriginAccessThirdPartyBlobURL,
+    // This refers to an attempt to access a Blob URL created in a context with
+    // a different origin.
+    kNotMappedCrossOrigin,
     kNotMappedOther
   };
 
diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/blob_url_store_impl.cc
index b8e434ca..5993d5c 100644
--- a/storage/browser/blob/blob_url_store_impl.cc
+++ b/storage/browser/blob/blob_url_store_impl.cc
@@ -173,8 +173,8 @@
   // mime types (which result in a resource load for the URL as well) don't get
   // blocked by partitioning. For more information, see crbug.com/426787402.
   bool is_top_level_blob_document_self_fetch =
-  top_level_blob_document_url_.has_value() &&
-  top_level_blob_document_url_.value() == url;
+      top_level_blob_document_url_.has_value() &&
+      top_level_blob_document_url_.value() == url;
 
   if (!is_top_level_blob_document_self_fetch) {
     const BlobUrlRegistry::MappingStatus mapping_status =
diff --git a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
index 3f5596a..d4e3f17e 100644
--- a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
@@ -40,6 +40,7 @@
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/common/tokens/tokens_mojom_traits.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
 #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_container.mojom.h"
@@ -774,4 +775,81 @@
             net::StorageAccessApiStatus::kAccessViaAPI);
 }
 
+// Tests that DedicatedWorkerHost::CreateBlobUrlStoreProvider successfully binds
+// the BlobURLStore receiver when the worker has a standard non-opaque origin.
+TEST_F(DedicatedWorkerServiceImplTest, CreateBlobUrlStoreProvider) {
+  TestDedicatedWorkerServiceObserver observer;
+  base::ScopedObservation<DedicatedWorkerService,
+                          DedicatedWorkerService::Observer>
+      scoped_observation(&observer);
+  scoped_observation.Observe(GetDedicatedWorkerService());
+
+  const GURL kUrl("http://example.com/");
+  std::unique_ptr<TestWebContents> web_contents = CreateWebContents(kUrl);
+  TestRenderFrameHost* render_frame_host = web_contents->GetPrimaryMainFrame();
+  const ChildProcessId render_process_host_id =
+      render_frame_host->GetProcess()->GetID();
+  const auto origin = url::Origin::Create(kUrl);
+  auto mock_dedicated_worker = std::make_unique<MockDedicatedWorker>(
+      render_process_host_id, render_frame_host->GetGlobalId(), origin);
+  observer.RunUntilWorkerEvent();
+  EXPECT_EQ(observer.dedicated_worker_infos().size(), 1u);
+  if (observer.dedicated_worker_infos().empty()) {
+    return;
+  }
+  const blink::DedicatedWorkerToken worker_token =
+      observer.dedicated_worker_infos().begin()->first;
+  DedicatedWorkerHost* host =
+      static_cast<DedicatedWorkerServiceImpl*>(GetDedicatedWorkerService())
+          ->GetDedicatedWorkerHostFromToken(worker_token);
+  EXPECT_TRUE(host);
+  if (!host) {
+    return;
+  }
+
+  // Verify that the worker host can create a BlobURLStore provider.
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
+
+// Tests that DedicatedWorkerHost::CreateBlobUrlStoreProvider successfully binds
+// the BlobURLStore receiver when the worker has an opaque origin (e.g. data:
+// URL workers).
+TEST_F(DedicatedWorkerServiceImplTest, CreateBlobUrlStoreProviderOpaqueOrigin) {
+  TestDedicatedWorkerServiceObserver observer;
+  base::ScopedObservation<DedicatedWorkerService,
+                          DedicatedWorkerService::Observer>
+      scoped_observation(&observer);
+  scoped_observation.Observe(GetDedicatedWorkerService());
+
+  const GURL kUrl("http://example.com/");
+  std::unique_ptr<TestWebContents> web_contents = CreateWebContents(kUrl);
+  TestRenderFrameHost* render_frame_host = web_contents->GetPrimaryMainFrame();
+  const ChildProcessId render_process_host_id =
+      render_frame_host->GetProcess()->GetID();
+  const auto origin = url::Origin();
+  auto mock_dedicated_worker = std::make_unique<MockDedicatedWorker>(
+      render_process_host_id, render_frame_host->GetGlobalId(), origin);
+  observer.RunUntilWorkerEvent();
+  EXPECT_EQ(observer.dedicated_worker_infos().size(), 1u);
+  if (observer.dedicated_worker_infos().empty()) {
+    return;
+  }
+  const blink::DedicatedWorkerToken worker_token =
+      observer.dedicated_worker_infos().begin()->first;
+  DedicatedWorkerHost* host =
+      static_cast<DedicatedWorkerServiceImpl*>(GetDedicatedWorkerService())
+          ->GetDedicatedWorkerHostFromToken(worker_token);
+  EXPECT_TRUE(host);
+  if (!host) {
+    return;
+  }
+
+  // Verify that the worker host with an opaque origin can create a
+  // BlobURLStore provider.
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
 }  // namespace content
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index 932f3ba..193b9ba 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -46,6 +46,7 @@
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
 #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
 #include "third_party/blink/public/mojom/worker/shared_worker_exception_details.mojom.h"
 #include "url/origin.h"
 
@@ -420,6 +421,36 @@
   EXPECT_FALSE(params->isolation_info.nonce().has_value());
 }
 
+TEST_F(SharedWorkerHostTest, CreateBlobUrlStoreProvider) {
+  base::WeakPtr<SharedWorkerHost> host = CreateHost();
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
+
+TEST_F(SharedWorkerHostTest, CreateBlobUrlStoreProviderOpaqueOrigin) {
+  const GURL kDataWorkerUrl("data:text/javascript,");
+  blink::StorageKey creator_storage_key =
+      blink::StorageKey::CreateFirstParty(url::Origin());
+  blink::StorageKey worker_storage_key =
+      blink::StorageKey::CreateFirstParty(url::Origin());
+  url::Origin renderer_origin = url::Origin();
+  SharedWorkerInstance instance(
+      kDataWorkerUrl, blink::mojom::ScriptType::kClassic,
+      network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+      worker_storage_key, renderer_origin,
+      blink::mojom::SharedWorkerCreationContextType::kSecure,
+      blink::mojom::SharedWorkerSameSiteCookies::kNone,
+      /*extended_lifetime=*/false);
+  auto host = std::make_unique<SharedWorkerHost>(
+      &service_, instance, site_instance_,
+      std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+      base::MakeRefCounted<PolicyContainerHost>());
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
+
 TEST_F(SharedWorkerHostTest,
        CreateNetworkFactoryParamsForSubresourcesWithNonce) {
   base::UnguessableToken nonce = base::UnguessableToken::Create();
diff --git a/storage/browser/blob/blob_url_registry_unittest.cc b/storage/browser/blob/blob_url_registry_unittest.cc
index 2186f3f..49297a5 100644
--- a/storage/browser/blob/blob_url_registry_unittest.cc
+++ b/storage/browser/blob/blob_url_registry_unittest.cc
@@ -124,7 +124,7 @@
   // Test using a storage key that doesn't correspond to the Blob URL.
   EXPECT_NE(storageKey1, storageKey2);
   EXPECT_EQ(registry.IsUrlMapped(kURL1, storageKey2),
-            BlobUrlRegistry::MappingStatus::kNotMappedOther);
+            BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin);
   EXPECT_FALSE(registry.RemoveUrlMapping(kURL1, storageKey2));
   EXPECT_EQ(registry.IsUrlMapped(kURL1, storageKey1),
             BlobUrlRegistry::MappingStatus::kIsMapped);
@@ -152,7 +152,7 @@
     EXPECT_TRUE(registry.GetBlobFromUrl(kURL1));
 
     EXPECT_EQ(registry.IsUrlMapped(kURL1, partitionedStorageKey2),
-              BlobUrlRegistry::MappingStatus::kNotMappedOther);
+              BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin);
     EXPECT_FALSE(registry.RemoveUrlMapping(kURL1, partitionedStorageKey2));
     EXPECT_EQ(registry.IsUrlMapped(kURL1, partitionedStorageKey1),
               BlobUrlRegistry::MappingStatus::kIsMapped);
@@ -161,6 +161,41 @@
   EXPECT_EQ(0u, registry.url_count());
 }
 
+TEST_P(BlobUrlRegistryTestP, RejectDuplicateUrlMapping) {
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  const std::string kBlobId1 = "Blob1";
+  const std::string kBlobId2 = "Blob2";
+  const GURL kURL1 = GURL("blob://Blob1");
+  const blink::StorageKey storageKey1 =
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(kURL1));
+  const blink::StorageKey storageKey2 = blink::StorageKey::CreateFirstParty(
+      url::Origin::Create(GURL("blob://Blob2")));
+
+  FakeBlob blob1(kBlobId1);
+  FakeBlob blob2(kBlobId2);
+
+  BlobUrlRegistry registry;
+  EXPECT_TRUE(registry.AddUrlMapping(kURL1, blob1.Clone(), storageKey1,
+                                     storageKey1.origin(),
+                                     /*render_process_host_id=*/0));
+
+  // Attempting to register the exact same URL with the same storage key
+  // should also be rejected.
+  EXPECT_FALSE(registry.AddUrlMapping(kURL1, blob1.Clone(), storageKey1,
+                                      storageKey1.origin(),
+                                      /*render_process_host_id=*/0));
+
+  // Attempting to register the exact same URL with a different storage key
+  // should be rejected to maintain mapping integrity.
+  EXPECT_FALSE(registry.AddUrlMapping(kURL1, blob2.Clone(), storageKey2,
+                                      storageKey2.origin(),
+                                      /*render_process_host_id=*/0));
+
+  EXPECT_EQ(registry.IsUrlMapped(kURL1, storageKey2),
+            BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin);
+  EXPECT_EQ(kBlobId1, UuidFromBlob(registry.GetBlobFromUrl(kURL1)));
+}
+
 INSTANTIATE_TEST_SUITE_P(
     BlobUrlRegistryTests,
     BlobUrlRegistryTestP,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
index 3f5596a..d4e3f17e 100644
--- a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
@@ -40,6 +40,7 @@
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/common/tokens/tokens_mojom_traits.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
 #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_container.mojom.h"
@@ -774,4 +775,81 @@
             net::StorageAccessApiStatus::kAccessViaAPI);
 }
 
+// Tests that DedicatedWorkerHost::CreateBlobUrlStoreProvider successfully binds
+// the BlobURLStore receiver when the worker has a standard non-opaque origin.
+TEST_F(DedicatedWorkerServiceImplTest, CreateBlobUrlStoreProvider) {
+  TestDedicatedWorkerServiceObserver observer;
+  base::ScopedObservation<DedicatedWorkerService,
+                          DedicatedWorkerService::Observer>
+      scoped_observation(&observer);
+  scoped_observation.Observe(GetDedicatedWorkerService());
+
+  const GURL kUrl("http://example.com/");
+  std::unique_ptr<TestWebContents> web_contents = CreateWebContents(kUrl);
+  TestRenderFrameHost* render_frame_host = web_contents->GetPrimaryMainFrame();
+  const ChildProcessId render_process_host_id =
+      render_frame_host->GetProcess()->GetID();
+  const auto origin = url::Origin::Create(kUrl);
+  auto mock_dedicated_worker = std::make_unique<MockDedicatedWorker>(
+      render_process_host_id, render_frame_host->GetGlobalId(), origin);
+  observer.RunUntilWorkerEvent();
+  EXPECT_EQ(observer.dedicated_worker_infos().size(), 1u);
+  if (observer.dedicated_worker_infos().empty()) {
+    return;
+  }
+  const blink::DedicatedWorkerToken worker_token =
+      observer.dedicated_worker_infos().begin()->first;
+  DedicatedWorkerHost* host =
+      static_cast<DedicatedWorkerServiceImpl*>(GetDedicatedWorkerService())
+          ->GetDedicatedWorkerHostFromToken(worker_token);
+  EXPECT_TRUE(host);
+  if (!host) {
+    return;
+  }
+
+  // Verify that the worker host can create a BlobURLStore provider.
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
+
+// Tests that DedicatedWorkerHost::CreateBlobUrlStoreProvider successfully binds
+// the BlobURLStore receiver when the worker has an opaque origin (e.g. data:
+// URL workers).
+TEST_F(DedicatedWorkerServiceImplTest, CreateBlobUrlStoreProviderOpaqueOrigin) {
+  TestDedicatedWorkerServiceObserver observer;
+  base::ScopedObservation<DedicatedWorkerService,
+                          DedicatedWorkerService::Observer>
+      scoped_observation(&observer);
+  scoped_observation.Observe(GetDedicatedWorkerService());
+
+  const GURL kUrl("http://example.com/");
+  std::unique_ptr<TestWebContents> web_contents = CreateWebContents(kUrl);
+  TestRenderFrameHost* render_frame_host = web_contents->GetPrimaryMainFrame();
+  const ChildProcessId render_process_host_id =
+      render_frame_host->GetProcess()->GetID();
+  const auto origin = url::Origin();
+  auto mock_dedicated_worker = std::make_unique<MockDedicatedWorker>(
+      render_process_host_id, render_frame_host->GetGlobalId(), origin);
+  observer.RunUntilWorkerEvent();
+  EXPECT_EQ(observer.dedicated_worker_infos().size(), 1u);
+  if (observer.dedicated_worker_infos().empty()) {
+    return;
+  }
+  const blink::DedicatedWorkerToken worker_token =
+      observer.dedicated_worker_infos().begin()->first;
+  DedicatedWorkerHost* host =
+      static_cast<DedicatedWorkerServiceImpl*>(GetDedicatedWorkerService())
+          ->GetDedicatedWorkerHostFromToken(worker_token);
+  EXPECT_TRUE(host);
+  if (!host) {
+    return;
+  }
+
+  // Verify that the worker host with an opaque origin can create a
+  // BlobURLStore provider.
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
 }  // namespace content
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index 932f3ba..193b9ba 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -46,6 +46,7 @@
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
 #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
 #include "third_party/blink/public/mojom/worker/shared_worker_exception_details.mojom.h"
 #include "url/origin.h"
 
@@ -420,6 +421,36 @@
   EXPECT_FALSE(params->isolation_info.nonce().has_value());
 }
 
+TEST_F(SharedWorkerHostTest, CreateBlobUrlStoreProvider) {
+  base::WeakPtr<SharedWorkerHost> host = CreateHost();
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
+
+TEST_F(SharedWorkerHostTest, CreateBlobUrlStoreProviderOpaqueOrigin) {
+  const GURL kDataWorkerUrl("data:text/javascript,");
+  blink::StorageKey creator_storage_key =
+      blink::StorageKey::CreateFirstParty(url::Origin());
+  blink::StorageKey worker_storage_key =
+      blink::StorageKey::CreateFirstParty(url::Origin());
+  url::Origin renderer_origin = url::Origin();
+  SharedWorkerInstance instance(
+      kDataWorkerUrl, blink::mojom::ScriptType::kClassic,
+      network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+      worker_storage_key, renderer_origin,
+      blink::mojom::SharedWorkerCreationContextType::kSecure,
+      blink::mojom::SharedWorkerSameSiteCookies::kNone,
+      /*extended_lifetime=*/false);
+  auto host = std::make_unique<SharedWorkerHost>(
+      &service_, instance, site_instance_,
+      std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+      base::MakeRefCounted<PolicyContainerHost>());
+  mojo::Remote<blink::mojom::BlobURLStore> remote;
+  host->CreateBlobUrlStoreProvider(remote.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(remote.is_bound());
+}
+
 TEST_F(SharedWorkerHostTest,
        CreateNetworkFactoryParamsForSubresourcesWithNonce) {
   base::UnguessableToken nonce = base::UnguessableToken::Create();
diff --git a/storage/browser/blob/blob_url_registry_unittest.cc b/storage/browser/blob/blob_url_registry_unittest.cc
index 2186f3f..49297a5 100644
--- a/storage/browser/blob/blob_url_registry_unittest.cc
+++ b/storage/browser/blob/blob_url_registry_unittest.cc
@@ -124,7 +124,7 @@
   // Test using a storage key that doesn't correspond to the Blob URL.
   EXPECT_NE(storageKey1, storageKey2);
   EXPECT_EQ(registry.IsUrlMapped(kURL1, storageKey2),
-            BlobUrlRegistry::MappingStatus::kNotMappedOther);
+            BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin);
   EXPECT_FALSE(registry.RemoveUrlMapping(kURL1, storageKey2));
   EXPECT_EQ(registry.IsUrlMapped(kURL1, storageKey1),
             BlobUrlRegistry::MappingStatus::kIsMapped);
@@ -152,7 +152,7 @@
     EXPECT_TRUE(registry.GetBlobFromUrl(kURL1));
 
     EXPECT_EQ(registry.IsUrlMapped(kURL1, partitionedStorageKey2),
-              BlobUrlRegistry::MappingStatus::kNotMappedOther);
+              BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin);
     EXPECT_FALSE(registry.RemoveUrlMapping(kURL1, partitionedStorageKey2));
     EXPECT_EQ(registry.IsUrlMapped(kURL1, partitionedStorageKey1),
               BlobUrlRegistry::MappingStatus::kIsMapped);
@@ -161,6 +161,41 @@
   EXPECT_EQ(0u, registry.url_count());
 }
 
+TEST_P(BlobUrlRegistryTestP, RejectDuplicateUrlMapping) {
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  const std::string kBlobId1 = "Blob1";
+  const std::string kBlobId2 = "Blob2";
+  const GURL kURL1 = GURL("blob://Blob1");
+  const blink::StorageKey storageKey1 =
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(kURL1));
+  const blink::StorageKey storageKey2 = blink::StorageKey::CreateFirstParty(
+      url::Origin::Create(GURL("blob://Blob2")));
+
+  FakeBlob blob1(kBlobId1);
+  FakeBlob blob2(kBlobId2);
+
+  BlobUrlRegistry registry;
+  EXPECT_TRUE(registry.AddUrlMapping(kURL1, blob1.Clone(), storageKey1,
+                                     storageKey1.origin(),
+                                     /*render_process_host_id=*/0));
+
+  // Attempting to register the exact same URL with the same storage key
+  // should also be rejected.
+  EXPECT_FALSE(registry.AddUrlMapping(kURL1, blob1.Clone(), storageKey1,
+                                      storageKey1.origin(),
+                                      /*render_process_host_id=*/0));
+
+  // Attempting to register the exact same URL with a different storage key
+  // should be rejected to maintain mapping integrity.
+  EXPECT_FALSE(registry.AddUrlMapping(kURL1, blob2.Clone(), storageKey2,
+                                      storageKey2.origin(),
+                                      /*render_process_host_id=*/0));
+
+  EXPECT_EQ(registry.IsUrlMapped(kURL1, storageKey2),
+            BlobUrlRegistry::MappingStatus::kNotMappedCrossOrigin);
+  EXPECT_EQ(kBlobId1, UuidFromBlob(registry.GetBlobFromUrl(kURL1)));
+}
+
 INSTANTIATE_TEST_SUITE_P(
     BlobUrlRegistryTests,
     BlobUrlRegistryTestP,
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.