Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Network
DescriptionPolicy bypass in Network
ComponentNetwork
Bug ClassLogic Error
Tracker512971938
Fix commit0618483faa74 (chromium/src) +96/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/loader/navigation_url_loader_impl_unittest.cc
modified
DocumentAssociatedData
content/browser/renderer_host/document_associated_data.h
modified
CONTENT_EXPORT
content/browser/renderer_host/document_associated_data.h
modified

Files Changed

  • content/browser/loader/navigation_url_loader_impl.cc
  • content/browser/loader/navigation_url_loader_impl_unittest.cc
  • content/browser/renderer_host/document_associated_data.h
From 0618483faa740158c0c1faf26e29ecace5d653ac Mon Sep 17 00:00:00 2001
From: Chris Fredrickson <[email protected]>
Date: Wed, 27 May 2026 10:40:24 -0700
Subject: [PATCH] [SAA] Add missing initiator check

The per-document Storage Access API status should only be carried over across a navigation if that navigation was initiated by the frame that is being navigated, and the navigation's url is same-origin with the initiator. This check was missing from the NavigationUrlLoader impl.

Fixed: 512971938
Change-Id: I5c6aab1cfd10c91a24434bb33ff699c1eda75d46
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7855964
Commit-Queue: Ken Buchanan <[email protected]>
Reviewed-by: Adam Rice <[email protected]>
Auto-Submit: Chris Fredrickson <[email protected]>
Reviewed-by: Ken Buchanan <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1637070}
---

diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 5dee133..c86806b 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -331,11 +331,21 @@
         *request_info.begin_params->trust_token_params;
   }
 
-  new_request->storage_access_api_status =
+  const bool is_storage_access_grant_eligible =
       frame_tree_node->current_frame_host()
-              ->document_associated_data()
-              .cookie_setting_overrides()
-              .Has(net::CookieSettingOverride::kStorageAccessGrantEligible)
+          ->document_associated_data()
+          .cookie_setting_overrides()
+          .Has(net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  const bool is_same_origin_initiator =
+      request_info.begin_params->initiator_frame_token ==
+          frame_tree_node->current_frame_host()->GetFrameToken() &&
+      request_info.common_params->initiator_origin &&
+      request_info.common_params->initiator_origin->IsSameOriginWith(
+          request_info.common_params->url);
+
+  new_request->storage_access_api_status =
+      is_storage_access_grant_eligible && is_same_origin_initiator
           ? net::StorageAccessApiStatus::kAccessViaAPI
           : net::StorageAccessApiStatus::kNone;
 
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index f5b00a1..e7638fcf 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -19,8 +19,10 @@
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/loader/navigation_url_loader.h"
+#include "content/browser/renderer_host/document_associated_data.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request_info.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_package/prefetched_signed_exchange_cache.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -60,8 +62,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/navigation/navigation_params.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
 #include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
+
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "content/public/browser/plugin_service.h"
 #endif
@@ -128,7 +132,10 @@
       bool upgrade_if_insecure = false,
       bool is_ad_tagged = false,
       std::vector<std::unique_ptr<NavigationLoaderInterceptor>> interceptors =
-          {}) {
+          {},
+      std::optional<blink::LocalFrameToken> initiator_frame_token =
+          std::nullopt,
+      std::optional<url::Origin> initiator_origin = std::nullopt) {
     // NavigationURLLoader assumes that the corresponding FrameTreeNode has an
     // associated NavigationRequest.
     // NOTE: This also creates and starts another `NavigationURLLoaderImpl`
@@ -140,7 +147,7 @@
 
     blink::mojom::BeginNavigationParamsPtr begin_params =
         blink::mojom::BeginNavigationParams::New(
-            std::nullopt /* initiator_frame_token */, headers, net::LOAD_NORMAL,
+            initiator_frame_token, headers, net::LOAD_NORMAL,
             false /* skip_service_worker */,
             blink::mojom::RequestContextType::LOCATION,
             blink::mojom::MixedContentContextType::kBlockable,
@@ -163,7 +170,9 @@
 
     auto common_params = blink::CreateCommonNavigationParams();
     common_params->url = url;
-    common_params->initiator_origin = url::Origin::Create(url);
+    common_params->initiator_origin = initiator_origin.has_value()
+                                          ? *initiator_origin
+                                          : url::Origin::Create(url);
     common_params->method = method;
     common_params->download_policy = download_policy;
     common_params->request_destination =
@@ -1566,4 +1575,72 @@
   EXPECT_FALSE(loader->GetResourceRequestForTesting().permissions_policy);
 }
 
+TEST_F(NavigationURLLoaderImplTest, StorageAccessApiStatus_AccessViaAPI) {
+  ASSERT_TRUE(http_test_server_.Start());
+  const GURL url = http_test_server_.GetURL("/foo");
+
+  TestRenderFrameHost* rfh =
+      static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame());
+  rfh->document_associated_data().PutCookieSettingOverride(
+      net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  TestNavigationURLLoaderDelegate delegate;
+  auto loader = CreateTestLoader(
+      url, "", "GET", &delegate, blink::NavigationDownloadPolicy(),
+      /*is_main_frame=*/true,
+      /*upgrade_if_insecure=*/false,
+      /*is_ad_tagged=*/false, {}, rfh->GetFrameToken());
+  loader->Start();
+  delegate.WaitForResponseStarted();
+
+  EXPECT_EQ(loader->GetResourceRequestForTesting().storage_access_api_status,
+            net::StorageAccessApiStatus::kAccessViaAPI);
+}
+
+TEST_F(NavigationURLLoaderImplTest,
+       StorageAccessApiStatus_None_MismatchedFrameToken) {
+  ASSERT_TRUE(http_test_server_.Start());
+  const GURL url = http_test_server_.GetURL("/foo");
+
+  TestRenderFrameHost* rfh =
+      static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame());
+  rfh->document_associated_data().PutCookieSettingOverride(
+      net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  TestNavigationURLLoaderDelegate delegate;
+  auto loader = CreateTestLoader(
+      url, "", "GET", &delegate, blink::NavigationDownloadPolicy(),
+      /*is_main_frame=*/true,
+      /*upgrade_if_insecure=*/false,
+      /*is_ad_tagged=*/false, {}, blink::LocalFrameToken());
+  loader->Start();
+  delegate.WaitForResponseStarted();
+
+  EXPECT_EQ(loader->GetResourceRequestForTesting().storage_access_api_status,
+            net::StorageAccessApiStatus::kNone);
+}
+
+TEST_F(NavigationURLLoaderImplTest, StorageAccessApiStatus_None_CrossOrigin) {
+  ASSERT_TRUE(http_test_server_.Start());
+  const GURL url = http_test_server_.GetURL("/foo");
+
+  TestRenderFrameHost* rfh =
+      static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame());
+  rfh->document_associated_data().PutCookieSettingOverride(
+      net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  TestNavigationURLLoaderDelegate delegate;
+  auto loader = CreateTestLoader(
+      url, "", "GET", &delegate, blink::NavigationDownloadPolicy(),
+      /*is_main_frame=*/true,
+      /*upgrade_if_insecure=*/false,
+      /*is_ad_tagged=*/false, {}, rfh->GetFrameToken(),
+      url::Origin::Create(GURL("http://a.com")));
+  loader->Start();
+  delegate.WaitForResponseStarted();
+
+  EXPECT_EQ(loader->GetResourceRequestForTesting().storage_access_api_status,
+            net::StorageAccessApiStatus::kNone);
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/document_associated_data.h b/content/browser/renderer_host/document_associated_data.h
index d60fc32..b670fa9 100644
--- a/content/browser/renderer_host/document_associated_data.h
+++ b/content/browser/renderer_host/document_associated_data.h
@@ -20,6 +20,7 @@
 #include "base/types/pass_key.h"
 #include "base/unguessable_token.h"
 #include "content/browser/loader/keep_alive_url_loader_service.h"
+#include "content/common/content_export.h"
 #include "net/cookies/cookie_setting_override.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/confidence_level.mojom.h"
@@ -41,7 +42,7 @@
 // directly while consumers of RenderFrameHostImpl should store data via
 // GetDocumentUserData(). Please refer to the description at
 // content/public/browser/document_user_data.h for more details.
-class DocumentAssociatedData : public base::SupportsUserData {
+class CONTENT_EXPORT DocumentAssociatedData : public base::SupportsUserData {
  public:
   // Helper for looking up a RenderFrameHostImpl based on the DocumentToken.
   // Restricted to RenderFrameHostImpl, which performs additional security
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index f5b00a1..e7638fcf 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -19,8 +19,10 @@
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/loader/navigation_url_loader.h"
+#include "content/browser/renderer_host/document_associated_data.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request_info.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_package/prefetched_signed_exchange_cache.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -60,8 +62,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/navigation/navigation_params.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
 #include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
+
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "content/public/browser/plugin_service.h"
 #endif
@@ -128,7 +132,10 @@
       bool upgrade_if_insecure = false,
       bool is_ad_tagged = false,
       std::vector<std::unique_ptr<NavigationLoaderInterceptor>> interceptors =
-          {}) {
+          {},
+      std::optional<blink::LocalFrameToken> initiator_frame_token =
+          std::nullopt,
+      std::optional<url::Origin> initiator_origin = std::nullopt) {
     // NavigationURLLoader assumes that the corresponding FrameTreeNode has an
     // associated NavigationRequest.
     // NOTE: This also creates and starts another `NavigationURLLoaderImpl`
@@ -140,7 +147,7 @@
 
     blink::mojom::BeginNavigationParamsPtr begin_params =
         blink::mojom::BeginNavigationParams::New(
-            std::nullopt /* initiator_frame_token */, headers, net::LOAD_NORMAL,
+            initiator_frame_token, headers, net::LOAD_NORMAL,
             false /* skip_service_worker */,
             blink::mojom::RequestContextType::LOCATION,
             blink::mojom::MixedContentContextType::kBlockable,
@@ -163,7 +170,9 @@
 
     auto common_params = blink::CreateCommonNavigationParams();
     common_params->url = url;
-    common_params->initiator_origin = url::Origin::Create(url);
+    common_params->initiator_origin = initiator_origin.has_value()
+                                          ? *initiator_origin
+                                          : url::Origin::Create(url);
     common_params->method = method;
     common_params->download_policy = download_policy;
     common_params->request_destination =
@@ -1566,4 +1575,72 @@
   EXPECT_FALSE(loader->GetResourceRequestForTesting().permissions_policy);
 }
 
+TEST_F(NavigationURLLoaderImplTest, StorageAccessApiStatus_AccessViaAPI) {
+  ASSERT_TRUE(http_test_server_.Start());
+  const GURL url = http_test_server_.GetURL("/foo");
+
+  TestRenderFrameHost* rfh =
+      static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame());
+  rfh->document_associated_data().PutCookieSettingOverride(
+      net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  TestNavigationURLLoaderDelegate delegate;
+  auto loader = CreateTestLoader(
+      url, "", "GET", &delegate, blink::NavigationDownloadPolicy(),
+      /*is_main_frame=*/true,
+      /*upgrade_if_insecure=*/false,
+      /*is_ad_tagged=*/false, {}, rfh->GetFrameToken());
+  loader->Start();
+  delegate.WaitForResponseStarted();
+
+  EXPECT_EQ(loader->GetResourceRequestForTesting().storage_access_api_status,
+            net::StorageAccessApiStatus::kAccessViaAPI);
+}
+
+TEST_F(NavigationURLLoaderImplTest,
+       StorageAccessApiStatus_None_MismatchedFrameToken) {
+  ASSERT_TRUE(http_test_server_.Start());
+  const GURL url = http_test_server_.GetURL("/foo");
+
+  TestRenderFrameHost* rfh =
+      static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame());
+  rfh->document_associated_data().PutCookieSettingOverride(
+      net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  TestNavigationURLLoaderDelegate delegate;
+  auto loader = CreateTestLoader(
+      url, "", "GET", &delegate, blink::NavigationDownloadPolicy(),
+      /*is_main_frame=*/true,
+      /*upgrade_if_insecure=*/false,
+      /*is_ad_tagged=*/false, {}, blink::LocalFrameToken());
+  loader->Start();
+  delegate.WaitForResponseStarted();
+
+  EXPECT_EQ(loader->GetResourceRequestForTesting().storage_access_api_status,
+            net::StorageAccessApiStatus::kNone);
+}
+
+TEST_F(NavigationURLLoaderImplTest, StorageAccessApiStatus_None_CrossOrigin) {
+  ASSERT_TRUE(http_test_server_.Start());
+  const GURL url = http_test_server_.GetURL("/foo");
+
+  TestRenderFrameHost* rfh =
+      static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame());
+  rfh->document_associated_data().PutCookieSettingOverride(
+      net::CookieSettingOverride::kStorageAccessGrantEligible);
+
+  TestNavigationURLLoaderDelegate delegate;
+  auto loader = CreateTestLoader(
+      url, "", "GET", &delegate, blink::NavigationDownloadPolicy(),
+      /*is_main_frame=*/true,
+      /*upgrade_if_insecure=*/false,
+      /*is_ad_tagged=*/false, {}, rfh->GetFrameToken(),
+      url::Origin::Create(GURL("http://a.com")));
+  loader->Start();
+  delegate.WaitForResponseStarted();
+
+  EXPECT_EQ(loader->GetResourceRequestForTesting().storage_access_api_status,
+            net::StorageAccessApiStatus::kNone);
+}
+
 }  // 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.