Medium watch CVSS 6.5 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in PictureInPicture
DescriptionInappropriate implementation in PictureInPicture
ComponentPictureInPicture
Sub-componentDownload Bubble
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker363930141
Fix commit704eae4e9117 (chromium/src) +19/-0
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
CreditedShaheen Fazim
Reported2024-09-02 · bounty $1000 (per vendor advisory)
Fix landed2025-12-22 (public git)
Disclosed2026-02-10
Patch gap50 days — fix public in source before the advisory

Background

Download bubble row
DownloadBubbleRowView is one entry in the toolbar download bubble; its transparent_button_ (activated by clicking the row) and quick_actions_ buttons perform download actions such as opening or acting on the file.
Picture-in-Picture (PiP) window
an always-on-top window (video or Document PiP) that a web page can open and populate, which can therefore sit above trusted browser UI.
`PictureInPictureOcclusionObserver` / `ScopedPictureInPictureOcclusionObservation`
an existing Chrome helper that tracks whether a given views::Widget is covered by a PiP window and calls OnOcclusionStateChanged(bool occluded) on the observer.
Clickjacking / input protection
tricking a user into clicking security-sensitive UI they cannot properly see; the row already used views::InputEventActivationProtector (input_protector_) to enforce a click delay, but that does not account for overlap with PiP windows.

Root Cause Analysis

Before this patch, DownloadBubbleRowView never consulted PiP occlusion, so its row-click and quick-action buttons stayed enabled even while a site-controlled, always-on-top PiP window covered the download bubble. The violated invariant is that security-sensitive browser UI should not accept activation while it is obscured by web-controlled content, which other Chrome surfaces enforce through the PiP occlusion tracker.

The fix makes DownloadBubbleRowView a PictureInPictureOcclusionObserver, adds a ScopedPictureInPictureOcclusionObservation pip_occlusion_observation_{this} member, starts observing the row’s widget with pip_occlusion_observation_.Observe(GetWidget()) when the view is attached to a widget, and in the new OnOcclusionStateChanged sets transparent_button_ and every quick_actions_ button to SetEnabled(!occluded).

As a result, the download actions are disabled for as long as a PiP window overlaps the bubble and re-enabled when it no longer does.

Key insight
The download bubble’s action buttons were clickable while covered by an attacker-controlled always-on-top PiP window; the patch subscribes the row to PiP occlusion tracking and disables its buttons whenever the bubble widget is occluded.

Attack Path

  1. Trigger a download a malicious page starts a download so the download bubble with its row and quick-action buttons is shown.
  2. Open a PiP window over the bubble the page opens a video or Document PiP window, which is always-on-top and positioned over or near the bubble (window placement details are not in the diff).
  3. Lure the user into clicking the PiP content entices clicks so that a click lands on, or is timed to land on, the obscured transparent_button_ or a quick_actions_ button (exact deception technique (inferred)).
  4. Unintended download action pre-patch, the still-enabled button executes the download action the user did not knowingly choose; post-patch, OnOcclusionStateChanged(true) has disabled those buttons while occluded.

Impact Assessment

A malicious site can use a PiP window to clickjack the download bubble in the browser UI, causing the user to trigger download actions they did not intend; it needs the user to interact with the page and to click in the overlapped region. The advisory rates this Medium (“Inappropriate implementation in PictureInPicture”) and does not report in-the-wild exploitation; any further consequence of the triggered action (e.g. running a downloaded file) is (inferred) and not shown by the patch.

Changed Functions

FunctionChangeNotes
for
chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
modified
DownloadBubbleRowView
chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
modified

Files Changed

  • chrome/browser/ui/views/download/BUILD.gn
  • chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
  • chrome/browser/ui/views/download/bubble/download_bubble_row_view.h

Audit Directions

  • Trusted UI without occlusion tracking
    enumerate other bubbles, prompts, and dialogs with consequential buttons (permission prompts, extension install, file-system access, payment, password bubbles) and confirm each observes PiP occlusion or equivalent.
  • Enable-state clobbering
    OnOcclusionStateChanged(false) unconditionally calls SetEnabled(true); check whether this can re-enable a quick action or row button that other logic had deliberately disabled, and whether a fresh InputEventActivationProtector delay applies after occlusion ends.
  • Observation lifetime across re-parenting
    verify Observe(GetWidget()) behaves correctly when the row is removed and re-added to widgets (e.g. bubble rebuilds or ShowInactive() paths), so no window exists where the row is visible but unobserved.
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 5a45ce2..0f360ea5 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
@@ -165,6 +165,7 @@
     focus_manager->AddFocusChangeListener(this);
     RegisterAccelerators(focus_manager);
   }
