Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Password Manager
DescriptionInsufficient validation of untrusted input in Password Manager
ComponentPassword Manager
Bug ClassLogic Error
Tracker503490678
Fix commit7433dd4e9c29 (chromium/src) +0/-123
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
for
components/password_manager/core/browser/generation/password_generator.cc
modified
PasswordGeneratorChunkingTest
components/password_manager/core/browser/generation/password_generator_unittest.cc
modified
PasswordGeneratorChunkingTest
components/password_manager/core/browser/generation/password_generator_unittest.cc
modified
TEST_F
components/password_manager/core/browser/generation/password_generator_unittest.cc
modified
for
components/password_manager/core/browser/generation/password_generator_unittest.cc
modified
if
components/password_manager/core/browser/generation/password_generator_unittest.cc
modified

Files Changed

  • components/password_manager/core/browser/features/password_features.cc
  • components/password_manager/core/browser/features/password_features.h
  • components/password_manager/core/browser/generation/password_generator.cc
  • components/password_manager/core/browser/generation/password_generator_unittest.cc
From 7433dd4e9c29505fa7d0e7187aee0ed2580a427d Mon Sep 17 00:00:00 2001
From: Maria Kazinova <[email protected]>
Date: Thu, 23 Apr 2026 01:20:04 -0700
Subject: [PATCH] [Passwords] Remove kPasswordGenerationChunking feature.

The feature is not going to be launched, so no need to keep it
in the codebase. Removing it and the related tests.

Bug: 503490678
Change-Id: I28bb2a52607753c1107ac4ecb05118ff8b8d624b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7785095
Commit-Queue: Maria Kazinova <[email protected]>
Reviewed-by: Rafał Godlewski <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1619371}
---

