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 <[email protected]> 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 <[email protected]> Reviewed-by: Rakina Zata Amni <[email protected]> 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