CVE-2026-5918
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcontent/browser/renderer_host/navigation_request_unittest.cc |
modified |
Files Changed
content/browser/renderer_host/navigation_request.cccontent/browser/renderer_host/navigation_request_unittest.cc
Patch
From 446871399858d161c079f14e8f6440a080889d36 Mon Sep 17 00:00:00 2001
From: Mike West <mkwst@chromium.org>
Date: Mon, 09 Mar 2026 02:12:25 -0700
Subject: [PATCH] Sanitize redirect chain when committing error pages.
When committing successful navigations, we execute
`SanitizeRedirectsForCommit(commit_params)` in order to reduce the
amount of cross-site information which might flow into a renderer. We
should do the same for error pages.
This CL copies the sanitization logic from
`NavigationRequest::CommitNavigation()` into
`NavigationRequest::CommitErrorPage()`, and adds a unittest based on
`NavigationRequestTest.SanitizeRedirectsForCommit`.
Bug: 490139441
Change-Id: I61ffc6550848a97dc036383ea9ddfce738472bff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7642049
Reviewed-by: Nasko Oskov <nasko@chromium.org>
Commit-Queue: Mike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1596201}
---
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 7ed0a1b..26d4c43 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -6510,6 +6510,13 @@
// Use a separate cache shard, and no cookies, for error pages.
isolation_info_for_subresources_ =
net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt);
+
+ // Before sending the commit parameters to the renderer process, sanitize
+ // the redirect URLs to avoid leaking potentially sensitive data into
+ // processes which are cross-site. There is no dependency on the
+ // cross-site-ness, therefore just sanitize unilaterally.
+ SanitizeRedirectsForCommit(commit_params_);
+
GetRenderFrameHost()->FailedNavigation(
this, *common_params_, *commit_params_, has_stale_copy_in_cache_,
net_error_, extended_error_code_, error_page_content, *document_token_);
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 0c4f49a..30df8db02 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -876,6 +876,39 @@
EXPECT_EQ(GURL("https://c.com"), commit_params->redirects[2]);
}
+// Test to ensure that SanitizeRedirectsForCommit is called when a navigation
+// fails and commits an error page.
+TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitErrorPage) {
+ const GURL start_url("https://a.com?param=1");
+ const GURL url_2("https://b.com?param=2#foo");
+ const GURL final_url("https://d.com?param=4");
+
+ std::unique_ptr<NavigationSimulator> navigation =
+ NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
+ navigation->Start();
+ navigation->Redirect(url_2);
+ navigation->Redirect(final_url);
+ navigation->Fail(net::ERR_CONNECTION_RESET);
+
+ NavigationRequest* request =
+ NavigationRequest::From(navigation->GetNavigationHandle());
+
+ // We expect the redirects in the NavigationRequest's commit_params_ to be
+ // sanitized.
+ const auto& commit_params = request->commit_params();
+
+ // redirects contains entries for A and B.
+ EXPECT_EQ(2u, commit_params.redirects.size());
+ EXPECT_EQ(GURL("https://a.com"), commit_params.redirects[0]);
+ EXPECT_EQ(GURL("https://b.com"), commit_params.redirects[1]);
+
+ // redirect_infos contains entries for B and D.
+ // The last entry (D) should NOT be sanitized.
+ EXPECT_EQ(2u, commit_params.redirect_infos.size());
+ EXPECT_EQ(GURL("https://b.com"), commit_params.redirect_infos[0].new_url);
+ EXPECT_EQ(final_url, commit_params.redirect_infos[1].new_url);
+}
+
TEST_F(NavigationRequestTest, AbortsDeletedNavigationInProgress) {
const GURL kUrl1 = GURL("http://a.com");
std::unique_ptr<NavigationSimulator> navigation =
Regression Test / PoC
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 0c4f49a..30df8db02 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -876,6 +876,39 @@
EXPECT_EQ(GURL("https://c.com"), commit_params->redirects[2]);
}
+// Test to ensure that SanitizeRedirectsForCommit is called when a navigation
+// fails and commits an error page.
+TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitErrorPage) {
+ const GURL start_url("https://a.com?param=1");
+ const GURL url_2("https://b.com?param=2#foo");
+ const GURL final_url("https://d.com?param=4");
+
+ std::unique_ptr<NavigationSimulator> navigation =
+ NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
+ navigation->Start();
+ navigation->Redirect(url_2);
+ navigation->Redirect(final_url);
+ navigation->Fail(net::ERR_CONNECTION_RESET);
+
+ NavigationRequest* request =
+ NavigationRequest::From(navigation->GetNavigationHandle());
+
+ // We expect the redirects in the NavigationRequest's commit_params_ to be
+ // sanitized.
+ const auto& commit_params = request->commit_params();
+
+ // redirects contains entries for A and B.
+ EXPECT_EQ(2u, commit_params.redirects.size());
+ EXPECT_EQ(GURL("https://a.com"), commit_params.redirects[0]);
+ EXPECT_EQ(GURL("https://b.com"), commit_params.redirects[1]);
+
+ // redirect_infos contains entries for B and D.
+ // The last entry (D) should NOT be sanitized.
+ EXPECT_EQ(2u, commit_params.redirect_infos.size());
+ EXPECT_EQ(GURL("https://b.com"), commit_params.redirect_infos[0].new_url);
+ EXPECT_EQ(final_url, commit_params.redirect_infos[1].new_url);
+}
+
TEST_F(NavigationRequestTest, AbortsDeletedNavigationInProgress) {
const GURL kUrl1 = GURL("http://a.com");
std::unique_ptr<NavigationSimulator> navigation =
Original Bug Report
Compromised renderer -> read cross-origin redirect URLs
VULNERABILITY DETAILS
(LLM-assisted writeup)
vCross-Origin Information Leak: Missing Redirect Sanitization in Error Page Commits
When a navigation succeeds, NavigationRequest::CommitNavigation calls SanitizeRedirectsForCommit(commit_params_) to strip paths and query parameters from the redirects and redirect_infos arrays. This is an explicit security measure to “avoid leaking potentially sensitive data into processes which are cross-site”.
However, if a navigation fails (e.g., due to an aborted connection, a DNS error, or a server error) after one or more cross-origin redirects, NavigationRequest::CommitErrorPage is invoked. CommitErrorPage does NOT call SanitizeRedirectsForCommit. It directly calls GetRenderFrameHost()->FailedNavigation(..., *commit_params_, ...).
Location:
File: content/browser/renderer_host/navigation_request.cc
Function: NavigationRequest::CommitErrorPage (around line 6511)
Suggested Fix:
Call SanitizeRedirectsForCommit(commit_params_) inside NavigationRequest::CommitErrorPage() before calling GetRenderFrameHost()->FailedNavigation().
VERSION
Chrome: built from source, synced to HEAD
OS: Linux
REPRODUCTION CASE
- Assume compromised renderer. Apply the following patch to the renderer and rebuilt Chrome:
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 2cf52ebc1ce7c..f9c6e73da744a 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3045,6 +3045,18 @@ void RenderFrameImpl::CommitFailedNavigation(
frame_token_);
RendererNavigationMetricsManager::Instance().MarkCommitStart(
commit_params->navigation_metrics_token);
+
+ LOG(ERROR) << "VULNERABILITY POC - CommitFailedNavigation PID: " << getpid();
+ if (frame_->Top()->IsWebLocalFrame()) {
+ LOG(ERROR) << "VULNERABILITY POC - Top Frame Origin: " << frame_->Top()->ToWebLocalFrame()->GetSecurityOrigin().ToString().Utf8();
+ } else {
+ LOG(ERROR) << "VULNERABILITY POC - Top Frame is Remote.";
+ }
+
+ for (const auto& r : commit_params->redirects) {
+ LOG(ERROR) << "VULNERABILITY POC LEAKED URL: " << r.spec();
+ }
+
DCHECK(navigation_client_impl_);
DCHECK(!NavigationTypeUtils::IsSameDocument(common_params->navigation_type));
- Run the attached script. It will start two HTTP servers, representing an attacker site and a victim site, and it will run Chrome.
python3 attacker_server.py &
python3 victim_server.py &
sleep 2
out/Default/chrome --headless --disable-gpu --enable-logging=stderr --virtual-time-budget=5000 http://127.0.0.1:8001/ 2>&1 | grep --line-buffered "VULNERABILITY POC"
Output:
[2996188:1:0305/151902.299670:ERROR:content/renderer/render_frame_impl.cc:3049] VULNERABILITY POC - CommitFailedNavigation PID: 1
[2996188:1:0305/151902.300732:ERROR:content/renderer/render_frame_impl.cc:3051] VULNERABILITY POC - Top Frame Origin: http://127.0.0.1:8001
[2996188:1:0305/151902.301149:ERROR:content/renderer/render_frame_impl.cc:3057] VULNERABILITY POC LEAKED URL: http://127.0.0.1:8002/login
[2996188:1:0305/151902.301374:ERROR:content/renderer/render_frame_impl.cc:3057] VULNERABILITY POC LEAKED URL: http://127.0.0.1:8002/auth1
[2996188:1:0305/151902.301557:ERROR:content/renderer/render_frame_impl.cc:3057] VULNERABILITY POC LEAKED URL: http://127.0.0.1:8002/auth2?token=SUPER_SECRET_TOKEN_1337
CREDIT INFORMATION
Reporter credit: Ryan Lothian