+  pip_occlusion_observation_.Observe(GetWidget());
 }
 
 void DownloadBubbleRowView::RemovedFromWidget() {
@@ -936,6 +937,13 @@
   return focused;
 }
 
+void DownloadBubbleRowView::OnOcclusionStateChanged(bool occluded) {
+  transparent_button_->SetEnabled(!occluded);
+  for (auto& [command, action_button] : quick_actions_) {
+    action_button->SetEnabled(!occluded);
+  }
+}
+
 std::u16string_view DownloadBubbleRowView::GetSecondaryLabelTextForTesting() {
   return secondary_label_->GetText();
 }
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
index 0118c89..f593884 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
@@ -14,6 +14,8 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/download/download_commands.h"
 #include "chrome/browser/download/download_ui_model.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_observer.h"
+#include "chrome/browser/picture_in_picture/scoped_picture_in_picture_occlusion_observation.h"
 #include "chrome/browser/ui/download/download_bubble_row_view_info.h"
 #include "chrome/browser/ui/download/download_item_mode.h"
 #include "chrome/browser/ui/views/controls/hover_button.h"
@@ -46,6 +48,7 @@
 class DownloadBubbleRowView : public views::View,
                               public views::ContextMenuController,
                               public views::FocusChangeListener,
+                              public PictureInPictureOcclusionObserver,
                               public DownloadBubbleRowViewInfoObserver {
   METADATA_HEADER(DownloadBubbleRowView, views::View)
 
@@ -92,6 +95,9 @@
   bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
   bool CanHandleAccelerators() const override;
 
+  // PictureInPictureOcclusionObserver:
+  void OnOcclusionStateChanged(bool occluded) override;
+
   // Returns the transparent button that is activated when the row is clicked.
   views::Button* transparent_button() { return transparent_button_; }
 
@@ -244,6 +250,10 @@
   // Mitigates the risk of clickjacking by enforcing a delay in click input.
   std::unique_ptr<views::InputEventActivationProtector> input_protector_;
 
+  // Observes PiP windows in order to deactivate the button controls when
+  // occluded by a PiP, to prevent clickjacking.
+  ScopedPictureInPictureOcclusionObservation pip_occlusion_observation_{this};
+
   // TODO(crbug.com/40233803): The size constraint is not passed down from the
   // views tree in the first round of layout, so setting a fixed width to bound
   // the view. This is assuming that the row view is loaded inside a bubble. It
diff --git a/chrome/browser/ui/views/download/BUILD.gn b/chrome/browser/ui/views/download/BUILD.gn
index 566d4863..1863fa6 100644
--- a/chrome/browser/ui/views/download/BUILD.gn
+++ b/chrome/browser/ui/views/download/BUILD.gn
@@ -68,6 +68,7 @@
       "//chrome/app:command_ids",
       "//chrome/browser:browser_process",
       "//chrome/browser:primitives",
+      "//chrome/browser/picture_in_picture",
       "//chrome/browser/profiles:profile",
       "//chrome/browser/profiles:profile_util",
       "//chrome/browser/safe_browsing",
Loading diff…

Original Bug Report

reported by [email protected]

User can unknowingly Execute External File Hidden behind PiP during Interaction

Security Bug


VULNERABILITY DETAILS An attacker can exploit a combination of opening a Picture-in-Picture (PiP) window and a hidden popup behind it. By manipulating the focus onto the concealed popup, an attacker can hijack keypresses when a user interacts with the website. This could lead to the unintended execution of a downloaded file. When the PiP window is closed on top of the downloaded file, the browser automatically shifts focus to this file, and with just two Enter key presses, the victim could unknowingly execute the file.

Using the method mentioned above, I have crafted an engaging game that requires the user to click on the PiP window to gather rewards and then asks the victim to press Enter twice to claim these rewards.

VERSION Chrome Version: 128.0.6613.114 (Official Build) (64-bit) Operating System: Windows 11

REPRODUCTION CASE

  1. Download the attached poc.html file.
  2. Open the poc.html file in the latest Chromium browser.
  3. Interact with the game and observe how the executable is successfully launched without the user’s awareness.

In this proof of concept, I used an example executable file from Sysinternals: adrestore.exe. However, this could be any arbitrary executable. The files are opened behind the PiP window, and when the user presses Enter twice, the first Enter closes the PiP window, and the second Enter opens the executable. This interaction can also occur without PiP, as any popup opened and closed in front of a downloaded file will automatically focus on the downloaded file, allowing the Enter key to initiate the launch of the file.

Please attach files directly, not in zip or other archive formats, and if you’ve created a demonstration site please also attach the files needed to reproduce the demonstration locally.

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

View on issue tracker