Low chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Autofill
DescriptionIncorrect authorization in Autofill
ComponentAutofill
Sub-componentAndroid
Bug ClassAuthorization Error
Tracker552440317
Fix commit7daefd85906a (chromium/src) +239/-7
CISA KEVNot listed
Creditedmute1008
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
modified
AndroidAutofillProviderWithCredManTest
components/android_autofill/browser/android_autofill_provider_unittest.cc
modified

Files Changed

  • components/android_autofill/browser/android_autofill_features.cc
  • components/android_autofill/browser/android_autofill_provider.cc
  • components/android_autofill/browser/android_autofill_provider_unittest.cc
  • components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
  • components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
From 7daefd85906a1692605903713f4fa09b208c9750 Mon Sep 17 00:00:00 2001
From: Friedrich Horschig <friedrichh@chromium.org>
Date: Tue, 08 Sep 2026 06:43:09 -0700
Subject: [PATCH] [Android][Autofill] Verify origin for select updates

Avoid updating the active autofill session's current field or shifting
focus when an unfocused or cross-origin select field changes its value.

Key changes:
- AndroidAutofillProvider::OnSelectControlSelectionChanged: only call UpdateCurrentField if the form is unlinked or the field matches the active session's current field.
- AutofillProvider.onFormFieldDidChange, prevent transferring virtual focus to an unfocused field on value changes.

Screencast Before:
http://go/scrcast/NDYxNTIzNTk4NDk0OTI0OHw1YWMzODVlOC1lNw

Screencast w/ Fix:
http://go/scrcast/NjY3MDc3NDA4MDM3MjczNnw5YjQwNmU0OS1jOA

