Medium watch CVSS 4.3 chrome Spoofing 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in browser UI
DescriptionIncorrect security UI in browser UI
ComponentChromium
Bug ClassSpoofing
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.
Tracker424995036
Fix commit948f7d11cebc (chromium/src) +7/-0
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedAnonymous
Reported2025-06-14 (per vendor advisory)
Fix landed2026-03-03 (public git)
Disclosed2026-04-07
Patch gap35 days — fix public in source before the advisory

Background

PopupWidgetHost
a browser-process host for a renderer-requested popup widget (e.g. a <select> picker) that can draw its own on-screen surface.
TOCTOU (time-of-check to time-of-use)
a flaw where a condition validated at one moment is not re-validated when it is actually acted upon, so state can change in between.
`Visibility::VISIBLE`
the tab-visibility enum value indicating the WebContents is the foreground/active tab; a background tab is not VISIBLE.
`ShowCreatedWidget`
per the commit, the WebContentsImpl routine that actually shows a previously created popup widget (its name is not printed in the shown @@ hunk).

Root Cause Analysis

Chromium validated tab visibility when a popup was created (in CreateNewPopupWidget) but did not re-check visibility at the moment the popup was shown, in the routine the commit identifies as ShowCreatedWidget. A compromised renderer could exploit this window: pass the creation-time visibility check and then have the popup actually shown after its tab became a background tab, letting the widget appear over the active (foreground) tab for UI spoofing.

The patch closes the TOCTOU by adding, in the show path, a guard if (GetVisibility() != Visibility::VISIBLE) that calls render_widget_host_impl->ShutdownAndDestroyWidget(true) and returns before the popup is positioned and displayed. Because the guard runs before the exclusion-area/bounds computation and the actual show, a background tab can no longer surface a popup. This is a tightening that adds a second, use-time visibility check to match the existing creation-time one.

Key insight
Visibility was checked only at popup creation, not at popup show time, so a renderer could race a tab into the background and still display the widget; the fix re-verifies GetVisibility() == Visibility::VISIBLE in the show path and destroys the widget otherwise.

Attack Path

  1. Compromise a renderer The attacker controls a renderer process (the threat model stated in the commit) able to drive popup-widget creation and showing.
  2. Create a popup while visible The tab is foreground/VISIBLE, so CreateNewPopupWidget’s visibility check passes and a PopupWidgetHost is created.
  3. Background the tab before show Between creation and the show call, the tab stops being VISIBLE (e.g. the user switches tabs or the renderer arranges the timing), exploiting the TOCTOU gap.
  4. Show over the active tab The old ShowCreatedWidget displayed the popup without re-checking visibility, so the widget renders over the now-foreground, different tab.
  5. Spoof UI The misattributed popup constitutes incorrect security UI, potentially misleading the user about its origin.

Impact Assessment

A compromised renderer could show a popup widget from a background tab over the active tab, yielding a browser-UI spoofing condition; the gain is deceptive UI, not memory corruption. The precondition is an already-compromised renderer plus the timing window where the tab transitions out of VISIBLE between popup creation and show. The vendor advisory rates this medium and does not report in-the-wild exploitation; no escalation beyond UI spoofing is demonstrated by the patch.

Files Changed

  • content/browser/web_contents/web_contents_impl.cc

Audit Directions

  • Check-at-create, use-later
    Audit any security-relevant precondition (visibility, focus, activation, permission) validated when an object is created but relied upon when it is later shown/used; re-validate at the point of use.
  • Renderer-controlled timing windows
    Treat gaps between a browser-side allocation and its activation as attacker-controllable under a compromised-renderer model, and look for other widget/dialog show paths lacking a use-time visibility check.
  • Popup and overlay surfaces
    Review sibling popup/overlay show routines (pickers, tooltips, fullscreen/PiP surfaces) for symmetric visibility enforcement between their create and show paths.
From 948f7d11cebca4b17171e7038d93d311db2c5cfa Mon Sep 17 00:00:00 2001
From: Keishi Hattori <[email protected]>
Date: Mon, 02 Mar 2026 23:50:36 -0800
Subject: [PATCH] Prevent background tabs from showing PopupWidgetHosts

A compromised renderer may try to show a popup to do UI spoofing.

Currently, Chromium checks if a tab is visible when *creating* a popup
(in CreateNewPopupWidget), but it does not re-verify visibility when
the popup is actually *shown* (in ShowCreatedWidget).

