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 <[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.
References
On This Page