Feature: AndroidAutofillFieldsUpdatedOnSelect
Fixed: 552440317
Change-Id: Ia3425ce3044e885c8d6750bd8b67b4188552b8a9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8354306
Commit-Queue: Friedrich Hauser <friedrichh@chromium.org>
Reviewed-by: Adem Derinel <derinel@google.com>
Cr-Commit-Position: refs/heads/main@{#1693747}
---
diff --git a/components/android_autofill/browser/android_autofill_features.cc b/components/android_autofill/browser/android_autofill_features.cc
index 15de0ee..881a0a4 100644
--- a/components/android_autofill/browser/android_autofill_features.cc
+++ b/components/android_autofill/browser/android_autofill_features.cc
@@ -42,7 +42,7 @@
              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.
+// TODO(crbug.com/502346855): Remove in M157 or later.
 BASE_FEATURE(kAndroidAutofillFieldsUpdatedOnSelect,
              base::FEATURE_ENABLED_BY_DEFAULT);
 
diff --git a/components/android_autofill/browser/android_autofill_provider.cc b/components/android_autofill/browser/android_autofill_provider.cc
index 7088548..c9b4868 100644
--- a/components/android_autofill/browser/android_autofill_provider.cc
+++ b/components/android_autofill/browser/android_autofill_provider.cc
@@ -531,7 +531,11 @@
   }
   if (base::FeatureList::IsEnabled(
           features::kAndroidAutofillFieldsUpdatedOnSelect)) {
-    UpdateCurrentField(manager, form, field);
+    if (!IsLinkedForm(form) ||
+        (session_state_ &&
+         session_state_->current_field.id == field.global_id())) {
+      UpdateCurrentField(manager, form, field);
+    }
   }
   if (!IsLinkedForm(form)) {
     StartNewSession(manager, form, field);
diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
index 1fbf25b..7352588 100644
--- a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
+++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
@@ -398,7 +398,11 @@
         short shortIndex = (short) index;
         FocusField focusField = mRequest.getFocusField();
         if (focusField == null || shortIndex != focusField.fieldIndex) {
-            onFocusChangedImpl(true, index, x, y, width, height, /* causedByValueChange= */ true);
+            if (!AndroidAutofillFeatures.ANDROID_AUTOFILL_FIELDS_UPDATED_ON_SELECT.isEnabled()
+                    || focusField == null) {
+                onFocusChangedImpl(
+                        true, index, x, y, width, height, /* causedByValueChange= */ true);
+            }
         } else {
             // Currently there is no api to notify both value and position
             // change, before the API is available, we still need to call
diff --git a/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java b/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
index 16ae698a..489c6990 100644
--- a/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
+++ b/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
@@ -635,6 +635,133 @@
         verify(mNativeMock, never()).hasPasskeyRequest(anyLong());
     }
 
+    @Test
+    @Features.EnableFeatures({
+        AndroidAutofillFeatures.ANDROID_AUTOFILL_FIELDS_UPDATED_ON_SELECT_NAME
+    })
+    public void testOnFormFieldDidChange_unfocusedFieldChanged_featureEnabled() {
+        int sessionId = 123;
+        FormFieldDataBuilder field1Builder = new FormFieldDataBuilder();
+        FormFieldDataBuilder field2Builder = new FormFieldDataBuilder();
+        FormData formData =
+                new FormData(
+                        sessionId,
+                        /* name= */ null,
+                        /* host= */ null,
+                        Arrays.asList(field1Builder.build(), field2Builder.build()));
+
+        int field0VirtualId = FormData.toFieldVirtualId(sessionId, (short) 0);
+        int field1VirtualId = FormData.toFieldVirtualId(sessionId, (short) 1);
+
+        // Start session focused on field 0.
+        mAutofillProvider.startAutofillSession(
+                formData,
+                /* focus= */ 0,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* width= */ 50,
+                /* height= */ 20,
+                /* hasServerPrediction= */ false);
+
+        assertEquals(field0VirtualId, mFocusVirtualId);
+
+        // Mutate unfocused field 1.
+        mAutofillProvider.onFormFieldDidChange(
+                /* index= */ 1, /* x= */ 0, /* y= */ 0, /* width= */ 50, /* height= */ 20);
+
+        // Focus should remain on field 0 and not shift to field 1.
+        assertEquals(field0VirtualId, mFocusVirtualId);
+        verify(mAutofillManager, never()).notifyViewExited(eq(mContainerView), eq(field0VirtualId));
+        verify(mAutofillManager, never())
+                .notifyViewEntered(eq(mContainerView), eq(field1VirtualId), any());
+    }
+
+    @Test
+    @Features.DisableFeatures({
+        AndroidAutofillFeatures.ANDROID_AUTOFILL_FIELDS_UPDATED_ON_SELECT_NAME
+    })
+    public void testOnFormFieldDidChange_unfocusedFieldChanged_featureDisabled() {
+        int sessionId = 123;
+        FormFieldDataBuilder field1Builder = new FormFieldDataBuilder();
+        FormFieldDataBuilder field2Builder = new FormFieldDataBuilder();
+        FormData formData =
+                new FormData(
+                        sessionId,
+                        /* name= */ null,
+                        /* host= */ null,
+                        Arrays.asList(field1Builder.build(), field2Builder.build()));
+
+        int field0VirtualId = FormData.toFieldVirtualId(sessionId, (short) 0);
+        int field1VirtualId = FormData.toFieldVirtualId(sessionId, (short) 1);
+
+        // Start session focused on field 0.
+        mAutofillProvider.startAutofillSession(
+                formData,
+                /* focus= */ 0,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* width= */ 50,
+                /* height= */ 20,
+                /* hasServerPrediction= */ false);
+
+        assertEquals(field0VirtualId, mFocusVirtualId);
+
+        // Mutate unfocused field 1.
+        mAutofillProvider.onFormFieldDidChange(
+                /* index= */ 1, /* x= */ 0, /* y= */ 0, /* width= */ 50, /* height= */ 20);
+
+        // With the feature disabled (killswitch), focus shifts to field 1.
+        assertEquals(field1VirtualId, mFocusVirtualId);
+        verify(mAutofillManager).notifyViewExited(eq(mContainerView), eq(field0VirtualId));
+        verify(mAutofillManager).notifyViewEntered(eq(mContainerView), eq(field1VirtualId), any());
+    }
+
+    @Test
+    @Features.EnableFeatures({
+        AndroidAutofillFeatures.ANDROID_AUTOFILL_FIELDS_UPDATED_ON_SELECT_NAME
+    })
+    public void testOnFormFieldDidChange_noFocusedField_featureEnabled() {
+        int sessionId = 123;
+        FormFieldDataBuilder field1Builder = new FormFieldDataBuilder();
+        FormFieldDataBuilder field2Builder = new FormFieldDataBuilder();
+        FormData formData =
+                new FormData(
+                        sessionId,
+                        /* name= */ null,
+                        /* host= */ null,
+                        Arrays.asList(field1Builder.build(), field2Builder.build()));
+
+        int field0VirtualId = FormData.toFieldVirtualId(sessionId, (short) 0);
+
+        // Start session focused on field 0.
+        mAutofillProvider.startAutofillSession(
+                formData,
+                /* focus= */ 0,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* width= */ 50,
+                /* height= */ 20,
+                /* hasServerPrediction= */ false);
+
+        // Clear focus.
+        mAutofillProvider.onFocusChanged(
+                /* focusOnForm= */ false,
+                /* focusField= */ 0,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* width= */ 0,
+                /* height= */ 0);
+
+        // Value changes on field 0 when no field is currently focused.
+        mAutofillProvider.onFormFieldDidChange(
+                /* index= */ 0, /* x= */ 0, /* y= */ 0, /* width= */ 50, /* height= */ 20);
+
+        // Focus should be gained on field 0.
+        assertEquals(field0VirtualId, mFocusVirtualId);
+        verify(mAutofillManager, Mockito.atLeastOnce())
+                .notifyViewEntered(eq(mContainerView), eq(field0VirtualId), any());
+    }
+
     FormData setupPrefillRequest(int sessionId) {
         FormFieldDataBuilder field1Builder = new FormFieldDataBuilder();
         field1Builder.mBounds =
diff --git a/components/android_autofill/browser/android_autofill_provider_unittest.cc b/components/android_autofill/browser/android_autofill_provider_unittest.cc
index ccf6806..26e8b02 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -802,19 +802,42 @@
   android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
                                          AutofillManagerTestApi::pass_key());
 
-  // Start an Autofill session.
+  FormFieldData field = form.fields()[0];
+  url::Origin select_origin = url::Origin::Create(GURL("https://bar.com"));
+  field.set_origin(select_origin);
+
+  // Simulate OnSelectControlSelectionChanged on an unlinked form.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, field);
+
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            select_origin);
+}
+
+// Tests that OnSelectControlSelectionChanged updates the origin when the
+// selection change occurs on the currently focused field.
+TEST_F(AndroidAutofillProviderTest,
+       OnSelectControlSelectionChangedUpdatesOriginForFocusedField) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
+                                         AutofillManagerTestApi::pass_key());
+
+  // Start an Autofill session on field 0.
   android_autofill_manager().OnAskForValuesToFillTest(
       form, form.fields()[0].global_id());
 
   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];
