Chrome · Chromium
CVE-2026-79108
Logic Error in Chromium
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc |
modified | |
StepTransitionObserverchrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc |
modified | |
BindLambdaForTestingchrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc |
modified |
Files Changed
chrome/browser/ui/views/webauthn/BUILD.gnchrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.ccchrome/browser/ui/views/webauthn/hover_list_view.cc
Patch
From 9d7d8afd1cb8ef089b616691c7a579996ef1bd2b Mon Sep 17 00:00:00 2001 From: Adem Derinel <[email protected]> Date: Mon, 06 Jul 2026 08:47:38 -0700 Subject: [PATCH] [WebAuthn] Prevent input protection bypass on HoverListView row clicks The WebAuthn tab-modal dialog HoverListView row buttons were not protected by DialogClientView InputEventActivationProtector. As a result, row clicks occurring immediately upon dialog presentation during the double-click/input protection interval (500 ms) were executed instead of dropped. This change intercepts row button selections across HoverListView (OnListItemSelected), querying DialogClientView::IsPossiblyUnintendedInteraction(event, false) before dispatching item selection callbacks (HoverListModel::OnListItemSelected) or accepting actions. Added regression browser test (AuthenticatorDialogViewTest.InputEventProtection) to verify row clicks inside the protection window are dropped. TAG=agy Fixed: 522304549 Change-Id: Ia4d54fce53a04da71b0095b988c4588f046c9c7a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8039609 Reviewed-by: Nina Satragno <[email protected]> Commit-Queue: Adem Derinel <[email protected]> Cr-Commit-Position: refs/heads/main@{#1657224} --- diff --git a/chrome/browser/ui/views/webauthn/BUILD.gn b/chrome/browser/ui/views/webauthn/BUILD.gn index fcb6201..08b0655 100644 --- a/chrome/browser/ui/views/webauthn/BUILD.gn +++ b/chrome/browser/ui/views/webauthn/BUILD.gn @@ -201,5 +201,6 @@ "//content/test:test_support", "//testing/gtest", "//ui/views", + "//ui/views:test_support", ] } diff --git a/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc b/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc index b8e0252..092bb168 100644 --- a/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc +++ b/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc @@ -8,7 +8,9 @@ #include "base/functional/callback_helpers.h" #include "base/memory/scoped_refptr.h" +#include "base/test/bind.h" #include "build/build_config.h" +#include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -17,11 +19,14 @@ #include "chrome/browser/ui/views/webauthn/authenticator_request_dialog_view_controller_views.h" #include "chrome/browser/ui/views/webauthn/authenticator_request_dialog_view_test_api.h" #include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h" +#include "chrome/browser/ui/views/webauthn/hover_list_view.h" #include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h" #include "chrome/browser/webauthn/authenticator_request_dialog_model.h" #include "content/public/test/browser_test.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/views/controls/label.h" +#include "ui/views/test/button_test_api.h" +#include "ui/views/window/dialog_client_view.h" namespace { @@ -92,6 +97,36 @@ } }; +HoverListView* FindHoverListView(views::View* root) { + if (!root) { + return nullptr; + } + if (auto* hover_list = views::AsViewClass<HoverListView>(root)) { + return hover_list; + } + for (views::View* child : root->children()) { + if (HoverListView* found = FindHoverListView(child)) { + return found; + } + } + return nullptr; +} + +views::Button* FindTopListItemButton(views::View* root) { + if (!root) { + return nullptr; + } + if (auto* button = views::AsViewClass<views::Button>(root)) { + return button; + } + for (views::View* child : root->children()) { + if (views::Button* found = FindTopListItemButton(child)) { + return found; + } + } + return nullptr; +} + } // namespace class StepTransitionObserver @@ -163,3 +198,61 @@ IN_PROC_BROWSER_TEST_F(AuthenticatorDialogViewTest, InvokeUi_ReplaceSheet) { ShowAndVerifyUi(); } + +IN_PROC_BROWSER_TEST_F(AuthenticatorDialogViewTest, + HoverListViewInputEventProtection) { + content::WebContents* const web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + CHECK(web_contents); + + int mechanism_callback_count_ = 0; + auto dialog_model = + base::MakeRefCounted<AuthenticatorRequestDialogModel>(nullptr); + dialog_model->relying_party_id = "example.com"; + dialog_model->mechanisms.emplace_back( + AuthenticatorRequestDialogModel::Mechanism::Transport( + AuthenticatorTransport::kUsbHumanInterfaceDevice), + u"Security key", kPasskeyUsbDarkCustomIcon, + base::BindRepeating( + base::BindLambdaForTesting([&]() { mechanism_callback_count_++; }))); + dialog_model->SetStep( + AuthenticatorRequestDialogModel::Step::kMechanismSelection); + + auto view_controller = + std::make_unique<AuthenticatorRequestDialogViewControllerViews>( + web_contents, dialog_model.get()); + + // Trigger OnStepTransition() so that view_controller creates the sheet for + // the current step and calls Show(). + view_controller->OnStepTransition(); + + AuthenticatorRequestSheetView* sheet = + test::AuthenticatorRequestDialogViewTestApi::GetSheet( + view_controller.get()); + ASSERT_TRUE(sheet); + + HoverListView* hover_list_view = FindHoverListView(sheet); + ASSERT_TRUE(hover_list_view); + + views::Button* top_row_button = FindTopListItemButton(hover_list_view); + ASSERT_TRUE(top_row_button); + + ui::MouseEvent click_event(ui::EventType::kMousePressed, gfx::Point(), + gfx::Point(), base::TimeTicks::Now(), + ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON); + + views::test::ButtonTestApi(top_row_button).NotifyClick(click_event); + EXPECT_EQ(mechanism_callback_count_, 0); + + ASSERT_TRUE(sheet->GetWidget()); + auto* dialog_delegate = + sheet->GetWidget()->widget_delegate()->AsDialogDelegate(); + ASSERT_TRUE(dialog_delegate); + auto* dialog_client_view = dialog_delegate->GetDialogClientView(); + ASSERT_TRUE(dialog_client_view); + + dialog_client_view->ResetViewShownTimeStampForTesting(); + views::test::ButtonTestApi(top_row_button).NotifyClick(click_event); + EXPECT_EQ(mechanism_callback_count_, 1); +} diff --git a/chrome/browser/ui/views/webauthn/hover_list_view.cc b/chrome/browser/ui/views/webauthn/hover_list_view.cc index 3f4e07a..f9e224b 100644 --- a/chrome/browser/ui/views/webauthn/hover_list_view.cc +++ b/chrome/browser/ui/views/webauthn/hover_list_view.cc @@ -19,6 +19,7 @@ #include "ui/base/models/image_model.h" #include "ui/base/ui_base_features.h" #include "ui/color/color_id.h" +#include "ui/events/event.h" #include "ui/gfx/geometry/insets.h" #include "ui/views/border.h" #include "ui/views/controls/button/button.h" @@ -26,6 +27,10 @@ #include "ui/views/controls/separator.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/fill_layout.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/dialog_client_view.h" +#include "ui/views/window/dialog_delegate.h" namespace { @@ -89,10 +94,12 @@ std::u16string description_text, bool enabled, int item_tag) { + // Safe because the hover button is added as a child view owned by `this`, + // guaranteeing `this` strictly outlives `hover_button` and its callback. auto hover_button = CreateHoverButtonForListItem(
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc b/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc
index b8e0252..092bb168 100644
--- a/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc
@@ -8,7 +8,9 @@
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
+#include "base/test/bind.h"
#include "build/build_config.h"
+#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -17,11 +19,14 @@
#include "chrome/browser/ui/views/webauthn/authenticator_request_dialog_view_controller_views.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_dialog_view_test_api.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
+#include "chrome/browser/ui/views/webauthn/hover_list_view.h"
#include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/controls/label.h"
+#include "ui/views/test/button_test_api.h"
+#include "ui/views/window/dialog_client_view.h"
namespace {
@@ -92,6 +97,36 @@
}
};
+HoverListView* FindHoverListView(views::View* root) {
+ if (!root) {
+ return nullptr;
+ }
+ if (auto* hover_list = views::AsViewClass<HoverListView>(root)) {
+ return hover_list;
+ }
+ for (views::View* child : root->children()) {
+ if (HoverListView* found = FindHoverListView(child)) {
+ return found;
+ }
+ }
+ return nullptr;
+}
+
+views::Button* FindTopListItemButton(views::View* root) {
+ if (!root) {
+ return nullptr;
+ }
+ if (auto* button = views::AsViewClass<views::Button>(root)) {
+ return button;
+ }
+ for (views::View* child : root->children()) {
+ if (views::Button* found = FindTopListItemButton(child)) {
+ return found;
+ }
+ }
+ return nullptr;
+}
+
} // namespace
class StepTransitionObserver
@@ -163,3 +198,61 @@
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogViewTest, InvokeUi_ReplaceSheet) {
ShowAndVerifyUi();
}
+
+IN_PROC_BROWSER_TEST_F(AuthenticatorDialogViewTest,
+ HoverListViewInputEventProtection) {
+ content::WebContents* const web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ CHECK(web_contents);
+
+ int mechanism_callback_count_ = 0;
+ auto dialog_model =
+ base::MakeRefCounted<AuthenticatorRequestDialogModel>(nullptr);
+ dialog_model->relying_party_id = "example.com";
+ dialog_model->mechanisms.emplace_back(
+ AuthenticatorRequestDialogModel::Mechanism::Transport(
+ AuthenticatorTransport::kUsbHumanInterfaceDevice),
+ u"Security key", kPasskeyUsbDarkCustomIcon,
+ base::BindRepeating(
+ base::BindLambdaForTesting([&]() { mechanism_callback_count_++; })));
+ dialog_model->SetStep(
+ AuthenticatorRequestDialogModel::Step::kMechanismSelection);
+
+ auto view_controller =
+ std::make_unique<AuthenticatorRequestDialogViewControllerViews>(
+ web_contents, dialog_model.get());
+
+ // Trigger OnStepTransition() so that view_controller creates the sheet for
+ // the current step and calls Show().
+ view_controller->OnStepTransition();
+
+ AuthenticatorRequestSheetView* sheet =
+ test::AuthenticatorRequestDialogViewTestApi::GetSheet(
+ view_controller.get());
+ ASSERT_TRUE(sheet);
+
+ HoverListView* hover_list_view = FindHoverListView(sheet);
+ ASSERT_TRUE(hover_list_view);
+
+ views::Button* top_row_button = FindTopListItemButton(hover_list_view);
+ ASSERT_TRUE(top_row_button);
+
+ ui::MouseEvent click_event(ui::EventType::kMousePressed, gfx::Point(),
+ gfx::Point(), base::TimeTicks::Now(),
+ ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_LEFT_MOUSE_BUTTON);
+
+ views::test::ButtonTestApi(top_row_button).NotifyClick(click_event);
+ EXPECT_EQ(mechanism_callback_count_, 0);
+
+ ASSERT_TRUE(sheet->GetWidget());
+ auto* dialog_delegate =
+ sheet->GetWidget()->widget_delegate()->AsDialogDelegate();
+ ASSERT_TRUE(dialog_delegate);
+ auto* dialog_client_view = dialog_delegate->GetDialogClientView();
+ ASSERT_TRUE(dialog_client_view);
+
+ dialog_client_view->ResetViewShownTimeStampForTesting();
+ views::test::ButtonTestApi(top_row_button).NotifyClick(click_event);
+ EXPECT_EQ(mechanism_callback_count_, 1);
+}
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