CVE-2026-5880
Overview
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
WebContentsis the foreground/active tab; a background tab is notVISIBLE. - `ShowCreatedWidget`
- per the commit, the
WebContentsImplroutine 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.
GetVisibility() == Visibility::VISIBLE in the show path and destroys the widget otherwise.Attack Path
- Compromise a renderer The attacker controls a renderer process (the threat model stated in the commit) able to drive popup-widget creation and showing.
- Create a popup while visible
The tab is foreground/
VISIBLE, soCreateNewPopupWidget’s visibility check passes and aPopupWidgetHostis created. - 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. - Show over the active tab
The old
ShowCreatedWidgetdisplayed the popup without re-checking visibility, so the widget renders over the now-foreground, different tab. - Spoof UI The misattributed popup constitutes incorrect security UI, potentially misleading the user about its origin.
Impact Assessment
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-laterAudit 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 windowsTreat 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 surfacesReview sibling popup/overlay show routines (pickers, tooltips, fullscreen/PiP surfaces) for symmetric visibility enforcement between their create and show paths.
Patch
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);
Original Bug Report
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
- Build Chromium with the attached poc.patch, which simulates a compromised renderer.
- Go to https://www.example.com.
- 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
- https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/content/browser/web_contents/web_contents_impl.cc#5446
- https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/content/browser/web_contents/web_contents_impl.cc#5582
- https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/third_party/blink/public/mojom/page/widget.mojom#229
- https://chromium.googlesource.com/chromium/src.git/+/186d68e449ac3356147d86062d7e25be371a283b/third_party/blink/renderer/core/input/event_handler.cc#844
- https://crbug.com/41494315
- https://www.example.com
- https://www.google.com