+  // Update selection on the same focused field.
+  FormFieldData field = form.fields()[0];
   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);
 
@@ -822,6 +845,80 @@
             new_origin);
 }
 
+// Tests that an unfocused select control's value change in a multi-origin form
+// does not overwrite the active session's focused field origin (b/552440317).
+TEST_F(AndroidAutofillProviderTest,
+       UnfocusedSelectDoesNotOverwriteFocusedFieldOrigin) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  url::Origin iframe_origin =
+      url::Origin::Create(GURL("https://iframe-payment.example"));
+  url::Origin parent_origin =
+      url::Origin::Create(GURL("https://parent-attacker.example"));
+
+  test_api(form).field(0).set_origin(iframe_origin);
+  test_api(form).field(1).set_origin(parent_origin);
+
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
+                                         AutofillManagerTestApi::pass_key());
+
+  // 1. User taps and focuses field 0 inside the iframe.
+  android_autofill_manager().OnAskForValuesToFillTest(
+      form, form.fields()[0].global_id());
+
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+
+  // 2. Parent page mutates unfocused select field 1.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, form.fields()[1]);
+
+  // The active session origin must remain iframe_origin.
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+}
+
+// Tests that when kAndroidAutofillFieldsUpdatedOnSelect is disabled
+// (killswitch), an unfocused select control does NOT overwrite the active
+// session origin.
+TEST_F(AndroidAutofillProviderTest,
+       UnfocusedSelectWithFeatureDisabledPreservesFocusedFieldOrigin) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      features::kAndroidAutofillFieldsUpdatedOnSelect);
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  url::Origin iframe_origin =
+      url::Origin::Create(GURL("https://iframe-payment.example"));
+  url::Origin parent_origin =
+      url::Origin::Create(GURL("https://parent-attacker.example"));
+
+  test_api(form).field(0).set_origin(iframe_origin);
+  test_api(form).field(1).set_origin(parent_origin);
+
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
+                                         AutofillManagerTestApi::pass_key());
+
+  // 1. User taps and focuses field 0 inside the iframe.
+  android_autofill_manager().OnAskForValuesToFillTest(
+      form, form.fields()[0].global_id());
+
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+
+  // 2. Parent page mutates unfocused select field 1.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, form.fields()[1]);
+
+  // With the feature disabled, the active session origin is preserved.
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+}
+
 class AndroidAutofillProviderWithCredManTest
     : public AndroidAutofillProviderTestBase {
  public:
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 ccf6806..26e8b02 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -802,19 +802,42 @@
   android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
                                          AutofillManagerTestApi::pass_key());
 
