CVE-2026-102314
Overview
Background
- `TabDialogManager` (TDM)
- the object that hosts a tab-modal dialog widget for a tab and manages the input-blocking state of that tab’s web contents while the dialog is up.
- `SetWebContentsBlocked`
- a capability method that toggles whether a tab’s web contents accepts input; blocking it is how a modal dialog prevents the page underneath from being interacted with.
- `params_->disable_input`
- the flag recording whether this dialog instance requested that the tab’s web contents be input-disabled when the dialog was shown.
- Legacy dialog
- an older, non-TDM dialog path that can independently set the same web-contents-blocked bit on the same tab.
Root Cause Analysis
When a tab-modal dialog widget was destroyed, TabDialogManager::WidgetDestroyed unconditionally called SetWebContentsBlocked(..., /*blocked=*/false), re-enabling input on the tab’s web contents. This assumed the TDM was always the party that had blocked the contents, but the blocked bit is shared: a concurrent legacy dialog could have set it while the TDM’s own dialog did not request input disabling (params_->disable_input false). In that case the TDM’s teardown blindly clobbered the shared state, unblocking web contents that another still-present dialog expected to remain blocked, an unbalanced state reset.
The fix captures did_block_web_contents = params_ && params_->disable_input before resetting params_, and only clears the blocked bit when the TDM had actually set it, so foreign dialog state is left intact.
did_block_web_contents so the TDM only undoes the blocking it itself performed.Attack Path
- Establish a legacy-dialog block
A page or flow triggers a legacy (non-TDM) dialog that calls
SetWebContentsBlocked(true)on the target tab, correctly disabling input on the underlying web contents. - Layer a non-disabling TDM dialog
A second, TDM-managed dialog is shown on the same tab whose
params_->disable_inputis false, so the TDM never touches the blocked bit on show. (inferred) - Dismiss the TDM dialog
When that TDM widget is destroyed,
WidgetDestroyedruns and, pre-patch, unconditionally callsSetWebContentsBlocked(false), clearing the block the legacy dialog still relies on. - Interact past a live dialog The web contents is now input-enabled while a modal dialog remains on screen, letting the page underneath be interacted with / mispresented, i.e. UI misrepresentation in the TabStrip per the advisory.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/tabs/tab_dialog_manager.cc |
modified |
Files Changed
chrome/browser/ui/tabs/tab_dialog_manager.cc
Audit Directions
- Shared boolean/flag ownershipLook for teardown paths that unconditionally reset a shared UI or security state bit without first confirming this object was the one that set it.
- Modality lifecycle racesAudit places where multiple dialog implementations (legacy vs. new manager) can co-exist on one tab/window and both mutate the same input-blocking or visibility state.
- Capabilities()->Set... resetsReview other callers of
capabilities()->SetWebContentsBlockedand similar toggles for balanced set/clear pairing tied to per-instance intent rather than a global assumption.
Patch
From 7f0f9b9fdd518e523072fabe73f6d95a4692e63b Mon Sep 17 00:00:00 2001 From: Allen Bauer <[email protected]> Date: Thu, 27 Aug 2026 12:13:19 -0700 Subject: [PATCH] [TabDialogManager] Only re-enable web contents if TDM had disabled it. This fixes an unbalanced state reset if the TDM is handling a dialog which did not disable web contents while another legacy dialog did. In that case, the TDM shouldn't blindly blast over the state controlling the web contents. Bug: 514059780 Change-Id: Iae1808523960bf874591841b74289626e9348bfa Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8307096 Reviewed-by: Keren Zhu <[email protected]> Commit-Queue: Allen Bauer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1687466} --- diff --git a/chrome/browser/ui/tabs/tab_dialog_manager.cc b/chrome/browser/ui/tabs/tab_dialog_manager.cc index 7d55c06..dcbcd60 100644 --- a/chrome/browser/ui/tabs/tab_dialog_manager.cc +++ b/chrome/browser/ui/tabs/tab_dialog_manager.cc @@ -405,15 +405,23 @@ void TabDialogManager::WidgetDestroyed(views::Widget* widget) { CHECK_EQ(widget, widget_.get()); + // Check if we had blocked the web contents in this iteration. + const bool did_block_web_contents = params_ && params_->disable_input; widget_ = nullptr; params_.reset(); tab_dialog_widget_observer_.reset(); scoped_ignore_input_events_.reset(); web_contents_modal_dialog_host_observer_.reset(); bounds_animation_.reset(); - tab_interface_->GetBrowserWindowInterface() - ->capabilities() - ->SetWebContentsBlocked(tab_interface_->GetContents(), /*blocked=*/false); + // Only clobber the blocked web contents bit if we were also the one that + // blocked it. Otherwise leave it alone, since it may have been set by some + // other legacy dialog. + if (did_block_web_contents) { + tab_interface_->GetBrowserWindowInterface() + ->capabilities() + ->SetWebContentsBlocked(tab_interface_->GetContents(), + /*blocked=*/false); + } // Resetting ScopedTabModalUI may cause the showing of a new dialog. // Leaving it at the end of the function to prevent its side effects // from being overridden.