Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Extensions
DescriptionPolicy bypass in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker519455164
Fix commitb2d2df8dfd69 (chromium/src) +19/-10
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
TEST
extensions/common/url_pattern_unittest.cc
modified

Files Changed

  • chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
  • extensions/common/url_pattern.cc
  • extensions/common/url_pattern_unittest.cc
From b2d2df8dfd694f0b45ca00a6f493da35ca8831d6 Mon Sep 17 00:00:00 2001
From: Eva Su <evasu@chromium.org>
Date: Fri, 12 Jun 2026 13:03:19 -0700
Subject: [PATCH] [Extensions] Fix ExtensionSettings bypass with multiple trailing dots

The `CanonicalizeHostForMatching` function in
`extensions/common/url_pattern.cc` previously only removed a single
trailing dot from hostnames. This allowed a bypass of the
`runtime_blocked_hosts` policy in ExtensionSettings if a URL contained
multiple trailing dots.

This change updates the function to remove all trailing dots from the
hostname before matching against the policy. This prevents the bypass
and correctly enforces the intended host restrictions.

Fixed: 519455164
Change-Id: I5fbc7a891079e89293a15e008fe6a2c02ecf0e59
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7907778
Reviewed-by: Tim <tjudkins@chromium.org>
Reviewed-by: Nina Satragno <nsatragno@chromium.org>
Commit-Queue: Eva Su <evasu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1646199}
---

diff --git a/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc b/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
index 884857bb..0963a5c 100644
--- a/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
+++ b/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
@@ -200,8 +200,15 @@
     {"http://localhost/", "localhost"},
 
     // Sanity check empty domain parts.
+    // URLPattern trims all trailing dots from hosts for matching (both from the
+    // pattern and from the evaluated host). Thus, patterns or hosts with any
+    // number of trailing dots are canonicalized to the version without trailing
+    // dots and match.
     {"https://google.com./", "google.com"},
     {"https://google.com./", "google.com."},
+    {"https://google.com/", "google.com."},
+    {"https://google.com/", "google.com.."},
+    {"https://google.com../", "google.com"},
 };
 
 constexpr PatternRpIdPair kInvalidRelyingPartyTestCases[] = {
@@ -240,12 +247,7 @@
     {"https://not-google.com/", "google.com)"},
     {"https://evil.appspot.com/", "appspot.com"},
     {"https://evil.co.uk/", "co.uk"},
-    // TODO(nsatragno): URLPattern erroneously trims trailing dots. Fix
-    // CanonicalizeHostForMatching and uncomment this line.
-    // {"https://google.com/", "google.com."},
-    {"https://google.com/", "google.com.."},
     {"https://google.com/", ".google.com"},
-    {"https://google.com../", "google.com"},
     {"https://.com/", "com."},
     {"https://.co.uk/", "co.uk."},
     {"https://1.2.3/", "1.2.3"},
diff --git a/extensions/common/url_pattern.cc b/extensions/common/url_pattern.cc
index c87bf37..1f733c42 100644
--- a/extensions/common/url_pattern.cc
+++ b/extensions/common/url_pattern.cc
@@ -123,12 +123,9 @@
   return path;
 }
 
-// Removes trailing dot from |host_piece| if any.
+// Removes trailing dot(s) from |host_piece| if any.
 std::string_view CanonicalizeHostForMatching(std::string_view host_piece) {
-  if (base::EndsWith(host_piece, ".")) {
-    host_piece.remove_suffix(1);
-  }
-  return host_piece;
+  return base::TrimString(host_piece, ".", base::TRIM_TRAILING);
 }
 
 }  // namespace
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index bb74cd70..dd9deee 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -967,6 +967,7 @@
 TEST(ExtensionURLPatternTest, TrailingDotDomain) {
   const GURL normal_domain("http://example.com/");
   const GURL trailing_dot_domain("http://example.com./");
+  const GURL multiple_trailing_dots_domain("http://example.com../");
 
   // Both patterns should match trailing dot and non trailing dot domains. More
   // information about this not obvious behaviour can be found in [1].
@@ -986,11 +987,20 @@
   const URLPattern pattern(URLPattern::SCHEME_HTTP, "*://example.com/*");
   EXPECT_TRUE(pattern.MatchesURL(normal_domain));
   EXPECT_TRUE(pattern.MatchesURL(trailing_dot_domain));
+  EXPECT_TRUE(pattern.MatchesURL(multiple_trailing_dots_domain));
 
   const URLPattern trailing_pattern(URLPattern::SCHEME_HTTP,
                                     "*://example.com./*");
   EXPECT_TRUE(trailing_pattern.MatchesURL(normal_domain));
   EXPECT_TRUE(trailing_pattern.MatchesURL(trailing_dot_domain));
+  EXPECT_TRUE(trailing_pattern.MatchesURL(multiple_trailing_dots_domain));
+
+  const URLPattern multiple_trailing_pattern(URLPattern::SCHEME_HTTP,
+                                             "*://example.com../*");
+  EXPECT_TRUE(multiple_trailing_pattern.MatchesURL(normal_domain));
+  EXPECT_TRUE(multiple_trailing_pattern.MatchesURL(trailing_dot_domain));
+  EXPECT_TRUE(
+      multiple_trailing_pattern.MatchesURL(multiple_trailing_dots_domain));
 }
 
 TEST(ExtensionURLPatternTest, MatchesEffectiveTLD) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc b/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
