Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Android Autofill
DescriptionPolicy bypass in Android Autofill
ComponentAndroid Autofill
Bug ClassLogic Error
Tracker502346855
Fix commit59a1f194784c (chromium/src) +76/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

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

Files Changed

  • android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
  • components/android_autofill/browser/android_autofill_features.cc
  • components/android_autofill/browser/android_autofill_features.h
  • components/android_autofill/browser/android_autofill_provider.cc
  • components/android_autofill/browser/android_autofill_provider.h
  • components/android_autofill/browser/android_autofill_provider_test_api.h
  • components/android_autofill/browser/android_autofill_provider_unittest.cc
From 59a1f194784cc5441e8b5502ef04177aa4f1b45c Mon Sep 17 00:00:00 2001
From: Friedrich Horschig <friedrichh@chromium.org>
Date: Tue, 14 Apr 2026 08:31:21 -0700
Subject: [PATCH] Fix stale origin in AndroidAutofillProvider

AndroidAutofillProvider failed to update the current field's origin when
starting a new session via a select control change. This left a stale
origin in the session state, which could be exploited by a malicious
subframe to bypass cross-origin fill policies in FormForest.

This CL fixes the issue by: 1. Extracting a helper function
UpdateCurrentField to update the current field's origin and type group.
2. Calling this helper in OnSelectControlSelectionChanged when the
feature flag AndroidAutofillFieldsUpdatedOnSelect is enabled.

Fixed: 502346855
Change-Id: Ic349f56202bfafbf9c33dcc19ad8a687d69fe9db
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7761883
Reviewed-by: Jihad Hanna <jihadghanna@google.com>
Commit-Queue: Friedrich Hauser <friedrichh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1614481}
---

diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 93a31ed..6add1bd0 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -241,6 +241,9 @@
                 "Enable improved visibility detection form fields sent to the Android "
                         + "Autofill framework."),
         Flag.baseFeature(
+                AndroidAutofillFeatures.ANDROID_AUTOFILL_FIELDS_UPDATED_ON_SELECT_NAME,
+                "Enable updating autofill field origin on select control change."),
+        Flag.baseFeature(
                 AutofillFeatures.AUTOFILL_ACCEPT_DOM_MUTATION_AFTER_AUTOFILL_SUBMISSION,
                 "Accepts DOM_MUTATION_AFTER_AUTOFILL submissions detected on password forms."),
         Flag.baseFeature(
diff --git a/components/android_autofill/browser/android_autofill_features.cc b/components/android_autofill/browser/android_autofill_features.cc
index 15bda908..3a046aa 100644
--- a/components/android_autofill/browser/android_autofill_features.cc
+++ b/components/android_autofill/browser/android_autofill_features.cc
@@ -20,7 +20,8 @@
     &kAndroidAutofillLazyFrameworkWrapper,
     &kAutofillVirtualViewStructureAndroidPasskeyLongPress,
     &kAndroidAutofillForwardIframeOrigin,
-    &kAndroidAutofillImprovedVisibilityDetection};
+    &kAndroidAutofillImprovedVisibilityDetection,
+    &kAndroidAutofillFieldsUpdatedOnSelect};
 
 }  // namespace
 
@@ -58,6 +59,11 @@
 BASE_FEATURE(kAndroidAutofillSupportForHttpAuth,
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// If enabled, fields are updated whenever a user interacts with a <select>.
+// TODO(crbug.com/502346855): Remove in M152 or later.
+BASE_FEATURE(kAndroidAutofillFieldsUpdatedOnSelect,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 static int64_t JNI_AndroidAutofillFeatures_GetFeature(JNIEnv* env,
                                                       int32_t ordinal) {
   return reinterpret_cast<int64_t>(
diff --git a/components/android_autofill/browser/android_autofill_features.h b/components/android_autofill/browser/android_autofill_features.h
index f40e165..f391b8e 100644
--- a/components/android_autofill/browser/android_autofill_features.h
+++ b/components/android_autofill/browser/android_autofill_features.h
@@ -21,6 +21,8 @@
 
 BASE_DECLARE_FEATURE(kAndroidAutofillSupportForHttpAuth);
 
+BASE_DECLARE_FEATURE(kAndroidAutofillFieldsUpdatedOnSelect);
+
 }  // namespace autofill::features
 
 #endif  // COMPONENTS_ANDROID_AUTOFILL_BROWSER_ANDROID_AUTOFILL_FEATURES_H_
diff --git a/components/android_autofill/browser/android_autofill_provider.cc b/components/android_autofill/browser/android_autofill_provider.cc
index 51643447..c884b37 100644
--- a/components/android_autofill/browser/android_autofill_provider.cc
+++ b/components/android_autofill/browser/android_autofill_provider.cc
@@ -208,10 +208,7 @@
         }
       });
 
