CVE-2026-87559
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc |
modified |
Files Changed
chrome/browser/ui/views/media_router/media_router_dialog_controller_views.ccchrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc
Patch
From c73d532470b3d530826dc5f8d8aea2135d9b172f Mon Sep 17 00:00:00 2001 From: Paul Jensen <[email protected]> Date: Mon, 03 Aug 2026 06:35:52 -0700 Subject: [PATCH] [Media Router] Block tab fullscreen while Cast dialog is showing CreateMediaRouterDialog only acquired a tab-fullscreen blocker when the initiator was already tab-fullscreen at open time and had an associated browser. Acquire the blocker unconditionally so the page also cannot enter tab fullscreen after the dialog is shown, matching the permission-prompt and chooser-bubble callers. The blocker is released in Reset() as before. Fixed: 514011926 Change-Id: I23839739489115dc16668e61135835ee8fbe8774 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8186138 Commit-Queue: Paul Jensen <[email protected]> Auto-Submit: Paul Jensen <[email protected]> Reviewed-by: Caroline Rising <[email protected]> Commit-Queue: Caroline Rising <[email protected]> Cr-Commit-Position: refs/heads/main@{#1672646} --- diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc index 569e0a0..3b4a81aa 100644 --- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc +++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc @@ -72,20 +72,12 @@ // Block tab fullscreen. There is no toolbar to anchor the cast dialog to in // tab fullscreen mode. It is unsafe to show the dialog entirely within the // content area, as this would make it susceptible to spoofing attacks. - if (browser) { - ExclusiveAccessManager* exclusive_access_manager = - ExclusiveAccessManager::From(browser); - FullscreenController* fullscreen_controller = - exclusive_access_manager->fullscreen_controller(); - if (fullscreen_controller->IsTabFullscreen()) { - auto blocker = - initiator()->ForSecurityDropFullscreen(display::kInvalidDisplayId); - if (!blocker) { - return; - } - fullscreen_blocker_ = std::move(*blocker); - } + auto blocker = + initiator()->ForSecurityDropFullscreen(display::kInvalidDisplayId); + if (!blocker) { + return; } + fullscreen_blocker_ = std::move(*blocker); BrowserView* browser_view = browser ? BrowserView::GetBrowserViewForBrowser(browser) : nullptr; diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc index e454ac2..cffdbb8 100644 --- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc +++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc @@ -2,24 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h" + #include "base/memory/raw_ptr.h" #include "base/run_loop.h" #include "build/build_config.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" +#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/frame/top_container_view.h" #include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h" #include "chrome/browser/ui/views/media_router/cast_dialog_coordinator.h" #include "chrome/browser/ui/views/media_router/cast_dialog_view.h" -#include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "components/media_router/browser/media_router_metrics.h" #include "components/media_router/browser/presentation/start_presentation_context.h" #include "components/media_router/common/mojom/media_router.mojom.h" #include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/page_transition_types.h" #include "ui/views/test/widget_test.h" @@ -103,6 +107,12 @@ .GetCastDialogWidget(); ASSERT_TRUE(widget); EXPECT_TRUE(widget->HasObserver(dialog_controller_)); + FullscreenController* fullscreen_controller = + ExclusiveAccessManager::From(browser())->fullscreen_controller(); + // While the dialog is showing, tab fullscreen cannot be entered. + EXPECT_FALSE(content::ExecJs(initiator_, + "document.documentElement.requestFullscreen()")); + EXPECT_FALSE(fullscreen_controller->IsTabFullscreen()); dialog_controller_->CloseMediaRouterDialog(); EXPECT_FALSE(dialog_controller_->IsShowingMediaRouterDialog()); EXPECT_EQ(dialog_controller_->GetCastDialogCoordinatorForTesting()
Regression Test / PoC
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc
index e454ac2..cffdbb8 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc
@@ -2,24 +2,28 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h"
+
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
+#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_coordinator.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
-#include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/media_router/browser/media_router_metrics.h"
#include "components/media_router/browser/presentation/start_presentation_context.h"
#include "components/media_router/common/mojom/media_router.mojom.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#include "ui/views/test/widget_test.h"
@@ -103,6 +107,12 @@
.GetCastDialogWidget();
ASSERT_TRUE(widget);
EXPECT_TRUE(widget->HasObserver(dialog_controller_));
+ FullscreenController* fullscreen_controller =
+ ExclusiveAccessManager::From(browser())->fullscreen_controller();
+ // While the dialog is showing, tab fullscreen cannot be entered.
+ EXPECT_FALSE(content::ExecJs(initiator_,
+ "document.documentElement.requestFullscreen()"));
+ EXPECT_FALSE(fullscreen_controller->IsTabFullscreen());
dialog_controller_->CloseMediaRouterDialog();
EXPECT_FALSE(dialog_controller_->IsShowingMediaRouterDialog());
EXPECT_EQ(dialog_controller_->GetCastDialogCoordinatorForTesting()
Original Bug Report
Potential UI spoofing in Cast dialog via fullscreen transition
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The Media Router (Cast) dialog fails to unconditionally acquire a fullscreen blocker, allowing a page to enter fullscreen mode after the dialog is requested. This hides the browser’s top-level UI and allows a malicious site to spoof the security context around the trusted dialog.
Affected files:
chrome/browser/ui/views/media_router/media_router_dialog_controller_views.ccchrome/browser/ui/views/media_router/cast_dialog_coordinator.ccchrome/browser/ui/views/toolbar/webui_pinned_toolbar_actions.cc
Estimated timestamp from git blame: 2026-04-13
A potential vulnerability exists in the Media Router dialog implementation where it fails to properly block fullscreen transitions, leading to possible UI spoofing.
In MediaRouterDialogControllerViews::CreateMediaRouterDialog (chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc), a fullscreen blocker is only acquired if the tab is already in fullscreen mode at the time the dialog is created:
if (browser) {
// ...
if (fullscreen_controller->IsTabFullscreen()) {
auto blocker =
initiator()->ForSecurityDropFullscreen(display::kInvalidDisplayId);
if (!blocker) {
return;
}
fullscreen_blocker_ = std::move(*blocker);
}
}
Because this check is conditional on IsTabFullscreen(), a malicious page can request and enter fullscreen mode after the Cast dialog has been requested but before it is fully displayed, or even while it is already visible. When the tab enters fullscreen, the browser’s top-level chrome (including the Omnibox and security indicators) is hidden. The page can then render a spoofed browser environment (e.g., a fake Omnibox with a trusted origin) behind and around the legitimate Cast dialog.
This is particularly concerning for the Cast dialog’s default ‘Cast tab’ mode, which does not display the origin of the tab being cast, relying instead on the browser’s Omnibox for origin verification. An attacker could trick a user into believing they are casting a trusted site while they are actually interacting with a malicious one.
Furthermore, the ‘Anchor Fallback’ path (lines 108-117), which is used when no browser window is found for the initiator, skips the fullscreen blocker logic entirely, despite a comment in the source code (line 73) explicitly noting that showing the dialog in the content area without a toolbar is ‘susceptible to spoofing attacks.’
Potential Reproduction Steps (Suggested)
- Navigate to a malicious page that requests fullscreen after a short delay (e.g., using
setTimeout). - Interact with the page (e.g., right-click) to grant it transient user activation.
- Open the Cast dialog via the context menu or browser toolbar.
- If the fullscreen request executes after the dialog is requested but while no blocker is active, the browser will grant fullscreen and hide the trusted UI.
- Observe the Cast dialog displayed over an attacker-controlled fullscreen surface with spoofed browser chrome.
Note: These steps are based on code analysis and have not been verified with a working proof of concept.
Suggested Fix
Call ForSecurityDropFullscreen unconditionally in CreateMediaRouterDialog to ensure a blocker is always acquired for the lifetime of the dialog, regardless of the initial fullscreen state or the anchoring method. This aligns with the implementation used by other security-sensitive components, such as the File System Access API.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.