CVE-2026-11192
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forcomponents/password_manager/core/browser/generation/password_generator.cc |
modified | |
PasswordGeneratorChunkingTestcomponents/password_manager/core/browser/generation/password_generator_unittest.cc |
modified | |
PasswordGeneratorChunkingTestcomponents/password_manager/core/browser/generation/password_generator_unittest.cc |
modified | |
TEST_Fcomponents/password_manager/core/browser/generation/password_generator_unittest.cc |
modified | |
forcomponents/password_manager/core/browser/generation/password_generator_unittest.cc |
modified | |
ifcomponents/password_manager/core/browser/generation/password_generator_unittest.cc |
modified |
Files Changed
components/password_manager/core/browser/features/password_features.cccomponents/password_manager/core/browser/features/password_features.hcomponents/password_manager/core/browser/generation/password_generator.cccomponents/password_manager/core/browser/generation/password_generator_unittest.cc
Patch
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);
Regression Test / PoC
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
Original Bug Report
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.cccomponents/password_manager/core/browser/password_requirements_service.cccomponents/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.cccomponents/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.
- Network Interception: An attacker with Remote Code Execution in the sandboxed Network Service intercepts an Autofill crowdsourcing query to
content-autofill.googleapis.com. - Spoofed Spec: The attacker constructs a spoofed
AutofillQueryResponsecontaining a maliciousPasswordRequirementsSpecwith:priority = 0xFFFFFFFF(UINT32_MAX)max_length = 4lower_case { character_set: "a" }upper_case { character_set: "A" }numeric { character_set: "1" }
- Cache Poisoning: The parsed spec is passed to
PasswordRequirementsService::AddSpecin the browser process. BecausepriorityisUINT32_MAX, it permanently wins the domain-keyed LRUCache against legitimate updates. - Generation Overrides: When the user triggers password generation,
autofill::GeneratePasswordmerges the malicious spec over secure defaults. The ProtobufMergeFromoverwrites the default character sets and length caps, but preserves the defaultmin: 1character requirements. - Zero-Entropy Generation:
GenerateMaxEntropyPasswordclamps the target length to 4. To fulfill themin: 1requirements for lower, upper, and numeric classes, it usesbase::RandGeneratoron the attacker’s length-1 strings, deterministically appending “a”, “A”, and “1”. The 4th character is randomly selected from the same constrained pool. - Safety Bypass: The generated password has a size of exactly 4. Execution returns to
GeneratePassword, where the safety checkif (password.size() < 4)evaluates to false. This successfully bypasses the fallback to the secure default specification. - 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::GeneratePasswordorPasswordRequirementsServiceshould validate that the providedPasswordRequirementsSpecmeets 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
GeneratePasswordto enforce a stricter minimum length (e.g.,password.size() < kDefaultPasswordLength / 2or at least 8) instead of 4. - Chunking Bounds Check: In
GenerateMaxEntropyChunkedPassword, ensure that the calculatednumber_of_dashesdoes not lead to out-of-bounds insertions whenmax_lengthexceeds the 200-character cap enforced byGenerateMaxEntropyPassword.
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.