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 <[email protected]> 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 <[email protected]> Commit-Queue: Cammie Smith Barnes <[email protected]> Reviewed-by: Alex Moshchuk <[email protected]> Reviewed-by: Josh Karlin <[email protected]> Reviewed-by: Kelvin Jiang <[email protected]> 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