diff --git a/components/password_manager/core/browser/features/password_features.cc b/components/password_manager/core/browser/features/password_features.cc
index 621e2b69..0dbe1ced 100644
--- a/components/password_manager/core/browser/features/password_features.cc
+++ b/components/password_manager/core/browser/features/password_features.cc
@@ -158,12 +158,6 @@
 BASE_FEATURE(kPasswordFormGroupedAffiliations,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
-BASE_FEATURE(kPasswordGenerationChunking,
-             "PasswordGenerationChunkPassword",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-
 BASE_FEATURE(kPasswordManagerLogToTerminal, base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kPreventPasswordManagerOnFederatedLogin,
diff --git a/components/password_manager/core/browser/features/password_features.h b/components/password_manager/core/browser/features/password_features.h
index 3e665df6..2b88d6c 100644
--- a/components/password_manager/core/browser/features/password_features.h
+++ b/components/password_manager/core/browser/features/password_features.h
@@ -176,12 +176,6 @@
 BASE_DECLARE_FEATURE(kPasswordFormGroupedAffiliations);
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
-// Enables "chunking" generated passwords by adding hyphens every 4 characters
-// to make them more readable.
-BASE_DECLARE_FEATURE(kPasswordGenerationChunking);
-#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
 BASE_DECLARE_FEATURE(kPasswordSaveInContextErrorResolutionOnDesktop);
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 
diff --git a/components/password_manager/core/browser/generation/password_generator.cc b/components/password_manager/core/browser/generation/password_generator.cc
index c2ad3b9..3428bc7 100644
--- a/components/password_manager/core/browser/generation/password_generator.cc
+++ b/components/password_manager/core/browser/generation/password_generator.cc
@@ -14,7 +14,6 @@
 #include "base/rand_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/proto/password_requirements.pb.h"
-#include "components/password_manager/core/browser/features/password_features.h"
 
 namespace autofill {
 
@@ -24,9 +23,6 @@
 // prediction is smaller than the default.)
 const uint32_t kDefaultPasswordLength = 15;
 
-// The minimum length to chunk password with
-// `password_manager::features::PasswordGenerationChunking` feature.
-const uint32_t kMinLengthToChunkPassword = 9;
 
 namespace {
 
@@ -90,15 +86,6 @@
          }) != password.end();
 }
 
-bool ChunkingPasswordEnabled() {
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
-  return base::FeatureList::IsEnabled(
-      password_manager::features::kPasswordGenerationChunking);
-#else
-  return false;
-#endif
-}
-
 // Generates a password according to |spec| and tries to maximize the entropy
 // while not caring for pronounceable passwords.
 //
@@ -220,40 +207,6 @@
   return password;
 }
 
-// Generates a max entropy password with a dash symbol every 4th character by
-// modifying `spec` in the following way:
-// * max_length() is reduced to make space for dashes,
-// * symbols() are removed to not conflict visually with the added dashes.
-//
-// If the modified `spec` contains all values for the required fields, then we
-// insert dash every 4th character. Otherwise, the password using default spec
-// is returned.
-std::u16string GenerateMaxEntropyChunkedPassword(
-    PasswordRequirementsSpec spec) {
-  // Disallow symbols so they do not conflict visually with the added dashes.
-  PasswordRequirementsSpec modified_spec = spec;
-  modified_spec.mutable_symbols()->set_min(0);
-  modified_spec.mutable_symbols()->set_max(0);
-  modified_spec.mutable_symbols()->mutable_character_set()->clear();
-  // Generate max entropy password without dashes.
-  int number_of_dashes = std::ceil(modified_spec.max_length() / 5.0) - 1;
-  modified_spec.set_max_length(modified_spec.max_length() - number_of_dashes);
-
-  std::u16string password =
-      GenerateMaxEntropyPassword(std::move(modified_spec));
-
-  // Catch cases where the modified spec is infeasible.
-  if (password.empty()) {
-    return GenerateMaxEntropyPassword(std::move(spec));
-  }
-
-  // Add dash every 4th character.
-  for (int i = 0; i < number_of_dashes; i++) {
-    password.insert((i + 1) * 4 + i, u"-");
-  }
-  return password;
-}
-
 }  // namespace
 
 void ConditionallyAddNumericDigitsToAlphabet(PasswordRequirementsSpec* spec) {
@@ -275,16 +228,6 @@
 
   std::u16string password;
 
-  // For specs that allow dash symbol and can be longer than 8 chars generate a
-  // chunked password with `PasswordGenerationChunking` feature enabled.
-  if (actual_spec.symbols().character_set().find('-') != std::string::npos &&
-      actual_spec.max_length() >= kMinLengthToChunkPassword &&
-      ChunkingPasswordEnabled()) {
-    password = GenerateMaxEntropyChunkedPassword(std::move(actual_spec));
-    CHECK_LE(4u, password.size());
-    return password;
-  }
-
   password = GenerateMaxEntropyPassword(std::move(actual_spec));
 
   // Catch cases where supplied spec is infeasible.
diff --git a/components/password_manager/core/browser/generation/password_generator_unittest.cc b/components/password_manager/core/browser/generation/password_generator_unittest.cc
index a1a28ad..c3e696d 100644
--- a/components/password_manager/core/browser/generation/password_generator_unittest.cc
+++ b/components/password_manager/core/browser/generation/password_generator_unittest.cc
@@ -9,9 +9,7 @@
 #include <string>
 
 #include "base/notreached.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/autofill/core/browser/proto/password_requirements.pb.h"
-#include "components/password_manager/core/browser/features/password_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace autofill {
@@ -308,58 +306,6 @@
   EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
 }
 
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
-class PasswordGeneratorChunkingTest : public testing::Test {
- public:
-  PasswordGeneratorChunkingTest() {
-    feature_list_.InitAndEnableFeature(
-        password_manager::features::kPasswordGenerationChunking);
-  }
-  ~PasswordGeneratorChunkingTest() override = default;
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-TEST_F(PasswordGeneratorChunkingTest, ChunkedWithDifferentMaxLengths) {
-  PasswordRequirementsSpec spec;
-  spec.set_min_length(0);
-  spec.mutable_symbols()->set_character_set("-");
-
-  for (uint32_t max_length = 9; max_length <= kDefaultPasswordLength;
-       max_length++) {
-    spec.set_max_length(max_length);
-    std::u16string password = GeneratePassword(spec);
-
-    // Check every 5th char is a dash except when it's a last char.
-    for (uint32_t i = 4; i < max_length; i += 5) {
-      if (i < max_length - 1) {
-        EXPECT_EQ(password[i], '-');
-      }
-    }
-    EXPECT_NE(password[max_length - 1], '-');
-  }
-}
-
-TEST_F(PasswordGeneratorChunkingTest, DashDisallowed) {
-  PasswordRequirementsSpec spec;
-  spec.set_min_length(0);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/password_manager/core/browser/generation/password_generator_unittest.cc b/components/password_manager/core/browser/generation/password_generator_unittest.cc
index a1a28ad..c3e696d 100644
--- a/components/password_manager/core/browser/generation/password_generator_unittest.cc
+++ b/components/password_manager/core/browser/generation/password_generator_unittest.cc
@@ -9,9 +9,7 @@
 #include <string>
 
 #include "base/notreached.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/autofill/core/browser/proto/password_requirements.pb.h"
-#include "components/password_manager/core/browser/features/password_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace autofill {
@@ -308,58 +306,6 @@
   EXPECT_EQ(kDefaultPasswordLength, GeneratePassword(spec_).length());
 }
 
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
-class PasswordGeneratorChunkingTest : public testing::Test {
- public:
-  PasswordGeneratorChunkingTest() {
-    feature_list_.InitAndEnableFeature(
-        password_manager::features::kPasswordGenerationChunking);
-  }
-  ~PasswordGeneratorChunkingTest() override = default;
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-TEST_F(PasswordGeneratorChunkingTest, ChunkedWithDifferentMaxLengths) {
-  PasswordRequirementsSpec spec;
-  spec.set_min_length(0);
-  spec.mutable_symbols()->set_character_set("-");
-
-  for (uint32_t max_length = 9; max_length <= kDefaultPasswordLength;
-       max_length++) {
-    spec.set_max_length(max_length);
-    std::u16string password = GeneratePassword(spec);
-
-    // Check every 5th char is a dash except when it's a last char.
-    for (uint32_t i = 4; i < max_length; i += 5) {
-      if (i < max_length - 1) {
-        EXPECT_EQ(password[i], '-');
-      }
-    }
-    EXPECT_NE(password[max_length - 1], '-');
-  }
-}
-
-TEST_F(PasswordGeneratorChunkingTest, DashDisallowed) {
-  PasswordRequirementsSpec spec;
-  spec.set_min_length(0);
-  spec.set_max_length(kDefaultPasswordLength);
-  spec.mutable_symbols()->set_character_set("");
-
-  EXPECT_TRUE(GeneratePassword(spec).find('-') == std::string::npos);
-}
-
-TEST_F(PasswordGeneratorChunkingTest, TooShortMaxLength) {
-  PasswordRequirementsSpec spec;
-  spec.set_min_length(0);
-  spec.set_max_length(8);
-  spec.mutable_symbols()->set_character_set("-");
-
-  EXPECT_TRUE(GeneratePassword(spec).find('-') == std::string::npos);
-}
-#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-
 }  // namespace
 
 }  // namespace autofill
