CVE-2026-87558
Overview
Background
- Web modal dialog
- A dialog scoped to a single tab’s
WebContents; when shown,SetWebContentsBlockedmarks the tab blocked and adjusts its state (including dropping HTML fullscreen). - `WebContents::ExitFullscreen`
- Leaves HTML fullscreen, which resizes the browser window and, on Windows, can synchronously dispatch native window-management messages (e.g.
WM_WINDOWPOSCHANGED) directly through the window procedure (WndProc). - `WeakPtr` / `GetWeakPtr`
- A non-owning pointer that automatically becomes null when its referent is destroyed, so a task bound to it silently no-ops instead of dereferencing a freed object.
- Reentrancy UAF
- A use-after-free that occurs when a synchronous call runs nested code that destroys an object still referenced by an outer, not-yet-unwound stack frame.
Root Cause Analysis
Per the commit description, the edited code lives in BrowserWindowModalDialogDelegate::SetWebContentsBlocked (the @@ hunk header does not name the enclosing function, so this attribution comes from the commit message, not the diff). When a web modal dialog is shown for a tab that is in HTML fullscreen and lacks AUTOMATIC_FULLSCREEN allow permission, the code called web_contents->ExitFullscreen(true) synchronously while the modal-dialog presentation call frame was still on the stack. On Windows, ExitFullscreen triggers a window resize that dispatches native messages directly via WndProc, which can reentrantly tear down the WebContents and the objects that initiated the dialog (dialog controllers, PaymentRequestState); when control returns up the stack, the presentation code continues to touch those now-freed objects. The invariant violated is that an object must outlive the call frame that is still operating on it.
The fix posts ExitFullscreen as a separate task on the UI thread bound to web_contents->GetWeakPtr(), so it runs only after the current synchronous frame fully unwinds, and is skipped entirely if the WebContents has already been destroyed.
WeakPtr breaks the reentrancy and neutralizes the dangling reference.Attack Path
- Enter HTML fullscreen
A page (renderer-controlled) puts its tab into HTML fullscreen without holding the
AUTOMATIC_FULLSCREENallow content setting. - Trigger a web modal dialog
The page initiates a flow that shows a tab-modal dialog (e.g. a Payments/
PaymentRequestUI), causingSetWebContentsBlockedto run and reach the synchronousweb_contents->ExitFullscreen(true)call. - Reentrant destruction
ExitFullscreenresizes the window and, on Windows, synchronously dispatchesWM_WINDOWPOSCHANGEDthroughWndProc, which reentrantly destroys theWebContentsand the initiating objects such asPaymentRequestState. - Use-after-free
As the modal-presentation stack unwinds, still-live frames dereference the freed
WebContents/controller objects, producing the UAF. (inferred) Any further exploitation into a controlled read/write or code execution is not shown by the patch.
Impact Assessment
AUTOMATIC_FULLSCREEN permission and that the resize triggers synchronous native window messages (notably on Windows). Escalation beyond the browser-process UAF to information disclosure or code execution is (inferred) and not demonstrated by the diff; the advisory reports this as a use-after-free in Payments and does not state in-the-wild exploitation.Files Changed
chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc
Audit Directions
- Synchronous fullscreen/window-state changes during UI presentationAudit other call sites that invoke
ExitFullscreen, window resize, or bounds changes while a dialog, bubble, or modal is being shown, since these can reentrantly dispatch native window messages. - Raw pointers across potentially-reentrant callsLook for code holding raw
WebContents*or controller pointers across any call that can pump messages or run nested loops, and requireWeakPtrguards or post-task deferral instead. - `SetWebContentsBlocked` and blocking-state handlersReview other implementations that react to a tab being blocked/unblocked (across dialog managers and payments/permission flows) for synchronous side effects that assume the
WebContentsand caller survive the handler.
Patch
From f93f6b65c55222664e2c3672a11dc3ee6f81eaec Mon Sep 17 00:00:00 2001 From: Keren Zhu <[email protected]> Date: Tue, 01 Sep 2026 17:08:27 -0700 Subject: [PATCH] Defer ExitFullscreen when blocking WebContents for modal dialogs When BrowserWindowModalDialogDelegate::SetWebContentsBlocked is called upon showing a modal dialog (e.g. via ShowWebModalDialogViews or TabDialogManager::ShowDialog), it drops HTML fullscreen for the tab. Calling ExitFullscreen synchronously can trigger direct WndProc calls (e.g., WM_WINDOWPOSCHANGED on Windows). This can lead to the WebContents and caller objects (e.g., dialog controllers, PaymentRequestState) being destroyed synchronously while modal dialog presentation is still on the stack, resulting in use-after-free crashes. This CL posts a task to the UI thread to defer the call to ExitFullscreen() using a WeakPtr, ensuring the current synchronous dialog presentation call frame completely unwinds before fullscreen exit occurs. TAG=agy CONV=bce8c045-c1e7-4d88-a581-ff5b1f83ff3c Bug: 553700283, 540138836, 553128689, 554558968, 553129531 Change-Id: I48e11121ff1a9e33c05705df1869bd749f05b6ee Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8328511 Reviewed-by: Allen Bauer <[email protected]> Reviewed-by: Thomas Lukaszewicz <[email protected]> Commit-Queue: Keren Zhu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1690328} --- diff --git a/chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc b/chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc index 96c238d..fd0a7db0 100644 --- a/chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc +++ b/chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc @@ -4,6 +4,7 @@ #include "chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.h" +#include "base/functional/bind.h" #include "base/types/to_address.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/devtools/devtools_ui_controller.h" @@ -20,6 +21,8 @@ #include "components/content_settings/core/common/content_settings.h" #include "components/content_settings/core/common/content_settings_types.h" #include "components/web_modal/web_contents_modal_dialog_host.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" DEFINE_USER_DATA(BrowserWindowModalDialogDelegate); @@ -73,7 +76,14 @@ if (content_settings->GetContentSetting( url, url, ContentSettingsType::AUTOMATIC_FULLSCREEN) != CONTENT_SETTING_ALLOW) { - web_contents->ExitFullscreen(true); + // Defer exiting fullscreen to prevent synchronous window management + // messages (e.g. direct WndProc calls on Windows) from destroying the + // WebContents or callers while modal dialog presentation is on the + // stack. + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&content::WebContents::ExitFullscreen, + web_contents->GetWeakPtr(), + /*will_cause_resize=*/true)); } } }