CVE-2025-8583
Overview
Background
- `InputEventActivationProtector`
- a
ui/viewshelper that records a “protected” timestamp (view_protected_time_stamp_) when a dialog or bubble is shown or moved, and lets callers reject input that arrives within the double-click interval after it, so a user cannot be tricked into clicking a button that appeared under their cursor. - `IsPossiblyUnintendedInteraction`
- the protector’s predicate; before this patch it returned
false(meaning “intended”) for every event that was not a mouse, touch or gesture event, so the timing check was never applied to keyboard events. - `allow_key_events`
- the new boolean parameter added by this patch; when
truethe old behavior is kept (key events are never treated as unintended), and whenfalsekey events go through the same short-interval check as pointer events. - Permission and FedCM prompts
- browser UI (
PermissionPromptBaseView,FedCmAccountSelectionView) that a web page can make appear on demand, and whose accept buttons grant a capability or sign the user in.
Root Cause Analysis
InputEventActivationProtector::IsPossiblyUnintendedInteraction was meant to discard activations that land too soon after a prompt appears, but its early exit if (!event.IsMouseEvent() && !event.IsTouchEvent() && !event.IsGestureEvent()) return false; classified every keyboard event as intended, before the kShortInterval timing comparison ever ran.
As a result, a key press (for example Enter or Space delivered to a focused button) that arrived the instant a permission prompt or FedCM dialog was shown was acted on immediately, even though the same activation by mouse would have been ignored. The invariant violated is that any user activation of a security-sensitive prompt’s buttons must come after the protection window, regardless of input modality.
The patch changes the early exit to if (allow_key_events || !event.IsKeyEvent()) return false;, so non-pointer events that are not key events still return early, while key events fall through to the timing check whenever the caller passes allow_key_events=false. Permission prompts (PermissionPromptBaseView::FilterUnintenedEventsAndRunCallbacks) and every FedCM account-selection handler pass false, while the generic dialog path, the bubble frame buttons, the download bubble and the payment sheet pass true to keep their previous keyboard behavior. This is a tightening of the check, applied only to the permission and FedCM call sites.
allow_key_events=false argument.Attack Path
- Get keystrokes flowing A malicious page gets the user to press or hold keys (for example by asking them to type or to press Enter/Space repeatedly), so key events are arriving continuously.
- Trigger a prompt at the right moment The page requests a permission (the updated tests cover geolocation, smart card, embedded and exclusive-access prompts) or starts a FedCM flow, timed so the prompt appears while keys are being pressed.
- Keystroke lands on the prompt
The next key event goes to the newly shown prompt’s focused control; before the patch,
IsPossiblyUnintendedInteractionreturnedfalsefor it, soFilterUnintenedEventsAndRunCallbacksor the FedCM handler ran the button callback with no delay. Which button has default focus is not shown in the diff (inferred: an accept/continue button). - Unintended grant The permission is granted, or the FedCM account or login action proceeds, without the user having read the prompt.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc |
modified |
Files Changed
chrome/browser/smart_card/smart_card_permission_uitest.ccchrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.ccchrome/browser/ui/views/download/bubble/download_bubble_row_view.ccchrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.ccchrome/browser/ui/views/payments/payment_sheet_view_controller.ccchrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.ccchrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.ccchrome/browser/ui/views/permissions/permission_prompt_base_view.ccchrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.ccchrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.ccui/views/bubble/bubble_frame_view.ccui/views/input_event_activation_protector.ccui/views/input_event_activation_protector.hui/views/test/mock_input_event_activation_protector.hui/views/window/dialog_client_view.ccui/views/window/dialog_client_view.hui/views/window/dialog_client_view_unittest.cc
Audit Directions
- Modality-specific security gatesLook for anti-clickjacking, anti-keyjacking or user-activation checks that whitelist event types (
IsMouseEvent/IsTouchEvent/IsGestureEvent) and fall through to “allowed” for everything else, including key, accessibility-action and synthesized events. - Opt-in protection flagsEvery call site that still passes
allow_key_events=true(DialogClientView::ButtonPressed,BubbleFrameViewbuttons, the download bubble, the payment sheet) should be checked for whether a page-triggered dialog with a consequential default button goes through it; a permission-like prompt built on plainDialogDelegatewould not get the new protection. - Prompts that bypass the base viewAudit other page-triggerable consent UIs (device choosers, file-system access, protocol handlers, Android and other non-Views implementations) for an equivalent post-show delay that also covers keyboard input.
Patch
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index 3670327..f72b2c30 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -643,7 +643,8 @@
!info_->main_button_enabled() || !info_->model()) {
return;
}
- if (input_protector_->IsPossiblyUnintendedInteraction(event)) {
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/true)) {
return;
}
if (info_->has_subpage()) {
@@ -661,7 +662,8 @@
DownloadCommands::Command command,
const ui::Event& event) {
if (!bubble_controller_ || !info_->model() ||
- input_protector_->IsPossiblyUnintendedInteraction(event)) {
+ input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/true)) {
return;
}
bubble_controller_->ProcessDownloadButtonPress(info_->model()->GetWeakPtr(),
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index e467f2f..65e3c80 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -933,7 +933,8 @@
void PaymentSheetViewController::PossiblyIgnorePrimaryButtonPress(
PaymentRequestSheetController::ButtonCallback callback,
const ui::Event& event) {
- if (input_protector_->IsPossiblyUnintendedInteraction(event)) {
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/true)) {
return;
}
callback.Run(event);
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc
index 95764b5..a02d83cb 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc
@@ -167,7 +167,8 @@
void PermissionPromptBaseView::FilterUnintenedEventsAndRunCallbacks(
int button_id,
const ui::Event& event) {
- if (GetDialogClientView()->IsPossiblyUnintendedInteraction(event)) {
+ if (GetDialogClientView()->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false)) {
return;
}
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index cd68db4..0833f2a 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -547,7 +547,8 @@
DCHECK(state_ != State::IDP_SIGNIN_STATUS_MISMATCH);
DCHECK(state_ != State::AUTO_REAUTHN);
- if (input_protector_->IsPossiblyUnintendedInteraction(event) ||
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false) ||
is_occluded_by_pip_) {
return;
}
@@ -609,7 +610,8 @@
void FedCmAccountSelectionView::OnLinkClicked(LinkType link_type,
const GURL& url,
const ui::Event& event) {
- if (input_protector_->IsPossiblyUnintendedInteraction(event) ||
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false) ||
is_occluded_by_pip_) {
return;
}
@@ -641,7 +643,8 @@
// Because the close button is a safe button to click and may be visible
// even when the widget is (partially) occluded, we do not check
// `is_occluded_by_pip_` here.
- if (input_protector_->IsPossiblyUnintendedInteraction(event)) {
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false)) {
return;
}
@@ -675,7 +678,8 @@
void FedCmAccountSelectionView::OnLoginToIdP(const GURL& idp_config_url,
const GURL& idp_login_url,
const ui::Event& event) {
- if (input_protector_->IsPossiblyUnintendedInteraction(event) ||
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false) ||
is_occluded_by_pip_) {
return;
}
@@ -698,7 +702,8 @@
}
void FedCmAccountSelectionView::OnGotIt(const ui::Event& event) {
- if (input_protector_->IsPossiblyUnintendedInteraction(event) ||
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false) ||
is_occluded_by_pip_) {
return;
}
@@ -707,7 +712,8 @@
}
void FedCmAccountSelectionView::OnMoreDetails(const ui::Event& event) {
- if (input_protector_->IsPossiblyUnintendedInteraction(event) ||
+ if (input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/false) ||
is_occluded_by_pip_) {
return;
}
diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc
index e9a2f4f5..c4babe9 100644
--- a/ui/views/bubble/bubble_frame_view.cc
+++ b/ui/views/bubble/bubble_frame_view.cc
@@ -115,7 +115,8 @@
auto minimize = CreateMinimizeButton(base::BindRepeating(
[](BubbleFrameView* view, const ui::Event& event) {
- if (view->input_protector_.IsPossiblyUnintendedInteraction(event)) {
+ if (view->input_protector_.IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/true)) {
return;
}
view->GetWidget()->Minimize();
@@ -127,7 +128,8 @@
auto close = CreateCloseButton(base::BindRepeating(
[](BubbleFrameView* view, const ui::Event& event) {
- if (view->input_protector_.IsPossiblyUnintendedInteraction(event)) {
+ if (view->input_protector_.IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/true)) {
return;
}
view->GetWidget()->CloseWithReason(
diff --git a/ui/views/input_event_activation_protector.cc b/ui/views/input_event_activation_protector.cc
index d20cdc8..ef58c7c4 100644
--- a/ui/views/input_event_activation_protector.cc
+++ b/ui/views/input_event_activation_protector.cc
@@ -36,7 +36,8 @@
}
bool InputEventActivationProtector::IsPossiblyUnintendedInteraction(
- const ui::Event& event) {
+ const ui::Event& event,
+ bool allow_key_events) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableInputEventActivationProtectionForTesting))
[[unlikely]] {
@@ -56,7 +57,9 @@
if (!event.IsMouseEvent() && !event.IsTouchEvent() &&
!event.IsGestureEvent()) {
- return false;
+ if (allow_key_events || !event.IsKeyEvent()) {
+ return false;
+ }
}
const base::TimeDelta kShortInterval =
diff --git a/ui/views/input_event_activation_protector.h b/ui/views/input_event_activation_protector.h
index 1f5842d..8c9d384 100644
--- a/ui/views/input_event_activation_protector.h
+++ b/ui/views/input_event_activation_protector.h
@@ -44,10 +44,14 @@
// visibility change).
void MaybeUpdateViewProtectedTimeStamp(bool force = false);
- // Returns true if the event is a mouse, touch, or pointer event that took
- // place within the double-click time interval after
- // |view_protected_time_stamp_|.
- virtual bool IsPossiblyUnintendedInteraction(const ui::Event& event);
+ // Returns true if the event is a mouse, touch, pointer, or key (optionally)
+ // event that took place within the double-click time interval after
+ // `view_protected_time_stamp_`.
+ //
+ // If `allow_key_events` is true, "key events" will NOT be considered
+ // for possibly unintended interaction checks.
+ virtual bool IsPossiblyUnintendedInteraction(const ui::Event& event,
+ bool allow_key_events);
// Implements WindowsStationarityMonitor::Observer:
void OnWindowStationaryStateChanged() override;
diff --git a/ui/views/window/dialog_client_view.cc b/ui/views/window/dialog_client_view.cc
index 20f12f5..aa8610a 100644
--- a/ui/views/window/dialog_client_view.cc
+++ b/ui/views/window/dialog_client_view.cc
@@ -307,8 +307,10 @@
input_protector_->ResetForTesting(); // IN-TEST
}
-bool DialogClientView::IsPossiblyUnintendedInteraction(const ui::Event& event) {
- return input_protector_->IsPossiblyUnintendedInteraction(event);
+bool DialogClientView::IsPossiblyUnintendedInteraction(const ui::Event& event,
+ bool allow_key_events) {
+ return input_protector_->IsPossiblyUnintendedInteraction(event,
+ allow_key_events);
}
DialogDelegate* DialogClientView::GetDialogDelegate() const {
@@ -401,7 +403,8 @@
void DialogClientView::ButtonPressed(ui::mojom::DialogButton type,
const ui::Event& event) {
DialogDelegate* const delegate = GetDialogDelegate();
- if (!delegate || input_protector_->IsPossiblyUnintendedInteraction(event)) {
+ if (!delegate || input_protector_->IsPossiblyUnintendedInteraction(
+ event, /*allow_key_events=*/true)) {
return;
}
diff --git a/ui/views/window/dialog_client_view.h b/ui/views/window/dialog_client_view.h
index a3623018..471d00c 100644
--- a/ui/views/window/dialog_client_view.h
+++ b/ui/views/window/dialog_client_view.h
@@ -119,7 +119,8 @@
input_protector_ = std::move(input_protector);
}
- bool IsPossiblyUnintendedInteraction(const ui::Event& event);
+ bool IsPossiblyUnintendedInteraction(const ui::Event& event,
+ bool allow_key_events);
// LayoutDelegate:
ProposedLayout CalculateProposedLayout(
diff --git a/chrome/browser/smart_card/smart_card_permission_uitest.cc b/chrome/browser/smart_card/smart_card_permission_uitest.cc
index 02d739ff..d907f1d 100644
--- a/chrome/browser/smart_card/smart_card_permission_uitest.cc
+++ b/chrome/browser/smart_card/smart_card_permission_uitest.cc
@@ -23,6 +23,7 @@
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/views_switches.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestTab);
@@ -159,6 +160,13 @@
ASSERT_TRUE(embedded_https_test_server().Start());
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
std::optional<bool> permission_decision_;
base::test::ScopedFeatureList scoped_feature_list_{
blink::features::kSmartCard};
diff --git a/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc b/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc
index 93c4fd01..30003b6 100644
--- a/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc
@@ -17,6 +17,7 @@
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/message_center.h"
+#include "ui/views/views_switches.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
@@ -52,6 +53,13 @@
https_server()->StartAcceptingConnections();
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc
index f5dfe68..7ffa4c16 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc
@@ -70,7 +70,7 @@
auto input_protector =
std::make_unique<NiceMock<views::MockInputEventActivationProtector>>();
input_protector_ = input_protector.get();
- ON_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_))
+ ON_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_, _))
.WillByDefault(Return(false));
row_view_->SetInputProtectorForTesting(std::move(input_protector));
}
@@ -173,7 +173,7 @@
// Test that the input protector can deny button clicks.
TEST_F(DownloadBubbleRowViewTest, InputProtectorDeniesClicks) {
- EXPECT_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_))
+ EXPECT_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_, _))
.WillRepeatedly(Return(true));
// Test main button
diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
index 3c49392..abfcebd 100644
--- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
@@ -39,6 +39,7 @@
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
+#include "ui/views/views_switches.h"
#include "ui/views/widget/any_widget_observer.h"
#include "url/origin.h"
@@ -94,6 +95,13 @@
{10, 10, 800, 800});
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
diff --git a/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc b/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc
index f8814796..7a4f8ee 100644
--- a/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc
@@ -17,6 +17,7 @@
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/views/views_switches.h"
#include "url/url_constants.h"
namespace {
@@ -55,6 +56,13 @@
InteractiveBrowserTest::SetUpOnMainThread();
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
InteractiveBrowserTest::TearDownOnMainThread();
EXPECT_TRUE(https_server_.ShutdownAndWaitUntilComplete());
diff --git a/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc b/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc
index e24af22..858c2e7c3 100644
--- a/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc
@@ -28,6 +28,7 @@
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/interaction/interaction_test_util_views.h"
#include "ui/views/view_utils.h"
+#include "ui/views/views_switches.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
@@ -68,6 +69,13 @@
https_server()->StartAcceptingConnections();
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
diff --git a/ui/views/test/mock_input_event_activation_protector.h b/ui/views/test/mock_input_event_activation_protector.h
index ad644ed..592433d 100644
--- a/ui/views/test/mock_input_event_activation_protector.h
+++ b/ui/views/test/mock_input_event_activation_protector.h
@@ -24,7 +24,7 @@
MOCK_METHOD(bool,
IsPossiblyUnintendedInteraction,
- (const ui::Event& event),
+ (const ui::Event& event, bool allow_key_events),
(override));
};
diff --git a/ui/views/window/dialog_client_view_unittest.cc b/ui/views/window/dialog_client_view_unittest.cc
index ddc3bb9..394c6334 100644
--- a/ui/views/window/dialog_client_view_unittest.cc
+++ b/ui/views/window/dialog_client_view_unittest.cc
@@ -612,6 +612,20 @@
EXPECT_TRUE(widget()->IsClosed());
... (file diff truncated)
Regression Test / PoC
diff --git a/chrome/browser/smart_card/smart_card_permission_uitest.cc b/chrome/browser/smart_card/smart_card_permission_uitest.cc
index 02d739ff..d907f1d 100644
--- a/chrome/browser/smart_card/smart_card_permission_uitest.cc
+++ b/chrome/browser/smart_card/smart_card_permission_uitest.cc
@@ -23,6 +23,7 @@
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/views_switches.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestTab);
@@ -159,6 +160,13 @@
ASSERT_TRUE(embedded_https_test_server().Start());
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
std::optional<bool> permission_decision_;
base::test::ScopedFeatureList scoped_feature_list_{
blink::features::kSmartCard};
diff --git a/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc b/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc
index 93c4fd01..30003b6 100644
--- a/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/privacy_hub/geolocation_switch_interactive_uitest.cc
@@ -17,6 +17,7 @@
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/message_center.h"
+#include "ui/views/views_switches.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
@@ -52,6 +53,13 @@
https_server()->StartAcceptingConnections();
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc
index f5dfe68..7ffa4c16 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view_unittest.cc
@@ -70,7 +70,7 @@
auto input_protector =
std::make_unique<NiceMock<views::MockInputEventActivationProtector>>();
input_protector_ = input_protector.get();
- ON_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_))
+ ON_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_, _))
.WillByDefault(Return(false));
row_view_->SetInputProtectorForTesting(std::move(input_protector));
}
@@ -173,7 +173,7 @@
// Test that the input protector can deny button clicks.
TEST_F(DownloadBubbleRowViewTest, InputProtectorDeniesClicks) {
- EXPECT_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_))
+ EXPECT_CALL(*input_protector_, IsPossiblyUnintendedInteraction(_, _))
.WillRepeatedly(Return(true));
// Test main button
diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
index 3c49392..abfcebd 100644
--- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
@@ -39,6 +39,7 @@
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
+#include "ui/views/views_switches.h"
#include "ui/views/widget/any_widget_observer.h"
#include "url/origin.h"
@@ -94,6 +95,13 @@
{10, 10, 800, 800});
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
diff --git a/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc b/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc
index f8814796..7a4f8ee 100644
--- a/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/exclusive_access_permission_prompt_interactive_uitest.cc
@@ -17,6 +17,7 @@
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/views/views_switches.h"
#include "url/url_constants.h"
namespace {
@@ -55,6 +56,13 @@
InteractiveBrowserTest::SetUpOnMainThread();
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
InteractiveBrowserTest::TearDownOnMainThread();
EXPECT_TRUE(https_server_.ShutdownAndWaitUntilComplete());
diff --git a/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc b/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc
index e24af22..858c2e7c3 100644
--- a/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/permission_rhs_indicators_interactive_uitest.cc
@@ -28,6 +28,7 @@
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/interaction/interaction_test_util_views.h"
#include "ui/views/view_utils.h"
+#include "ui/views/views_switches.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
@@ -68,6 +69,13 @@
https_server()->StartAcceptingConnections();
}
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ InteractiveBrowserTestT::SetUpCommandLine(command_line);
+ // Disables the disregarding of potentially unintended input events.
+ command_line->AppendSwitch(
+ views::switches::kDisableInputEventActivationProtectionForTesting);
+ }
+
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
diff --git a/ui/views/test/mock_input_event_activation_protector.h b/ui/views/test/mock_input_event_activation_protector.h
index ad644ed..592433d 100644
--- a/ui/views/test/mock_input_event_activation_protector.h
+++ b/ui/views/test/mock_input_event_activation_protector.h
@@ -24,7 +24,7 @@
MOCK_METHOD(bool,
IsPossiblyUnintendedInteraction,
- (const ui::Event& event),
+ (const ui::Event& event, bool allow_key_events),
(override));
};
diff --git a/ui/views/window/dialog_client_view_unittest.cc b/ui/views/window/dialog_client_view_unittest.cc
index ddc3bb9..394c6334 100644
--- a/ui/views/window/dialog_client_view_unittest.cc
+++ b/ui/views/window/dialog_client_view_unittest.cc
@@ -612,6 +612,20 @@
EXPECT_TRUE(widget()->IsClosed());
}
+// Ensures that key events are not ignored for short time, after view has been
+// shown.
+TEST_F(DialogClientViewTest, DoesNotIgnoreKeyEvents_ReturnKeyAfterShown) {
+ widget()->Show();
+ SetDialogButtons(static_cast<int>(ui::mojom::DialogButton::kCancel) |
+ static_cast<int>(ui::mojom::DialogButton::kOk));
+
+ // Should not ignore key events right after the dialog is shown.
+ ui::KeyEvent press_enter(ui::EventType::kKeyPressed, ui::VKEY_RETURN,
+ ui::EF_NONE, ui::EventTimeForNow());
+ test::ButtonTestApi(client_view()->ok_button()).NotifyClick(press_enter);
+ EXPECT_TRUE(widget()->IsClosed());
+}
+
// Ensures that taps are ignored for a short time after the view has been shown.
TEST_F(DialogClientViewTest, IgnorePossiblyUnintendedClicks_TapAfterShown) {
widget()->Show();
@@ -983,4 +997,146 @@
EXPECT_THAT(client_view(), HasHorizontalButtons());
}
+struct IsPossiblyUnintendedInteractionTestCase {
+ enum class EventType {
+ kKey,
+ kMouse,
+ };
+ std::string test_name;
+ EventType event_type;
+ bool is_delayed_interaction;
+ bool allow_key_events;
+ bool is_possibly_unintended_interaction;
+};
+
+class InteractionTest : public DialogClientViewTest,
+ public testing::WithParamInterface<
+ IsPossiblyUnintendedInteractionTestCase> {
+ public:
+ InteractionTest() = default;
+
+ InteractionTest(const InteractionTest&) = delete;
+ InteractionTest& operator=(const InteractionTest&) = delete;
+
+ std::unique_ptr<ui::KeyEvent> KeyEventNow() {
+ return std::make_unique<ui::KeyEvent>(ui::EventType::kKeyPressed,
+ ui::VKEY_RETURN, ui::EF_NONE,
+ ui::EventTimeForNow());
+ }
+
+ std::unique_ptr<ui::KeyEvent> KeyEventDelayed() {
+ return std::make_unique<ui::KeyEvent>(
+ ui::EventType::kKeyPressed, ui::VKEY_RETURN, ui::EF_NONE,
+ ui::EventTimeForNow() + base::Milliseconds(GetDoubleClickInterval()));
+ }
+
+ std::unique_ptr<ui::MouseEvent> MouseEventNow() {
+ return std::make_unique<ui::MouseEvent>(
+ ui::EventType::kMousePressed, gfx::PointF(), gfx::PointF(),
+ ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
+ }
+
+ std::unique_ptr<ui::MouseEvent> MouseEventDelayed() {
+ return std::make_unique<ui::MouseEvent>(
+ ui::EventType::kMousePressed, gfx::PointF(), gfx::PointF(),
+ ui::EventTimeForNow() + base::Milliseconds(GetDoubleClickInterval()),
+ ui::EF_NONE, ui::EF_NONE);
+ }
+};
+
+TEST_P(InteractionTest, IsPossiblyUnintendedInteraction) {
+ const IsPossiblyUnintendedInteractionTestCase& test_case = GetParam();
+
+ widget()->Show();
+
+ std::unique_ptr<ui::Event> event;
+ switch (test_case.event_type) {
+ case IsPossiblyUnintendedInteractionTestCase::EventType::kKey:
+ event =
+ test_case.is_delayed_interaction ? KeyEventDelayed() : KeyEventNow();
+ break;
+ case IsPossiblyUnintendedInteractionTestCase::EventType::kMouse:
+ event = test_case.is_delayed_interaction ? MouseEventDelayed()
+ : MouseEventNow();
+ break;
+ }
+ ASSERT_NE(event, nullptr);
+
+ EXPECT_EQ(client_view()->IsPossiblyUnintendedInteraction(
+ *event, test_case.allow_key_events),
+ test_case.is_possibly_unintended_interaction);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AllInteractions,
+ InteractionTest,
+ testing::ValuesIn<IsPossiblyUnintendedInteractionTestCase>({
+ {
+ .test_name = "NotPermissionRelevantKeyEventNow",
+ .event_type =
+ IsPossiblyUnintendedInteractionTestCase::EventType::kKey,
+ .is_delayed_interaction = false,
+ .allow_key_events = true,
+ .is_possibly_unintended_interaction = false,
+ },
+ {
+ .test_name = "PermissionRelevantKeyEventNow",
+ .event_type =
+ IsPossiblyUnintendedInteractionTestCase::EventType::kKey,
+ .is_delayed_interaction = false,
+ .allow_key_events = false,
+ .is_possibly_unintended_interaction = true,
+ },
+ {
+ .test_name = "NotPermissionRelevantKeyEventDelayed",
+ .event_type =
+ IsPossiblyUnintendedInteractionTestCase::EventType::kKey,
+ .is_delayed_interaction = true,
+ .allow_key_events = true,
+ .is_possibly_unintended_interaction = false,
+ },
+ {
+ .test_name = "PermissionRelevantKeyEventDelayed",
+ .event_type =
+ IsPossiblyUnintendedInteractionTestCase::EventType::kKey,
+ .is_delayed_interaction = true,
+ .allow_key_events = false,
+ .is_possibly_unintended_interaction = false,
+ },
+ {
... (truncated)
Original Bug Report
User can still unknowingly allow Permission Prompt Hidden behind PiP during Interaction
VULNERABILITY DETAILS This issue is opened based on a previous issue, Bug 364508693, which does not seem to have been properly fixed. I hope the fix has landed, but when testing on the latest Chromium build 1369320, this issue is still reproducible, indicating that the fix did not completely address the problem.
VERSION Chrome Version: 132.0.6780.0 (Developer Build) (64-bit) Operating System: Windows 11
REPRODUCTION CASE The steps to reproduce this issue are the same as those in Bug 364508693.
- Download the attachment poc.html and permission.html files.
- Host the page on a local server or open poc.html directly from the same folder.
- Visit the webpage using the latest Chrome browser.
- Click “Verify” and then “Start,” press Tab three times, and confirm.
- Press Enter twice and observe that the camera permission is unknowingly approved without the user’s awareness.
CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Shaheen Fazim