CVE-2026-11291
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/android_autofill/browser/android_autofill_provider.cc |
modified | |
AndroidAutofillProviderWithCredManTestcomponents/android_autofill/browser/android_autofill_provider_unittest.cc |
modified |
Files Changed
android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.javacomponents/android_autofill/browser/android_autofill_features.cccomponents/android_autofill/browser/android_autofill_features.hcomponents/android_autofill/browser/android_autofill_provider.cccomponents/android_autofill/browser/android_autofill_provider.hcomponents/android_autofill/browser/android_autofill_provider_test_api.hcomponents/android_autofill/browser/android_autofill_provider_unittest.cc
Patch
From 59a1f194784cc5441e8b5502ef04177aa4f1b45c Mon Sep 17 00:00:00 2001 From: Friedrich Horschig <[email protected]> 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 <[email protected]> Commit-Queue: Friedrich Hauser <[email protected]> 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
Regression Test / PoC
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:
Original Bug Report
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.cccomponents/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
- A user navigates to a top-level page (
O_main). - 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 theallow="autofill"permission policy. - The user interacts with a field in Form A. The browser calls
AndroidAutofillProvider::OnAskForValuesToFill(components/android_autofill/browser/android_autofill_provider.cc:211), which setssession_state_->current_field.origintoO_mainand callsStartNewSession(manager, Form A, field). - The user leaves Form A without completing the autofill. The session state remains active.
- 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. - The browser receives the
SelectControlSelectionChangedIPC, andAndroidAutofillProvider::OnSelectControlSelectionChangedis called for Form B. 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).- Vulnerability:
StartNewSessionupdatessession_state_->formto Form B but completely fails to updatesession_state_->current_field. Thecurrent_field.originremains stale atO_main. - The Android Autofill service presents a suggestion UI for the select element. The user taps the suggestion.
AndroidAutofillProvider::OnAutofillAvailable(components/android_autofill/browser/android_autofill_provider.cc:367) is invoked and callsFillOrPreviewFormusing the stalesession_state_->current_field.origin(O_main) as thetriggered_origin.- The fill request reaches
FormForest::GetRendererFormsOfBrowserFields. TheIsSafeToFilllambda (components/autofill/core/browser/foundations/form_forest.cc:603) evaluates the security policy for the fields inO_sibling. - Because the
triggered_origin(O_main) matches the main frame origin, and the target frame hasallow="autofill", the check erroneously passes. The browser fills sensitive data intoO_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.