-  session_state_->current_field = {field.global_id(),
-                                   manager->ComputeFieldTypeGroupForField(
-                                       form.global_id(), field.global_id()),
-                                   field.origin()};
+  UpdateCurrentField(manager, form, field);
 
   if (credman_sheet_status_ == CredManBottomSheetLifecycle::kIsShowing) {
     return;  // CredMan prevents 3P autofill UI. Start the session on refocus!
@@ -354,6 +351,21 @@
       manager->has_server_prediction(form.global_id()));
 }
 
+void AndroidAutofillProvider::UpdateCurrentField(
+    AndroidAutofillManager* manager,
+    const FormData& form,
+    const FormFieldData& field) {
+  if (!session_state_) {
+    session_state_.emplace();
+  }
+  session_state_->current_field = {
+      field.global_id(),
+      manager ? manager->ComputeFieldTypeGroupForField(form.global_id(),
+                                                       field.global_id())
+              : FieldTypeGroup::kNoGroup,
+      field.origin()};
+}
+
 void AndroidAutofillProvider::OnAutofillAvailable() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -470,6 +482,10 @@
     AndroidAutofillManager* manager,
     const FormData& form,
     const FormFieldData& field) {
+  if (base::FeatureList::IsEnabled(
+          features::kAndroidAutofillFieldsUpdatedOnSelect)) {
+    UpdateCurrentField(manager, form, field);
+  }
   if (!IsLinkedForm(form)) {
     StartNewSession(manager, form, field);
     // TODO(crbug.com/40929724): Return early at this point?
diff --git a/components/android_autofill/browser/android_autofill_provider.h b/components/android_autofill/browser/android_autofill_provider.h
index a443866..bde265d 100644
--- a/components/android_autofill/browser/android_autofill_provider.h
+++ b/components/android_autofill/browser/android_autofill_provider.h
@@ -183,6 +183,10 @@
                        const FormData& form,
                        const FormFieldData& field);
 
+  void UpdateCurrentField(AndroidAutofillManager* manager,
+                          const FormData& form,
+                          const FormFieldData& field);
+
   void Reset();
 
   // Cancels the current Autofill session, resetting cached session data.
diff --git a/components/android_autofill/browser/android_autofill_provider_test_api.h b/components/android_autofill/browser/android_autofill_provider_test_api.h
index d909122..afd3507 100644
--- a/components/android_autofill/browser/android_autofill_provider_test_api.h
+++ b/components/android_autofill/browser/android_autofill_provider_test_api.h
@@ -23,6 +23,11 @@
                ? provider_->session_state_->current_field.id
                : FieldGlobalId{};
   }
+  const url::Origin last_focused_field_origin() && {
+    return provider_->session_state_
+               ? provider_->session_state_->current_field.origin
+               : url::Origin{};
+  }
 
   TouchToFillKeyboardSuppressor& keyboard_suppressor() {
     return *provider_->keyboard_suppressor_;
diff --git a/components/android_autofill/browser/android_autofill_provider_unittest.cc b/components/android_autofill/browser/android_autofill_provider_unittest.cc
index ef293892..f0d0ea5e 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -796,6 +796,37 @@
   Reset(autofill_driver());
 }
 
+// Tests that OnSelectControlSelectionChanged updates the current field's origin
+// when the feature flag is enabled.
+TEST_F(AndroidAutofillProviderTest,
+       OnSelectControlSelectionChangedUpdatesOrigin) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
+
+  // Start an Autofill session.
+  android_autofill_manager().SimulateOnAskForValuesToFill(form,
+                                                          form.fields()[0]);
+
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            form.fields()[0].origin());
+
+  // Create a new field with a different origin.
+  FormFieldData field = form.fields()[1];
+  url::Origin new_origin = url::Origin::Create(GURL("https://bar.com"));
+  field.set_origin(new_origin);
+
+  // Simulate OnSelectControlSelectionChanged.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, field);
+
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            new_origin);
+}
+
 class AndroidAutofillProviderWithCredManTest
     : public AndroidAutofillProviderTestBase {
  public:
diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AndroidAutofillFeatures.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AndroidAutofillFeatures.java
index 9b9d3124..6c8f410 100644
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 ef293892..f0d0ea5e 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -796,6 +796,37 @@
   Reset(autofill_driver());
 }
 
