Chrome · Omnibox
CVE-2026-87610
Logic Error in Omnibox
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcomponents/omnibox/browser/aim_eligibility_service_unittest.cc |
modified |
Files Changed
components/omnibox/browser/aim_eligibility_service.cccomponents/omnibox/browser/aim_eligibility_service_unittest.cc
Patch
From 9e1d112b91b8752e43d3fdd11cdd1cb6a9fc8f7b Mon Sep 17 00:00:00 2001 From: Duncan Mercer <[email protected]> Date: Mon, 17 Aug 2026 18:01:19 -0700 Subject: [PATCH] [Omnibox] Use MatchPattern for AIM host matching AimEligibilityService::IsAimHost previously evaluated server-provided interception_allowed_hosts using RE2::FullMatch. Because the server supplies unescaped hostnames (e.g. www.google.com), unescaped dots matched any character in regex, allowing attacker-controlled near- domains (e.g. www0google.com) to match and gain privileged protocol access in Contextual Tasks. This CL updates IsAimHost to use base::MatchPattern with case- insensitive comparison so that dots are treated as literal characters and wildcards are restricted to * and ?. Bug: b:547592631 Change-Id: Ic0b9991722a19f2764fb36226d0c2fd672f17e31 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8254236 Reviewed-by: Justin Donnelly <[email protected]> Commit-Queue: Justin Donnelly <[email protected]> Auto-Submit: Duncan Mercer <[email protected]> Reviewed-by: Matthew Jones <[email protected]> Cr-Commit-Position: refs/heads/main@{#1680968} --- diff --git a/components/omnibox/browser/aim_eligibility_service.cc b/components/omnibox/browser/aim_eligibility_service.cc index 0d6ba3f7..5e1a853 100644 --- a/components/omnibox/browser/aim_eligibility_service.cc +++ b/components/omnibox/browser/aim_eligibility_service.cc @@ -15,6 +15,7 @@ #include "base/logging.h" #include "base/memory/scoped_refptr.h" #include "base/metrics/histogram_functions.h" +#include "base/strings/pattern.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" @@ -48,7 +49,6 @@ #include "services/network/public/mojom/url_response_head.mojom.h" #include "third_party/omnibox_proto/aim_eligibility_client_request.pb.h" #include "third_party/omnibox_proto/aim_eligibility_response.pb.h" -#include "third_party/re2/src/re2/re2.h" #include "url/gurl.h" namespace { @@ -541,16 +541,18 @@ const GURL& url, std::optional<std::string> host_override) const { OMNIBOX_LOG("aim_url_check") << "IsAimHost: Checking host..."; - if (host_override && host_override.value() == url.host()) { + if (host_override && + base::EqualsCaseInsensitiveASCII(host_override.value(), url.host())) { OMNIBOX_LOG("aim_url_check") << "Found overridden host!"; return true; } OMNIBOX_LOG("aim_url_check") << "IsAimHost: Available hosts: " << GetMostRecentResponse().interception_allowed_hosts().size(); + std::string lower_url_host = base::ToLowerASCII(url.host()); for (const auto& host_pattern : GetMostRecentResponse().interception_allowed_hosts()) { - if (re2::RE2::FullMatch(url.host(), host_pattern)) { + if (base::MatchPattern(lower_url_host, base::ToLowerASCII(host_pattern))) { OMNIBOX_LOG("aim_url_check") << "IsAimHost: Matched : " << host_pattern; return true; } diff --git a/components/omnibox/browser/aim_eligibility_service_unittest.cc b/components/omnibox/browser/aim_eligibility_service_unittest.cc index 5ccdbade..dd1dc30 100644 --- a/components/omnibox/browser/aim_eligibility_service_unittest.cc +++ b/components/omnibox/browser/aim_eligibility_service_unittest.cc @@ -253,13 +253,8 @@ rule.mutable_required_params()->Add(CreateQueryParam("b", "2")); response.mutable_aim_detection_url_rule()->Add(std::move(rule)); - // The example here does not represent what should be sent from the - // backend. If a zero-or-one subdomain needs to be accounted for two - // rules should be sent instead: - // - google.com - // - *.google.com - // This will avoid catching cases like fakegoogle.com. - response.mutable_interception_allowed_hosts()->Add(".*.?google.com"); + response.mutable_interception_allowed_hosts()->Add("google.com"); + response.mutable_interception_allowed_hosts()->Add("*.google.com"); response.mutable_interception_allowed_paths()->Add("/search"); @@ -278,6 +273,47 @@ GURL("https://google.example.com/search?a=1&b=2"), std::nullopt)); } +TEST_F(AimEligibilityServiceTest, IsAimUrl_SecurityNearDomains) { + omnibox::AimEligibilityResponse response; + + omnibox::AimEligibilityResponse::AimDetectionUrlRule rule; + rule.mutable_required_params()->Add(CreateQueryParam("udm", "50")); + response.mutable_aim_detection_url_rule()->Add(std::move(rule)); + + response.mutable_interception_allowed_hosts()->Add("www.google.com"); + response.mutable_interception_allowed_hosts()->Add("google.com"); + + response.mutable_interception_allowed_paths()->Add("/search"); + + aim_eligibility_service_->SetAimEligibilityResponse(std::move(response)); + + // Legitimate domains match. + EXPECT_TRUE(aim_eligibility_service_->IsAimUrl( + GURL("https://www.google.com/search?udm=50"), std::nullopt)); + EXPECT_TRUE(aim_eligibility_service_->IsAimUrl( + GURL("https://google.com/search?udm=50"), std::nullopt)); + EXPECT_TRUE(aim_eligibility_service_->IsAimUrl( + GURL("https://WWW.GOOGLE.COM/search?udm=50"), std::nullopt)); + + // Attacker-controlled near-domains and similar strings MUST NOT match. + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://www0google.com/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://wwwagoogle.com/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://www-google.com/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://google0com/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://googleicom/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://fakegoogle.com/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://google.com.evil.com/search?udm=50"), std::nullopt)); + EXPECT_FALSE(aim_eligibility_service_->IsAimUrl( + GURL("https://notgoogle.com/search?udm=50"), std::nullopt)); +} + TEST_F(AimEligibilityServiceTest, HasNoCobrowseParams_ExactMatch) { omnibox::AimEligibilityResponse response; response.mutable_no_cobrowse_params()->Add(CreateQueryParam("ncb", "1"));
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/components/omnibox/browser/aim_eligibility_service_unittest.cc b/components/omnibox/browser/aim_eligibility_service_unittest.cc
index 5ccdbade..dd1dc30 100644
--- a/components/omnibox/browser/aim_eligibility_service_unittest.cc
+++ b/components/omnibox/browser/aim_eligibility_service_unittest.cc
@@ -253,13 +253,8 @@
rule.mutable_required_params()->Add(CreateQueryParam("b", "2"));
response.mutable_aim_detection_url_rule()->Add(std::move(rule));
- // The example here does not represent what should be sent from the
- // backend. If a zero-or-one subdomain needs to be accounted for two
- // rules should be sent instead:
- // - google.com
- // - *.google.com
- // This will avoid catching cases like fakegoogle.com.
- response.mutable_interception_allowed_hosts()->Add(".*.?google.com");
+ response.mutable_interception_allowed_hosts()->Add("google.com");
+ response.mutable_interception_allowed_hosts()->Add("*.google.com");
response.mutable_interception_allowed_paths()->Add("/search");
@@ -278,6 +273,47 @@
GURL("https://google.example.com/search?a=1&b=2"), std::nullopt));
}
+TEST_F(AimEligibilityServiceTest, IsAimUrl_SecurityNearDomains) {
+ omnibox::AimEligibilityResponse response;
+
+ omnibox::AimEligibilityResponse::AimDetectionUrlRule rule;
+ rule.mutable_required_params()->Add(CreateQueryParam("udm", "50"));
+ response.mutable_aim_detection_url_rule()->Add(std::move(rule));
+
+ response.mutable_interception_allowed_hosts()->Add("www.google.com");
+ response.mutable_interception_allowed_hosts()->Add("google.com");
+
+ response.mutable_interception_allowed_paths()->Add("/search");
+
+ aim_eligibility_service_->SetAimEligibilityResponse(std::move(response));
+
+ // Legitimate domains match.
+ EXPECT_TRUE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://www.google.com/search?udm=50"), std::nullopt));
+ EXPECT_TRUE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://google.com/search?udm=50"), std::nullopt));
+ EXPECT_TRUE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://WWW.GOOGLE.COM/search?udm=50"), std::nullopt));
+
+ // Attacker-controlled near-domains and similar strings MUST NOT match.
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://www0google.com/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://wwwagoogle.com/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://www-google.com/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://google0com/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://googleicom/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://fakegoogle.com/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://google.com.evil.com/search?udm=50"), std::nullopt));
+ EXPECT_FALSE(aim_eligibility_service_->IsAimUrl(
+ GURL("https://notgoogle.com/search?udm=50"), std::nullopt));
+}
+
TEST_F(AimEligibilityServiceTest, HasNoCobrowseParams_ExactMatch) {
omnibox::AimEligibilityResponse response;
response.mutable_no_cobrowse_params()->Add(CreateQueryParam("ncb", "1"));
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