Loading diff…

Original Bug Report

reported by [email protected]

Potential weak password generation via spoofed Autofill network response

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: A compromised network service can spoof Autofill responses to inject a malicious PasswordRequirementsSpec. This allows an attacker to bypass minimum length checks and force the browser to generate highly predictable 4-character passwords. These weak passwords are then presented as ‘strong’ suggestions and persistently saved to the user’s password manager.

Affected files:

  • components/password_manager/core/browser/generation/password_generator.cc
  • components/password_manager/core/browser/password_requirements_service.cc
  • components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.cc
  • components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc

Estimated timestamp from git blame: 2024-09-13

Summary

The Chromium browser process ingests PasswordRequirementsSpec protocol buffers from Autofill crowdsourcing query responses. These specifications guide the password generation feature. However, the browser process stores and uses these specifications without sufficient validation of the character set entropy or constraints. A compromised network process can supply a malicious specification that severely restricts the entropy of generated passwords, reducing the search space to fewer than 24 permutations. The browser UI continues to suggest the resulting password as a ‘strong’ generated password, which is subsequently saved to the Password Manager.

Additionally, providing a max_length significantly larger than 200 causes a deterministic out-of-bounds insert crash in GenerateMaxEntropyChunkedPassword, leading to a denial of service of the browser process.

Vulnerability Details & Potential Steps to Reproduce

Note: The following are suggested potential steps, as our tooling agent has not executed a live proof of concept.

  1. Network Interception: An attacker with Remote Code Execution in the sandboxed Network Service intercepts an Autofill crowdsourcing query to content-autofill.googleapis.com.
  2. Spoofed Spec: The attacker constructs a spoofed AutofillQueryResponse containing a malicious PasswordRequirementsSpec with:
    • priority = 0xFFFFFFFF (UINT32_MAX)
    • max_length = 4
    • lower_case { character_set: "a" }
    • upper_case { character_set: "A" }
    • numeric { character_set: "1" }
  3. Cache Poisoning: The parsed spec is passed to PasswordRequirementsService::AddSpec in the browser process. Because priority is UINT32_MAX, it permanently wins the domain-keyed LRUCache against legitimate updates.
  4. Generation Overrides: When the user triggers password generation, autofill::GeneratePassword merges the malicious spec over secure defaults. The Protobuf MergeFrom overwrites the default character sets and length caps, but preserves the default min: 1 character requirements.
  5. Zero-Entropy Generation: GenerateMaxEntropyPassword clamps the target length to 4. To fulfill the min: 1 requirements for lower, upper, and numeric classes, it uses base::RandGenerator on the attacker’s length-1 strings, deterministically appending “a”, “A”, and “1”. The 4th character is randomly selected from the same constrained pool.
  6. Safety Bypass: The generated password has a size of exactly 4. Execution returns to GeneratePassword, where the safety check if (password.size() < 4) evaluates to false. This successfully bypasses the fallback to the secure default specification.
  7. Persistence: The highly predictable 4-character password is automatically filled, presented as a strong suggestion, and ultimately saved to the Password Manager upon form submission.

Suggested Fix

  • Input Validation: autofill::GeneratePassword or PasswordRequirementsService should validate that the provided PasswordRequirementsSpec meets a minimum entropy threshold before merging. Character sets provided by the network should not be trusted blindly.
  • Strict Length Enforcement: Update the safety check in GeneratePassword to enforce a stricter minimum length (e.g., password.size() < kDefaultPasswordLength / 2 or at least 8) instead of 4.
  • Chunking Bounds Check: In GenerateMaxEntropyChunkedPassword, ensure that the calculated number_of_dashes does not lead to out-of-bounds insertions when max_length exceeds the 200-character cap enforced by GenerateMaxEntropyPassword.

Evaluated with Chrome root at commit: 661452647ddb2827305122ff3273bd5dea403f09


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker