Chrome · Navigation
CVE-2026-87541
Logic Error in Navigation
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/navigation_request.cc |
modified | |
forcontent/browser/renderer_host/navigation_request.cc |
modified |
Files Changed
chrome/browser/extensions/process_manager_browsertest.ccchrome/browser/subresource_filter/subresource_filter_browsertest.ccchrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cccontent/browser/renderer_host/navigation_request.cc
Patch
From c600e0a60ac08d7485c6f4c0100d104ac3503d80 Mon Sep 17 00:00:00 2001
From: Camillia Smith Barnes <cammie@chromium.org>
Date: Mon, 10 Aug 2026 18:06:00 -0700
Subject: [PATCH] Navigation: Sanitize error page final URL committed in current process
SanitizeRedirectsForCommit() preserves the last redirect_infos entry and
common_params->url on the assumption that the renderer receiving them is
the one committing that URL. That does not hold for CommitErrorPage()
when ComputeErrorPageProcess() returns kCurrentProcess
(renderer-initiated navigation blocked after a redirect, subframe error
isolation off): the error page commits in the initiator's process, which
may be cross-origin to the post-redirect URL.
When committing an error page in kCurrentProcess after a redirect, also
reduce common_params_->url and the last redirect_infos entry to their
origin, behind the new kSanitizeFailedSubframeNavigationUrls feature.
Notes on Potential Regressions: Because this changes the URL that claims
to be committing, it introduces a few minor behavioral
changes/regressions:
* Error Page Strings: Some strings shown in error pages that usually
depend on the full URL will now display only the origin instead of the
full path.
* "Reload Frame" Context Menu: Subframe reloads via the "Reload Frame"
context menu option might break or stop working (e.g., if a developer
modifies a CSP that caused the block and attempts to reload just the
frame).
* Embedded Reload Buttons: Reload buttons embedded directly in error
pages could break, though these are generally hidden in subframes.
These minor regressions are considered an acceptable trade-off for the
security benefits of preventing redirect URL leaks. This change acts as
a bandaid and the regressions will be resolved when Subframe Error Page
Isolation (crbug.com/40134629) ships. The
`kSanitizeFailedSubframeNavigationUrls` feature flag acts as a kill
switch in the event of major breakage in the wild.
Bug: 517156678
Change-Id: I11f4869923b517e899fc56d2b6d4ba420ef28c53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8178743
Reviewed-by: Charlie Reis <creis@chromium.org>
Commit-Queue: Cammie Smith Barnes <cammie@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Josh Karlin <jkarlin@chromium.org>
Reviewed-by: Kelvin Jiang <kelvinjiang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1676871}
---
diff --git a/chrome/browser/extensions/process_manager_browsertest.cc b/chrome/browser/extensions/process_manager_browsertest.cc
index 985e218b..6733a6a 100644
--- a/chrome/browser/extensions/process_manager_browsertest.cc
+++ b/chrome/browser/extensions/process_manager_browsertest.cc
@@ -1673,9 +1673,12 @@
<< "The initial navigation should be allowed, but not the server "
"redirect to extension2's manifest";
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
- EXPECT_EQ(extension2_manifest, nav_observer.last_navigation_url());
- EXPECT_EQ(extension2_manifest,
- ChildFrameAt(main_frame, 1)->GetLastCommittedURL());
+ // We expect the URL to be sanitized, per https://crbug.com/517156678.
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error
+ // Page Isolation ships.
+ GURL expected_url = extension2_manifest.DeprecatedGetOriginAsURL();
+ EXPECT_EQ(expected_url, nav_observer.last_navigation_url());
+ EXPECT_EQ(expected_url, ChildFrameAt(main_frame, 1)->GetLastCommittedURL());
EXPECT_EQ(1u, pm->GetAllFrames().size());
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension1->id()).size());
EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension2->id()).size());
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
index 15c1c9a..403cc30 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -383,7 +383,11 @@
content::RenderFrameHost* frame = FindFrameByName(kSubframeNames[0]);
ASSERT_TRUE(frame);
- EXPECT_EQ(disallowed_subdocument_url, frame->GetLastCommittedURL());
+ // We expect the URL to be sanitized, per https://crbug.com/517156678.
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error Page
+ // Isolation ships.
+ EXPECT_EQ(disallowed_subdocument_url.DeprecatedGetOriginAsURL(),
+ frame->GetLastCommittedURL());
ExpectFramesIncludedInLayout(kSubframeNames, kExpectOnlySecondSubframe);
}
diff --git a/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
index 36d4826..ffc1ccec 100644
--- a/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
@@ -273,7 +273,11 @@
EXPECT_FALSE(WasParsedScriptElementLoaded(subframe));
EXPECT_TRUE(subframe->IsErrorDocument());
- EXPECT_EQ(kUrlWithIncludedScript, subframe->GetLastCommittedURL());
+ // We expect the URL to be sanitized, per https://crbug.com/517156678.
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error Page
+ // Isolation ships.
+ GURL expected_url = url::Origin::Create(kUrlWithIncludedScript).GetURL();
+ EXPECT_EQ(expected_url, subframe->GetLastCommittedURL());
}
} // namespace subresource_filter
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index f55259a4..4c1b80e 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -6818,7 +6818,7 @@
// 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_);
+ SanitizeRedirectsForCommit(common_params_, commit_params_);
GetRenderFrameHost()->FailedNavigation(
this, *common_params_, *commit_params_, has_stale_copy_in_cache_,
@@ -7277,7 +7277,7 @@
// 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);
+ SanitizeRedirectsForCommit(common_params, commit_params);
GetRenderFrameHost()->CommitNavigation(
this, std::move(common_params), std::move(commit_params),
@@ -8424,10 +8424,26 @@
}
void NavigationRequest::SanitizeRedirectsForCommit(
+ blink::mojom::CommonNavigationParamsPtr& common_params,
blink::mojom::CommitNavigationParamsPtr& commit_params) {
if (!base::FeatureList::IsEnabled(kSanitizeRedirectUrlsDuringNavigation)) {
return;
}
+
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error
+ // Pages are isolated.
+ const bool should_sanitize_final_url_for_error_page =
+ base::FeatureList::IsEnabled(
+ features::kSanitizeFailedSubframeNavigationUrls) &&
+ ComputeErrorPageProcess() == ErrorPageProcess::kCurrentProcess &&
+ !commit_params->redirect_params.empty() &&
+ !url::Origin::Create(common_params->url)
+ .IsSameOriginWith(GetRenderFrameHost()->GetLastCommittedOrigin());
+
+ if (should_sanitize_final_url_for_error_page) {
+ common_params->url = common_params->url.DeprecatedGetOriginAsURL();
+ }
+
// It is safe to convert GURL to an Origin and back in the code below because
// we only want to discard the rest of the URL (e.g., path and params). The
// actual underlying Origin is not needed, which could be inherited or opaque
@@ -8438,36 +8454,51 @@
// In the redirect_params vector, the last entry contains the URL we are going
// to commit after following all redirects. We should not be sanitizing it, as
- // we need to commit the real URL as part of the navigation.
+ // we need to commit the real URL as part of the navigation. Make an exception
+ // if the error page commits in the initiator's process and the navigation was
+ // redirected before failing, in which case that final URL may be cross-origin
+ // to the receiving process and must be reduced to origin as well.
if (!commit_params->redirect_params.empty()) {
- auto redirect_params_span = base::span(commit_params->redirect_params);
- for (blink::mojom::NavigationRedirectParamsPtr& redirect :
- redirect_params_span.first(redirect_params_span.size() - 1)) {
- redirect->redirect_info.new_url =
- redirect->redirect_info.new_url.DeprecatedGetOriginAsURL();
+ base::span<blink::mojom::NavigationRedirectParamsPtr> redirect_params_span(
+ commit_params->redirect_params);
+ if (!should_sanitize_final_url_for_error_page) {
+ redirect_params_span =
+ redirect_params_span.first(redirect_params_span.size() - 1);
+ }
+ for (auto& redirect_param : redirect_params_span) {
+ redirect_param->redirect_info.new_url =
+ redirect_param->redirect_info.new_url.DeprecatedGetOriginAsURL();
}
}
if (base::FeatureList::IsEnabled(
features::kSanitizeLocationHeadersDuringNavigation)) {
- url::Origin final_origin = url::Origin::Create(common_params_->url);
+ // The expected origin of the process that will host the committed document.
+ // We use this to determine if a redirect is cross-origin to the committing
+ // process. For successful navigations, we assume the committing process
+ // will match the origin of the destination URL. For error pages that are
+ // allowed to commit in the current process, we use the current origin of
+ // the RenderFrameHost.
+ const url::Origin expected_commit_process_origin =
+ should_sanitize_final_url_for_error_page
+ ? GetRenderFrameHost()->GetLastCommittedOrigin()
+ : url::Origin::Create(common_params->url);
// Sanitize the "Location" headers for redirects that are cross-origin to
- // the final committed URL.
+ // the final committed URL (or receiving process for error pages).
// TODO(crbug.com/495463654): Consider if we need to handle cases that
// inherit an origin (e.g. about:blank), or if we cross a CSP sandbox
// boundary where the origin becomes unique/opaque.
- for (size_t i = 0; i < commit_params->redirect_params.size(); ++i) {
- auto& response_head = commit_params->redirect_params[i]->response_head;
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/extensions/process_manager_browsertest.cc b/chrome/browser/extensions/process_manager_browsertest.cc
index 985e218b..6733a6a 100644
--- a/chrome/browser/extensions/process_manager_browsertest.cc
+++ b/chrome/browser/extensions/process_manager_browsertest.cc
@@ -1673,9 +1673,12 @@
<< "The initial navigation should be allowed, but not the server "
"redirect to extension2's manifest";
EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, nav_observer.last_net_error_code());
- EXPECT_EQ(extension2_manifest, nav_observer.last_navigation_url());
- EXPECT_EQ(extension2_manifest,
- ChildFrameAt(main_frame, 1)->GetLastCommittedURL());
+ // We expect the URL to be sanitized, per https://crbug.com/517156678.
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error
+ // Page Isolation ships.
+ GURL expected_url = extension2_manifest.DeprecatedGetOriginAsURL();
+ EXPECT_EQ(expected_url, nav_observer.last_navigation_url());
+ EXPECT_EQ(expected_url, ChildFrameAt(main_frame, 1)->GetLastCommittedURL());
EXPECT_EQ(1u, pm->GetAllFrames().size());
EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension1->id()).size());
EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension2->id()).size());
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
index 15c1c9a..403cc30 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -383,7 +383,11 @@
content::RenderFrameHost* frame = FindFrameByName(kSubframeNames[0]);
ASSERT_TRUE(frame);
- EXPECT_EQ(disallowed_subdocument_url, frame->GetLastCommittedURL());
+ // We expect the URL to be sanitized, per https://crbug.com/517156678.
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error Page
+ // Isolation ships.
+ EXPECT_EQ(disallowed_subdocument_url.DeprecatedGetOriginAsURL(),
+ frame->GetLastCommittedURL());
ExpectFramesIncludedInLayout(kSubframeNames, kExpectOnlySecondSubframe);
}
diff --git a/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
index 36d4826..ffc1ccec 100644
--- a/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
@@ -273,7 +273,11 @@
EXPECT_FALSE(WasParsedScriptElementLoaded(subframe));
EXPECT_TRUE(subframe->IsErrorDocument());
- EXPECT_EQ(kUrlWithIncludedScript, subframe->GetLastCommittedURL());
+ // We expect the URL to be sanitized, per https://crbug.com/517156678.
+ // TODO(crbug.com/40134629): Remove the sanitization once Subframe Error Page
+ // Isolation ships.
+ GURL expected_url = url::Origin::Create(kUrlWithIncludedScript).GetURL();
+ EXPECT_EQ(expected_url, subframe->GetLastCommittedURL());
}
} // namespace subresource_filter
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 013e09d..0b532ad 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -811,8 +811,9 @@
NavigationRequest* request =
NavigationRequest::From(navigation->GetNavigationHandle());
+ auto common_params = request->common_params().Clone();
auto commit_params = request->commit_params().Clone();
- request->SanitizeRedirectsForCommit(commit_params);
+ request->SanitizeRedirectsForCommit(common_params, commit_params);
// redirect_params contains entries for B, C, and D, but not the starting URL.
// Ensure that the full URL for D is preserved.
@@ -877,9 +878,10 @@
NavigationRequest* request =
NavigationRequest::From(navigation->GetNavigationHandle());
+ auto common_params = request->common_params().Clone();
auto commit_params = request->commit_params().Clone();
- request->SanitizeRedirectsForCommit(commit_params);
+ request->SanitizeRedirectsForCommit(common_params, commit_params);
EXPECT_EQ(4u, commit_params->redirect_params.size());
@@ -955,9 +957,10 @@
NavigationRequest* request =
NavigationRequest::From(navigation->GetNavigationHandle());
+ auto common_params = request->common_params().Clone();
auto commit_params = request->commit_params().Clone();
- request->SanitizeRedirectsForCommit(commit_params);
+ request->SanitizeRedirectsForCommit(common_params, commit_params);
EXPECT_EQ(3u, commit_params->redirect_params.size());
@@ -1014,9 +1017,10 @@
NavigationRequest* request =
NavigationRequest::From(navigation->GetNavigationHandle());
+ auto common_params = request->common_params().Clone();
auto commit_params = request->commit_params().Clone();
- request->SanitizeRedirectsForCommit(commit_params);
+ request->SanitizeRedirectsForCommit(common_params, commit_params);
EXPECT_EQ(2u, commit_params->redirect_params.size());
@@ -1081,6 +1085,251 @@
EXPECT_EQ(start_url, request->original_url());
}
+// Test that when a redirected subframe navigation is blocked and the resulting
+// error page commits in the initiator's process, the final URL is reduced to
+// its origin in the parameters sent to the renderer. See crbug.com/517156678.
+TEST_F(NavigationRequestTest,
+ SanitizeRedirectsForCommitErrorPageInCurrentProcess) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ /*enabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls,
+ features::kSanitizeLocationHeadersDuringNavigation},
+ /*disabled_features=*/{});
+
+ // Commit an initial page so the subframe has a parent document.
+ NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://a.com/"),
+ main_test_rfh());
+ auto* child_frame = static_cast<TestRenderFrameHost*>(
+ content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
+
+ const GURL start_url("https://b.com/start?param=1");
+ const GURL final_url("https://c.com/path?param=2");
+ std::unique_ptr<NavigationSimulator> navigation =
+ NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
+ navigation->Start();
+
+ auto headers =
+ base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
+ headers->SetHeader("Location", "https://c.com/path?param=2");
+ navigation->SetRedirectHeaders(headers);
+
+ navigation->Redirect(final_url);
+ navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);
+
+ NavigationRequest* request =
+ NavigationRequest::From(navigation->GetNavigationHandle());
+ ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
+ request->ComputeErrorPageProcess());
+
+ // The error page commits in the initiator's process, so the final URL (which
+ // is the post-redirect target) should be reduced to its origin in both the
+ // common and commit params.
+ EXPECT_EQ(GURL("https://c.com/"), request->common_params().url);
+ ASSERT_EQ(1u, request->commit_params().redirect_params.size());
+ EXPECT_EQ(GURL("https://c.com/"),
+ request->commit_params().redirect_params[0]->redirect_info.new_url);
+ ASSERT_EQ(1u, request->commit_params().redirects.size());
+ EXPECT_EQ(GURL("https://b.com/"), request->commit_params().redirects[0]);
+
+ if (base::FeatureList::IsEnabled(
+ features::kSanitizeLocationHeadersDuringNavigation)) {
+ size_t iter = 0;
+ std::optional<std::string_view> location =
+ request->commit_params()
+ .redirect_params[0]
+ ->response_head->headers->EnumerateHeader(&iter, "Location");
+ ASSERT_TRUE(location.has_value());
+ EXPECT_EQ("https://c.com/", location.value());
+ }
+}
+
+TEST_F(
+ NavigationRequestTest,
+ SanitizeRedirectsForCommitErrorPageInCurrentProcess_FinalURLFeatureDisabled) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ /*enabled_features=*/{features::kSanitizeLocationHeadersDuringNavigation},
+ /*disabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls});
+
+ // Commit an initial page so the subframe has a parent document.
+ NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://a.com/"),
+ main_test_rfh());
+ auto* child_frame = static_cast<TestRenderFrameHost*>(
+ content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
+
+ const GURL start_url("https://b.com/start?param=1");
+ const GURL final_url("https://c.com/path?param=2");
+ std::unique_ptr<NavigationSimulator> navigation =
+ NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
+ navigation->Start();
+
+ auto headers =
+ base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
+ headers->SetHeader("Location", "https://c.com/path?param=2");
+ navigation->SetRedirectHeaders(headers);
+
+ navigation->Redirect(final_url);
+ navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);
+
+ NavigationRequest* request =
+ NavigationRequest::From(navigation->GetNavigationHandle());
+ ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
+ request->ComputeErrorPageProcess());
+
+ // The feature is disabled, so the final URL should NOT be reduced to its
+ // origin.
+ EXPECT_EQ(final_url, request->common_params().url);
+ ASSERT_EQ(1u, request->commit_params().redirect_params.size());
+ EXPECT_EQ(final_url,
+ request->commit_params().redirect_params[0]->redirect_info.new_url);
+ ASSERT_EQ(1u, request->commit_params().redirects.size());
+ EXPECT_EQ(GURL("https://b.com/"), request->commit_params().redirects[0]);
+
+ // Even if kSanitizeLocationHeadersDuringNavigation is enabled, it should not
+ // sanitize the Location header because sanitize_final_url is false (due to
+ // the disabled feature flag), which makes it use the final URL's origin
+ // (c.com) as target_commit_origin, which is same-origin with the redirect
+ // target (c.com).
+ if (base::FeatureList::IsEnabled(
+ features::kSanitizeLocationHeadersDuringNavigation)) {
+ size_t iter = 0;
+ std::optional<std::string_view> location =
+ request->commit_params()
+ .redirect_params[0]
+ ->response_head->headers->EnumerateHeader(&iter, "Location");
+ ASSERT_TRUE(location.has_value());
+ EXPECT_EQ("https://c.com/path?param=2", location.value());
+ }
+}
+
+TEST_F(NavigationRequestTest,
+ DontSanitizeRedirectsForCommitErrorPageInCurrentProcessSameOrigin) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ /*enabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls,
+ features::kSanitizeLocationHeadersDuringNavigation},
+ /*disabled_features=*/{});
+
+ // Commit an initial page so the subframe has a parent document.
+ NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://c.com/"),
+ main_test_rfh());
+ auto* child_frame = static_cast<TestRenderFrameHost*>(
+ content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
+
+ const GURL start_url("https://b.com/start?param=1");
+ const GURL final_url("https://c.com/path?param=2");
+ std::unique_ptr<NavigationSimulator> navigation =
+ NavigationSimulator::CreateRendererInitiated(start_url, child_frame);
+ navigation->Start();
+
+ auto headers =
+ base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 302 Found");
+ headers->SetHeader("Location", "https://c.com/path?param=2");
+ navigation->SetRedirectHeaders(headers);
+
+ navigation->Redirect(final_url);
+ navigation->Fail(net::ERR_BLOCKED_BY_CLIENT);
+
+ NavigationRequest* request =
+ NavigationRequest::From(navigation->GetNavigationHandle());
+ ASSERT_EQ(NavigationRequest::ErrorPageProcess::kCurrentProcess,
+ request->ComputeErrorPageProcess());
+
+ // The final URL is same-origin with the receiving process (c.com), so it
+ // should NOT be reduced to its origin.
+ EXPECT_EQ(final_url, request->common_params().url);
+ ASSERT_EQ(1u, request->commit_params().redirect_params.size());
+ EXPECT_EQ(final_url,
+ request->commit_params().redirect_params[0]->redirect_info.new_url);
+ ASSERT_EQ(1u, request->commit_params().redirects.size());
+ EXPECT_EQ(GURL("https://b.com/"), request->commit_params().redirects[0]);
+
+ if (base::FeatureList::IsEnabled(
+ features::kSanitizeLocationHeadersDuringNavigation)) {
+ size_t iter = 0;
+ std::optional<std::string_view> location =
+ request->commit_params()
+ .redirect_params[0]
+ ->response_head->headers->EnumerateHeader(&iter, "Location");
+ ASSERT_TRUE(location.has_value());
+ EXPECT_EQ("https://c.com/path?param=2", location.value());
+ }
+}
+
+// Test that when a subframe navigation with multiple redirects (same-origin to
+// each other, but cross-origin to the main page) is blocked and commits an
+// error page in the initiator's process, all redirect URLs are reduced to
+// origin.
+TEST_F(
+ NavigationRequestTest,
+ SanitizeRedirectsForCommitErrorPageInCurrentProcessMultipleRedirectsSameOriginWithEachOther) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ /*enabled_features=*/{features::kSanitizeFailedSubframeNavigationUrls,
+ features::kSanitizeLocationHeadersDuringNavigation},
+ /*disabled_features=*/{});
+
+ // Commit an initial page so the subframe has a parent document (origin A).
+ NavigationSimulator::NavigateAndCommitFromDocument(GURL("https://a.com/"),
+ main_test_rfh());
+ auto* child_frame = static_cast<TestRenderFrameHost*>(
+ content::RenderFrameHostTester::For(main_rfh())->AppendChild("child"));
+
+ const GURL start_url("https://b.com/start?param=1");
+ const GURL url_2("https://b.com/path1?param=2");
... (truncated)
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