CVE-2026-87581
Overview
Background
- Modal dialog blocking
- When a web-modal dialog is shown,
BrowserWindowModalDialogDelegate::SetWebContentsBlockedmarks the tab’sWebContentsas blocked and, as part of that, tears down HTML fullscreen for the tab. - `ExitFullscreen`
content::WebContents::ExitFullscreenrestores the browser window from HTML fullscreen, which on some platforms drives synchronous native window-management messages.- Synchronous WndProc reentry
- On Windows, resizing/restoring a window can dispatch
WM_WINDOWPOSCHANGEDdirectly into aWndProchandler on the current stack, re-entering browser code before the original call frame returns. - `WeakPtr` + `PostTask`
- Binding a method to a
base::WeakPtrand posting it tocontent::GetUIThreadTaskRunner({})runs it in a later task and cancels it automatically if the target object has been destroyed in the meantime.
Root Cause Analysis
While presenting a modal dialog, the delegate synchronously called web_contents->ExitFullscreen(true) from within the dialog-presentation code path (reached via ShowWebModalDialogViews / TabDialogManager::ShowDialog). Exiting fullscreen can synchronously dispatch native window-management messages (e.g. a direct WndProc WM_WINDOWPOSCHANGED on Windows), which re-enters browser code while the modal dialog presentation call is still unwound on the stack. That reentry can destroy the WebContents and the callers that own the presentation flow — dialog controllers and, in the Payments component, PaymentRequestState — after which the still-executing outer frame continues to touch those freed objects, producing a use-after-free.
The fix defers the fullscreen exit by posting WebContents::ExitFullscreen to the UI thread task runner bound through web_contents->GetWeakPtr(), so it only runs after the current synchronous frame fully unwinds, and is cancelled entirely if the WebContents was torn down first.
ExitFullscreen synchronously inside an active modal-dialog presentation frame, allowing native window messages to reentrantly free objects the frame still uses; deferring the call via a posted, WeakPtr-guarded task removes the reentrancy and makes the operation safe against destruction.Attack Path
- Trigger a payment/modal flow in fullscreen
A page enters HTML fullscreen and initiates a flow that shows a web-modal dialog (e.g. a Payments UI backed by
PaymentRequestState). - Dialog presentation blocks the WebContents
Showing the dialog calls
SetWebContentsBlocked, which decides to drop fullscreen and callsweb_contents->ExitFullscreen(true)synchronously. - Synchronous window message re-enters browser code
ExitFullscreendrives a native window-management message (e.g.WM_WINDOWPOSCHANGEDvia directWndProc) that runs handlers on the current stack. (inferred: platform-specific to Windows as described in the CL) - Owning objects are destroyed mid-frame
The reentrant handling tears down the
WebContentsand callers such as dialog controllers /PaymentRequestStatewhile the modal presentation frame is still active. - Freed memory is used
Control returns to the outer presentation frame, which dereferences the now-freed
WebContents/caller objects, causing the use-after-free.
Impact Assessment
AUTOMATIC_FULLSCREEN content-setting allowance and the platform delivering synchronous window messages on fullscreen exit.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, resize, or restore windows synchronously while a dialog/UI presentation frame is active, since these can reentrantly destroy the owningWebContents. - Raw pointer use across potentially-reentrant callsLook for code that holds a raw
WebContents*or controller pointer across a call capable of running nested message loops or nativeWndProchandlers, and preferWeakPtrguards or deferredPostTask. - `SetWebContentsBlocked` and modal-dialog teardown pathsReview other actions taken inside dialog blocking/unblocking (across web_modal delegates) for synchronous side effects that assume the
WebContentsand caller survive the call.
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)); } } }