Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Autofill
DescriptionInappropriate implementation in Autofill
ComponentAutofill
Bug ClassLogic Error
Tracker513237800
Fix commit0115ebdbf25d (chromium/src) +117/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
components/android_autofill/browser/android_autofill_provider.cc
modified

Files Changed

  • components/android_autofill/browser/android_autofill_provider.cc
  • components/android_autofill/browser/android_autofill_provider_unittest.cc
From 0115ebdbf25d6c907cdbbfa7b7d9206b502c56d8 Mon Sep 17 00:00:00 2001
From: Jihad Hanna <jihadghanna@google.com>
Date: Wed, 03 Jun 2026 02:15:47 -0700
Subject: [PATCH] Fix state mismatch between Autofill Session and Credential Manager

The three bug reports linked below were about stale/incorrect state
management for AndroidAutofillProvider::session_state_.

This CL adds state handling to avoid the exploitations described in the
reports. It also adds regression tests for the three described
scenarios.

Fixed: 513237800, 518084475, 518115316
Change-Id: I9a1143804764a9473e2fc4f3e84ea44387d76bbc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7889818
Reviewed-by: Dominic Battré <battre@chromium.org>
Commit-Queue: Jihad Hanna <jihadghanna@google.com>
Cr-Commit-Position: refs/heads/main@{#1640793}
---

diff --git a/components/android_autofill/browser/android_autofill_provider.cc b/components/android_autofill/browser/android_autofill_provider.cc
index 58af3927..ad881906 100644
--- a/components/android_autofill/browser/android_autofill_provider.cc
+++ b/components/android_autofill/browser/android_autofill_provider.cc
@@ -200,7 +200,13 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (credman_sheet_status_ == CredManBottomSheetLifecycle::kIsShowing) {
-    return;  // CredMan prevents 3P autofill UI. Start the session on refocus!
+    // While CredMan is active, the user cannot legitimately interact with the
+    // page. We ignore this request to prevent a compromised renderer from
+    // spoofing the session origin (overwriting `current_field`) in the
+    // background. We preserve the session state that triggered CredMan so that
+    // the subsequent fill goes to the correct frame. If the user dismisses
+    // CredMan, a new session will be started on the next focus event.
+    return;
   }
 
   // We need to create session state here outside of StartNewSession because
@@ -498,6 +504,13 @@
     AndroidAutofillManager* manager,
     const FormData& form,
     const FormFieldData& field) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (credman_sheet_status_ == CredManBottomSheetLifecycle::kIsShowing) {
+    // Ignore selection changes while CredMan is showing to prevent a
+    // compromised renderer from spoofing the session origin (see
+    // crbug.com/518115316).
+    return;
+  }
   if (base::FeatureList::IsEnabled(
           features::kAndroidAutofillFieldsUpdatedOnSelect)) {
     UpdateCurrentField(manager, form, field);
@@ -566,6 +579,11 @@
           GetRenderFrameHost(manager, field.host_frame());
       ShouldShowCredManForField(field, rfh) &&
       ShowCredManSheet(rfh, form.global_id(), field_to_focus)) {
+    // Proactively update the current field and its origin. Because the
+    // subsequent `OnAskForValuesToFill()` IPC will be ignored while CredMan is
+    // showing (to block spoofing), we must set the correct origin now before
+    // the block takes effect, otherwise the session will retain a stale origin.
+    UpdateCurrentField(manager, form, field);
     return;  // The focus event will be completed after CredMan closes.
   }
   if (field_to_focus) {
diff --git a/components/android_autofill/browser/android_autofill_provider_unittest.cc b/components/android_autofill/browser/android_autofill_provider_unittest.cc
index 79a3d526..c692e59 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -837,12 +837,15 @@
     autofill_provider().MaybeInitKeyboardSuppressor();
 
     // Navigation creates the AndroidAutofillManager for the main frame.
-    NavigateAndCommit(GURL("about:blank"));
+    NavigateAndCommit(GURL("https://foo.com"));
     FocusWebContentsOnMainFrame();
 
     // Load a form with webuthn-annotated username and regular password fields.
     test_webauthn_form_ = CreateFormDataForFrame(
         CreateTestWebAuthnPasswordFormData(), main_frame_token());
+    url::Origin foo_origin = url::Origin::Create(GURL("https://foo.com"));
+    test_api(test_webauthn_form_).field(0).set_origin(foo_origin);
+    test_api(test_webauthn_form_).field(1).set_origin(foo_origin);
 
     InitializeWebAuthnFactoryWithMock();
   }