This CL adds the check to ShowCreatedWidget to close the
TOCTOU vulnerability.

Bug: 424995036
Change-Id: Ib45df5cf566a95d1da48b386ad55976d2d2366fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7593475
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: Keishi Hattori <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1593031}
---

diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 64bc568..a4f4036 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -5776,6 +5776,13 @@
   }
 
   RenderWidgetHostImpl* render_widget_host_impl = widget_host_view->host();
+
+  // A background tab cannot show a popup over the active tab.
+  if (GetVisibility() != Visibility::VISIBLE) {
+    render_widget_host_impl->ShutdownAndDestroyWidget(true);
+    return;
+  }
+
   auto permission_exclusion_area_bounds =
       PermissionControllerImpl::FromBrowserContext(GetBrowserContext())
           ->GetExclusionAreaBoundsInScreen(outermost_web_contents);
Loading diff…

Original Bug Report

reported by [email protected]

Compromised renderer can spoof trusted browser UI via PopupWidgetHost

VULNERABILITY DETAILS

The PopupWidgetHost interface [1] allows a renderer to create a popup window, intended for features like dropdown menus. If the renderer is compromised, then it can set arbitrary bounds on the popup window, and paint arbitrary content in it. This allows the attacker to spoof trusted browser UI such as the omnibox, tricking the user into thinking they’re on a different origin.

For simplicity, the attached poc.patch hardcodes the popup window bounds. A real attacker would likely want to adjust the bounds according to the browser window position (window.screenX, window.screenY), the browser window width (window.outerWidth), and the height of the browser UI (window.outerHeight - window.innerHeight). They may also choose to only cover the omnibox, to avoid the need to spoof other UI elements like the tabstrip.

Similarly, the attached poc.patch hardcodes a bitmap to use as the popup window content. A real attacker may generate the content more dynamically so as to match the user’s OS and respond to user input.

The attached poc.patch also doesn’t prevent the user from closing the popup, for example by clicking the page. A real attacker could prevent that by ignoring such clicks [2], or they could simply show a new popup whenever the current one gets closed.

Other examples of what the attacker could use this for include:

  • Spoof other security-sensitive UI such as permission prompts, tricking the user into thinking they’re granting permission for some other feature or for some other origin.
  • Spoof content or security-sensitive UI relating to other tabs/windows, including those displaying content from other origins. Note that although creating a popup window requires the originating page to be visible [3], showing an already-created popup window doesn’t have that requirement [4], so such spoofs are possible even if the attacker’s page is invisible or occluded (bypassing the fix for https://crbug.com/41494315), by showing the popup window after the user switches to another tab, or by showing it over an auxiliary tab/window created by the attacker via window.open.
  • Spoof a credential prompt in its entirety, for example by showing a fake OAuth popup window, or a fake HTTP authentication prompt, or a fake OS-level prompt for user/network credentials.
  • Cover the entire screen with an uncloseable attacker-controlled window, perhaps claiming to be ransomware.

VERSION

Chrome Version: 137.0.7151.104 stable
Operating System: Windows 10.0.19045

REPRODUCTION CASE

  1. Build Chromium with the attached poc.patch, which simulates a compromised renderer.
  2. Go to https://www.example.com.
  3. Observe that the URL in the omnibox changes to https://www.google.com, despite the content area displaying https://www.example.com.

BISECT

With the attached poc.patch I can repro on at least:

  • Chromium 139.0.7237.0 r1473483 (recent ToT)
  • Chromium 137.0.7151.104
  • Chromium 108.0.5359.0 r1058968

So the bug has existed for at least a few years and it looks like it impacts all active Chrome release channels including current Chrome 137.0.7151.104 stable.

I tested on Windows but I suspect this applies to other platforms too.

FIX

A potential fix could be to restrict popup windows to the content area (like Firefox does, at least for dropdown menus), and deny attempts to show a popup window if the originating page isn’t active.

CREDIT INFORMATION

Reporter credit: Anonymous

[1] https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/third_party/blink/public/mojom/page/widget.mojom#229
[2] https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/third_party/blink/renderer/core/input/event_handler.cc#844
[3] https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/content/browser/web_contents/web_contents_impl.cc#5446
[4] https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/content/browser/web_contents/web_contents_impl.cc#5582

View on issue tracker