Low chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in API
DescriptionIncorrect authorization in API
ComponentAPI
Bug ClassAuthorization Error
Tracker519458746
Fix commitea54f4b92b87 (chromium/src) +179/-53
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
components/content_settings/core/common/content_settings_pattern.cc
modified
while
components/content_settings/core/common/content_settings_pattern.cc
modified
for
components/url_pattern_index/url_pattern_index.cc
modified
TEST
components/content_settings/core/common/content_settings_pattern_unittest.cc
modified

Files Changed

  • components/url_pattern_index/url_pattern_index.cc
  • components/content_settings/core/common/content_settings_pattern_unittest.cc
  • components/content_settings/core/common/content_settings_pattern.cc
  • components/url_pattern_index/url_pattern_index_unittest.cc
From ea54f4b92b8701fe399346b02d6a6c3388baad1b Mon Sep 17 00:00:00 2001
From: Dominic Battre <battre@chromium.org>
Date: Wed, 16 Sep 2026 02:12:47 -0700
Subject: [PATCH] Reland "Reland "Fix trailing dot normalization bypasses.""

This reverts commit c71bfcc01a4cbb1c2765fb56a5415e0f014a1c2a.

Reason for revert: Fixes to logic that broke the fuzz tests.

Original change's description:
> Revert "Reland "Fix trailing dot normalization bypasses.""
>
> This reverts commit bd5707e4b57c6edb080dc4718b6e51497a718403.
>
> Reason for revert: more problems with fuzzer
>
> Failure Link: https://issues.chromium.org/issues/560465534, https://issues.chromium.org/issues/560246192
>
>
>
> Original change's description:
> > Reland "Fix trailing dot normalization bypasses."
> >
> > This reverts commit 1a015be3b28655f2c61d4a4be1f229f3dafe6419.
> >
> > Reason for revert: This CL fixes idempotency for content settings.
> >
> > Original change's description:
> > > Revert "Fix trailing dot normalization bypasses."
> > >
> > > This reverts commit 5ee694c112b1dd9d04b441f5b0e27eb10bbbb759.
> > >
> > > Reason for revert: This broke the fuzzing tests because it broke idempotency. Want to reland a version that contains https://chromium-review.git.corp.google.com/c/chromium/src/+/8382808/1
> > >
> > > Failure Link: b:559255785
> > >
> > > Original change's description:
> > > > Fix trailing dot normalization bypasses.
> > > >
> > > > This CL fixes issues where hostnames with multiple trailing dots could
> > > > bypass domain-based filters in Declarative Net Request (DNR) and
> > > > subresource_filter.
> > > >
> > > > After this CL we should review all call sites of IsDomain to decide
> > > > whether they should be changed to DomainIsIgnoringTrailingDots.
> > > >
> > > > TAG=agy
> > > > CONV=ebcc4572-5439-4145-af7c-6acdf5c1da58
> > > >
> > > > Bug: 519458746
> > > > Change-Id: Ic1f03aaa857ffc52d57254fe744842a4c24e69c2
> > > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8074441
> > > > Commit-Queue: Dominic Battré <battre@chromium.org>
> > > > Reviewed-by: Balazs Engedy <engedy@chromium.org>
> > > > Cr-Commit-Position: refs/heads/main@{#1694536}
> > >
> > > Bug: 519458746, 559255785
> > > No-Presubmit: true
> > > No-Tree-Checks: true
> > > No-Try: true
> > > Bug: 519458746
> > > Change-Id: Ie56e3e8988d1a9f8f75676176c502e45620c1ae8
> > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8379446
> > > Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> > > Reviewed-by: Balazs Engedy <engedy@chromium.org>
> > > Commit-Queue: Dominic Battré <battre@chromium.org>
> > > Cr-Commit-Position: refs/heads/main@{#1695332}
> >
> > Bug: 519458746, 559255785
> > Bug: 519458746
> > Change-Id: Ie6454d2372b06b449ed93739e1b5ec5f067ab3dd
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8380659
> > Reviewed-by: Balazs Engedy <engedy@chromium.org>
> > Auto-Submit: Dominic Battré <battre@chromium.org>
> > Commit-Queue: Balazs Engedy <engedy@chromium.org>
> > Cr-Commit-Position: refs/heads/main@{#1696115}
>
> Bug: 519458746, 559255785
> Bug: 519458746
> Change-Id: I749c23d92a54067b8b2cdb41db87e0826775d01d
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8406065
> Reviewed-by: Balazs Engedy <engedy@chromium.org>
> Commit-Queue: Dominic Battré <battre@chromium.org>
> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> Cr-Commit-Position: refs/heads/main@{#1697245}

Bug: 519458746, 559255785
Bug: 519458746
Change-Id: I86484354de41a3cf3881181217e9b7514e8d5d80
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8409409
Reviewed-by: Balazs Engedy <engedy@chromium.org>
Commit-Queue: Dominic Battré <battre@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1698769}
---
diff --git a/components/content_settings/core/common/content_settings_pattern.cc b/components/content_settings/core/common/content_settings_pattern.cc
index 8ddebace..8a88770e 100644
--- a/components/content_settings/core/common/content_settings_pattern.cc
+++ b/components/content_settings/core/common/content_settings_pattern.cc
@@ -168,6 +168,12 @@
   // the canonicalization was successful.
   static bool Canonicalize(PatternParts* parts);
 
+  // Canonicalizes the host part. Repeating the process ensures that hosts with
+  // trailing dots that were not visible before canonicalization (e.g. because
+  // UTS#46 / IDNA stripped ignorable characters after trailing dots, or mapped
+  // Unicode fullwidth dots) are trimmed and re-canonicalized consistently.
+  static bool CanonicalizeHost(PatternParts* parts);
+
   // Returns true when the pattern |parts| represent a valid pattern.
   static bool Validate(const PatternParts& parts);
 
@@ -280,44 +286,59 @@
     }
   }
 
-  // Canonicalize the host part.
-  url::CanonHostInfo host_info;
-  std::string canonicalized_host;
-  if (parts->scheme == url::kFileScheme) {
-    canonicalized_host = net::CanonicalizeFileHost(parts->host, &host_info);
-  } else {
-    canonicalized_host = net::CanonicalizeHost(parts->host, &host_info);
-  }
+  return CanonicalizeHost(parts);
+}
 
