Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Network
DescriptionInsufficient validation of untrusted input in Network
ComponentNetwork
Bug ClassLogic Error
Tracker511814550
Fix commit8e358dad79e5 (chromium/src) +15/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
net_error_page_support_for_testing
chrome/browser/net/net_error_tab_helper.h
modified
TEST_F
chrome/browser/net/net_error_tab_helper_unittest.cc
modified

Files Changed

  • chrome/browser/net/net_error_tab_helper.cc
  • chrome/browser/net/net_error_tab_helper.h
  • chrome/browser/net/net_error_tab_helper_unittest.cc
From 8e358dad79e55803f30c919e031614360e93d2b3 Mon Sep 17 00:00:00 2001
From: Nidhi Jaju <[email protected]>
Date: Mon, 11 May 2026 01:42:49 -0700
Subject: [PATCH] Check IsInPrimaryMainFrame in NetErrorTabHelper::DownloadPageLater

This CL adds a check in NetErrorTabHelper::DownloadPageLater to ensure
the calling frame is the primary main frame.

Bug: 511814550
Change-Id: I0c50c68bdef7a9ade6756c53ab8e9aee56bdbc10
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7828909
Commit-Queue: Nidhi Jaju <[email protected]>
Reviewed-by: Kenichi Ishibashi <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1628426}
---

diff --git a/chrome/browser/net/net_error_tab_helper.cc b/chrome/browser/net/net_error_tab_helper.cc
index f252a3d..d18ffa78 100644
--- a/chrome/browser/net/net_error_tab_helper.cc
+++ b/chrome/browser/net/net_error_tab_helper.cc
@@ -150,6 +150,11 @@
   if (!entry || entry->GetPageType() != content::PAGE_TYPE_ERROR)
     return;
 
+  if (!net_error_page_support_.GetCurrentTargetFrame()
+           ->IsInPrimaryMainFrame()) {
+    return;
+  }
+
   // Only download the page for HTTP/HTTPS URLs.
   GURL url(entry->GetVirtualURL());
   if (!url.SchemeIsHTTPOrHTTPS())
diff --git a/chrome/browser/net/net_error_tab_helper.h b/chrome/browser/net/net_error_tab_helper.h
index 094b42d2..03a9cc3 100644
--- a/chrome/browser/net/net_error_tab_helper.h
+++ b/chrome/browser/net/net_error_tab_helper.h
@@ -114,6 +114,11 @@
     return network_diagnostics_receivers_;
   }
 
+  content::RenderFrameHostReceiverSet<chrome::mojom::NetErrorPageSupport>&
+  net_error_page_support_for_testing() {
+    return net_error_page_support_;
+  }
+
  private:
   friend class content::WebContentsUserData<NetErrorTabHelper>;
 
diff --git a/chrome/browser/net/net_error_tab_helper_unittest.cc b/chrome/browser/net/net_error_tab_helper_unittest.cc
index 2fd1ccbf..24a2ae6 100644
--- a/chrome/browser/net/net_error_tab_helper_unittest.cc
+++ b/chrome/browser/net/net_error_tab_helper_unittest.cc
@@ -69,6 +69,8 @@
   void SetCurrentTargetFrame(content::RenderFrameHost* render_frame_host) {
     network_diagnostics_receivers_for_testing().SetCurrentTargetFrameForTesting(
         render_frame_host);
+    net_error_page_support_for_testing().SetCurrentTargetFrameForTesting(
+        render_frame_host);
   }
 
   chrome::mojom::NetworkDiagnostics* network_diagnostics_interface() {
@@ -172,6 +174,7 @@
                                             bool succeeded) {
     GURL url(url_string);
     LoadURL(url, succeeded);
+    tab_helper()->SetCurrentTargetFrame(web_contents()->GetPrimaryMainFrame());
     tab_helper()->DownloadPageLater();
     EXPECT_EQ(0, tab_helper()->times_download_page_later_invoked());
   }
