Adds a unit test to components_unittests (Android-only target) that demonstrates the desync deterministically: while CredMan is showing for the victim form F1, an AskForValuesToFill for an unrelated attacker-origin form F2 overwrites session_state_->current_field.origin without touching session_state_->form, so a subsequent OnAutofillAvailable would fill F1 with triggered_origin = attacker.test. Build & run on an Android checkout: autoninja -C out/Android components_unittests out/Android/bin/run_components_unittests \ --gtest_filter=AndroidAutofillProviderWithCredManTest.CredManEarlyReturnDesyncsOriginFromSessionForm --- a/components/android_autofill/browser/android_autofill_provider_unittest.cc +++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc @@ -1059,6 +1059,57 @@ TEST_F(AndroidAutofillProviderWithCredManTest, NotifyFocusOnCredManError) { completed_callback.Run(/*success=*/false); } +// Regression POC: while the CredMan bottom sheet is showing for the linked +// form F1, an AskForValuesToFill for an unrelated form F2 (different origin) +// hits the early-return at OnAskForValuesToFill() AFTER UpdateCurrentField() +// has overwritten current_field.origin. session_state_->form is still F1, so +// OnAutofillAvailable() will call FillOrPreviewForm(F1, ..., F2.origin) and +// IsSafeToFill evaluates with the attacker-chosen triggered_origin. +TEST_F(AndroidAutofillProviderWithCredManTest, + CredManEarlyReturnDesyncsOriginFromSessionForm) { + base::RepeatingCallback completed_callback; + EXPECT_CALL(cred_man_delegate(), SetRequestCompletionCallback) + .WillOnce(SaveArg<0>(&completed_callback)); + + // Step 2+3: user taps the webauthn field in F1 -> StartNewSession(F1) and + // ShowCredManSheet -> credman_sheet_status_ = kIsShowing. + FocusFormField(webauthn_email_field()); + ASSERT_TRUE(test_api(autofill_provider()).form()); + EXPECT_EQ(test_api(autofill_provider()).form()->form().global_id(), + test_form().global_id()); + const url::Origin victim_origin = + test_api(autofill_provider()).last_focused_field_origin(); + + // Step 4: compromised renderer in another origin sends AskForValuesToFill + // for an unrelated form F2 while CredMan is showing. + const url::Origin attacker_origin = + url::Origin::Create(GURL("https://attacker.test")); + FormData f2 = CreateFormDataForFrame(CreateTestPersonalInformationFormData(), + main_frame_token()); + test_api(f2).field(0).set_origin(attacker_origin); + android_autofill_manager().OnFormsSeen({f2}, /*removed_forms=*/{}); + // No new framework session must start (early-return path). + EXPECT_CALL(provider_bridge(), StartAutofillSession).Times(0); + android_autofill_manager().SimulateOnAskForValuesToFill(f2, f2.fields()[0]); + Mock::VerifyAndClearExpectations(&provider_bridge()); + + // BUG: current_field.origin was overwritten with the attacker origin... + EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(), + attacker_origin); + // ...but session_state_->form is still the victim's F1. + ASSERT_TRUE(test_api(autofill_provider()).form()); + EXPECT_EQ(test_api(autofill_provider()).form()->form().global_id(), + test_form().global_id()); + EXPECT_NE(test_api(autofill_provider()).last_focused_field_origin(), + victim_origin); + + // Step 5: OnCredManUiClosed does not repair current_field. + completed_callback.Run(/*success=*/true); + EXPECT_EQ(test_api(autofill_provider()).last_focused_field_origin(), + attacker_origin); + // OnAutofillAvailable() would now call FillOrPreviewForm(R_mgr, F1, ..., + // triggered_origin = attacker_origin). +} + class AndroidAutofillProviderPrefillRequestTest : public AndroidAutofillProviderTestBase,