Low chrome Spoofing 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in WebAppInstalls
DescriptionUI misrepresentation in WebAppInstalls
ComponentWebAppInstalls
Bug ClassSpoofing
Tracker533119681
Fix commit12db949bfb00 (chromium/src) +68/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/intent_picker_bubble_view.cc
modified
WebContents
chrome/browser/ui/views/intent_picker_bubble_view.h
modified
Event
chrome/browser/ui/views/intent_picker_bubble_view.h
modified
Checkbox
chrome/browser/ui/views/intent_picker_bubble_view.h
modified
Widget
chrome/browser/ui/views/intent_picker_bubble_view.h
modified

Files Changed

  • chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
  • chrome/browser/ui/views/intent_picker_bubble_view.h
  • chrome/browser/ui/views/intent_picker_bubble_view.cc
From 12db949bfb0039e8f194030e871905d2effc11cb Mon Sep 17 00:00:00 2001
From: Kunjan Vinay Patel <ksukh@google.com>
Date: Wed, 09 Sep 2026 19:26:17 -0700
Subject: [PATCH] Fix input protection bypass in IntentPickerBubbleView

IntentPickerBubbleView allowed immediate acceptance of the dialog via
double-clicks on app rows/grid items or VKEY_RETURN on grid items by
calling AcceptDialog() directly. This bypassed DialogClientView's
InputEventActivationProtector, leaving the dialog susceptible to
rapid unintended interactions and clickjacking.
This CL:
- Threads the triggering ui::Event through the selection callbacks in
  both list and grid view.
- Gates AcceptDialog() in OnAppSelected() behind
  DialogClientView::IsPossiblyUnintendedInteraction().
- Adds a unit test verifying that double-clicks within the initial
  protection cooldown window are ignored while clicks after the
  cooldown succeed.

Bug: 533119681
Change-Id: I1750bc01dde3706895aa09c42338f8576b32dd7b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8375122
Commit-Queue: Kunjan Patel <ksukh@chromium.org>
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Reviewed-by: Darryl James <dljames@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1695114}
---
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.cc b/chrome/browser/ui/views/intent_picker_bubble_view.cc
index 342b447..43d0f6fa 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.cc
@@ -60,6 +60,7 @@
 #include "ui/views/style/typography_provider.h"
 #include "ui/views/view_class_properties.h"
 #include "ui/views/view_utils.h"
+#include "ui/views/window/dialog_client_view.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "ui/chromeos/devicetype_utils.h"
@@ -97,8 +98,8 @@
 
 // Callback for when an app is selected in the app list. First parameter is the
 // index, second parameter is true if the dialog should be immediately accepted.
-using AppSelectedCallback =
-    base::RepeatingCallback<void(std::optional<size_t>, bool)>;
+using AppSelectedCallback = base::RepeatingCallback<
+    void(std::optional<size_t>, bool, const ui::Event*)>;
 
 // Grid view:
 
@@ -110,7 +111,8 @@
  public:
   // Callback for when this app is selected. Parameter is true if the dialog
   // should be immediately accepted.
-  using ButtonSelectedCallback = base::RepeatingCallback<void(bool)>;
+  using ButtonSelectedCallback =
+      base::RepeatingCallback<void(bool, const ui::Event*)>;
 
   IntentPickerAppGridButton(ButtonSelectedCallback selected_callback,
                             const ui::ImageModel& icon_model,
@@ -185,7 +187,7 @@
   void OnFocus() override {
     Button::OnFocus();
     if (select_on_focus_) {
-      selected_callback_.Run(false);
+      selected_callback_.Run(false, nullptr);
     }
   }
   bool HandleAccessibleAction(const ui::AXActionData& action_data) override {
@@ -217,7 +219,7 @@
     bool should_open = IsDoubleClick(event) ||
                        (event.IsKeyEvent() &&
                         event.AsKeyEvent()->key_code() == ui::VKEY_RETURN);
-    selected_callback_.Run(should_open);
+    selected_callback_.Run(should_open, &event);
   }
 
   // Updates the accessible name of the bubble in the ViewsAX cache.
@@ -300,7 +302,7 @@
   }
 
   void SetSelectedIndex(std::optional<size_t> index) override {
-    SetSelectedIndexInternal(index, false);
+    SetSelectedIndexInternal(index, false, nullptr);
   }
 
   std::optional<size_t> GetSelectedIndex() const override {
@@ -309,7 +311,8 @@
 
  private:
   void SetSelectedIndexInternal(std::optional<size_t> new_index,
-                                bool accepted) {
+                                bool accepted,
+                                const ui::Event* event) {
     if (selected_app_index_.has_value()) {
       GetButtonAtIndex(selected_app_index_.value())->SetSelected(false);
     }
@@ -324,7 +327,7 @@
 
     selected_app_index_ = new_index;
 
-    selected_callback_.Run(new_index, accepted);
+    selected_callback_.Run(new_index, accepted, event);
   }
 
   IntentPickerAppGridButton* GetButtonAtIndex(size_t index) {
@@ -475,7 +478,7 @@
       accepted = true;
     }
 
-    selected_callback_.Run(index, accepted);
+    selected_callback_.Run(index, accepted, event);
   }
 
   size_t CalculateNextAppIndex(int delta) {
@@ -686,7 +689,8 @@
 }
 
 void IntentPickerBubbleView::OnAppSelected(std::optional<size_t> index,
-                                           bool accepted) {
+                                           bool accepted,
+                                           const ui::Event* event) {
   SetButtonEnabled(ui::mojom::DialogButton::kOk, index.has_value());
 
   if (index.has_value()) {
@@ -695,6 +699,13 @@
 
   if (accepted) {
     DCHECK(index.has_value());
+    DCHECK(event);
+    if (GetDialogClientView() &&
+        GetDialogClientView()->IsPossiblyUnintendedInteraction(
+            *event,
+            /*allow_key_events=*/ShouldAllowKeyEventsDuringInputProtection())) {
+      return;
+    }
     AcceptDialog();
   }
 }
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.h b/chrome/browser/ui/views/intent_picker_bubble_view.h
index bc991eef..d9e635d 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.h
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.h
@@ -26,6 +26,10 @@
 class WebContents;
 }  // namespace content
 
