Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Omnibox
DescriptionIncorrect authorization in Omnibox
ComponentOmnibox
Bug ClassLogic Error
Tracker547592631
Fix commit9e1d112b91b8 (chromium/src) +48/-10
CISA KEVNot listed
CreditedArni Hardarson (Neonix Security)
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
TEST_F
components/omnibox/browser/aim_eligibility_service_unittest.cc
modified

Files Changed

  • components/omnibox/browser/aim_eligibility_service.cc
  • components/omnibox/browser/aim_eligibility_service_unittest.cc
From 9e1d112b91b8752e43d3fdd11cdd1cb6a9fc8f7b Mon Sep 17 00:00:00 2001
From: Duncan Mercer <mercerd@google.com>
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 <jdonnelly@chromium.org>
Commit-Queue: Justin Donnelly <jdonnelly@chromium.org>
Auto-Submit: Duncan Mercer <mercerd@google.com>
Reviewed-by: Matthew Jones <mdjones@chromium.org>
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.