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 <derinel@google.com>
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 <nsatragno@chromium.org>
Commit-Queue: Adem Derinel <derinel@google.com>
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