@@ -1074,6 +1077,9 @@
     sub_frame_webauthn_form_ = CreateFormDataForFrame(
         CreateTestWebAuthnPasswordFormData(),
         LocalFrameToken(sub_frame_->GetFrameToken().value()));
+    url::Origin bar_origin = url::Origin::Create(GURL("https://bar.com"));
+    test_api(sub_frame_webauthn_form_).field(0).set_origin(bar_origin);
+    test_api(sub_frame_webauthn_form_).field(1).set_origin(bar_origin);
     android_autofill_manager().OnFormsSeen({sub_frame_webauthn_form_},
                                            /*removed_forms=*/{});
 
@@ -1133,6 +1139,97 @@
   FocusSubFrameFormField(sub_frame_webauthn_email_field());
 }
 
+// Tests that when CredMan is triggered, the current field's origin is updated
+// proactively to the frame origin of the passkey field, ensuring we don't use
+// a stale origin if CredMan is dismissed and autofill completes.
+// (see crbug.com/518084475).
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+       CredManEarlyReturnLeavesStaleCurrentFieldOrigin_Fixed) {
+  // 1. Start session on main frame (origin https://foo.com).
+  android_autofill_manager().OnFormsSeen({test_form()}, {});
+  // Focus main frame field to start session and set origin to foo.com.
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      test_form(), non_webauthn_password_field());
+  android_autofill_manager().SimulateOnFocusOnFormField(
+      test_form(), non_webauthn_password_field());
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+
+  // 2. Focus subframe field (origin https://bar.com) which triggers CredMan.
+  // Expect CredMan to be triggered on subframe.
+  EXPECT_CALL(*sub_frame_mock_delegate_, TriggerCredManUi);
+
+  // Simulate Focus FIRST (which should update origin to bar.com).
+  android_autofill_manager().SimulateOnFocusOnFormField(
+      sub_frame_test_form(), sub_frame_webauthn_email_field());
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://bar.com")));
+
+  // Simulate AskForValuesToFill() SECOND (which returns early because CredMan
+  // is showing) and verify origin is STILL bar.com (not reverted or stale
+  // foo.com).
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      sub_frame_test_form(), sub_frame_webauthn_email_field());
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://bar.com")));
+}
+
+// Tests that a compromised renderer cannot spoof the session origin by sending
+// a malicious AskForValuesToFill IPC while a CredMan sheet is active.
+// (see crbug.com/513237800).
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+       CredManActiveBlocksSpoofedAskForValuesToFill) {
+  // 1. Start session on main frame (origin https://foo.com) and trigger
+  // CredMan.
+  android_autofill_manager().OnFormsSeen({test_form()}, {});
+  EXPECT_CALL(cred_man_delegate(), TriggerCredManUi);
+  android_autofill_manager().SimulateOnFocusOnFormField(test_form(),
+                                                        webauthn_email_field());
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      test_form(), webauthn_email_field());
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+
+  // 2. Spoof AskForValuesToFill() from attacker.com: Attacker sends fake
+  // AskForValuesToFill() while CredMan is showing. Verify that origin remains
+  // foo.com and that the spoof is blocked.
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      sub_frame_test_form(), sub_frame_webauthn_email_field());
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+}
+
+// Tests that a compromised renderer cannot spoof the session origin by sending
+// a malicious SelectControlSelectionChanged() IPC while a CredMan sheet is
+// active (see crbug.com/518115316).
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+       CredManActiveBlocksSpoofedSelectControlSelectionChanged) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  // 1. Start session on main frame (origin https://foo.com) and trigger
+  // CredMan.
+  android_autofill_manager().OnFormsSeen({test_form()}, {});
+  EXPECT_CALL(cred_man_delegate(), TriggerCredManUi);
+  android_autofill_manager().SimulateOnFocusOnFormField(test_form(),
+                                                        webauthn_email_field());
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      test_form(), webauthn_email_field());
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+
+  // 2. Spoof SelectControlSelectionChanged() from attacker.com: Attacker sends
+  // fake SelectControlSelectionChanged() while CredMan is showing. Verify that
+  // origin remains foo.com and that the spoof is blocked.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), sub_frame_test_form(),
+      sub_frame_webauthn_email_field());
+
+  // Verify origin remains foo.com (spoof blocked!).
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+}
+
 using AndroidAutofillProviderPrefillRequestTest = AndroidAutofillProviderTest;
 
 // Tests that we can send another prefill request after navigation.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/android_autofill/browser/android_autofill_provider_unittest.cc b/components/android_autofill/browser/android_autofill_provider_unittest.cc
