Chrome · Extensions
CVE-2026-78960
Logic Error in Extensions
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fservices/network/public/cpp/cors/cors_unittest.cc |
modified | |
forservices/network/public/cpp/cors/cors_unittest.cc |
modified | |
forthird_party/blink/common/service_worker/service_worker_loader_helpers.cc |
modified |
Files Changed
services/network/public/cpp/cors/cors.ccservices/network/public/cpp/cors/cors.hservices/network/public/cpp/cors/cors_unittest.ccthird_party/blink/common/service_worker/service_worker_loader_helpers.ccthird_party/blink/renderer/platform/loader/cors/cors.ccthird_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
Patch
From 77d1561238e8a4d00c5c1068a6daf76dbca12a6f Mon Sep 17 00:00:00 2001 From: Maksim Sisov <[email protected]> Date: Wed, 15 Jul 2026 05:01:24 -0700 Subject: [PATCH] Add network::cors::IsCorsSafelistedResponseHeaderName The CORS-safelisted response-header-name list is duplicated in two Blink copies: the renderer WTF predicate IsCorsSafelistedResponseHeader() and the blink-common IsCorsExposedResponseHeader() helper. There is no browser-process-callable primitive for it, which a later change needs. Add IsCorsSafelistedResponseHeaderName() next to the request-side safelist predicates in network::cors and fold both Blink copies onto it. The blink-common copy keeps its content-range media carve-out as an explicit branch. No behavior change: the renderer set was matched case-insensitively before and the new predicate lowercases the input. Bug: 533917984 Change-Id: I41ba7c40c3a4474c21e98bd8be998f538e4f3513 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8084477 Reviewed-by: Kenichi Ishibashi <[email protected]> Commit-Queue: Maksim Sisov <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662521} --- diff --git a/services/network/public/cpp/cors/cors.cc b/services/network/public/cpp/cors/cors.cc index 14334f7..ec75705 100644 --- a/services/network/public/cpp/cors/cors.cc +++ b/services/network/public/cpp/cors/cors.cc @@ -257,6 +257,21 @@ /*is_ad_auction_trusted_signals_request=*/false); } +bool IsCorsSafelistedResponseHeaderName(std::string_view name) { + // https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name + static constexpr auto kSafelistedResponseHeaderNames = + base::MakeFixedFlatSet<std::string_view>({ + "cache-control", + "content-language", + "content-length", + "content-type", + "expires", + "last-modified", + "pragma", + }); + return kSafelistedResponseHeaderNames.contains(base::ToLowerASCII(name)); +} + bool IsCorsSafelistedHeader(const std::string& name, const std::string& value, bool is_ad_auction_trusted_signals_request) { diff --git a/services/network/public/cpp/cors/cors.h b/services/network/public/cpp/cors/cors.h index 8c086d5..6728fb6a 100644 --- a/services/network/public/cpp/cors/cors.h +++ b/services/network/public/cpp/cors/cors.h @@ -107,6 +107,12 @@ bool IsNoCorsSafelistedHeader(const std::string& name, const std::string& value); +// Returns true if `name` is a CORS-safelisted response header name. +// The match is case-insensitive. +// https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name +COMPONENT_EXPORT(NETWORK_CPP) +bool IsCorsSafelistedResponseHeaderName(std::string_view name); + // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names // |headers| must not contain multiple headers for the same name. // The returned list is NOT sorted. diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc index 991dfd7..590471ee1 100644 --- a/services/network/public/cpp/cors/cors_unittest.cc +++ b/services/network/public/cpp/cors/cors_unittest.cc @@ -218,6 +218,33 @@ EXPECT_FALSE(IsCorsSafelistedHeader("user-agent", "foo")); } +TEST_F(CorsTest, SafelistedResponseHeaderName) { + // The seven CORS-safelisted response header names, in mixed case to exercise + // the case-insensitive match. + static constexpr std::string_view kSafelisted[] = { + "cache-control", "Content-Language", "CONTENT-LENGTH", "content-type", + "Expires", "Last-Modified", "pragma", + }; + for (std::string_view name : kSafelisted) { + SCOPED_TRACE(name); + EXPECT_TRUE(IsCorsSafelistedResponseHeaderName(name)); + } + + // "content-range" is deliberately excluded: it is a media-only carve-out + // kept out of the JS-visible safelist. The rest are ordinary non-safelisted + // headers. + static constexpr std::string_view kNotSafelisted[] = { + "content-range", + "set-cookie", + "x-auth-token", + "sec-ch-ua", + }; + for (std::string_view name : kNotSafelisted) { + SCOPED_TRACE(name); + EXPECT_FALSE(IsCorsSafelistedResponseHeaderName(name)); + } +} + TEST_F(CorsTest, SafelistedAccept) { EXPECT_TRUE(IsCorsSafelistedHeader("accept", "text/html")); EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", "text/html")); diff --git a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc index f64bffbc..cf2a042 100644 --- a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc +++ b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc @@ -13,7 +13,6 @@ #include <vector> #include "base/byte_size.h" -#include "base/containers/fixed_flat_set.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "base/strings/to_string.h" @@ -22,6 +21,7 @@ #include "net/http/http_util.h" #include "net/url_request/redirect_info.h" #include "net/url_request/redirect_util.h" +#include "services/network/public/cpp/cors/cors.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/resource_request_body.h" #include "services/network/public/mojom/fetch_api.mojom-shared.h" @@ -33,30 +33,18 @@ namespace blink { namespace { -// LINT.IfChange(kCorsSafelistedResponseHeaderNames) -// https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name -constexpr auto kCorsSafelistedResponseHeaderNames = - base::MakeFixedFlatSet<std::string_view>({ - "cache-control", - "content-language", - "content-length", - // "content-range" is not a standard CORS-safelisted response header, - // but it is required by C++ media loaders (e.g. WebMediaPlayer) to - // process "206 Partial Content" range responses. We permit it in - // URLResponseHead to avoid breaking media playback, while it remains - // filtered out and hidden from JavaScript's view in the renderer. - "content-range", - "content-type", - "expires", - "last-modified", - "pragma", - }); -// LINT.ThenChange(third_party/blink/renderer/platform/loader/cors/cors.cc:allowed_cross_origin_response_headers) - bool IsCorsExposedResponseHeader( std::string_view name, const std::vector<std::string>& cors_exposed_header_names) { - if (kCorsSafelistedResponseHeaderNames.contains(base::ToLowerASCII(name))) { + if (network::cors::IsCorsSafelistedResponseHeaderName(name)) { + return true; + } + // "content-range" is not a standard CORS-safelisted response header, but it + // is required by C++ media loaders (e.g. WebMediaPlayer) to process "206 + // Partial Content" range responses. We permit it in URLResponseHead to avoid + // breaking media playback, while it remains filtered out and hidden from + // JavaScript's view in the renderer. + if (base::ToLowerASCII(name) == "content-range") { return true; } for (const auto& exposed : cors_exposed_header_names) { diff --git a/third_party/blink/renderer/platform/loader/cors/cors.cc b/third_party/blink/renderer/platform/loader/cors/cors.cc index 107209d2..60e2ae1 100644 --- a/third_party/blink/renderer/platform/loader/cors/cors.cc +++ b/third_party/blink/renderer/platform/loader/cors/cors.cc @@ -205,22 +205,7 @@ } bool IsCorsSafelistedResponseHeader(const String& name) { - // LINT.IfChange(allowed_cross_origin_response_headers) - // https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name - // TODO(dcheng): Consider using a flat_set here with a transparent comparator. - DEFINE_THREAD_SAFE_STATIC_LOCAL(HTTPHeaderSet, - allowed_cross_origin_response_headers, - ({ - "cache-control", - "content-language", - "content-length", - "content-type", - "expires", - "last-modified", - "pragma", - })); - // LINT.ThenChange(third_party/blink/common/service_worker/service_worker_loader_helpers.cc:kCorsSafelistedResponseHeaderNames) - return allowed_cross_origin_response_headers.contains(name.Ascii()); + return network::cors::IsCorsSafelistedResponseHeaderName(name.Latin1()); } // In the spec, https://fetch.spec.whatwg.org/#ref-for-concept-request-mode, diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py index 5b56353d..c3e4b58 100755 --- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py +++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py @@ -547,8 +547,8 @@ ],
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc
index 991dfd7..590471ee1 100644
--- a/services/network/public/cpp/cors/cors_unittest.cc
+++ b/services/network/public/cpp/cors/cors_unittest.cc
@@ -218,6 +218,33 @@
EXPECT_FALSE(IsCorsSafelistedHeader("user-agent", "foo"));
}
+TEST_F(CorsTest, SafelistedResponseHeaderName) {
+ // The seven CORS-safelisted response header names, in mixed case to exercise
+ // the case-insensitive match.
+ static constexpr std::string_view kSafelisted[] = {
+ "cache-control", "Content-Language", "CONTENT-LENGTH", "content-type",
+ "Expires", "Last-Modified", "pragma",
+ };
+ for (std::string_view name : kSafelisted) {
+ SCOPED_TRACE(name);
+ EXPECT_TRUE(IsCorsSafelistedResponseHeaderName(name));
+ }
+
+ // "content-range" is deliberately excluded: it is a media-only carve-out
+ // kept out of the JS-visible safelist. The rest are ordinary non-safelisted
+ // headers.
+ static constexpr std::string_view kNotSafelisted[] = {
+ "content-range",
+ "set-cookie",
+ "x-auth-token",
+ "sec-ch-ua",
+ };
+ for (std::string_view name : kNotSafelisted) {
+ SCOPED_TRACE(name);
+ EXPECT_FALSE(IsCorsSafelistedResponseHeaderName(name));
+ }
+}
+
TEST_F(CorsTest, SafelistedAccept) {
EXPECT_TRUE(IsCorsSafelistedHeader("accept", "text/html"));
EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", "text/html"));
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