CVE-2026-87474
Overview
Background
- `SetWebContentsBlocked`
- the
BrowserWindowModalDialogDelegatemethod invoked when a modal dialog is shown over a tab, which (among other things) drops the tab’s HTML fullscreen state. - `WebContents::ExitFullscreen`
- a synchronous call that tears down fullscreen presentation and, on Windows, can dispatch window-management messages (e.g.
WM_WINDOWPOSCHANGED) via directWndProccalls before returning. - Re-entrancy / synchronous destruction
- when a synchronous call triggers native window messages, those messages can run nested code that destroys the very objects still live on the current call stack.
- `GetUIThreadTaskRunner` / `PostTask` with `WeakPtr`
- the mechanism used to defer work to a fresh, fully-unwound UI-thread call frame, with a
WeakPtrguaranteeing the callback no-ops if its target was already freed.
Root Cause Analysis
When a web-modal dialog is presented (e.g. via ShowWebModalDialogViews or TabDialogManager::ShowDialog), SetWebContentsBlocked calls web_contents->ExitFullscreen(true) synchronously while the dialog-presentation call frame — including callers such as dialog controllers and PaymentRequestState — is still on the stack. On Windows, ExitFullscreen can synchronously drive window-management messages through direct WndProc invocations, which re-enter Chromium code and can destroy the WebContents and those callers before the outer presentation frame unwinds. The remaining stack then operates on freed objects, producing a use-after-free.
The fix replaces the synchronous call with content::GetUIThreadTaskRunner({})->PostTask(...) that binds WebContents::ExitFullscreen to web_contents->GetWeakPtr(), so fullscreen exit runs only after the current frame has fully unwound and is skipped entirely if the WebContents was meanwhile destroyed. The WeakPtr invalidation is what closes the UAF: a stale target simply drops the deferred call.
ExitFullscreen) from inside a still-live modal-dialog presentation frame; deferring it to a posted task guarded by a WeakPtr ensures the frame unwinds first and the call is dropped if its target no longer exists.Attack Path
- Trigger a modal dialog over a fullscreen tab
A page (e.g. a Payments flow) that has entered HTML fullscreen causes a web-modal dialog to be shown, invoking
SetWebContentsBlocked. - Synchronous fullscreen exit
SetWebContentsBlockedcallsweb_contents->ExitFullscreen(true)while the dialog-presentation frame and its callers (dialog controllers,PaymentRequestState) remain on the stack. - Re-entrant destruction
ExitFullscreendispatches native window-management messages (e.g.WM_WINDOWPOSCHANGEDvia directWndProc) that re-enter browser code and synchronously destroy theWebContentsand/or those caller objects (inferred, per the commit message). - Use-after-free
Control returns to the still-executing presentation frame, which dereferences the now-freed
WebContents/caller objects, causing the use-after-free crash.
Impact Assessment
(inferred). Preconditions include the tab being in HTML fullscreen without AUTOMATIC_FULLSCREEN set to CONTENT_SETTING_ALLOW, and, per the commit message, the platform delivering synchronous window messages (e.g. Windows).Files Changed
chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc
Audit Directions
- Synchronous re-entrant window/OS calls from live framesAudit other sites that call
ExitFullscreen, resize, or otherwise trigger nativeWndProc/window-management messages while a caller object is still on the stack; these can synchronously destroy objects mid-frame. - Raw pointers across modal-dialog presentationReview modal-dialog show paths (
SetWebContentsBlocked,ShowWebModalDialogViews,TabDialogManager::ShowDialog) forWebContents/controller pointers used after any callout that can nest a run loop or dispatch platform messages. - Deferring vs. WeakPtr guardingLook for callbacks bound with raw pointers (rather than
GetWeakPtr()) that are posted from teardown-capable code paths, where the target may be freed before the task runs.
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)); } } }