Chrome · Autofill
CVE-2026-14134
Logic Error in Autofill
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/android_autofill/browser/android_autofill_provider.cc |
modified |
Files Changed
components/android_autofill/browser/android_autofill_provider.cccomponents/android_autofill/browser/android_autofill_provider.h
Patch
From b84e41ea4c7e98cb7f5826f63eab9c45d07c294f Mon Sep 17 00:00:00 2001 From: Jihad Hanna <[email protected]> Date: Wed, 20 May 2026 10:16:31 -0700 Subject: [PATCH] Route Passkey events to the CredManDelegate of the correct frame Fixed: 514055973 Change-Id: I19cb9a1cef2964997a7d5d5868491891bd5128f0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7864240 Reviewed-by: Christoph Schwering <[email protected]> Commit-Queue: Jihad Hanna <[email protected]> Reviewed-by: Friedrich Hauser <[email protected]> Cr-Commit-Position: refs/heads/main@{#1633670} --- diff --git a/components/android_autofill/browser/android_autofill_provider.cc b/components/android_autofill/browser/android_autofill_provider.cc index 4af27bc..5acc410f 100644 --- a/components/android_autofill/browser/android_autofill_provider.cc +++ b/components/android_autofill/browser/android_autofill_provider.cc @@ -36,6 +36,7 @@ #include "components/webauthn/android/webauthn_cred_man_delegate_factory.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "ui/android/window_android.h" #include "ui/gfx/geometry/rect_f.h" @@ -98,16 +99,25 @@ .render_frame_host(); } +content::RenderFrameHost* GetRenderFrameHost( + AutofillManager* manager, + const LocalFrameToken& frame_token) { + content::RenderFrameHost* result = nullptr; + GetRenderFrameHost(manager)->ForEachRenderFrameHost( + [&result, &frame_token](content::RenderFrameHost* rfh) { + if (LocalFrameToken(rfh->GetFrameToken().value()) == frame_token) { + result = rfh; + } + }); + return result; +} + WebAuthnCredManDelegate* GetCredManDelegate(content::RenderFrameHost* rfh) { return WebAuthnCredManDelegateFactory::GetFactory( content::WebContents::FromRenderFrameHost(rfh)) ->GetRequestDelegate(rfh); } -WebAuthnCredManDelegate* GetCredManDelegate(AutofillManager* manager) { - return GetCredManDelegate(GetRenderFrameHost(manager)); -} - bool AllowCredManOnField(const FormFieldData& field) { return field.parsed_autocomplete() && field.parsed_autocomplete()->webauthn; } @@ -204,13 +214,10 @@ session_state_.emplace(); } - GetRenderFrameHost(manager)->ForEachRenderFrameHost( - [this, &field](content::RenderFrameHost* rfh) { - LocalFrameToken frame_token(rfh->GetFrameToken().value()); - if (frame_token == field.host_frame()) { - session_state_->last_queried_field_rfh_id = rfh->GetGlobalId(); - } - }); + if (content::RenderFrameHost* rfh = + GetRenderFrameHost(manager, field.host_frame())) { + session_state_->last_queried_field_rfh_id = rfh->GetGlobalId(); + } UpdateCurrentField(manager, form, field); @@ -426,22 +433,34 @@ } bool AndroidAutofillProvider::HasPasskeyRequest() { - if (!session_state_ || !session_state_->manager || !session_state_->form || - !GetCredManDelegate(GetRenderFrameHost(session_state_->manager.get()))) { + if (!session_state_ || !session_state_->manager || !session_state_->form) { return false; } + const FormFieldData* field = session_state_->form->form().FindFieldByGlobalId( session_state_->current_field.id); - return field && AllowCredManOnField(*field); + if (!field || !AllowCredManOnField(*field)) { + return false; + } + + content::RenderFrameHost* rfh = + GetRenderFrameHost(session_state_->manager.get(), field->host_frame()); + return rfh && GetCredManDelegate(rfh); } void AndroidAutofillProvider::OnTriggerPasskeyRequest() { - if (session_state_ && session_state_->manager) { - if (content::RenderFrameHost* rfh = - GetRenderFrameHost(session_state_->manager.get())) { - if (WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh)) { - delegate->TriggerCredManUi(RequestPasswords(false)); - } + if (!session_state_ || !session_state_->manager || !session_state_->form) { + return; + } + const FormFieldData* field = session_state_->form->form().FindFieldByGlobalId( + session_state_->current_field.id); + if (!field) { + return; + } + if (content::RenderFrameHost* rfh = GetRenderFrameHost( + session_state_->manager.get(), field->host_frame())) { + if (WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh)) { + delegate->TriggerCredManUi(RequestPasswords(false)); } } } @@ -546,9 +565,10 @@ const FormFieldData& field) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::optional<FieldInfo> field_to_focus = StartFocusChange(form, field); - if (ShouldShowCredManForField(field, GetRenderFrameHost(manager)) && - ShowCredManSheet(GetRenderFrameHost(manager), form.global_id(), - field_to_focus)) { + if (content::RenderFrameHost* rfh = + GetRenderFrameHost(manager, field.host_frame()); + ShouldShowCredManForField(field, rfh) && + ShowCredManSheet(rfh, form.global_id(), field_to_focus)) { return; // The focus event will be completed after CredMan closes. } if (field_to_focus) { @@ -671,7 +691,9 @@ const FormFieldData* found_field = form_data.FindFieldByGlobalId(field); const bool intends_to_show_credman = found_field && - IntendsToShowCredMan(*found_field, GetRenderFrameHost(&manager)); + IntendsToShowCredMan( + *found_field, + GetRenderFrameHost(&manager, found_field->host_frame())); return intends_to_show_credman || (ArePrefillRequestsSupported() && !has_used_cached_form_ && cached_data_ && cached_data_->cached_form && @@ -737,6 +759,7 @@ CHECK_EQ(credman_sheet_status_, CredManBottomSheetLifecycle::kNotShown); if (WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh)) { credman_sheet_status_ = CredManBottomSheetLifecycle::kIsShowing; + credman_sheet_rfh_id_ = rfh->GetGlobalId(); delegate->SetRequestCompletionCallback(base::BindRepeating( &AndroidAutofillProvider::OnCredManUiClosed, weak_ptr_factory_.GetWeakPtr(), std::move(form_id), @@ -779,11 +802,14 @@ } void AndroidAutofillProvider::Reset() { - if (session_state_ && session_state_->manager) { - if (WebAuthnCredManDelegate* delegate = - GetCredManDelegate(session_state_->manager.get())) { - delegate->SetRequestCompletionCallback(base::DoNothing()); + if (credman_sheet_rfh_id_) { + if (content::RenderFrameHost* rfh = + content::RenderFrameHost::FromID(credman_sheet_rfh_id_)) { + if (WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh)) { + delegate->SetRequestCompletionCallback(base::DoNothing()); + } } + credman_sheet_rfh_id_ = {}; } // Clear all session-specific state. @@ -922,6 +948,7 @@ WebAuthnCredManDelegate::State has_passkeys, bool success) { credman_sheet_status_ = CredManBottomSheetLifecycle::kClosed; + credman_sheet_rfh_id_ = {}; if (keyboard_suppressor_) { keyboard_suppressor_->Unsuppress(); } diff --git a/components/android_autofill/browser/android_autofill_provider.h b/components/android_autofill/browser/android_autofill_provider.h index bde265d..e7cfabe 100644 --- a/components/android_autofill/browser/android_autofill_provider.h +++ b/components/android_autofill/browser/android_autofill_provider.h @@ -14,6 +14,7 @@ #include "components/autofill/core/browser/foundations/autofill_manager.h" #include "components/autofill/core/common/unique_ids.h" #include "components/webauthn/android/webauthn_cred_man_delegate.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/web_contents_observer.h" namespace content { @@ -337,6 +338,10 @@ CredManBottomSheetLifecycle credman_sheet_status_ = CredManBottomSheetLifecycle::kNotShown; + // The RenderFrameHost ID of the frame that is currently showing the CredMan + // sheet. Used to reset the delegate callback on the correct frame. + content::GlobalRenderFrameHostId credman_sheet_rfh_id_; + // This is used by the keyboard suppressor. We update it with the result of // the platform method call `showAutofillDialog`. Since we are not notified
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 b6dea4e..79a3d526 100644
--- a/components/android_autofill/browser/android_autofill_provider_unittest.cc
+++ b/components/android_autofill/browser/android_autofill_provider_unittest.cc
@@ -1060,6 +1060,79 @@
completed_callback.Run(/*success=*/false);
}
+class AndroidAutofillProviderWithCredManMultiFrameTest
+ : public AndroidAutofillProviderWithCredManTest {
+ public:
+ void SetUp() override {
+ AndroidAutofillProviderWithCredManTest::SetUp();
+
+ // Create a subframe, a form in the subframe and register it in the root
+ // manager.
+ sub_frame_ = content::RenderFrameHostTester::For(main_frame())
+ ->AppendChild(std::string("child"));
+ sub_frame_ = NavigateAndCommitFrame(sub_frame_, GURL("https://bar.com"));
+ sub_frame_webauthn_form_ = CreateFormDataForFrame(
+ CreateTestWebAuthnPasswordFormData(),
+ LocalFrameToken(sub_frame_->GetFrameToken().value()));
+ android_autofill_manager().OnFormsSeen({sub_frame_webauthn_form_},
+ /*removed_forms=*/{});
+
+ // Create a mock delegate for the subframe and mock passkeys to be
+ // default-available.
+ auto sub_frame_mock_delegate =
+ std::make_unique<NiceMock<webauthn::MockWebAuthnCredManDelegate>>();
+ sub_frame_mock_delegate_ = sub_frame_mock_delegate.get();
+ webauthn::test_api(web_authn_delegate_factory())
+ .EmplaceDelegateForFrame(sub_frame_,
+ std::move(sub_frame_mock_delegate));
+ ON_CALL(*sub_frame_mock_delegate_, HasPasskeys())
+ .WillByDefault(
+ Return(webauthn::WebAuthnCredManDelegate::State::kHasPasskeys));
+ }
+
+ void TearDown() override {
+ sub_frame_mock_delegate_ = nullptr;
+ sub_frame_ = nullptr;
+ AndroidAutofillProviderWithCredManTest::TearDown();
+ }
+
+ void FocusSubFrameFormField(const FormFieldData& field) {
+ keyboard_suppressor().OnBeforeAskForValuesToFill(
+ android_autofill_manager(), sub_frame_webauthn_form_.global_id(),
+ field.global_id(), sub_frame_webauthn_form_);
+ android_autofill_manager().SimulateOnAskForValuesToFill(
+ sub_frame_webauthn_form_, field);
+ android_autofill_manager().SimulateOnFocusOnFormField(
+ sub_frame_webauthn_form_, field);
+ }
+
+ const FormData& sub_frame_test_form() const {
+ return sub_frame_webauthn_form_;
+ }
+
+ const FormFieldData& sub_frame_webauthn_email_field() const {
+ return sub_frame_test_form().fields()[0];
+ }
+
+ protected:
+ raw_ptr<content::RenderFrameHost> sub_frame_ = nullptr;
+ raw_ptr<webauthn::MockWebAuthnCredManDelegate> sub_frame_mock_delegate_ =
+ nullptr;
+ FormData sub_frame_webauthn_form_;
+};
+
+// Tests that focus events are correctly routed to the delegate of the
+// appropriate frame.
+TEST_F(AndroidAutofillProviderWithCredManMultiFrameTest,
+ CredentialManagerSheetTriggeredOnSubFrameDelegate) {
+ // Expect CredMan UI to be triggered on the subframe's delegate
+ EXPECT_CALL(*sub_frame_mock_delegate_, TriggerCredManUi);
+ // Expect CredMan UI to NOT be triggered on the main frame's delegate
+ EXPECT_CALL(cred_man_delegate(), TriggerCredManUi).Times(0);
+
+ FocusSubFrameFormField(sub_frame_webauthn_email_field());
+}
+
using AndroidAutofillProviderPrefillRequestTest = AndroidAutofillProviderTest;
// Tests that we can send another prefill request after navigation.
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.
References
On This Page