From 0a5a365bc83419957235b8f0ff20b0b54ef0abc4 Mon Sep 17 00:00:00 2001 From: Tom Crowley Date: Wed, 10 Jun 2026 14:11:18 +0100 Subject: [PATCH] Strip Sec-Fetch-* headers on redirect Fixes a network-service / renderer process kill on HTTP redirects of renderer-initiated subresource requests. On redirect, CEF restarts the request through the URLLoaderFactory (InterceptedRequest::FollowRedirect -> Restart -> ContinueToBeforeRedirect) instead of following it on the existing loader. The restart re-submits the network-service-managed Sec-Fetch-* headers that were populated on the prior hop. With kRestrictForbiddenSecurityHeaders now enabled by default in Chromium, CorsURLLoaderFactory::IsValidRequest -> ContainsForbiddenSecurityHeader rejects those renderer-forbidden headers via mojo::ReportBadMessage, tearing down the network service. Only renderer-initiated subresources (fetch/XHR/img/script) trigger this; browser-initiated top-level navigations skip the check, which is why a top-level redirect doesn't reproduce it. The fix strips the Sec-Fetch- header family by prefix (mirroring Chromium's own network::MaybeRemoveSecHeaders) from remove_headers before the restart, so new members like Sec-Fetch-Frame-Top / Sec-Fetch-Frame-Ancestors are covered without maintaining an explicit list. URLLoader re-derives them downstream (SetFetchMetadataHeaders), so this is lossless and mirrors CorsURLLoader's normal redirect path. Adds CorsTest.XhrRedirectSubresource and CorsTest.FetchRedirectSubresource regression tests. --- .../net_service/proxy_url_loader_factory.cc | 16 ++++- tests/ceftests/cors_unittest.cc | 62 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/libcef/browser/net_service/proxy_url_loader_factory.cc b/libcef/browser/net_service/proxy_url_loader_factory.cc index 92dab6d3a..872c0a922 100644 --- a/libcef/browser/net_service/proxy_url_loader_factory.cc +++ b/libcef/browser/net_service/proxy_url_loader_factory.cc @@ -14,6 +14,7 @@ #include "base/memory/raw_ptr.h" #include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" #include "cef/libcef/browser/context.h" #include "cef/libcef/browser/origin_whitelist_impl.h" #include "cef/libcef/browser/thread_util.h" @@ -1070,8 +1071,19 @@ void InterceptedRequest::ContinueToBeforeRedirect( } // Remove existing Cookie headers. They may be re-added after Restart(). - const std::vector remove_headers{ - net::HttpRequestHeaders::kCookie}; + std::vector remove_headers{net::HttpRequestHeaders::kCookie}; + + // Restarting the request (vs. following the redirect in place) re-validates + // it via CorsURLLoaderFactory::IsValidRequest, which rejects the + // network-managed Sec-Fetch-* headers carried over from the previous hop and + // kills the process. Strip the whole family by prefix (like + // network::MaybeRemoveSecHeaders); URLLoader re-derives them downstream. + for (const auto& header : request_.headers.GetHeaderVector()) { + if (base::StartsWith(header.key, "Sec-Fetch-", + base::CompareCase::INSENSITIVE_ASCII)) { + remove_headers.push_back(header.key); + } + } // Use common logic for sanitizing request headers including Origin and // Content-*. diff --git a/tests/ceftests/cors_unittest.cc b/tests/ceftests/cors_unittest.cc index 53514b8b4..dd4b5a9dd 100644 --- a/tests/ceftests/cors_unittest.cc +++ b/tests/ceftests/cors_unittest.cc @@ -1663,6 +1663,68 @@ void SetupRedirectGetRequest(RedirectMode mode, CORS_TEST_REDIRECT_GET_ALL(302, MODE_302) CORS_TEST_REDIRECT_GET_ALL(307, MODE_307) +// Regression test for a renderer-initiated subresource request that hits +// an HTTP redirect. +namespace { + +// Unlike the RedirectGet* tests (which redirect a top-level, browser-initiated +// navigation), this issues a renderer-initiated subresource request (XHR/fetch) +// to a URL that returns a redirect. On redirect CEF restarts the request +// through the URLLoaderFactory (see InterceptedRequest::FollowRedirect -> +// Restart), carrying the network-service-managed Sec-Fetch-* headers populated +// on the prior hop. CorsURLLoaderFactory::IsValidRequest then rejected those +// renderer-forbidden headers via mojo::ReportBadMessage +// (kRestrictForbiddenSecurityHeaders, enabled by default), tearing down the +// renderer. Browser-initiated requests skip that check, which is why only a +// subresource reproduces it. Everything is same-origin so no CORS +// headers/preflight are needed; the check rejects any Sec-Fetch-Site value. +void SetupExecRedirectRequest(ExecMode mode, + TestSetup* setup, + const std::string& test_name, + Resource* main_resource, + Resource* redirect_resource, + Resource* target_resource) { + const std::string& base_path = "/" + test_name; + + // Final resource the subresource request lands on after the redirect. + target_resource->Init(HandlerType::SERVER, base_path + ".target.txt", + kMimeTypeText, kDefaultText); + + // Subresource request returns a redirect to the final resource. + redirect_resource->Init(HandlerType::SERVER, base_path + ".sub.txt", + kMimeTypeText, std::string()); + SetupRedirectResponse(RedirectMode::MODE_302, target_resource->GetPathURL(), + redirect_resource->response); + + // Main page issues the subresource request to the redirecting URL. + main_resource->Init(HandlerType::SERVER, base_path, kMimeTypeHtml, + GetExecMainHtml(mode, redirect_resource->GetPathURL())); + main_resource->expected_success_query_ct = 1; + + setup->AddResource(main_resource); + setup->AddResource(redirect_resource); + setup->AddResource(target_resource); +} + +} // namespace + +#define CORS_TEST_EXEC_REDIRECT(test_name, mode) \ + TEST(CorsTest, test_name) { \ + TestSetup setup; \ + Resource resource_main; \ + Resource resource_redirect; \ + Resource resource_target; \ + SetupExecRedirectRequest(ExecMode::mode, &setup, "CorsTest." #test_name, \ + &resource_main, &resource_redirect, \ + &resource_target); \ + CefRefPtr handler = new CorsTestHandler(&setup); \ + handler->ExecuteTest(); \ + ReleaseAndWaitForDestructor(handler); \ + } + +CORS_TEST_EXEC_REDIRECT(XhrRedirectSubresource, XHR) +CORS_TEST_EXEC_REDIRECT(FetchRedirectSubresource, FETCH) + namespace { struct PostResource : CookieResource {