+namespace ui {
+class Event;
+}  // namespace ui
+
 namespace views {
 class Checkbox;
 class Widget;
@@ -141,7 +145,9 @@
   // |accepted| is true, the dialog should be immediately accepted with that app
   // selected. If |index| is nullopt, no app is selected, and the Accept button
   // will be disabled
-  void OnAppSelected(std::optional<size_t> index, bool accepted);
+  void OnAppSelected(std::optional<size_t> index,
+                     bool accepted,
+                     const ui::Event* event);
 
   void Initialize();
 
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc b/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
index 8859af0e..1b83d34 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
@@ -50,6 +50,7 @@
 #include "ui/views/test/views_test_base.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget_utils.h"
+#include "ui/views/window/dialog_client_view.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -539,6 +540,9 @@
                        BubbleType::kLinkCapturing,
                        /*initiating_origin=*/std::nullopt);
 
+  // Bypass input protection cooldown.
+  bubble()->GetDialogClientView()->ResetViewShownTimeStampForTesting();
+
   views::test::ButtonTestApi button(GetButtonAtIndex(0));
 
   button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
@@ -552,6 +556,42 @@
   EXPECT_EQ(last_close_reason(), apps::IntentPickerCloseReason::OPEN_APP);
 }
 
+TEST_P(IntentPickerBubbleViewLayoutTest,
+       DoubleClickBlockedDuringInputProtection) {
+  AddApp(apps::PickerEntryType::kWeb, "web_app_id", "Web App");
+  auto bubble_widget =
+      CreateBubbleView(/*use_icons=*/false, /*show_stay_in_chrome=*/false,
+                       BubbleType::kLinkCapturing,
+                       /*initiating_origin=*/std::nullopt);
+
+  views::test::ButtonTestApi button(GetButtonAtIndex(0));
+
+  // Double-click immediately upon bubble appearance (within protection window).
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_NONE, ui::EF_NONE));
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_IS_DOUBLE_CLICK, ui::EF_NONE));
+
+  EXPECT_FALSE(bubble_widget->IsClosed());
+  EXPECT_EQ(bubble()->GetSelectedIndex(), 0u);
+  EXPECT_TRUE(last_selected_launch_name().empty());
+
+  // Reset cooldown and verify double-click now succeeds.
+  bubble()->GetDialogClientView()->ResetViewShownTimeStampForTesting();
+
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_NONE, ui::EF_NONE));
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_IS_DOUBLE_CLICK, ui::EF_NONE));
+
+  EXPECT_EQ(last_selected_launch_name(), "web_app_id");
+  EXPECT_EQ(last_close_reason(), apps::IntentPickerCloseReason::OPEN_APP);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     All,
     IntentPickerBubbleViewLayoutTest,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc b/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
index 8859af0e..1b83d34 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
@@ -50,6 +50,7 @@
 #include "ui/views/test/views_test_base.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget_utils.h"
+#include "ui/views/window/dialog_client_view.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -539,6 +540,9 @@
                        BubbleType::kLinkCapturing,
                        /*initiating_origin=*/std::nullopt);
 
+  // Bypass input protection cooldown.
+  bubble()->GetDialogClientView()->ResetViewShownTimeStampForTesting();
+
   views::test::ButtonTestApi button(GetButtonAtIndex(0));
 
   button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
@@ -552,6 +556,42 @@
   EXPECT_EQ(last_close_reason(), apps::IntentPickerCloseReason::OPEN_APP);
 }
 
+TEST_P(IntentPickerBubbleViewLayoutTest,
+       DoubleClickBlockedDuringInputProtection) {
+  AddApp(apps::PickerEntryType::kWeb, "web_app_id", "Web App");
+  auto bubble_widget =
+      CreateBubbleView(/*use_icons=*/false, /*show_stay_in_chrome=*/false,
+                       BubbleType::kLinkCapturing,
+                       /*initiating_origin=*/std::nullopt);
+
+  views::test::ButtonTestApi button(GetButtonAtIndex(0));
+
+  // Double-click immediately upon bubble appearance (within protection window).
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_NONE, ui::EF_NONE));
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_IS_DOUBLE_CLICK, ui::EF_NONE));
+
+  EXPECT_FALSE(bubble_widget->IsClosed());
+  EXPECT_EQ(bubble()->GetSelectedIndex(), 0u);
+  EXPECT_TRUE(last_selected_launch_name().empty());
+
+  // Reset cooldown and verify double-click now succeeds.
+  bubble()->GetDialogClientView()->ResetViewShownTimeStampForTesting();
+
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_NONE, ui::EF_NONE));
+  button.NotifyClick(ui::MouseEvent(ui::EventType::kMousePressed, gfx::PointF(),
+                                    gfx::PointF(), ui::EventTimeForNow(),
+                                    ui::EF_IS_DOUBLE_CLICK, ui::EF_NONE));
+
+  EXPECT_EQ(last_selected_launch_name(), "web_app_id");
+  EXPECT_EQ(last_close_reason(), apps::IntentPickerCloseReason::OPEN_APP);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     All,
     IntentPickerBubbleViewLayoutTest,
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.