Chrome · ServiceWorker
CVE-2026-87614
Logic Error in ServiceWorker
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/service_worker/service_worker_version.cc |
modified | |
ServiceWorkerVersionFencedFrameTestcontent/browser/service_worker/service_worker_version_unittest.cc |
modified | |
switchcontent/browser/service_worker/service_worker_version_unittest.cc |
modified | |
TEST_Pcontent/browser/service_worker/service_worker_version_unittest.cc |
modified |
Files Changed
content/browser/service_worker/service_worker_test_utils.cccontent/browser/service_worker/service_worker_test_utils.hcontent/browser/service_worker/service_worker_version.cccontent/browser/service_worker/service_worker_version_unittest.cc
Patch
From 17630fd0572bfd5c86f1b19cc4b2dc3cdfaf2cbb Mon Sep 17 00:00:00 2001 From: Shunya Shishido <[email protected]> Date: Mon, 03 Aug 2026 04:07:54 -0700 Subject: [PATCH] [ServiceWorker] Restrict window APIs in fenced-frame workers WindowClient.focus(), Clients.openWindow() and PaymentRequestEvent.openWindow() are gated in the renderer on user interaction/activation gates. None of those can be triggered from a fenced frame context for service workers. Mirror that expectation in the browser process: ServiceWorkerVersion already stores the registration's ancestor frame type, so check it in FocusClient(), OpenNewTab(), and OpenPaymentHandlerWindow() and ReportBadMessage when it is kFencedFrame. Add ServiceWorkerVersionFencedFrameTest unit tests and plumb an optional ancestor_frame_type through CreateNewServiceWorkerRegistration() so the fixture can construct a kFencedFrame registration. TAG=agy CONV=66793587-3f0d-4c60-ba41-165335ec8a6f Bug: 497359396 Change-Id: I2157fafdc610b1c606fc28c52f732c2320858d40 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8173283 Reviewed-by: Yoshisato Yanagisawa <[email protected]> Commit-Queue: Shunya Shishido <[email protected]> Cr-Commit-Position: refs/heads/main@{#1672583} --- diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc index f7ae128..f6b0bcf1 100644 --- a/content/browser/service_worker/service_worker_test_utils.cc +++ b/content/browser/service_worker/service_worker_test_utils.cc @@ -475,7 +475,8 @@ scoped_refptr<ServiceWorkerRegistration> CreateNewServiceWorkerRegistration( ServiceWorkerRegistry& registry, const blink::mojom::ServiceWorkerRegistrationOptions& options, - const blink::StorageKey& key) { + const blink::StorageKey& key, + blink::mojom::AncestorFrameType ancestor_frame_type) { scoped_refptr<ServiceWorkerRegistration> registration; // Using nestable run loop because: // * The CreateNewRegistration() internally uses a mojo remote and the @@ -488,7 +489,7 @@ // problematic. base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); registry.CreateNewRegistration( - options, key, blink::mojom::AncestorFrameType::kNormalFrame, + options, key, ancestor_frame_type, base::BindLambdaForTesting( [&](scoped_refptr<ServiceWorkerRegistration> new_registration) { registration = std::move(new_registration); diff --git a/content/browser/service_worker/service_worker_test_utils.h b/content/browser/service_worker/service_worker_test_utils.h index c6d36e4..6d16ac5c 100644 --- a/content/browser/service_worker/service_worker_test_utils.h +++ b/content/browser/service_worker/service_worker_test_utils.h @@ -30,6 +30,7 @@ #include "services/network/public/mojom/referrer_policy.mojom-shared.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom-forward.h" +#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h" #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h" #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h" @@ -164,7 +165,9 @@ scoped_refptr<ServiceWorkerRegistration> CreateNewServiceWorkerRegistration( ServiceWorkerRegistry& registry, const blink::mojom::ServiceWorkerRegistrationOptions& options, - const blink::StorageKey& key); + const blink::StorageKey& key, + blink::mojom::AncestorFrameType ancestor_frame_type = + blink::mojom::AncestorFrameType::kNormalFrame); // Calls CreateNewVersion() synchronously. scoped_refptr<ServiceWorkerVersion> CreateNewServiceWorkerVersion( diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc index 35ca907..965cf81 100644 --- a/content/browser/service_worker/service_worker_version.cc +++ b/content/browser/service_worker/service_worker_version.cc @@ -73,6 +73,7 @@ #include "third_party/blink/public/common/service_worker/service_worker_type_converters.h" #include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h" +#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h" #include "third_party/perfetto/include/perfetto/tracing/track.h" namespace content { @@ -1866,6 +1867,12 @@ void ServiceWorkerVersion::OpenNewTab(const GURL& url, OpenNewTabCallback callback) { + if (ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame) { + associated_interface_receiver_.ReportBadMessage( + "Received Clients#openWindow() request from a fenced frame."); + receiver_.reset(); + return; + } // TODO(crbug.com/40177656): After StorageKey implements partitioning update // this to reject with InvalidAccessError if key_ is partitioned. OpenWindow(url, service_worker_client_utils::WindowType::NEW_TAB_WINDOW, @@ -1883,6 +1890,14 @@ return; } + if (ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame) { + associated_interface_receiver_.ReportBadMessage( + "Received PaymentRequestEvent#openWindow() request from a fenced " + "frame."); + receiver_.reset(); + return; + } + if (!url.is_valid() || !key_.origin().IsSameOriginWith(url)) { associated_interface_receiver_.ReportBadMessage( "Received PaymentRequestEvent#openWindow() request for a cross-origin " @@ -2074,6 +2089,12 @@ std::move(callback).Run(std::move(result)); return; } + if (ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame) { + associated_interface_receiver_.ReportBadMessage( + "Received WindowClient#focus() request from a fenced frame."); + receiver_.reset(); + return; + } ServiceWorkerClient* service_worker_client = context_->service_worker_client_owner().GetServiceWorkerClientByClientID( client_uuid); diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc index b965e009..7e45b499 100644 --- a/content/browser/service_worker/service_worker_version_unittest.cc +++ b/content/browser/service_worker/service_worker_version_unittest.cc @@ -54,6 +54,7 @@ #include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/common/tokens/tokens.h" #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h" +#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h" #include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h" #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h" #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h" @@ -150,6 +151,10 @@ protected: using FetchHandlerExistence = blink::mojom::FetchHandlerExistence; + virtual blink::mojom::AncestorFrameType GetAncestorFrameType() const { + return blink::mojom::AncestorFrameType::kNormalFrame; + } + struct CachedMetadataUpdateListener : public ServiceWorkerVersion::Observer { CachedMetadataUpdateListener() = default; ~CachedMetadataUpdateListener() override = default; @@ -176,7 +181,8 @@ blink::mojom::ServiceWorkerRegistrationOptions options; options.scope = scope_; registration_ = CreateNewServiceWorkerRegistration( - helper_->context()->registry(), options, GetTestStorageKey(scope_)); + helper_->context()->registry(), options, GetTestStorageKey(scope_), + GetAncestorFrameType()); version_ = CreateNewServiceWorkerVersion( helper_->context()->registry(), registration_.get(), GURL("https://www.example.com/test/service_worker.js"), @@ -2780,5 +2786,92 @@ bad_message_observer.WaitForBadMessage()); } +class ServiceWorkerVersionFencedFrameTest : public ServiceWorkerVersionTest { + protected: + blink::mojom::AncestorFrameType GetAncestorFrameType() const override { + return blink::mojom::AncestorFrameType::kFencedFrame; + } +}; + +INSTANTIATE_TEST_SUITE_P( + All, + ServiceWorkerVersionFencedFrameTest, + testing::ValuesIn({StorageKeyTestCase::kFirstParty, + StorageKeyTestCase::kThirdParty}), + [](const testing::TestParamInfo<StorageKeyTestCase>& info) { + switch (info.param) { + case (StorageKeyTestCase::kFirstParty): + return "FirstPartyStorageKey"; + case (StorageKeyTestCase::kThirdParty): + return "ThirdPartyStorageKey"; + } + }); + +// Verifies that FocusClient() rejects calls for a service worker registered +// in a fenced frame and reports a bad message. +TEST_P(ServiceWorkerVersionFencedFrameTest, FocusClient_Rejected) { + auto* service_worker = + helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get()); + ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, + StartServiceWorker(version_.get())); + service_worker->RunUntilInitializeGlobalScope(); + version_->SetStatus(ServiceWorkerVersion::ACTIVATED); + + mojo::test::BadMessageObserver bad_message_observer; +
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index b965e009..7e45b499 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -54,6 +54,7 @@
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
@@ -150,6 +151,10 @@
protected:
using FetchHandlerExistence = blink::mojom::FetchHandlerExistence;
+ virtual blink::mojom::AncestorFrameType GetAncestorFrameType() const {
+ return blink::mojom::AncestorFrameType::kNormalFrame;
+ }
+
struct CachedMetadataUpdateListener : public ServiceWorkerVersion::Observer {
CachedMetadataUpdateListener() = default;
~CachedMetadataUpdateListener() override = default;
@@ -176,7 +181,8 @@
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = scope_;
registration_ = CreateNewServiceWorkerRegistration(
- helper_->context()->registry(), options, GetTestStorageKey(scope_));
+ helper_->context()->registry(), options, GetTestStorageKey(scope_),
+ GetAncestorFrameType());
version_ = CreateNewServiceWorkerVersion(
helper_->context()->registry(), registration_.get(),
GURL("https://www.example.com/test/service_worker.js"),
@@ -2780,5 +2786,92 @@
bad_message_observer.WaitForBadMessage());
}
+class ServiceWorkerVersionFencedFrameTest : public ServiceWorkerVersionTest {
+ protected:
+ blink::mojom::AncestorFrameType GetAncestorFrameType() const override {
+ return blink::mojom::AncestorFrameType::kFencedFrame;
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ ServiceWorkerVersionFencedFrameTest,
+ testing::ValuesIn({StorageKeyTestCase::kFirstParty,
+ StorageKeyTestCase::kThirdParty}),
+ [](const testing::TestParamInfo<StorageKeyTestCase>& info) {
+ switch (info.param) {
+ case (StorageKeyTestCase::kFirstParty):
+ return "FirstPartyStorageKey";
+ case (StorageKeyTestCase::kThirdParty):
+ return "ThirdPartyStorageKey";
+ }
+ });
+
+// Verifies that FocusClient() rejects calls for a service worker registered
+// in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, FocusClient_Rejected) {
+ auto* service_worker =
+ helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+ ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+ StartServiceWorker(version_.get()));
+ service_worker->RunUntilInitializeGlobalScope();
+ version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+ mojo::test::BadMessageObserver bad_message_observer;
+
+ base::test::TestFuture<blink::mojom::FocusResultPtr> future;
+ service_worker->host()->FocusClient("some-uuid", future.GetCallback());
+
+ EXPECT_EQ("Received WindowClient#focus() request from a fenced frame.",
+ bad_message_observer.WaitForBadMessage());
+}
+
+// Verifies that OpenNewTab() rejects calls for a service worker registered
+// in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, OpenNewTab_Rejected) {
+ auto* service_worker =
+ helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+ ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+ StartServiceWorker(version_.get()));
+ service_worker->RunUntilInitializeGlobalScope();
+ version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+ mojo::test::BadMessageObserver bad_message_observer;
+
+ GURL url = scope_.Resolve("new_tab.html");
+ base::test::TestFuture<bool, blink::mojom::ServiceWorkerClientInfoPtr,
+ const std::optional<std::string>&>
+ future;
+
+ service_worker->host()->OpenNewTab(url, future.GetCallback());
+
+ EXPECT_EQ("Received Clients#openWindow() request from a fenced frame.",
+ bad_message_observer.WaitForBadMessage());
+}
+
+// Verifies that OpenPaymentHandlerWindow() rejects calls for a service worker
+// registered in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, OpenPaymentHandlerWindow_Rejected) {
+ auto* service_worker =
+ helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+ ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+ StartServiceWorker(version_.get()));
+ service_worker->RunUntilInitializeGlobalScope();
+ version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+ mojo::test::BadMessageObserver bad_message_observer;
+
+ GURL url = scope_.Resolve("payment_handler.html");
+ base::test::TestFuture<bool, blink::mojom::ServiceWorkerClientInfoPtr,
+ const std::optional<std::string>&>
+ future;
+
+ service_worker->host()->OpenPaymentHandlerWindow(url, future.GetCallback());
+
+ EXPECT_EQ(
+ "Received PaymentRequestEvent#openWindow() request from a fenced frame.",
+ bad_message_observer.WaitForBadMessage());
+}
+
} // namespace service_worker_version_unittest
} // namespace content
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