index 884857bb..0963a5c 100644
--- a/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
+++ b/chrome/browser/webauthn/chrome_web_authentication_delegate_unittest.cc
@@ -200,8 +200,15 @@
     {"http://localhost/", "localhost"},
 
     // Sanity check empty domain parts.
+    // URLPattern trims all trailing dots from hosts for matching (both from the
+    // pattern and from the evaluated host). Thus, patterns or hosts with any
+    // number of trailing dots are canonicalized to the version without trailing
+    // dots and match.
     {"https://google.com./", "google.com"},
     {"https://google.com./", "google.com."},
+    {"https://google.com/", "google.com."},
+    {"https://google.com/", "google.com.."},
+    {"https://google.com../", "google.com"},
 };
 
 constexpr PatternRpIdPair kInvalidRelyingPartyTestCases[] = {
@@ -240,12 +247,7 @@
     {"https://not-google.com/", "google.com)"},
     {"https://evil.appspot.com/", "appspot.com"},
     {"https://evil.co.uk/", "co.uk"},
-    // TODO(nsatragno): URLPattern erroneously trims trailing dots. Fix
-    // CanonicalizeHostForMatching and uncomment this line.
-    // {"https://google.com/", "google.com."},
-    {"https://google.com/", "google.com.."},
     {"https://google.com/", ".google.com"},
-    {"https://google.com../", "google.com"},
     {"https://.com/", "com."},
     {"https://.co.uk/", "co.uk."},
     {"https://1.2.3/", "1.2.3"},
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index bb74cd70..dd9deee 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -967,6 +967,7 @@
 TEST(ExtensionURLPatternTest, TrailingDotDomain) {
   const GURL normal_domain("http://example.com/");
   const GURL trailing_dot_domain("http://example.com./");
+  const GURL multiple_trailing_dots_domain("http://example.com../");
 
   // Both patterns should match trailing dot and non trailing dot domains. More
   // information about this not obvious behaviour can be found in [1].
@@ -986,11 +987,20 @@
   const URLPattern pattern(URLPattern::SCHEME_HTTP, "*://example.com/*");
   EXPECT_TRUE(pattern.MatchesURL(normal_domain));
   EXPECT_TRUE(pattern.MatchesURL(trailing_dot_domain));
+  EXPECT_TRUE(pattern.MatchesURL(multiple_trailing_dots_domain));
 
   const URLPattern trailing_pattern(URLPattern::SCHEME_HTTP,
                                     "*://example.com./*");
   EXPECT_TRUE(trailing_pattern.MatchesURL(normal_domain));
   EXPECT_TRUE(trailing_pattern.MatchesURL(trailing_dot_domain));
+  EXPECT_TRUE(trailing_pattern.MatchesURL(multiple_trailing_dots_domain));
+
+  const URLPattern multiple_trailing_pattern(URLPattern::SCHEME_HTTP,
+                                             "*://example.com../*");
+  EXPECT_TRUE(multiple_trailing_pattern.MatchesURL(normal_domain));
+  EXPECT_TRUE(multiple_trailing_pattern.MatchesURL(trailing_dot_domain));
+  EXPECT_TRUE(
+      multiple_trailing_pattern.MatchesURL(multiple_trailing_dots_domain));
 }
 
 TEST(ExtensionURLPatternTest, MatchesEffectiveTLD) {
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.