Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient data validation in Passwords
DescriptionInsufficient data validation in Passwords
ComponentPasswords
Bug ClassLogic Error
Tracker513313107
Fix commit4baa9ba51777 (chromium/src) +205/-98
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-16

Changed Functions

FunctionChangeNotes
AutofillSuggestionDelegate
components/password_manager/core/browser/password_autofill_manager_unittest.cc
modified

Files Changed

  • components/password_manager/core/browser/password_autofill_manager.cc
  • components/password_manager/core/browser/password_autofill_manager.h
  • components/password_manager/core/browser/password_autofill_manager_unittest.cc
From 4baa9ba5177784b29f09aad5f2a004d049e740f5 Mon Sep 17 00:00:00 2001
From: Christoph Schwering <schwering@google.com>
Date: Tue, 09 Jun 2026 04:10:24 -0700
Subject: [PATCH] [Passwords] Check UiSessionId before updating suggestions

Before this CL, pam2.UpdatePopup() could update the Autofill
suggestions created by pam1.ShowPopup().

This CL fixes that checking if the popup that is currently being
shown has previously been created by the same PAM's PAM::ShowPopup().

Bug: 513313107
Change-Id: I242d8a80f034b574ab082c1383d80e9a3e74d0e8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7911241
Reviewed-by: Jan Keitel <jkeitel@google.com>
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: Mohamed Amir Yosef <mamir@chromium.org>
Commit-Queue: Christoph Schwering <schwering@google.com>
Cr-Commit-Position: refs/heads/main@{#1643848}
---

diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 4c3b661..db47aad 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -742,8 +742,8 @@
         autofill::AutofillSuggestionTriggerSource::kPasswordManager,
         /*form_control_ax_id=*/0, autofill::PopupAnchorType::kField);
   }
-  autofill_client_->ShowAutofillSuggestions(last_popup_open_args_,
-                                            weak_ptr_factory_.GetWeakPtr());
+  last_session_id_ = autofill_client_->ShowAutofillSuggestions(
+      last_popup_open_args_, weak_ptr_factory_.GetWeakPtr());
   return true;
 }
 
@@ -751,6 +751,10 @@
   if (!password_manager_driver_->CanShowAutofillUi()) {
     return;
   }