index 79a3d526..c692e59 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -837,12 +837,15 @@
     autofill_provider().MaybeInitKeyboardSuppressor();
 
     // Navigation creates the AndroidAutofillManager for the main frame.
-    NavigateAndCommit(GURL("about:blank"));
+    NavigateAndCommit(GURL("https://foo.com"));
     FocusWebContentsOnMainFrame();
 
     // Load a form with webuthn-annotated username and regular password fields.
     test_webauthn_form_ = CreateFormDataForFrame(
         CreateTestWebAuthnPasswordFormData(), main_frame_token());
+    url::Origin foo_origin = url::Origin::Create(GURL("https://foo.com"));
+    test_api(test_webauthn_form_).field(0).set_origin(foo_origin);
+    test_api(test_webauthn_form_).field(1).set_origin(foo_origin);
 
     InitializeWebAuthnFactoryWithMock();
   }
@@ -1074,6 +1077,9 @@
     sub_frame_webauthn_form_ = CreateFormDataForFrame(
         CreateTestWebAuthnPasswordFormData(),
         LocalFrameToken(sub_frame_->GetFrameToken().value()));
+    url::Origin bar_origin = url::Origin::Create(GURL("https://bar.com"));
+    test_api(sub_frame_webauthn_form_).field(0).set_origin(bar_origin);
+    test_api(sub_frame_webauthn_form_).field(1).set_origin(bar_origin);
     android_autofill_manager().OnFormsSeen({sub_frame_webauthn_form_},
                                            /*removed_forms=*/{});
 
@@ -1133,6 +1139,97 @@
   FocusSubFrameFormField(sub_frame_webauthn_email_field());
 }
 
+// Tests that when CredMan is triggered, the current field's origin is updated
+// proactively to the frame origin of the passkey field, ensuring we don't use
+// a stale origin if CredMan is dismissed and autofill completes.
+// (see crbug.com/518084475).
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+       CredManEarlyReturnLeavesStaleCurrentFieldOrigin_Fixed) {
+  // 1. Start session on main frame (origin https://foo.com).
+  android_autofill_manager().OnFormsSeen({test_form()}, {});
+  // Focus main frame field to start session and set origin to foo.com.
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      test_form(), non_webauthn_password_field());
+  android_autofill_manager().SimulateOnFocusOnFormField(
+      test_form(), non_webauthn_password_field());
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+
+  // 2. Focus subframe field (origin https://bar.com) which triggers CredMan.
+  // Expect CredMan to be triggered on subframe.
+  EXPECT_CALL(*sub_frame_mock_delegate_, TriggerCredManUi);
+
+  // Simulate Focus FIRST (which should update origin to bar.com).
+  android_autofill_manager().SimulateOnFocusOnFormField(
+      sub_frame_test_form(), sub_frame_webauthn_email_field());
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://bar.com")));
+
+  // Simulate AskForValuesToFill() SECOND (which returns early because CredMan
+  // is showing) and verify origin is STILL bar.com (not reverted or stale
+  // foo.com).
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      sub_frame_test_form(), sub_frame_webauthn_email_field());
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://bar.com")));
+}
+
+// Tests that a compromised renderer cannot spoof the session origin by sending
+// a malicious AskForValuesToFill IPC while a CredMan sheet is active.
+// (see crbug.com/513237800).
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+       CredManActiveBlocksSpoofedAskForValuesToFill) {
+  // 1. Start session on main frame (origin https://foo.com) and trigger
+  // CredMan.
+  android_autofill_manager().OnFormsSeen({test_form()}, {});
+  EXPECT_CALL(cred_man_delegate(), TriggerCredManUi);
+  android_autofill_manager().SimulateOnFocusOnFormField(test_form(),
+                                                        webauthn_email_field());
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      test_form(), webauthn_email_field());
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+
+  // 2. Spoof AskForValuesToFill() from attacker.com: Attacker sends fake
+  // AskForValuesToFill() while CredMan is showing. Verify that origin remains
+  // foo.com and that the spoof is blocked.
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      sub_frame_test_form(), sub_frame_webauthn_email_field());
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+}
+
+// Tests that a compromised renderer cannot spoof the session origin by sending
+// a malicious SelectControlSelectionChanged() IPC while a CredMan sheet is
+// active (see crbug.com/518115316).
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+       CredManActiveBlocksSpoofedSelectControlSelectionChanged) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  // 1. Start session on main frame (origin https://foo.com) and trigger
+  // CredMan.
+  android_autofill_manager().OnFormsSeen({test_form()}, {});
+  EXPECT_CALL(cred_man_delegate(), TriggerCredManUi);
+  android_autofill_manager().SimulateOnFocusOnFormField(test_form(),
+                                                        webauthn_email_field());
+  android_autofill_manager().SimulateOnAskForValuesToFill(
+      test_form(), webauthn_email_field());
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+
+  // 2. Spoof SelectControlSelectionChanged() from attacker.com: Attacker sends
+  // fake SelectControlSelectionChanged() while CredMan is showing. Verify that
+  // origin remains foo.com and that the spoof is blocked.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), sub_frame_test_form(),
+      sub_frame_webauthn_email_field());
+
+  // Verify origin remains foo.com (spoof blocked!).
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            url::Origin::Create(GURL("https://foo.com")));
+}
+
 using AndroidAutofillProviderPrefillRequestTest = AndroidAutofillProviderTest;
 
 // Tests that we can send another prefill request after navigation.
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential logic error in AndroidAutofillProvider desyncs session state allowing cross-origin bypass

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 potential logic error in AndroidAutofillProvider::OnAskForValuesToFill allows a compromised renderer to spoof the triggered origin of an active Autofill session. This occurs because the internal session origin is updated before checking the status of the Credential Manager UI, leading to state desynchronization. This could potentially allow an attacker to bypass cross-origin Autofill security policies and leak sensitive user data.

