CVE-2026-8572
Overview
Background
- `RestrictedCookieManager`
- the network-service component that serves renderer cookie requests, bound per-context to a fixed origin and isolation state.
- `ValidateAccessToCookiesAt`
- the guard that checks the renderer-supplied
url,site_for_cookies, andtop_frame_originagainst the browser-bound values before cookie access. - `ReportBadMessage`
- the Mojo mechanism that flags a protocol violation and terminates the offending renderer connection.
Root Cause Analysis
RestrictedCookieManager::ValidateAccessToCookiesAt (services/network/restricted_cookie_manager.cc:1136) compares the renderer-provided site_for_cookies against BoundSiteForCookies() and the renderer-provided top_frame_origin against BoundTopFrameOrigin(). In the pre-fix code a mismatch on either value was recorded into site_for_cookies_ok / top_frame_origin_ok but produced only a LOG(ERROR) (restricted_cookie_manager.cc:1150-1154 and :1159-1162); execution then fell through, and the function could still return true via the same-origin check at :1180-1181. The TODO comments at :1148 and :1157 note these had been downgraded from a DCHECK. So a renderer that supplied an incorrect site_for_cookies or top_frame_origin was not stopped — the inconsistency was merely logged while the operation continued.
The fix adds receiver_.ReportBadMessage("Incorrect site_for_cookies"); return false; and receiver_.ReportBadMessage("Incorrect top_frame_origin"); return false; inside the respective mismatch branches, so either mismatch now aborts the call and terminates the connection. This tightens the check from log-only to hard rejection, re-enforcing the browser-bound isolation context.
site_for_cookies/top_frame_origin were logged but not enforced; the fix turns each mismatch into a ReportBadMessage plus return false.Attack Path
- Supply inconsistent context
A renderer calls a
RestrictedCookieManagermethod with asite_for_cookiesortop_frame_originthat disagrees with its bound context. - Bypass the guard
Pre-fix,
ValidateAccessToCookiesAtonly logged the mismatch (restricted_cookie_manager.cc:1150-1162) and did not reject it. - Operate under the wrong isolation context The cookie operation proceeds using attacker-influenced context values, which the advisory classifies as insufficient policy enforcement affecting cookie access/partitioning decisions. (inferred — the downstream effect is not in the shown source.)
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifservices/network/restricted_cookie_manager.cc |
modified | |
TEST_Pservices/network/restricted_cookie_manager_unittest.cc |
modified |
Files Changed
services/network/restricted_cookie_manager.ccservices/network/restricted_cookie_manager_unittest.cc
Audit Directions
- Log-only validationFlag security checks that compute an
*_okboolean but onlyLOGon failure instead of rejecting; a downgradedDCHECK(see the TODO at:1148) is a red flag. - Renderer-supplied contextVerify that browser-bound isolation values are authoritatively enforced on every call, not trusted from the renderer.
Patch
From 225000ab6d13272cf32d41ea8cdba1ff65835a86 Mon Sep 17 00:00:00 2001 From: Yoichi Osato <[email protected]> Date: Mon, 30 Mar 2026 22:09:41 -0700 Subject: [PATCH] Report mojo bad message for incorrect site_for_cookies and top_frame_origin RestrictedCookieManager now explicitly reports a bad message and terminates the connection if the renderer provides an incorrect site_for_cookies or top_frame_origin. Previously, these mismatches were only logged as errors without further action. This change ensures that inconsistencies between the renderer's provided context and the browser's bound state are treated as protocol violations. Fixed: 495405493 Change-Id: Ifcecd1250f4fc9d5015369ac9df7914e317d8974 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7707502 Reviewed-by: Kenichi Ishibashi <[email protected]> Commit-Queue: Yoichi Osato <[email protected]> Reviewed-by: Charlie Reis <[email protected]> Cr-Commit-Position: refs/heads/main@{#1607622} --- diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc index e12ef95d..84099f23 100644 --- a/services/network/restricted_cookie_manager.cc +++ b/services/network/restricted_cookie_manager.cc @@ -1145,20 +1145,20 @@ bool site_for_cookies_ok = BoundSiteForCookies().IsEquivalent(site_for_cookies); - // TODO(crbug.com/402207912): Switch back to a DCEHCK once this condition - // always holds again. if (!site_for_cookies_ok) { LOG(ERROR) << "site_for_cookies from renderer='" << site_for_cookies.ToDebugString() << "' from browser='" << BoundSiteForCookies().ToDebugString() << "';"; + receiver_.ReportBadMessage("Incorrect site_for_cookies"); + return false; } bool top_frame_origin_ok = (top_frame_origin == BoundTopFrameOrigin()); - // TODO(crbug.com/402207912): Switch back to a DCEHCK once this condition - // always holds again. if (!top_frame_origin_ok) { LOG(ERROR) << "top_frame_origin from renderer='" << top_frame_origin << "' from browser='" << BoundTopFrameOrigin() << "';"; + receiver_.ReportBadMessage("Incorrect top_frame_origin"); + return false; } if (metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability)) { diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc index ebbcd2db..1219d50 100644 --- a/services/network/restricted_cookie_manager_unittest.cc +++ b/services/network/restricted_cookie_manager_unittest.cc @@ -429,7 +429,10 @@ return std::make_unique<TestCookieChangeListener>(std::move(receiver)); } - void ExpectBadMessage() { expecting_bad_message_ = true; } + void ExpectBadMessage(const std::string& reason = "") { + expecting_bad_message_ = true; + expected_bad_message_reason_ = reason; + } bool received_bad_message() { return received_bad_message_; } @@ -438,6 +441,9 @@ protected: void OnBadMessage(const std::string& reason) { EXPECT_TRUE(expecting_bad_message_) << "Unexpected bad message: " << reason; + if (!expected_bad_message_reason_.empty()) { + EXPECT_EQ(expected_bad_message_reason_, reason); + } received_bad_message_ = true; } @@ -485,6 +491,7 @@ mojo::Receiver<mojom::RestrictedCookieManager> receiver_; std::unique_ptr<RestrictedCookieManagerSync> sync_service_; bool expecting_bad_message_ = false; + std::string expected_bad_message_reason_; bool received_bad_message_ = false; }; @@ -550,7 +557,7 @@ auto options = mojom::CookieManagerGetOptions::New(); options->name = "new-name"; options->match_type = mojom::CookieMatchType::EQUALS; - ExpectBadMessage(); + ExpectBadMessage("Incorrect url origin"); std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl( kDefaultUrl, kDefaultSiteForCookies, kDefaultOrigin, net::StorageAccessApiStatus::kNone, std::move(options)); @@ -779,7 +786,7 @@ SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com", "/"); - ExpectBadMessage(); + ExpectBadMessage("Incorrect url origin"); EXPECT_THAT(sync_service_->GetAllForUrl( kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin, net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()), @@ -787,6 +794,24 @@ EXPECT_TRUE(received_bad_message()); } +TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromWrongSiteForCookies) { + ExpectBadMessage("Incorrect site_for_cookies"); + EXPECT_THAT(sync_service_->GetAllForUrl( + kDefaultUrlWithPath, kOtherSiteForCookies, kDefaultOrigin, + net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()), + IsEmpty()); + EXPECT_TRUE(received_bad_message()); +} + +TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromWrongTopFrameOrigin) { + ExpectBadMessage("Incorrect top_frame_origin"); + EXPECT_THAT(sync_service_->GetAllForUrl( + kDefaultUrlWithPath, kDefaultSiteForCookies, kOtherOrigin, + net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()), + IsEmpty()); + EXPECT_TRUE(received_bad_message()); +} + TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromOpaqueOrigin) { SetSessionCookie("cookie-name", "cookie-value", "example.com", "/"); @@ -794,7 +819,7 @@ ASSERT_TRUE(opaque_origin.opaque()); service_->OverrideOriginForTesting(opaque_origin); - ExpectBadMessage(); + ExpectBadMessage("Access is denied in this context"); EXPECT_THAT(sync_service_->GetAllForUrl( kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin, net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()), @@ -877,7 +902,7 @@ SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com", "/"); - ExpectBadMessage(); + ExpectBadMessage("Incorrect url origin"); std::string cookies_out; base::ReadOnlySharedMemoryRegion mapped_region; uint64_t version; @@ -1358,7 +1383,7 @@ } TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieFromWrongOrigin) { - ExpectBadMessage(); + ExpectBadMessage("Incorrect url origin"); EXPECT_FALSE(sync_service_->SetCanonicalCookie( mojom::RestrictedCanonicalCookieParams::New( "new-name", "new-value", "notexample.com", "/", base::Time(), @@ -1376,7 +1401,7 @@ ASSERT_TRUE(opaque_origin.opaque()); service_->OverrideOriginForTesting(opaque_origin); - ExpectBadMessage(); + ExpectBadMessage("Access is denied in this context"); EXPECT_FALSE(sync_service_->SetCanonicalCookie( mojom::RestrictedCanonicalCookieParams::New( "new-name", "new-value", "example.com", "/", base::Time(), @@ -1390,7 +1415,7 @@ } TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieWithMismatchingDomain) { - ExpectBadMessage(); + ExpectBadMessage("Setting cookies on other domains is disallowed."); EXPECT_FALSE(sync_service_->SetCanonicalCookie( mojom::RestrictedCanonicalCookieParams::New( "new-name", "new-value", "not-example.com", "/", base::Time(), @@ -1404,7 +1429,7 @@ } TEST_P(RestrictedCookieManagerTest, SetCookieFromStringWrongOrigin) { - ExpectBadMessage(); + ExpectBadMessage("Incorrect url origin"); auto response = sync_service_->SetCookieFromString( kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin, net::StorageAccessApiStatus::kNone, /*get_version_shared_memory=*/false, @@ -1712,7 +1737,7 @@ mojo::PendingRemote<network::mojom::CookieChangeListener> bad_listener_remote; mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver = bad_listener_remote.InitWithNewPipeAndPassReceiver(); - ExpectBadMessage(); + ExpectBadMessage("Incorrect url origin"); sync_service_->AddChangeListener( kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin, net::StorageAccessApiStatus::kNone, std::move(bad_listener_remote)); @@ -1753,7 +1778,7 @@ mojo::PendingRemote<network::mojom::CookieChangeListener> bad_listener_remote; mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver = bad_listener_remote.InitWithNewPipeAndPassReceiver(); - ExpectBadMessage(); + ExpectBadMessage("Access is denied in this context"); sync_service_->AddChangeListener( kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
Regression Test / PoC
diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc
index ebbcd2db..1219d50 100644
--- a/services/network/restricted_cookie_manager_unittest.cc
+++ b/services/network/restricted_cookie_manager_unittest.cc
@@ -429,7 +429,10 @@
return std::make_unique<TestCookieChangeListener>(std::move(receiver));
}
- void ExpectBadMessage() { expecting_bad_message_ = true; }
+ void ExpectBadMessage(const std::string& reason = "") {
+ expecting_bad_message_ = true;
+ expected_bad_message_reason_ = reason;
+ }
bool received_bad_message() { return received_bad_message_; }
@@ -438,6 +441,9 @@
protected:
void OnBadMessage(const std::string& reason) {
EXPECT_TRUE(expecting_bad_message_) << "Unexpected bad message: " << reason;
+ if (!expected_bad_message_reason_.empty()) {
+ EXPECT_EQ(expected_bad_message_reason_, reason);
+ }
received_bad_message_ = true;
}
@@ -485,6 +491,7 @@
mojo::Receiver<mojom::RestrictedCookieManager> receiver_;
std::unique_ptr<RestrictedCookieManagerSync> sync_service_;
bool expecting_bad_message_ = false;
+ std::string expected_bad_message_reason_;
bool received_bad_message_ = false;
};
@@ -550,7 +557,7 @@
auto options = mojom::CookieManagerGetOptions::New();
options->name = "new-name";
options->match_type = mojom::CookieMatchType::EQUALS;
- ExpectBadMessage();
+ ExpectBadMessage("Incorrect url origin");
std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
kDefaultUrl, kDefaultSiteForCookies, kDefaultOrigin,
net::StorageAccessApiStatus::kNone, std::move(options));
@@ -779,7 +786,7 @@
SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com",
"/");
- ExpectBadMessage();
+ ExpectBadMessage("Incorrect url origin");
EXPECT_THAT(sync_service_->GetAllForUrl(
kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
@@ -787,6 +794,24 @@
EXPECT_TRUE(received_bad_message());
}
+TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromWrongSiteForCookies) {
+ ExpectBadMessage("Incorrect site_for_cookies");
+ EXPECT_THAT(sync_service_->GetAllForUrl(
+ kDefaultUrlWithPath, kOtherSiteForCookies, kDefaultOrigin,
+ net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
+ IsEmpty());
+ EXPECT_TRUE(received_bad_message());
+}
+
+TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromWrongTopFrameOrigin) {
+ ExpectBadMessage("Incorrect top_frame_origin");
+ EXPECT_THAT(sync_service_->GetAllForUrl(
+ kDefaultUrlWithPath, kDefaultSiteForCookies, kOtherOrigin,
+ net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
+ IsEmpty());
+ EXPECT_TRUE(received_bad_message());
+}
+
TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromOpaqueOrigin) {
SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
@@ -794,7 +819,7 @@
ASSERT_TRUE(opaque_origin.opaque());
service_->OverrideOriginForTesting(opaque_origin);
- ExpectBadMessage();
+ ExpectBadMessage("Access is denied in this context");
EXPECT_THAT(sync_service_->GetAllForUrl(
kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
@@ -877,7 +902,7 @@
SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com",
"/");
- ExpectBadMessage();
+ ExpectBadMessage("Incorrect url origin");
std::string cookies_out;
base::ReadOnlySharedMemoryRegion mapped_region;
uint64_t version;
@@ -1358,7 +1383,7 @@
}
TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieFromWrongOrigin) {
- ExpectBadMessage();
+ ExpectBadMessage("Incorrect url origin");
EXPECT_FALSE(sync_service_->SetCanonicalCookie(
mojom::RestrictedCanonicalCookieParams::New(
"new-name", "new-value", "notexample.com", "/", base::Time(),
@@ -1376,7 +1401,7 @@
ASSERT_TRUE(opaque_origin.opaque());
service_->OverrideOriginForTesting(opaque_origin);
- ExpectBadMessage();
+ ExpectBadMessage("Access is denied in this context");
EXPECT_FALSE(sync_service_->SetCanonicalCookie(
mojom::RestrictedCanonicalCookieParams::New(
"new-name", "new-value", "example.com", "/", base::Time(),
@@ -1390,7 +1415,7 @@
}
TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieWithMismatchingDomain) {
- ExpectBadMessage();
+ ExpectBadMessage("Setting cookies on other domains is disallowed.");
EXPECT_FALSE(sync_service_->SetCanonicalCookie(
mojom::RestrictedCanonicalCookieParams::New(
"new-name", "new-value", "not-example.com", "/", base::Time(),
@@ -1404,7 +1429,7 @@
}
TEST_P(RestrictedCookieManagerTest, SetCookieFromStringWrongOrigin) {
- ExpectBadMessage();
+ ExpectBadMessage("Incorrect url origin");
auto response = sync_service_->SetCookieFromString(
kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
net::StorageAccessApiStatus::kNone, /*get_version_shared_memory=*/false,
@@ -1712,7 +1737,7 @@
mojo::PendingRemote<network::mojom::CookieChangeListener> bad_listener_remote;
mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver =
bad_listener_remote.InitWithNewPipeAndPassReceiver();
- ExpectBadMessage();
+ ExpectBadMessage("Incorrect url origin");
sync_service_->AddChangeListener(
kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
net::StorageAccessApiStatus::kNone, std::move(bad_listener_remote));
@@ -1753,7 +1778,7 @@
mojo::PendingRemote<network::mojom::CookieChangeListener> bad_listener_remote;
mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver =
bad_listener_remote.InitWithNewPipeAndPassReceiver();
- ExpectBadMessage();
+ ExpectBadMessage("Access is denied in this context");
sync_service_->AddChangeListener(
kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
net::StorageAccessApiStatus::kNone, std::move(bad_listener_remote));