Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Autofill
DescriptionInappropriate implementation in Autofill
ComponentAutofill
Bug ClassLogic Error
Tracker514055973
Fix commitb84e41ea4c7e (chromium/src) +133/-28
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

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

Files Changed

  • components/android_autofill/browser/android_autofill_provider.cc
  • components/android_autofill/browser/android_autofill_provider.h
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.