Affected files:

  • components/android_autofill/browser/android_autofill_provider.cc
  • components/android_autofill/browser/android_autofill_manager.cc
  • components/autofill/content/browser/content_autofill_driver.cc
  • components/autofill/core/browser/foundations/form_forest.cc

Estimated timestamp from git blame: 2024-09-18

Root Cause Analysis

The potential vulnerability is located in AndroidAutofillProvider::OnAskForValuesToFill within components/android_autofill/browser/android_autofill_provider.cc. The function updates the session_state_->current_field.origin before performing an early-return check for the Credential Manager (CredMan) UI status.

// components/android_autofill/browser/android_autofill_provider.cc
void AndroidAutofillProvider::OnAskForValuesToFill(...) {
  // ...
  if (!session_state_) {
    session_state_.emplace();
  }
  // ...
  UpdateCurrentField(manager, form, field); // Updates origin unconditionally

  if (credman_sheet_status_ == CredManBottomSheetLifecycle::kIsShowing) {
    return; // Early return if CredMan UI is showing
  }

  if (!IsLinkedForm(form)) {
    StartNewSession(manager, form, field); // Resets manager and form data
  }
}

When UpdateCurrentField is called, session_state_->current_field.origin is overwritten with the origin provided in the renderer’s IPC. If credman_sheet_status_ is kIsShowing, the function returns before StartNewSession (or Reset) can be called. This results in a state where session_state_->current_field.origin reflects a new origin, while session_state_->manager and session_state_->form still point to the previous, legitimate session.

Potential Exploit Scenario

An attacker with control over a renderer (e.g., via a compromised subframe or a malicious site) could potentially follow these steps:

  1. A user interacts with a legitimate form (e.g., a login form in a bank’s iframe) that triggers a CredMan passkey selection UI. AndroidAutofillProvider initializes a session for the bank’s origin.
  2. While the CredMan UI is visible, a compromised renderer sends a malicious AskForValuesToFill IPC for a field under the attacker’s control.
  3. AndroidAutofillProvider::OnAskForValuesToFill updates the session’s origin to the attacker’s origin but returns early due to the active CredMan UI, leaving the bank’s manager and form active in the session state.
  4. The user dismisses CredMan and selects a standard password autofill suggestion for the bank’s form from the keyboard accessory.
  5. The browser executes the fill operation using the desynchronized state, passing the attacker’s origin as the triggered_origin to FormForest::IsSafeToFill.
  6. Security checks in FormForest::IsSafeToFill (Clause 2 or 4) are bypassed because the spoofed triggered_origin matches the attacker’s fields in the form structure, causing sensitive data to be leaked into the attacker’s fields.

Suggested Fix

To prevent this desynchronization, the update to the session’s field state should be performed after the CredMan status check, or the entire state should be properly reset/updated before the early return. Specifically, consider moving the call to UpdateCurrentField after the check for credman_sheet_status_ == kIsShowing.

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