Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in UI
DescriptionUI misrepresentation in UI
ComponentUI
Bug ClassLogic Error
Tracker514011926
Fix commitc73d532470b3 (chromium/src) +16/-14
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
if
chrome/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.cc
  • chrome/browser/ui/views/media_router/media_router_dialog_controller_views_interactive_uitest.cc
From c73d532470b3d530826dc5f8d8aea2135d9b172f Mon Sep 17 00:00:00 2001
From: Paul Jensen <pauljensen@chromium.org>
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 <pauljensen@chromium.org>
Auto-Submit: Paul Jensen <pauljensen@chromium.org>
Reviewed-by: Caroline Rising <corising@chromium.org>
Commit-Queue: Caroline Rising <corising@chromium.org>
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()
Loading diff…

Regression Test / PoC

shipped with the fix
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()
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • chrome/browser/ui/views/media_router/cast_dialog_coordinator.cc
  • chrome/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)

  1. Navigate to a malicious page that requests fullscreen after a short delay (e.g., using setTimeout).
  2. Interact with the page (e.g., right-click) to grant it transient user activation.
  3. Open the Cast dialog via the context menu or browser toolbar.
  4. 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.
  5. 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.

View on issue tracker