-  // Start an Autofill session.
+  FormFieldData field = form.fields()[0];
+  url::Origin select_origin = url::Origin::Create(GURL("https://bar.com"));
+  field.set_origin(select_origin);
+
+  // Simulate OnSelectControlSelectionChanged on an unlinked form.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, field);
+
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            select_origin);
+}
+
+// Tests that OnSelectControlSelectionChanged updates the origin when the
+// selection change occurs on the currently focused field.
+TEST_F(AndroidAutofillProviderTest,
+       OnSelectControlSelectionChangedUpdatesOriginForFocusedField) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
+                                         AutofillManagerTestApi::pass_key());
+
+  // Start an Autofill session on field 0.
   android_autofill_manager().OnAskForValuesToFillTest(
       form, form.fields()[0].global_id());
 
   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];
+  // Update selection on the same focused field.
+  FormFieldData field = form.fields()[0];
   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);
 
@@ -822,6 +845,80 @@
             new_origin);
 }
 
+// Tests that an unfocused select control's value change in a multi-origin form
+// does not overwrite the active session's focused field origin (b/552440317).
+TEST_F(AndroidAutofillProviderTest,
+       UnfocusedSelectDoesNotOverwriteFocusedFieldOrigin) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAndroidAutofillFieldsUpdatedOnSelect};
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  url::Origin iframe_origin =
+      url::Origin::Create(GURL("https://iframe-payment.example"));
+  url::Origin parent_origin =
+      url::Origin::Create(GURL("https://parent-attacker.example"));
+
+  test_api(form).field(0).set_origin(iframe_origin);
+  test_api(form).field(1).set_origin(parent_origin);
+
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
+                                         AutofillManagerTestApi::pass_key());
+
+  // 1. User taps and focuses field 0 inside the iframe.
+  android_autofill_manager().OnAskForValuesToFillTest(
+      form, form.fields()[0].global_id());
+
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+
+  // 2. Parent page mutates unfocused select field 1.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, form.fields()[1]);
+
+  // The active session origin must remain iframe_origin.
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+}
+
+// Tests that when kAndroidAutofillFieldsUpdatedOnSelect is disabled
+// (killswitch), an unfocused select control does NOT overwrite the active
+// session origin.
+TEST_F(AndroidAutofillProviderTest,
+       UnfocusedSelectWithFeatureDisabledPreservesFocusedFieldOrigin) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      features::kAndroidAutofillFieldsUpdatedOnSelect);
+
+  FormData form = CreateFormDataForFrame(
+      CreateTestPersonalInformationFormData(), main_frame_token());
+  url::Origin iframe_origin =
+      url::Origin::Create(GURL("https://iframe-payment.example"));
+  url::Origin parent_origin =
+      url::Origin::Create(GURL("https://parent-attacker.example"));
+
+  test_api(form).field(0).set_origin(iframe_origin);
+  test_api(form).field(1).set_origin(parent_origin);
+
+  android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{},
+                                         AutofillManagerTestApi::pass_key());
+
+  // 1. User taps and focuses field 0 inside the iframe.
+  android_autofill_manager().OnAskForValuesToFillTest(
+      form, form.fields()[0].global_id());
+
+  ASSERT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+
+  // 2. Parent page mutates unfocused select field 1.
+  autofill_provider().OnSelectControlSelectionChanged(
+      &android_autofill_manager(), form, form.fields()[1]);
+
+  // With the feature disabled, the active session origin is preserved.
+  EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(),
+            iframe_origin);
+}
+
 class AndroidAutofillProviderWithCredManTest
     : public AndroidAutofillProviderTestBase {
  public:
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.