@@ -373,6 +376,7 @@
 TEST_F(NetErrorTabHelperTest, DownloadPageLater) {
   GURL url("http://somewhere:123/");
   LoadURL(url, false /*succeeded*/);
+  tab_helper()->SetCurrentTargetFrame(web_contents()->GetPrimaryMainFrame());
   tab_helper()->DownloadPageLater();
   EXPECT_EQ(url, tab_helper()->download_page_later_url());
   EXPECT_EQ(1, tab_helper()->times_download_page_later_invoked());
@@ -381,6 +385,7 @@
 TEST_F(NetErrorTabHelperTest, NoDownloadPageLaterOnNonErrorPage) {
   GURL url("http://somewhere:123/");
   LoadURL(url, true /*succeeded*/);
+  tab_helper()->SetCurrentTargetFrame(web_contents()->GetPrimaryMainFrame());
   tab_helper()->DownloadPageLater();
   EXPECT_EQ(0, tab_helper()->times_download_page_later_invoked());
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/net/net_error_tab_helper_unittest.cc b/chrome/browser/net/net_error_tab_helper_unittest.cc
index 2fd1ccbf..24a2ae6 100644
--- a/chrome/browser/net/net_error_tab_helper_unittest.cc
+++ b/chrome/browser/net/net_error_tab_helper_unittest.cc
@@ -69,6 +69,8 @@
   void SetCurrentTargetFrame(content::RenderFrameHost* render_frame_host) {
     network_diagnostics_receivers_for_testing().SetCurrentTargetFrameForTesting(
         render_frame_host);
+    net_error_page_support_for_testing().SetCurrentTargetFrameForTesting(
+        render_frame_host);
   }
 
   chrome::mojom::NetworkDiagnostics* network_diagnostics_interface() {
@@ -172,6 +174,7 @@
                                             bool succeeded) {
     GURL url(url_string);
     LoadURL(url, succeeded);
+    tab_helper()->SetCurrentTargetFrame(web_contents()->GetPrimaryMainFrame());
     tab_helper()->DownloadPageLater();
     EXPECT_EQ(0, tab_helper()->times_download_page_later_invoked());
   }
@@ -373,6 +376,7 @@
 TEST_F(NetErrorTabHelperTest, DownloadPageLater) {
   GURL url("http://somewhere:123/");
   LoadURL(url, false /*succeeded*/);
+  tab_helper()->SetCurrentTargetFrame(web_contents()->GetPrimaryMainFrame());
   tab_helper()->DownloadPageLater();
   EXPECT_EQ(url, tab_helper()->download_page_later_url());
   EXPECT_EQ(1, tab_helper()->times_download_page_later_invoked());
@@ -381,6 +385,7 @@
 TEST_F(NetErrorTabHelperTest, NoDownloadPageLaterOnNonErrorPage) {
   GURL url("http://somewhere:123/");
   LoadURL(url, true /*succeeded*/);
+  tab_helper()->SetCurrentTargetFrame(web_contents()->GetPrimaryMainFrame());
   tab_helper()->DownloadPageLater();
   EXPECT_EQ(0, tab_helper()->times_download_page_later_invoked());
 }
Loading diff…

Original Bug Report

reported by [email protected]

Missing IsInPrimaryMainFrame check in NetErrorTabHelper leads to SameSite=Strict bypass

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logic bug in NetErrorTabHelper allows a compromised renderer to schedule a background download of an error page. The background navigation lacks an initiator_origin, meaning it is treated as browser-initiated and bypasses SameSite=Strict cookie protections.

Affected files:

  • chrome/browser/net/net_error_tab_helper.cc
  • components/offline_pages/content/background_loader/background_loader_contents.cc

Estimated timestamp from git blame: 2020-06-23

Summary

A logic bug in NetErrorTabHelper::DownloadPageLater allows a compromised renderer to bypass SameSite=Strict cookie restrictions. The browser-process Mojo handler fails to validate that the calling frame is the primary main frame before scheduling a background page download. This results in the scheduling of a background fetch that is executed as a browser-initiated navigation, bypassing SameSite=Strict restrictions.

Vulnerability Details

NetErrorTabHelper::DownloadPageLater() (in chrome/browser/net/net_error_tab_helper.cc) validates that the main frame is showing an error page by checking web_contents()->GetController().GetLastCommittedEntry()->GetPageType(). However, it does not check if the Mojo IPC was actually sent by the primary main frame.

When a cross-site navigation fails and an error page is committed, the previous renderer process is moved to a pending-deletion state but remains alive briefly to run unload handlers. During this window, an attacker with a compromised renderer can still send IPC messages to the NetErrorPageSupport interface, which is bound to every RenderFrameHost.

Because the browser’s NavigationController state is updated when the error page commits, the attacker’s call to DownloadPageLater from the pending-deletion frame will pass the PAGE_TYPE_ERROR check. The browser then extracts the failed navigation’s URL (the VirtualURL of the error page) and schedules a background download via OfflinePageUtils::ScheduleDownload.

Impact: SameSite=Strict Bypass

The background download is eventually performed by BackgroundLoaderContents::LoadPage (in components/offline_pages/content/background_loader/background_loader_contents.cc), which calls LoadURL without specifying an initiator_origin.

Per Chrome’s navigation logic, a navigation with a null initiator_origin is treated as a trusted, browser-initiated (e.g., omnibox) navigation. This causes the browser to include SameSite=Strict cookies in the request, which would normally be blocked for cross-site navigations initiated by a renderer.

This primitive allows an attacker to trigger a delayed, credentialed GET request (CSRF) to an attacker-specified URL. The request may be executed immediately or retried later by the Offline Pages component.

Potential Exploitation Steps

  1. An attacker compromises a renderer process (e.g., via a V8 vulnerability).
  2. The compromised renderer initiates a navigation to a target cross-site URL (e.g., https://target.com/sensitive_action) that is known to fail or is artificially forced to fail (e.g., DNS error).
  3. The browser commits an error page, and the attacker’s frame enters a pending-deletion state.
  4. The compromised renderer sends a DownloadPageLater IPC via the chrome::mojom::NetErrorPageSupport interface.
  5. NetErrorTabHelper::DownloadPageLater processes the IPC, passes the error page checks, and schedules a background download for the target URL.
  6. The background loader executes the GET request with SameSite=Strict cookies attached.

Recommendations

Add a check in NetErrorTabHelper::DownloadPageLater to ensure the calling frame is the primary main frame, matching the pattern used in RunNetworkDiagnosticsHelper:

if (!net_error_page_support_.GetCurrentTargetFrame()->IsInPrimaryMainFrame()) {
  return;
}

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker