Chrome · Network
CVE-2026-14079
Logic Error in Network
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcontent/browser/loader/navigation_url_loader_impl_unittest.cc |
modified | |
DocumentAssociatedDatacontent/browser/renderer_host/document_associated_data.h |
modified | |
CONTENT_EXPORTcontent/browser/renderer_host/document_associated_data.h |
modified |
Files Changed
content/browser/loader/navigation_url_loader_impl.cccontent/browser/loader/navigation_url_loader_impl_unittest.cccontent/browser/renderer_host/document_associated_data.h
Patch
From 0618483faa740158c0c1faf26e29ecace5d653ac Mon Sep 17 00:00:00 2001
From: Chris Fredrickson <cfredric@chromium.org>
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 <kenrb@chromium.org>
Reviewed-by: Adam Rice <ricea@chromium.org>
Auto-Submit: Chris Fredrickson <cfredric@chromium.org>
Reviewed-by: Ken Buchanan <kenrb@chromium.org>
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.
References
On This Page