Chrome · FileHandling
CVE-2026-87473
Logic Error in FileHandling
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Fchrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc |
modified |
Files Changed
chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.ccchrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.ccchrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.hchrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.ccchrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc
Patch
From a245abe0732f3d9f180163b2de0ab52f54ec59c1 Mon Sep 17 00:00:00 2001 From: Kunjan Vinay Patel <[email protected]> Date: Mon, 10 Aug 2026 14:31:48 -0700 Subject: [PATCH] [PWA] Prevent keyjacking on web app launch consent dialogs. This change enhances security for web app launch dialogs (covering both File and Protocol handlers) to prevent accidental or malicious bypasses via rapid key presses: - Sets the default focused button to 'Cancel' in the base class LaunchAppUserChoiceDialogView. - Enforces the 500ms input activation delay for keyboard events by overriding ShouldAllowKeyEventsDuringInputProtection() to return false. Previously, these dialogs could be immediately accepted via the 'Enter' key because the default button was 'OK' (specifically for File Handlers) and key events were not blocked during the initial activation delay. Bug: 537101736 Change-Id: Ie7396ee149ca498e0df19441c548c8eaa22e9d42 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8226085 Reviewed-by: Dibyajyoti Pal <[email protected]> Commit-Queue: Kunjan Patel <[email protected]> Reviewed-by: Luke Deng <[email protected]> Cr-Commit-Position: refs/heads/main@{#1676726} --- diff --git a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc index 6aa7bdb2..0af938e 100644 --- a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc @@ -226,6 +226,24 @@ EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize()); } +IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest, + DefaultButtonAndInputProtection) { + views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, + "FileHandlerLaunchDialogView"); + LaunchAppWithFiles({{base::FilePath::FromASCII("foo.txt")}}); + views::Widget* widget = waiter.WaitIfNeededAndGet(); + ASSERT_NE(widget, nullptr); + views::DialogDelegate* dialog_delegate = + widget->widget_delegate()->AsDialogDelegate(); + ASSERT_NE(dialog_delegate, nullptr); + + EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(), + static_cast<int>(ui::mojom::DialogButton::kCancel)); + EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection()); + + views::test::CancelDialog(widget); +} + IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest, DisallowAndRemember) { // One normal browser window exists. EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize()); diff --git a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc index 4141892..003b136 100644 --- a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc +++ b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc @@ -83,6 +83,8 @@ SetCanResize(false); set_draggable(true); + SetDefaultButton(static_cast<int>(ui::mojom::DialogButton::kCancel)); + SetAcceptCallback(base::BindOnce(&LaunchAppUserChoiceDialogView::OnAccepted, base::Unretained(this))); @@ -233,6 +235,11 @@ gfx::ImageSkia::CreateFrom1xBitmap(std::move(masked_bitmap)))); } +bool LaunchAppUserChoiceDialogView::ShouldAllowKeyEventsDuringInputProtection() + const { + return false; +} + BEGIN_METADATA(LaunchAppUserChoiceDialogView) END_METADATA diff --git a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h index 406708d3..7c164a5b 100644 --- a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h +++ b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h @@ -46,6 +46,9 @@ void Init(); + // views::DialogDelegate: + bool ShouldAllowKeyEventsDuringInputProtection() const override; + static void SetDefaultRememberSelectionForTesting(bool remember_selection); protected: diff --git a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc index 868700c..00c5c32 100644 --- a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc @@ -108,6 +108,29 @@ } IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest, + DefaultButtonAndInputProtection) { + views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, + "ProtocolHandlerLaunchDialogView"); + GURL protocol_url("web+test://test"); + webapps::AppId test_app_id = InstallTestWebApp(browser()->GetProfile()); + + ShowWebAppProtocolLaunchDialog(protocol_url, browser()->GetProfile(), + test_app_id, base::DoNothing()); + + views::Widget* widget = waiter.WaitIfNeededAndGet(); + ASSERT_NE(widget, nullptr); + views::DialogDelegate* dialog_delegate = + widget->widget_delegate()->AsDialogDelegate(); + ASSERT_NE(dialog_delegate, nullptr); + + EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(), + static_cast<int>(ui::mojom::DialogButton::kCancel)); + EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection()); + + views::test::CancelDialog(widget); +} + +IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest, ProtocolHandlerIntentPickerDialog_DisallowAndRemember) { ProtocolHandlerLaunchDialogView::SetDefaultRememberSelectionForTesting(true); ShowDialogAndCloseWithReason( diff --git a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc index 94f1812..32b758e 100644 --- a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc +++ b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc @@ -39,7 +39,6 @@ SetButtonLabel( ui::mojom::DialogButton::kCancel, l10n_util::GetStringUTF16(IDS_WEB_APP_PERMISSION_NEGATIVE_BUTTON)); - SetDefaultButton(static_cast<int>(ui::mojom::DialogButton::kCancel)); } ProtocolHandlerLaunchDialogView::~ProtocolHandlerLaunchDialogView() = default;
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
index 6aa7bdb2..0af938e 100644
--- a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
@@ -226,6 +226,24 @@
EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize());
}
+IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest,
+ DefaultButtonAndInputProtection) {
+ views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+ "FileHandlerLaunchDialogView");
+ LaunchAppWithFiles({{base::FilePath::FromASCII("foo.txt")}});
+ views::Widget* widget = waiter.WaitIfNeededAndGet();
+ ASSERT_NE(widget, nullptr);
+ views::DialogDelegate* dialog_delegate =
+ widget->widget_delegate()->AsDialogDelegate();
+ ASSERT_NE(dialog_delegate, nullptr);
+
+ EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(),
+ static_cast<int>(ui::mojom::DialogButton::kCancel));
+ EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection());
+
+ views::test::CancelDialog(widget);
+}
+
IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest, DisallowAndRemember) {
// One normal browser window exists.
EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize());
diff --git a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
index 868700c..00c5c32 100644
--- a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
@@ -108,6 +108,29 @@
}
IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest,
+ DefaultButtonAndInputProtection) {
+ views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+ "ProtocolHandlerLaunchDialogView");
+ GURL protocol_url("web+test://test");
+ webapps::AppId test_app_id = InstallTestWebApp(browser()->GetProfile());
+
+ ShowWebAppProtocolLaunchDialog(protocol_url, browser()->GetProfile(),
+ test_app_id, base::DoNothing());
+
+ views::Widget* widget = waiter.WaitIfNeededAndGet();
+ ASSERT_NE(widget, nullptr);
+ views::DialogDelegate* dialog_delegate =
+ widget->widget_delegate()->AsDialogDelegate();
+ ASSERT_NE(dialog_delegate, nullptr);
+
+ EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(),
+ static_cast<int>(ui::mojom::DialogButton::kCancel));
+ EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection());
+
+ views::test::CancelDialog(widget);
+}
+
+IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest,
ProtocolHandlerIntentPickerDialog_DisallowAndRemember) {
ProtocolHandlerLaunchDialogView::SetDefaultRememberSelectionForTesting(true);
ShowDialogAndCloseWithReason(
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