-  if (host_info.IsIPAddress() && parts->has_domain_wildcard) {
-    return false;
-  }
+// static
+bool ContentSettingsPattern::Builder::CanonicalizeHost(PatternParts* parts) {
+  // Canonicalize the host part. Repeating the process ensures that hosts with
+  // trailing dots that were not visible before canonicalization (e.g. because
+  // UTS#46 / IDNA stripped ignorable characters after trailing dots, or mapped
+  // Unicode fullwidth dots) are trimmed and re-canonicalized consistently.
+  while (true) {
+    while (parts->host.length() > 1 && parts->host.ends_with(".")) {
+      parts->host.pop_back();
+    }
 
-  // A domain wildcard pattern involves exactly one separating dot, inside the
-  // square brackets. This is a common misunderstanding of that pattern that we
-  // want to check for. See: https://crbug.com/823706.
-  if (parts->has_domain_wildcard && base::StartsWith(canonicalized_host, ".")) {
-    return false;
-  }
+    url::CanonHostInfo host_info;
+    std::string canonicalized_host;
+    if (parts->scheme == url::kFileScheme) {
+      canonicalized_host = net::CanonicalizeFileHost(parts->host, &host_info);
+    } else {
+      canonicalized_host = net::CanonicalizeHost(parts->host, &host_info);
+    }
 
-  // Omit a single ending dot as long as there is at least one non-dot character
-  // before it, which is in line with the behavior of net::TrimEndingDot; but
-  // consider two ending dots an invalid pattern, otherwise canonicalization of
-  // a canonical pattern would not be idempotent.
-  if (base::EndsWith(canonicalized_host, "..", base::CompareCase::SENSITIVE)) {
-    return false;
-  } else if (canonicalized_host.size() >= 2u &&
-             base::EndsWith(canonicalized_host, ".",
-                            base::CompareCase::SENSITIVE)) {
-    canonicalized_host.pop_back();
-  }
+    if (host_info.family == url::CanonHostInfo::BROKEN) {
+      return false;
+    }
 
-  if ((parts->host.find('*') == std::string::npos) &&
-      !canonicalized_host.empty()) {
-    // Valid host.
+    if (host_info.IsIPAddress() && parts->has_domain_wildcard) {
+      return false;
+    }
+
+    // A domain wildcard pattern involves exactly one separating dot, inside the
+    // square brackets. This is a common misunderstanding of that pattern that
+    // we want to check for. See: https://crbug.com/823706.
+    if (parts->has_domain_wildcard &&
+        base::StartsWith(canonicalized_host, ".")) {
+      return false;
+    }
+
+    while (canonicalized_host.length() > 1 &&
+           canonicalized_host.ends_with(".")) {
+      canonicalized_host.pop_back();
+    }
+
+    if (parts->host.find('*') != std::string::npos ||
+        canonicalized_host.empty()) {
+      parts->host.clear();
+      break;
+    }
+
+    if (parts->host == canonicalized_host) {
+      break;
+    }
     parts->host = std::move(canonicalized_host);
-  } else {
-    parts->host.clear();
   }
 
   return true;
@@ -633,11 +654,9 @@
     return parts_.is_path_wildcard || parts_.path == local_url->path();
   }
 
-  // Match the host part. Code is the same as url::TrimEndingDot but that method
-  // unnecessarily creates a new std::string.
+  // Match the host part, removing all trailing dots.
   std::string_view trimmed_host = local_url->host();
-  size_t len = trimmed_host.length();
-  if (len > 1 && trimmed_host[len - 1] == '.') {
+  while (trimmed_host.length() > 1 && trimmed_host.back() == '.') {
     trimmed_host.remove_suffix(1);
   }
 
diff --git a/components/url_pattern_index/url_pattern_index.cc b/components/url_pattern_index/url_pattern_index.cc
index b46c0b9..ccb2c25bf 100644
--- a/components/url_pattern_index/url_pattern_index.cc
+++ b/components/url_pattern_index/url_pattern_index.cc
@@ -235,17 +235,24 @@
     domains_included.reserve(domains_size);
 
     for (const auto& domain_list_item : domain_list_items) {
-      const std::string& domain = domain_list_item.domain();
+      std::string_view domain = domain_list_item.domain();
 
       // Non-ascii characters in domains are unsupported.
       if (!base::IsStringASCII(domain))
         return false;
 
+      // Trimming trailing dots from the domain.
+      while (domain.length() > 1 && domain.back() == '.') {
+        domain.remove_suffix(1);
+      }
+
       // Note: This is not always correct. Chrome's URL parser uses upper-case
       // for percent encoded hosts. E.g. https://,.com is encoded as
       // https://%2C.com.
-      auto offset = builder->CreateSharedString(
-          HasNoUpperAscii(domain) ? domain : base::ToLowerASCII(domain));
+      std::string domain_lower = HasNoUpperAscii(domain)
+                                     ? std::string(domain)
+                                     : base::ToLowerASCII(domain);
+      auto offset = builder->CreateSharedString(domain_lower);
 
       if (domain_list_item.exclude())
         domains_excluded.push_back(offset);
@@ -584,22 +591,24 @@
   if (host.empty())
     return 0;
 
+  // If the host name ends with dots, then ignore them.
+  while (host.length() > 1 && host.back() == '.') {
+    host.remove_suffix(1);
+  }
+
   // If the |domains| list is short, then the simple strategy is usually faster.
   if (domains.size() <= 5) {
     for (auto* domain : domains) {
       const std::string_view domain_piece = ToStringView(domain);
-      if (url::DomainIs(host, domain_piece))
+      if (url::DomainIs(host, domain_piece)) {
         return domain_piece.size();
+      }
     }
     return 0;
   }
 
   // Otherwise look for each subdomain of the `host` using binary search.
 
-  // If the host name ends with a dot, then ignore it.
-  if (host.back() == '.')
-    host.remove_suffix(1);
-
   // The |left| bound of the search is shared between iterations, because
   // subdomains are considered in decreasing order of their lengths, therefore
   // each consecutive lower_bound will be at least as far as the previous.
diff --git a/components/content_settings/core/common/content_settings_pattern_unittest.cc b/components/content_settings/core/common/content_settings_pattern_unittest.cc
index df92c3a2..8d7d9e5 100644
--- a/components/content_settings/core/common/content_settings_pattern_unittest.cc
+++ b/components/content_settings/core/common/content_settings_pattern_unittest.cc
@@ -344,12 +344,19 @@
                 ContentSettingsPattern::Wildcard()));
 }
 