+// Tests that OnSelectControlSelectionChanged updates the current field's origin
+// when the feature flag is enabled.
+TEST_F(AndroidAutofillProviderTest,
+       OnSelectControlSelectionChangedUpdatesOrigin) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
+
+  // Start an Autofill session.
+  android_autofill_manager().SimulateOnAskForValuesToFill(form,
+                                                          form.fields()[0]);
+
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            form.fields()[0].origin());
+
+  // Create a new field with a different origin.
+  FormFieldData field = form.fields()[1];
+  url::Origin new_origin = url::Origin::Create(GURL("https://bar.com"));
+  field.set_origin(new_origin);
+
+  // Simulate OnSelectControlSelectionChanged.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, field);
+
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            new_origin);
+}
+
 class AndroidAutofillProviderWithCredManTest
     : public AndroidAutofillProviderTestBase {
  public:
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential cross-origin fill policy bypass via stale triggered_origin in AndroidAutofillProvider

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.

Overview: AndroidAutofillProvider fails to update the current field’s origin when starting a new session via a select control change. This leaves a stale origin in the session state, which can be exploited by a malicious subframe to bypass cross-origin fill policies in FormForest.

Affected files:

  • components/android_autofill/browser/android_autofill_provider.cc
  • components/android_autofill/browser/android_autofill_provider.h

Estimated timestamp from git blame: 2025-01-08

Overview

In AndroidAutofillProvider, the session_state_ tracks the current_field being interacted with, including its origin. This origin is later used as the triggered_origin when data is returned by the Android Autofill service to be filled into the form.

A vulnerability exists because session_state_->current_field is updated in OnAskForValuesToFill but is not updated when a new session is started via OnSelectControlSelectionChanged. If a user interacts with a field in the main frame, and a malicious cross-origin subframe subsequently triggers a new session via a <select> control change, the current_field.origin remains set to the stale main frame origin. This stale origin is passed down to FormForest::IsSafeToFill, satisfying its security checks and allowing the malicious subframe to trigger autofill into a privileged sibling frame.

Potential Exploitation Steps

  1. A user navigates to a top-level page (O_main).
  2. The page contains two forms: Form A (entirely in the main frame) and Form B (a cross-frame form). Form B wraps a malicious iframe (O_attacker) and a privileged sibling iframe (O_sibling) that has the allow="autofill" permission policy.
  3. The user interacts with a field in Form A. The browser calls AndroidAutofillProvider::OnAskForValuesToFill (components/android_autofill/browser/android_autofill_provider.cc:211), which sets session_state_->current_field.origin to O_main and calls StartNewSession(manager, Form A, field).
  4. The user leaves Form A without completing the autofill. The session state remains active.
  5. The attacker’s iframe (O_attacker) uses a script (e.g., following transient user activation) to change the selection of a <select> element within its portion of Form B.
  6. The browser receives the SelectControlSelectionChanged IPC, and AndroidAutofillProvider::OnSelectControlSelectionChanged is called for Form B.
  7. IsLinkedForm(Form B) returns false because the current session is linked to Form A. Thus, StartNewSession(manager, Form B, field) is called (components/android_autofill/browser/android_autofill_provider.cc:481).
  8. Vulnerability: StartNewSession updates session_state_->form to Form B but completely fails to update session_state_->current_field. The current_field.origin remains stale at O_main.
  9. The Android Autofill service presents a suggestion UI for the select element. The user taps the suggestion.
  10. AndroidAutofillProvider::OnAutofillAvailable (components/android_autofill/browser/android_autofill_provider.cc:367) is invoked and calls FillOrPreviewForm using the stale session_state_->current_field.origin (O_main) as the triggered_origin.
  11. The fill request reaches FormForest::GetRendererFormsOfBrowserFields. The IsSafeToFill lambda (components/autofill/core/browser/foundations/form_forest.cc:603) evaluates the security policy for the fields in O_sibling.
  12. Because the triggered_origin (O_main) matches the main frame origin, and the target frame has allow="autofill", the check erroneously passes. The browser fills sensitive data into O_sibling, bypassing the cross-origin fill policy which should have used the true interaction origin (O_attacker) and blocked the fill.

Suggested Fix

[Removed speculative fix]

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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