+  if (last_session_id_ !=
+      autofill_client_->GetSessionIdForCurrentAutofillSuggestions()) {
+    return;
+  }
   if (!ContainsOtherThanManagePasswords(suggestions)) {
     autofill_client_->HideSuggestions(
         autofill::SuggestionHidingReason::kNoSuggestions,
diff --git a/components/password_manager/core/browser/password_autofill_manager.h b/components/password_manager/core/browser/password_autofill_manager.h
index 9843b64..283bcc6 100644
--- a/components/password_manager/core/browser/password_autofill_manager.h
+++ b/components/password_manager/core/browser/password_autofill_manager.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_AUTOFILL_MANAGER_H_
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <variant>
 #include <vector>
@@ -259,6 +260,11 @@
 
   const raw_ptr<PasswordManagerClient> password_client_;
 
+  // The ID of the last ShowPopup() call. UpdatePopup() is a no-op if the
+  // current session ID isn't the same as the `last_session_id_`.
+  std::optional<autofill::AutofillClient::SuggestionUiSessionId>
+      last_session_id_;
+
   // The arguments of the last ShowPopup() call and UpdatePopup(), to be re-used
   // by OnUnlockReauthCompleted().
   autofill::AutofillClient::PopupOpenArgs last_popup_open_args_;
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 8d0dfd92..08b7d896 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -37,6 +37,7 @@
 #include "components/autofill/core/browser/suggestions/suggestion_test_helpers.h"
 #include "components/autofill/core/browser/suggestions/suggestion_type.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
+#include "components/autofill/core/browser/ui/autofill_suggestion_delegate.h"
 #include "components/autofill/core/common/aliases.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
@@ -81,37 +82,34 @@
 #include "components/webauthn/android/webauthn_cred_man_delegate.h"
 #endif
 
-namespace autofill {
-class AutofillSuggestionDelegate;
-}
-
 namespace password_manager {
 
 namespace {
 
-using autofill::FillingProduct;
-using autofill::MockIdentityCredentialDelegate;
-using autofill::Suggestion;
-using autofill::SuggestionAdditionalLabelsContains;
-using autofill::SuggestionVectorIconsAre;
-using autofill::SuggestionVectorIdsAre;
-using autofill::SuggestionVectorMainTextsAre;
-using autofill::password_generation::PasswordGenerationType;
-using base::test::RunOnceCallback;
-using favicon_base::FaviconImageCallback;
-using gfx::test::AreImagesEqual;
-using testing::_;
-using testing::AllOf;
-using testing::DoAll;
-using testing::ElementsAre;
-using testing::Eq;
-using testing::Field;
-using testing::NiceMock;
-using testing::Return;
-using testing::ReturnRef;
-using testing::SaveArg;
-using testing::SizeIs;
-using testing::Unused;
+using ::autofill::AutofillClient;
+using ::autofill::FillingProduct;
+using ::autofill::MockIdentityCredentialDelegate;
+using ::autofill::Suggestion;
+using ::autofill::SuggestionAdditionalLabelsContains;
+using ::autofill::SuggestionVectorIconsAre;
+using ::autofill::SuggestionVectorIdsAre;
+using ::autofill::SuggestionVectorMainTextsAre;
+using ::autofill::password_generation::PasswordGenerationType;
+using ::base::test::RunOnceCallback;
+using ::favicon_base::FaviconImageCallback;
+using ::gfx::test::AreImagesEqual;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SaveArg;
+using ::testing::SizeIs;
+using ::testing::Unused;
 
 using ReauthSucceeded = PasswordManagerClient::ReauthSucceeded;
 using SuggestionPosition =
@@ -268,7 +266,7 @@
   MockAutofillClient() = default;
   MOCK_METHOD(AutofillClient::SuggestionUiSessionId,
               ShowAutofillSuggestions,
-              (const autofill::AutofillClient::PopupOpenArgs& open_args,
+              (const AutofillClient::PopupOpenArgs& open_args,
                base::WeakPtr<autofill::AutofillSuggestionDelegate> delegate),
               (override));
   MOCK_METHOD(void,
@@ -313,10 +311,21 @@
   std::vector<Suggestion> autofill_suggestions_;
 };
 
-// Copies the first parameter into `args` and returns a (empty) identifier.
-auto SavePopupOpenArgs(autofill::AutofillClient::PopupOpenArgs& args) {
-  return DoAll(SaveArg<0>(&args),
-               Return(autofill::AutofillClient::SuggestionUiSessionId()));
+// Copies the first parameter into `open_args` and calls
+// TestAutofillClient::ShowAutofillSuggestions().
+testing::Action<AutofillClient::SuggestionUiSessionId(
+    const AutofillClient::PopupOpenArgs&,
+    base::WeakPtr<autofill::AutofillSuggestionDelegate>)>
+SavePopupOpenArgsAndShowSuggestions(
+    autofill::TestAutofillClient& autofill_client,
+    AutofillClient::PopupOpenArgs& open_args) {
+  return [&](const AutofillClient::PopupOpenArgs& args,
+             base::WeakPtr<autofill::AutofillSuggestionDelegate> delegate)
+             -> AutofillClient::SuggestionUiSessionId {
+    open_args = args;
+    return autofill_client.TestAutofillClient::ShowAutofillSuggestions(
+        args, delegate);
+  };
 }
 
 base::CancelableTaskTracker::TaskId
@@ -494,9 +503,10 @@
   password_autofill_manager_->OnAddPasswordFillData(data);
 
   // Show the popup and verify the suggestions.
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
 
   password_autofill_manager_->ShowSuggestions(kTriggeringField);
   ASSERT_GE(open_args.suggestions.size(), 1u);
@@ -553,9 +563,10 @@
   password_autofill_manager_->OnAddPasswordFillData(data);
 
   // Show the popup and verify local and account-stored suggestion coexist.
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 8d0dfd92..08b7d896 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -37,6 +37,7 @@
 #include "components/autofill/core/browser/suggestions/suggestion_test_helpers.h"
 #include "components/autofill/core/browser/suggestions/suggestion_type.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
+#include "components/autofill/core/browser/ui/autofill_suggestion_delegate.h"
 #include "components/autofill/core/common/aliases.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
@@ -81,37 +82,34 @@
 #include "components/webauthn/android/webauthn_cred_man_delegate.h"
 #endif
 
-namespace autofill {
-class AutofillSuggestionDelegate;
-}
-
 namespace password_manager {
 
 namespace {
 
-using autofill::FillingProduct;
-using autofill::MockIdentityCredentialDelegate;
-using autofill::Suggestion;
-using autofill::SuggestionAdditionalLabelsContains;
-using autofill::SuggestionVectorIconsAre;
-using autofill::SuggestionVectorIdsAre;
-using autofill::SuggestionVectorMainTextsAre;
-using autofill::password_generation::PasswordGenerationType;
-using base::test::RunOnceCallback;
-using favicon_base::FaviconImageCallback;
-using gfx::test::AreImagesEqual;
-using testing::_;
-using testing::AllOf;
-using testing::DoAll;
-using testing::ElementsAre;
-using testing::Eq;
-using testing::Field;
-using testing::NiceMock;
-using testing::Return;
-using testing::ReturnRef;
-using testing::SaveArg;
-using testing::SizeIs;
-using testing::Unused;
+using ::autofill::AutofillClient;
+using ::autofill::FillingProduct;
+using ::autofill::MockIdentityCredentialDelegate;
+using ::autofill::Suggestion;
+using ::autofill::SuggestionAdditionalLabelsContains;
+using ::autofill::SuggestionVectorIconsAre;
+using ::autofill::SuggestionVectorIdsAre;
+using ::autofill::SuggestionVectorMainTextsAre;
+using ::autofill::password_generation::PasswordGenerationType;
+using ::base::test::RunOnceCallback;
+using ::favicon_base::FaviconImageCallback;
+using ::gfx::test::AreImagesEqual;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SaveArg;
+using ::testing::SizeIs;
+using ::testing::Unused;
 
 using ReauthSucceeded = PasswordManagerClient::ReauthSucceeded;
 using SuggestionPosition =
@@ -268,7 +266,7 @@
   MockAutofillClient() = default;
   MOCK_METHOD(AutofillClient::SuggestionUiSessionId,
               ShowAutofillSuggestions,
-              (const autofill::AutofillClient::PopupOpenArgs& open_args,
+              (const AutofillClient::PopupOpenArgs& open_args,
                base::WeakPtr<autofill::AutofillSuggestionDelegate> delegate),
               (override));
   MOCK_METHOD(void,
@@ -313,10 +311,21 @@
   std::vector<Suggestion> autofill_suggestions_;
 };
 
-// Copies the first parameter into `args` and returns a (empty) identifier.
-auto SavePopupOpenArgs(autofill::AutofillClient::PopupOpenArgs& args) {
-  return DoAll(SaveArg<0>(&args),
-               Return(autofill::AutofillClient::SuggestionUiSessionId()));
+// Copies the first parameter into `open_args` and calls
+// TestAutofillClient::ShowAutofillSuggestions().
+testing::Action<AutofillClient::SuggestionUiSessionId(
+    const AutofillClient::PopupOpenArgs&,
+    base::WeakPtr<autofill::AutofillSuggestionDelegate>)>
+SavePopupOpenArgsAndShowSuggestions(
+    autofill::TestAutofillClient& autofill_client,
+    AutofillClient::PopupOpenArgs& open_args) {
+  return [&](const AutofillClient::PopupOpenArgs& args,
+             base::WeakPtr<autofill::AutofillSuggestionDelegate> delegate)
+             -> AutofillClient::SuggestionUiSessionId {
+    open_args = args;
+    return autofill_client.TestAutofillClient::ShowAutofillSuggestions(
+        args, delegate);
+  };
 }
 
 base::CancelableTaskTracker::TaskId
@@ -494,9 +503,10 @@
   password_autofill_manager_->OnAddPasswordFillData(data);
 
   // Show the popup and verify the suggestions.
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
 
   password_autofill_manager_->ShowSuggestions(kTriggeringField);
   ASSERT_GE(open_args.suggestions.size(), 1u);
@@ -553,9 +563,10 @@
   password_autofill_manager_->OnAddPasswordFillData(data);
 
   // Show the popup and verify local and account-stored suggestion coexist.
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   password_autofill_manager_->ShowSuggestions(kTriggeringField);
   ASSERT_GE(open_args.suggestions.size(), 2u);
   EXPECT_THAT(open_args.suggestions,
@@ -620,9 +631,10 @@
   // First, simulate displaying suggestions matching an empty prefix. Also
   // verify that both the values and labels are filled correctly. The 'value'
   // should be the user name; the 'label' should be the realm.
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   autofill::TriggeringField field = kTriggeringField;
   password_autofill_manager_->ShowSuggestions(field);
   EXPECT_THAT(
@@ -642,7 +654,8 @@
 
   // Now simulate displaying suggestions matching "John".
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   field.typed_username = u"John";
   password_autofill_manager_->ShowSuggestions(field);
   EXPECT_THAT(open_args.suggestions,
@@ -655,7 +668,8 @@
 
   // Finally, simulate displaying all suggestions, without any prefix matching.
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   field.typed_username = u"";
   password_autofill_manager_->ShowSuggestions(field);
   EXPECT_THAT(
@@ -703,9 +717,10 @@
   // First, simulate displaying suggestions matching an empty prefix. Also
   // verify that both the values and labels are filled correctly. The 'value'
   // should be the user name; the 'label' should be the realm.
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   autofill::TriggeringField field = kTriggeringField;
   password_autofill_manager_->ShowSuggestions(field);
   EXPECT_THAT(
@@ -731,7 +746,8 @@
 
   // Now simulate displaying suggestions matching "John".
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   field.typed_username = u"John";
   password_autofill_manager_->ShowSuggestions(field);
   EXPECT_THAT(open_args.suggestions,
@@ -757,7 +773,8 @@
 
   // Finally, simulate displaying all suggestions, without any prefix matching.
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   field.typed_username = u"";
   password_autofill_manager_->ShowSuggestions(field);
   EXPECT_THAT(
@@ -791,9 +808,10 @@
 
   password_autofill_manager_->OnAddPasswordFillData(data);
 
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   password_autofill_manager_->ShowSuggestions(kTriggeringField);
   EXPECT_THAT(open_args.suggestions, SuggestionAdditionalLabelsContains(
                                          u"android://com.example2.android/"));
@@ -817,9 +835,10 @@
 
   password_autofill_manager_->OnAddPasswordFillData(data);
 
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   autofill::TriggeringField field = kTriggeringField;
   field.typed_username = test_username_;
   password_autofill_manager_->ShowSuggestions(field);
@@ -888,9 +907,10 @@
 
   password_autofill_manager_->OnAddPasswordFillData(data);
 
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
 
   password_autofill_manager_->ShowSuggestions(kTriggeringField);
   histograms.ExpectUniqueSample(kDropdownShownHistogram,
@@ -952,9 +972,10 @@
 
   password_autofill_manager_->OnAddPasswordFillData(data);
 
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   autofill::TriggeringField field = kTriggeringField;
   field.typed_username = test_username_;
   password_autofill_manager_->ShowSuggestions(field);
@@ -1021,9 +1042,10 @@
   // Bring up the drop-down with the generation option.
   std::u16string generation_string =
       l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_GENERATE_PASSWORD);
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   EXPECT_TRUE(
       password_autofill_manager_->MaybeShowPasswordSuggestionsWithGeneration(
           element_bounds, base::i18n::RIGHT_TO_LEFT,
@@ -1078,9 +1100,10 @@
   std::u16string generation_string =
       l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_GENERATE_PASSWORD);
 
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
 
   EXPECT_TRUE(
       password_autofill_manager_->MaybeShowPasswordSuggestionsWithGeneration(
@@ -1112,9 +1135,10 @@
 
   password_autofill_manager_->OnAddPasswordFillData(data);
 
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
+      .WillOnce(
+          SavePopupOpenArgsAndShowSuggestions(autofill_client, open_args));
   password_autofill_manager_->ShowSuggestions(kTriggeringField);
   ASSERT_THAT(open_args.suggestions.size(),
               testing::Ge(1u));  // No footer on Android.
@@ -1132,9 +1156,10 @@
   InitializePasswordAutofillManager(&client, &autofill_client);
 
   // Show the popup
-  autofill::AutofillClient::PopupOpenArgs open_args;
+  AutofillClient::PopupOpenArgs open_args;
   EXPECT_CALL(autofill_client, ShowAutofillSuggestions)
-      .WillOnce(SavePopupOpenArgs(open_args));
... (truncated)
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential cross-origin credential disclosure via shared Autofill popup reuse

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 https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logic flaw in the Autofill popup management potentially allows an attacker-controlled frame to capture credentials from a cross-origin frame. By inducing a popup update while a malicious frame is the active delegate, sensitive cleartext data from recovery suggestions can be routed to the attacker’s renderer.

Affected files:

  • components/password_manager/core/browser/password_autofill_manager.cc
  • chrome/browser/ui/autofill/chrome_autofill_client.cc
  • components/password_manager/core/browser/password_suggestion_generator.cc
  • components/password_manager/content/browser/content_password_manager_driver.cc
  • chrome/browser/ui/autofill/autofill_popup_controller_impl.cc

Estimated timestamp from git blame: 2025-07-03

Description

A potential logic vulnerability in ChromeAutofillClient and PasswordAutofillManager could allow for cross-origin credential disclosure. While PasswordAutofillManager (PAM) is instantiated per-frame, the UI controller for the Autofill popup is shared per-tab and managed by ChromeAutofillClient.

The issue resides in how existing popups are updated. When a frame’s PasswordAutofillManager::OnAddPasswordFillData is triggered (e.g., during an iframe load), it checks if an autofill popup is already visible in the tab. If so, it updates the popup’s suggestions via UpdatePopup, which calls ChromeAutofillClient::UpdateAutofillSuggestions.

In chrome/browser/ui/autofill/chrome_autofill_client.cc, UpdateAutofillSuggestions reuses the existing AutofillSuggestionController and its previously bound delegate without verifying that the caller matches the original owner of the popup. Consequently, if an attacker frame opens a password popup and a victim frame (e.g., in a cross-origin iframe) triggers an update, the victim’s suggestions are displayed while the attacker’s PAM remains the delegate.

Security Impact

Certain password suggestions used in the recovery flow (kBackupPasswordEntry and kTroubleSigningInEntry) carry cleartext credentials in their Suggestion::payload (specifically within PasswordSuggestionDetails).

When a user interacts with these injected victim suggestions (e.g., by selecting or accepting them), the callbacks are routed to the attacker’s PasswordAutofillManager. The attacker’s PAM extracts the cleartext credentials from the payload and forwards them to its own renderer process via PreviewSuggestion or FillSuggestion:

// components/password_manager/core/browser/password_autofill_manager.cc
if (suggestion.type == autofill::SuggestionType::kBackupPasswordEntry) {
  const auto payload = suggestion.GetPayload<autofill::Suggestion::PasswordSuggestionDetails>();
  password_manager_driver_->PreviewSuggestion(payload.username, payload.backup_password.value());
  return;
}

This behavior bypasses Site-Isolation by allowing one renderer process to receive credentials intended for another.

Potential Reproduction Steps

  1. A user has saved credentials for victim.com that include a backup password (e.g., from a recent automated password change flow).
  2. A malicious renderer for attacker.com opens a password autofill popup on a field it controls. The PAM_Attacker is now the active delegate.
  3. The attacker frame loads victim.com in a cross-origin iframe within the same tab.
  4. As victim.com loads, its PAM_Victim receives fill data, sees the existing popup, and calls UpdateAutofillSuggestions with the victim’s credentials.
  5. The ChromeAutofillClient updates the popup but leaves PAM_Attacker as the delegate.
  6. The user selects a recovery-related suggestion in the popup (still anchored to the attacker’s field).
  7. PAM_Attacker::DidSelectSuggestion is invoked, which extracts the victim’s backup password from the suggestion payload and sends it to the attacker.com renderer via Mojo.

Suggested Fix

ChromeAutofillClient::UpdateAutofillSuggestions should verify that the delegate requesting the update matches the delegate_ currently bound to the suggestion_controller_. If they do not match, the existing popup should be hidden or the update rejected.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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