Chrome · Workers
CVE-2026-79289
Logic Error in Workers
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/worker_host/dedicated_worker_host.cc |
modified | |
RenderFrameHostcontent/browser/worker_host/dedicated_worker_host_factory_impl.h |
modified | |
CONTENT_EXPORTcontent/browser/worker_host/dedicated_worker_host_factory_impl.h |
modified |
Files Changed
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/worker_host/dedicated_worker_host.cccontent/browser/worker_host/dedicated_worker_host_factory_impl.cccontent/browser/worker_host/dedicated_worker_host_factory_impl.h
Patch
From cef96269cafaa6f303d44ef4ef6c7f08cf8228d9 Mon Sep 17 00:00:00 2001
From: Yoshisto Yanagisawa <yyanagisawa@chromium.org>
Date: Thu, 09 Jul 2026 00:37:53 -0700
Subject: [PATCH] [DedicatedWorker] Scope DedicatedWorkerHostFactoryImpl to the creator document
DedicatedWorkerHostFactoryImpl captures its creator's StorageKey,
IsolationInfo and ClientSecurityState at bind time, so it should not
outlive the document that requested it. Convert it to a
DocumentService<blink::mojom::DedicatedWorkerHostFactory> so the
receiver is torn down when the ancestor RenderFrameHost is deleted or
commits a cross-document navigation. Both the frame-creator path
(RenderFrameHostImpl::CreateDedicatedWorkerHostFactory) and the
worker-creator path (DedicatedWorkerHost::CreateNestedDedicatedWorker)
now go through a static Create() that scopes the instance to the
ancestor RFH's current document. Existing unit tests are migrated to the
new API and a regression test is added.
Bug: 497876969
Change-Id: I2322c94b8e788e6960cd0b499b5e2eb6bd8571af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8035382
Commit-Queue: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1659365}
---
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 4c38ac1..f19b1a8f 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -14859,14 +14859,13 @@
// When a dedicated worker is created from the frame script, the frame is both
// the creator and the ancestor.
- mojo::MakeSelfOwnedReceiver(
- std::make_unique<DedicatedWorkerHostFactoryImpl>(
- worker_process_id,
- /*creator=*/GetGlobalId(), GetWeakDocumentPtr(), GetStorageKey(),
- isolation_info_, BuildClientSecurityState(),
- policy_container_host()->policies(),
- /*creator_coep_reporter=*/coep_reporter, GetNetworkRestrictionsID()),
- std::move(receiver));
+ DedicatedWorkerHostFactoryImpl::Create(
+ *this, std::move(receiver), worker_process_id,
+ /*creator=*/GetGlobalId(),
+ /*ancestor_document=*/GetWeakDocumentPtr(), GetStorageKey(),
+ isolation_info_, BuildClientSecurityState(),
+ policy_container_host()->policies(),
+ /*creator_coep_reporter=*/coep_reporter, GetNetworkRestrictionsID());
}
#if BUILDFLAG(IS_ANDROID) || (BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_IOS_TVOS))
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index 656b953..c7fc66c 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -857,16 +857,23 @@
void DedicatedWorkerHost::CreateNestedDedicatedWorker(
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderFrameHost* ancestor_render_frame_host =
+ ancestor_document_.AsRenderFrameHostIfValid();
+ if (!ancestor_render_frame_host) {
+ // The ancestor frame may have already been closed. In that case, the worker
+ // will soon be terminated too, so abort the connection.
+ return;
+ }
+
base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter =
GetWorkerCoepReporter();
- mojo::MakeSelfOwnedReceiver(
- std::make_unique<DedicatedWorkerHostFactoryImpl>(
- worker_process_host_->GetID(), /*creator=*/token_, ancestor_document_,
- GetWorkerStorageKey(), isolation_info_,
- worker_client_security_state_->Clone(), creator_policies_,
- creator_coep_reporter, network_restrictions_id_),
- std::move(receiver));
+ DedicatedWorkerHostFactoryImpl::Create(
+ *ancestor_render_frame_host, std::move(receiver),
+ worker_process_host_->GetID(), /*creator=*/token_, ancestor_document_,
+ GetWorkerStorageKey(), isolation_info_,
+ worker_client_security_state_->Clone(), creator_policies_,
+ creator_coep_reporter, network_restrictions_id_);
}
void DedicatedWorkerHost::CreateIdleManager(
diff --git a/content/browser/worker_host/dedicated_worker_host_factory_impl.cc b/content/browser/worker_host/dedicated_worker_host_factory_impl.cc
index e6ccdba4..b32393d1 100644
--- a/content/browser/worker_host/dedicated_worker_host_factory_impl.cc
+++ b/content/browser/worker_host/dedicated_worker_host_factory_impl.cc
@@ -47,7 +47,32 @@
} // namespace
+// static
+void DedicatedWorkerHostFactoryImpl::Create(
+ RenderFrameHost& ancestor_render_frame_host,
+ mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver,
+ ChildProcessId worker_process_id,
+ DedicatedWorkerCreator creator,
+ WeakDocumentPtr ancestor_document,
+ const blink::StorageKey& creator_storage_key,
+ const net::IsolationInfo& isolation_info,
+ network::mojom::ClientSecurityStatePtr creator_client_security_state,
+ const PolicyContainerPolicies& creator_policies,
+ base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter,
+ const base::UnguessableToken& creator_network_restrictions_id) {
+ // The factory deletes itself when the receiver is disconnected or when the
+ // ancestor RenderFrameHost commits a cross-document navigation.
+ new DedicatedWorkerHostFactoryImpl(
+ ancestor_render_frame_host, std::move(receiver), worker_process_id,
+ creator, std::move(ancestor_document), creator_storage_key,
+ isolation_info, std::move(creator_client_security_state),
+ creator_policies, std::move(creator_coep_reporter),
+ creator_network_restrictions_id);
+}
+
DedicatedWorkerHostFactoryImpl::DedicatedWorkerHostFactoryImpl(
+ RenderFrameHost& ancestor_render_frame_host,
+ mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver,
ChildProcessId worker_process_id,
DedicatedWorkerCreator creator,
WeakDocumentPtr ancestor_document,
@@ -57,7 +82,8 @@
const PolicyContainerPolicies& creator_policies,
base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter,
const base::UnguessableToken& creator_network_restrictions_id)
- : worker_process_id_(worker_process_id),
+ : DocumentService(ancestor_render_frame_host, std::move(receiver)),
+ worker_process_id_(worker_process_id),
creator_(creator),
ancestor_document_(std::move(ancestor_document)),
creator_storage_key_(creator_storage_key),
diff --git a/content/browser/worker_host/dedicated_worker_host_factory_impl.h b/content/browser/worker_host/dedicated_worker_host_factory_impl.h
index 15e5e923..8b94d54 100644
--- a/content/browser/worker_host/dedicated_worker_host_factory_impl.h
+++ b/content/browser/worker_host/dedicated_worker_host_factory_impl.h
@@ -9,9 +9,11 @@
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/common/content_export.h"
#include "content/public/browser/dedicated_worker_creator.h"
+#include "content/public/browser/document_service.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/common/child_process_id.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/isolation_info.h"
#include "net/storage_access_api/status.h"
@@ -23,23 +25,31 @@
namespace content {
-// A factory for creating DedicatedWorkerHosts. Its lifetime is managed by the
-// renderer over mojo via SelfOwnedReceiver. It lives on the UI thread.
+class RenderFrameHost;
+
+// A factory for creating DedicatedWorkerHosts. Its lifetime is scoped to the
+// current document of the ancestor RenderFrameHost via DocumentService. It
+// lives on the UI thread.
//
// A factory instance creates at most one `DedicatedWorkerHost` instance.
-class CONTENT_EXPORT DedicatedWorkerHostFactoryImpl
- : public blink::mojom::DedicatedWorkerHostFactory {
+class CONTENT_EXPORT DedicatedWorkerHostFactoryImpl final
+ : public DocumentService<blink::mojom::DedicatedWorkerHostFactory> {
public:
using CreateWorkerHostCallback = base::OnceCallback<void(
const network::CrossOriginEmbedderPolicy&,
mojo::PendingRemote<blink::mojom::BackForwardCacheControllerHost>)>;
+ // Creates and binds an instance scoped to `ancestor_render_frame_host`'s
+ // current document.
+ //
// `creator_client_security_state` specifies the client security state of
// the creator frame or worker. Must not be nullptr.
// `creator_policies` specifies the security policies of the creator.
// `creator_network_restrictions_id` specifies the network restrictions of
// the creator as per its connection allowlists.
- DedicatedWorkerHostFactoryImpl(
+ static void Create(
+ RenderFrameHost& ancestor_render_frame_host,
+ mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver,
ChildProcessId worker_process_id,
DedicatedWorkerCreator creator,
WeakDocumentPtr ancestor_document,
@@ -55,6 +65,21 @@
DedicatedWorkerHostFactoryImpl& operator=(
const DedicatedWorkerHostFactoryImpl&) = delete;
+ private:
+ DedicatedWorkerHostFactoryImpl(
+ RenderFrameHost& ancestor_render_frame_host,
+ mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver,
+ ChildProcessId worker_process_id,
+ DedicatedWorkerCreator creator,
+ WeakDocumentPtr ancestor_document,
+ const blink::StorageKey& creator_storage_key,
+ const net::IsolationInfo& isolation_info,
+ network::mojom::ClientSecurityStatePtr creator_client_security_state,
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 4b99649..7249859 100644
--- a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
@@ -18,6 +18,7 @@
#include "content/browser/worker_host/dedicated_worker_host.h"
#include "content/browser/worker_host/dedicated_worker_host_factory_impl.h"
#include "content/common/content_navigation_policy.h"
+#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/test/navigation_simulator.h"
@@ -27,7 +28,6 @@
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/base/isolation_info.h"
#include "net/storage_access_api/status.h"
@@ -62,17 +62,15 @@
GURL(), std::nullopt, std::nullopt, base::UnguessableToken::Create(),
net::NetworkAnonymizationKey());
- mojo::MakeSelfOwnedReceiver(
- std::make_unique<DedicatedWorkerHostFactoryImpl>(
- worker_process_id, /*creator=*/render_frame_host_id,
- RenderFrameHostImpl::FromID(render_frame_host_id)
- ->GetWeakDocumentPtr(),
- blink::StorageKey::CreateFirstParty(origin),
- net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
- network::mojom::ClientSecurityState::New(),
- PolicyContainerPolicies(), coep_reporter->GetWeakPtr(),
- network::GetTestNetworkRestrictionsId()),
- factory_.BindNewPipeAndPassReceiver());
+ DedicatedWorkerHostFactoryImpl::Create(
+ *RenderFrameHostImpl::FromID(render_frame_host_id),
+ factory_.BindNewPipeAndPassReceiver(), worker_process_id,
+ /*creator=*/render_frame_host_id,
+ RenderFrameHostImpl::FromID(render_frame_host_id)->GetWeakDocumentPtr(),
+ blink::StorageKey::CreateFirstParty(origin),
+ net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
+ network::mojom::ClientSecurityState::New(), PolicyContainerPolicies(),
+ coep_reporter->GetWeakPtr(), network::GetTestNetworkRestrictionsId());
auto fetch_client_settings_object =
blink::mojom::FetchClientSettingsObject::New();
@@ -368,6 +366,29 @@
void SetUp() override { RenderViewHostImplTestHarness::SetUp(); }
};
+TEST_F(DedicatedWorkerHostFactoryImplTest,
+ FactoryResetOnCrossDocumentNavigation) {
+ // Disable back/forward cache so the previous document is destroyed on
+ // navigation rather than being preserved.
+ web_contents()->GetController().GetBackForwardCache().DisableForTesting(
+ BackForwardCache::TEST_REQUIRES_NO_CACHING);
+
+ const GURL kUrlA("http://a.example.com/");
+ const GURL kUrlB("http://b.example.com/");
+
+ NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), kUrlA);
+
+ mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
+ static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame())
+ ->CreateDedicatedWorkerHostFactory(factory.BindNewPipeAndPassReceiver());
+ factory.FlushForTesting();
+ ASSERT_TRUE(factory.is_connected());
+
+ NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), kUrlB);
+ factory.FlushForTesting();
+ EXPECT_FALSE(factory.is_connected());
+}
+
TEST_F(DedicatedWorkerHostFactoryImplTest, CrossOriginScriptOriginCheck) {
url::ScopedSchemeRegistryForTests scoped_registry;
url::AddStandardScheme("isolated-app", url::SCHEME_WITH_HOST);
@@ -401,7 +422,8 @@
base::UnguessableToken::Create(),
net::NetworkAnonymizationKey());
- return std::make_unique<DedicatedWorkerHostFactoryImpl>(
+ DedicatedWorkerHostFactoryImpl::Create(
+ *creator_rfh, factory.BindNewPipeAndPassReceiver(),
creator_rfh->GetProcess()->GetID(),
static_cast<RenderFrameHostImpl*>(creator_rfh)->GetGlobalId(),
static_cast<RenderFrameHostImpl*>(creator_rfh)
@@ -440,9 +462,7 @@
features::kEnforceDedicatedWorkerSameOriginCheck);
mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
- auto factory_impl = create_factory(factory, kIwaOriginA);
- mojo::Receiver<blink::mojom::DedicatedWorkerHostFactory> receiver(
- factory_impl.get(), factory.BindNewPipeAndPassReceiver());
+ create_factory(factory, kIwaOriginA);
mojo::test::BadMessageObserver bad_message_observer;
start_script_load(factory, kIwaAppB);
@@ -458,9 +478,7 @@
features::kEnforceDedicatedWorkerSameOriginCheck);
mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
- auto factory_impl = create_factory(factory, kIwaOriginA);
- mojo::Receiver<blink::mojom::DedicatedWorkerHostFactory> receiver(
- factory_impl.get(), factory.BindNewPipeAndPassReceiver());
+ create_factory(factory, kIwaOriginA);
mojo::test::BadMessageObserver bad_message_observer;
start_script_load(factory, kIwaAppB);
@@ -477,9 +495,7 @@
features::kEnforceDedicatedWorkerSameOriginCheck);
mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
- auto factory_impl = create_factory(factory, kExtOriginA);
- mojo::Receiver<blink::mojom::DedicatedWorkerHostFactory> receiver(
- factory_impl.get(), factory.BindNewPipeAndPassReceiver());
+ create_factory(factory, kExtOriginA);
mojo::test::BadMessageObserver bad_message_observer;
// Cross-origin load (even to https) should be blocked for extensions now.
@@ -497,9 +513,7 @@
features::kEnforceDedicatedWorkerSameOriginCheck);
mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
- auto factory_impl = create_factory(factory, kIwaOriginA);
- mojo::Receiver<blink::mojom::DedicatedWorkerHostFactory> receiver(
- factory_impl.get(), factory.BindNewPipeAndPassReceiver());
+ create_factory(factory, kIwaOriginA);
mojo::test::BadMessageObserver bad_message_observer;
start_script_load(factory, kIwaAppA);
@@ -516,9 +530,7 @@
features::kEnforceDedicatedWorkerSameOriginCheck);
mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
- auto factory_impl = create_factory(factory, kIwaOriginA);
- mojo::Receiver<blink::mojom::DedicatedWorkerHostFactory> receiver(
- factory_impl.get(), factory.BindNewPipeAndPassReceiver());
+ create_factory(factory, kIwaOriginA);
mojo::test::BadMessageObserver bad_message_observer;
start_script_load(factory, GURL("data:text/javascript,console.log('hi')"));
@@ -553,7 +565,8 @@
base::UnguessableToken::Create(),
net::NetworkAnonymizationKey());
- return std::make_unique<DedicatedWorkerHostFactoryImpl>(
+ DedicatedWorkerHostFactoryImpl::Create(
+ *opaque_rfh, factory.BindNewPipeAndPassReceiver(),
opaque_rfh->GetProcess()->GetID(),
static_cast<RenderFrameHostImpl*>(opaque_rfh)->GetGlobalId(),
static_cast<RenderFrameHostImpl*>(opaque_rfh)
@@ -567,9 +580,7 @@
};
mojo::Remote<blink::mojom::DedicatedWorkerHostFactory> factory;
- auto factory_impl = create_opaque_factory(factory);
- mojo::Receiver<blink::mojom::DedicatedWorkerHostFactory> receiver(
- factory_impl.get(), factory.BindNewPipeAndPassReceiver());
+ create_opaque_factory(factory);
mojo::test::BadMessageObserver bad_message_observer;
// Attempt to load a script that would normally be same-origin to the
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