-TEST(ContentSettingsPatternTest, TrimEndingDotFromHost) {
+TEST(ContentSettingsPatternTest, TrimTrailingDotsFromHost) {
   EXPECT_TRUE(Pattern("www.example.com").IsValid());
   EXPECT_TRUE(
       Pattern("www.example.com").Matches(GURL("http://www.example.com")));
   EXPECT_TRUE(
       Pattern("www.example.com").Matches(GURL("http://www.example.com.")));
+  EXPECT_TRUE(
+      Pattern("www.example.com").Matches(GURL("http://www.example.com..")));
+
+  EXPECT_TRUE(
+      Pattern("https://example.com..").Matches(GURL("https://example.com")));
+  EXPECT_TRUE(
+      Pattern("https://example.com").Matches(GURL("https://example.com..")));
 
   EXPECT_TRUE(Pattern("www.example.com.").IsValid());
   EXPECT_STREQ("www.example.com",
@@ -357,6 +364,9 @@
 
   EXPECT_TRUE(Pattern("www.example.com.") == Pattern("www.example.com"));
   EXPECT_TRUE(Pattern("www.example.com.") == Pattern("www.example.com."));
+  EXPECT_TRUE(Pattern("www.example.com..") == Pattern("www.example.com"));
+  EXPECT_TRUE(Pattern("http://www.example.com..") ==
+              Pattern("http://www.example.com"));
 
   EXPECT_TRUE(Pattern(".").IsValid());
   EXPECT_STREQ(".", Pattern(".").ToString().c_str());
@@ -370,8 +380,41 @@
   EXPECT_STREQ("a..b", Pattern("a..b.").ToString().c_str());
   EXPECT_TRUE(Pattern("a..b.").Matches(GURL("http://a..b.")));
 
-  EXPECT_FALSE(Pattern("..").IsValid());
-  EXPECT_FALSE(Pattern("a..").IsValid());
+  EXPECT_TRUE(Pattern("..").IsValid());   // Equivalent to Pattern(".").
+  EXPECT_TRUE(Pattern("a..").IsValid());  // Equivalent to Pattern("a").
+
+  // Hosts that end in a numeric label (or punycode resulting in one) are
+  // treated as IPv4 addresses per WHATWG URL spec. When preceding labels are
+  // non-numeric, they are broken IPv4 addresses and must be rejected even when
+  // multiple trailing dots are present.
+  EXPECT_FALSE(Pattern("example.2").IsValid());
+  EXPECT_FALSE(Pattern("example.2.").IsValid());
+  EXPECT_FALSE(Pattern("example.2..").IsValid());
+  EXPECT_FALSE(Pattern("9127.xn-91ԵxhԸ.0chr-Ὲ.2").IsValid());
+  EXPECT_FALSE(Pattern("9127.xn-91ԵxhԸ.0chr-Ὲ.2.").IsValid());
+  EXPECT_FALSE(Pattern("9127.xn-91ԵxhԸ.0chr-Ὲ.2..").IsValid());
+
+  // Trailing dots disguised by ignorable characters (like soft hyphen U+00AD)
+  // or fullwidth/ideographic dots must be handled consistently and rejected if
+  // broken.
+  EXPECT_FALSE(Pattern("example.2.\xc2\xad").IsValid());
+  EXPECT_FALSE(Pattern("example.2\xe3\x80\x82").IsValid());
+  EXPECT_FALSE(Pattern("example.2\xe3\x80\x82\xe3\x80\x82").IsValid());
+  EXPECT_FALSE(
+      Pattern("...........\xc2\xad....1....\xc2\xad..........\xc2\xad....")
+          .IsValid());
+
+  // Valid patterns with trailing dots or non-ASCII dots that resolve to IPv4.
+  EXPECT_TRUE(Pattern("1..").IsValid());
+  EXPECT_STREQ("0.0.0.1", Pattern("1..").ToString().c_str());
+  EXPECT_TRUE(Pattern("1..") == Pattern("0.0.0.1"));
+  EXPECT_TRUE(Pattern("1\xe3\x80\x82\xe3\x80\x82").IsValid());
+  EXPECT_STREQ("0.0.0.1",
+               Pattern("1\xe3\x80\x82\xe3\x80\x82").ToString().c_str());
+  EXPECT_TRUE(Pattern("example.com\xe3\x80\x82\xe3\x80\x82").IsValid());
+  EXPECT_STREQ(
+      "example.com",
+      Pattern("example.com\xe3\x80\x82\xe3\x80\x82").ToString().c_str());
 }
 
 TEST(ContentSettingsPatternTest, FromString_WithNoWildcards) {
@@ -743,10 +786,6 @@
   // Invalid file pattern strings.
   EXPECT_FALSE(Pattern("file://").IsValid());
   EXPECT_STREQ("", Pattern("file://").ToString().c_str());
-
-  // Host having multiple ending dots.
-  EXPECT_FALSE(Pattern("www.example.com..").IsValid());
-  EXPECT_STREQ("", Pattern("www.example.com..").ToString().c_str());
 }
 
 TEST(ContentSettingsPatternTest, UnequalOperator) {
diff --git a/components/url_pattern_index/url_pattern_index_unittest.cc b/components/url_pattern_index/url_pattern_index_unittest.cc
index e8cdcf86..806a86fe 100644
--- a/components/url_pattern_index/url_pattern_index_unittest.cc
+++ b/components/url_pattern_index/url_pattern_index_unittest.cc
@@ -447,13 +447,72 @@
       {{"domain.com"}, "http://.domain.com", true},
       {{"domain.com"}, "http://.domain.com.", true},
       {{".domain.com"}, "http://.domain.com", true},
-      {{"domain.com."}, "http://domain.com", false},
+      {{"domain.com."}, "http://domain.com", true},
       {{"domain.com."}, "http://domain.com.", true},
+      {{"domain.com.."}, "http://domain.com", true},
+      {{"domain.com.."}, "http://domain.com.", true},
+      {{"domain.com.."}, "http://domain.com..", true},
+      {{"domain.com..."}, "http://domain.com", true},
+      {{"domain.com..."}, "http://domain.com..", true},
 
       {{"domain..com"}, "http://domain.com", false},
       {{"domain.com"}, "http://domain..com", false},
       {{"domain..com"}, "http://domain..com", true},
 
+      // Multiple trailing dots.
+      {{"domain.com"}, "http://domain.com..", true},
+      {{"domain.com"}, "http://domain.com...", true},
+      {{"domain.com"}, "http://sub.domain.com..", true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com"},
+       "http://domain.com..",
+       true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com"},
+       "http://domain.com...",
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/content_settings/core/common/content_settings_pattern_unittest.cc b/components/content_settings/core/common/content_settings_pattern_unittest.cc
index df92c3a2..8d7d9e5 100644
--- a/components/content_settings/core/common/content_settings_pattern_unittest.cc
+++ b/components/content_settings/core/common/content_settings_pattern_unittest.cc
@@ -344,12 +344,19 @@
                 ContentSettingsPattern::Wildcard()));
 }
 
-TEST(ContentSettingsPatternTest, TrimEndingDotFromHost) {
+TEST(ContentSettingsPatternTest, TrimTrailingDotsFromHost) {
   EXPECT_TRUE(Pattern("www.example.com").IsValid());
   EXPECT_TRUE(
       Pattern("www.example.com").Matches(GURL("http://www.example.com")));
   EXPECT_TRUE(
       Pattern("www.example.com").Matches(GURL("http://www.example.com.")));
+  EXPECT_TRUE(
+      Pattern("www.example.com").Matches(GURL("http://www.example.com..")));
+
+  EXPECT_TRUE(
+      Pattern("https://example.com..").Matches(GURL("https://example.com")));
+  EXPECT_TRUE(
+      Pattern("https://example.com").Matches(GURL("https://example.com..")));
 
   EXPECT_TRUE(Pattern("www.example.com.").IsValid());
   EXPECT_STREQ("www.example.com",
@@ -357,6 +364,9 @@
 
   EXPECT_TRUE(Pattern("www.example.com.") == Pattern("www.example.com"));
   EXPECT_TRUE(Pattern("www.example.com.") == Pattern("www.example.com."));
+  EXPECT_TRUE(Pattern("www.example.com..") == Pattern("www.example.com"));
+  EXPECT_TRUE(Pattern("http://www.example.com..") ==
+              Pattern("http://www.example.com"));
 
   EXPECT_TRUE(Pattern(".").IsValid());
   EXPECT_STREQ(".", Pattern(".").ToString().c_str());
@@ -370,8 +380,41 @@
   EXPECT_STREQ("a..b", Pattern("a..b.").ToString().c_str());
   EXPECT_TRUE(Pattern("a..b.").Matches(GURL("http://a..b.")));
 
-  EXPECT_FALSE(Pattern("..").IsValid());
-  EXPECT_FALSE(Pattern("a..").IsValid());
+  EXPECT_TRUE(Pattern("..").IsValid());   // Equivalent to Pattern(".").
+  EXPECT_TRUE(Pattern("a..").IsValid());  // Equivalent to Pattern("a").
+
+  // Hosts that end in a numeric label (or punycode resulting in one) are
+  // treated as IPv4 addresses per WHATWG URL spec. When preceding labels are
+  // non-numeric, they are broken IPv4 addresses and must be rejected even when
+  // multiple trailing dots are present.
+  EXPECT_FALSE(Pattern("example.2").IsValid());
+  EXPECT_FALSE(Pattern("example.2.").IsValid());
+  EXPECT_FALSE(Pattern("example.2..").IsValid());
+  EXPECT_FALSE(Pattern("9127.xn-91ԵxhԸ.0chr-Ὲ.2").IsValid());
+  EXPECT_FALSE(Pattern("9127.xn-91ԵxhԸ.0chr-Ὲ.2.").IsValid());
+  EXPECT_FALSE(Pattern("9127.xn-91ԵxhԸ.0chr-Ὲ.2..").IsValid());
+
+  // Trailing dots disguised by ignorable characters (like soft hyphen U+00AD)
+  // or fullwidth/ideographic dots must be handled consistently and rejected if
+  // broken.
+  EXPECT_FALSE(Pattern("example.2.\xc2\xad").IsValid());
+  EXPECT_FALSE(Pattern("example.2\xe3\x80\x82").IsValid());
+  EXPECT_FALSE(Pattern("example.2\xe3\x80\x82\xe3\x80\x82").IsValid());
+  EXPECT_FALSE(
+      Pattern("...........\xc2\xad....1....\xc2\xad..........\xc2\xad....")
+          .IsValid());
+
+  // Valid patterns with trailing dots or non-ASCII dots that resolve to IPv4.
+  EXPECT_TRUE(Pattern("1..").IsValid());
+  EXPECT_STREQ("0.0.0.1", Pattern("1..").ToString().c_str());
+  EXPECT_TRUE(Pattern("1..") == Pattern("0.0.0.1"));
+  EXPECT_TRUE(Pattern("1\xe3\x80\x82\xe3\x80\x82").IsValid());
+  EXPECT_STREQ("0.0.0.1",
+               Pattern("1\xe3\x80\x82\xe3\x80\x82").ToString().c_str());
+  EXPECT_TRUE(Pattern("example.com\xe3\x80\x82\xe3\x80\x82").IsValid());
+  EXPECT_STREQ(
+      "example.com",
+      Pattern("example.com\xe3\x80\x82\xe3\x80\x82").ToString().c_str());
 }
 
 TEST(ContentSettingsPatternTest, FromString_WithNoWildcards) {
@@ -743,10 +786,6 @@
   // Invalid file pattern strings.
   EXPECT_FALSE(Pattern("file://").IsValid());
   EXPECT_STREQ("", Pattern("file://").ToString().c_str());
-
-  // Host having multiple ending dots.
-  EXPECT_FALSE(Pattern("www.example.com..").IsValid());
-  EXPECT_STREQ("", Pattern("www.example.com..").ToString().c_str());
 }
 
 TEST(ContentSettingsPatternTest, UnequalOperator) {
diff --git a/components/url_pattern_index/url_pattern_index_unittest.cc b/components/url_pattern_index/url_pattern_index_unittest.cc
index e8cdcf86..806a86fe 100644
--- a/components/url_pattern_index/url_pattern_index_unittest.cc
+++ b/components/url_pattern_index/url_pattern_index_unittest.cc
@@ -447,13 +447,72 @@
       {{"domain.com"}, "http://.domain.com", true},
       {{"domain.com"}, "http://.domain.com.", true},
       {{".domain.com"}, "http://.domain.com", true},
-      {{"domain.com."}, "http://domain.com", false},
+      {{"domain.com."}, "http://domain.com", true},
       {{"domain.com."}, "http://domain.com.", true},
+      {{"domain.com.."}, "http://domain.com", true},
+      {{"domain.com.."}, "http://domain.com.", true},
+      {{"domain.com.."}, "http://domain.com..", true},
+      {{"domain.com..."}, "http://domain.com", true},
+      {{"domain.com..."}, "http://domain.com..", true},
 
       {{"domain..com"}, "http://domain.com", false},
       {{"domain.com"}, "http://domain..com", false},
       {{"domain..com"}, "http://domain..com", true},
 
+      // Multiple trailing dots.
+      {{"domain.com"}, "http://domain.com..", true},
+      {{"domain.com"}, "http://domain.com...", true},
+      {{"domain.com"}, "http://sub.domain.com..", true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com"},
+       "http://domain.com..",
+       true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com"},
+       "http://domain.com...",
+       true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com"},
+       "http://sub.domain.com..",
+       true},
+      {{"~domain.com"}, "http://domain.com..", false},
+      {{"~domain.com"}, "http://sub.domain.com..", false},
+      {{"~d1.com", "~d2.com", "~d3.com", "~d4.com", "~d5.com", "~domain.com"},
+       "http://domain.com..",
+       false},
+      {{"~d1.com", "~d2.com", "~d3.com", "~d4.com", "~d5.com", "~domain.com"},
+       "http://sub.domain.com..",
+       false},
+
+      // Trailing dots on rules in long domain lists (> 5 domains, binary
+      // search path).
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com."},
+       "http://domain.com",
+       true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com."},
+       "http://domain.com.",
+       true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com.."},
+       "http://domain.com",
+       true},
+      {{"d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "domain.com.."},
+       "http://domain.com..",
+       true},
+      {{"~d1.com", "~d2.com", "~d3.com", "~d4.com", "~d5.com", "~domain.com."},
+       "http://domain.com",
+       false},
+      {{"~d1.com", "~d2.com", "~d3.com", "~d4.com", "~d5.com", "~domain.com.."},
+       "http://domain.com",
+       false},
+      {{"~d1.com", "~d2.com", "~d3.com", "~d4.com", "~d5.com", "~domain.com.."},
+       "http://sub.domain.com..",
+       false},
+
+      // Short domain patterns.
+      {{"d.de"}, "http://d.de", true},
+      {{"d.de"}, "http://d.de.", true},
+      {{"d.de"}, "http://d.de..", true},
+      {{"~d.de"}, "http://d.de", false},
+      {{"~d.de"}, "http://d.de.", false},
+      {{"~d.de"}, "http://d.de..", false},
+
       {{"~domain.com"}, "", true},
       {{"~domain.com"}, "http://domain.com", false},
       {{"~ddomain.com"}, "http://domain.com", true},
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.