Medium watch ✔︎ verified CVSS 3.1 chrome Policy Bypass 🔧 Commit mapped

Overview

Medium
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Network
DescriptionInsufficient policy enforcement in Network
ComponentNetwork
Bug ClassPolicy Bypass
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety — factors set by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker495405493
Fix commit225000ab6d13 (chromium/src) +40/-15
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-23 (per vendor advisory)
Fix landed2026-03-31 (public git)
Disclosed2026-05-12
Patch gap42 days — fix public in source before the advisory

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, and top_frame_origin against 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.

Key insight
Mismatches between the renderer-supplied and browser-bound site_for_cookies/top_frame_origin were logged but not enforced; the fix turns each mismatch into a ReportBadMessage plus return false.

Attack Path

  1. Supply inconsistent context A renderer calls a RestrictedCookieManager method with a site_for_cookies or top_frame_origin that disagrees with its bound context.
  2. Bypass the guard Pre-fix, ValidateAccessToCookiesAt only logged the mismatch (restricted_cookie_manager.cc:1150-1162) and did not reject it.
  3. 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

The advisory rates this medium, not in the wild, as insufficient policy enforcement in Network. A compromised or buggy renderer could present mismatched isolation parameters that were previously accepted, which may affect cookie access or partitioning decisions; the fix now treats the mismatch as a protocol violation and severs the connection.

Changed Functions

FunctionChangeNotes
if
services/network/restricted_cookie_manager.cc
modified
TEST_P
services/network/restricted_cookie_manager_unittest.cc
modified

Files Changed

  • services/network/restricted_cookie_manager.cc
  • services/network/restricted_cookie_manager_unittest.cc

Audit Directions

  • Log-only validation
    Flag security checks that compute an *_ok boolean but only LOG on failure instead of rejecting; a downgraded DCHECK (see the TODO at :1148) is a red flag.
  • Renderer-supplied context
    Verify that browser-bound isolation values are authoritatively enforced on every call, not trusted from the renderer.
From 225000ab6d13272cf32d41ea8cdba1ff65835a86 Mon Sep 17 00:00:00 2001
From: Yoichi Osato <yoichio@chromium.org>
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 <bashi@chromium.org>
Commit-Queue: Yoichi Osato <yoichio@chromium.org>
Reviewed-by: Charlie Reis <creis@chromium.org>
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,
Loading diff…

Regression Test / PoC

shipped with the fix
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));
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.