Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Fullscreen UI
DescriptionIncorrect security UI in Fullscreen UI
ComponentFullscreen UI
Bug ClassLogic Error
Tracker390571618
Fix commit5eb4b823dced (chromium/src) +71/-5
CISA KEVNot listed
Creditedsyrf
Disclosed2025-10-28

Files Changed

  • chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
  • chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
  • chrome/browser/ui/views/permissions/permission_prompt_bubble.h
From 5eb4b823dced000b8d61678f1c885f38c2dc4fbe Mon Sep 17 00:00:00 2001
From: Keren Zhu <kerenzhu@chromium.org>
Date: Mon, 08 Sep 2025 10:57:48 -0700
Subject: [PATCH] Exit content fullscreen when a permission prompt bubble is shown.

In content fullscreen there is almost no browser UI. Showing a
permission prompt bubble is therefore prone to spoofing attacks. This CL
exits the content fullscreen on showing a permission bubble. It also
prevents re-entering content fullscreen while the permission prompt
bubble is visible.

Fixed: 390571618
Change-Id: I87c1a58ebd68a39d0beda8a5cf056de89f92d679
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6921231
Reviewed-by: Tom Lukaszewicz <tluk@chromium.org>
Commit-Queue: Keren Zhu <kerenzhu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1512541}
---

diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
index 40e862f9..35fb551 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
@@ -34,6 +34,7 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/metrics/content/subprocess_metrics_provider.h"
 #include "components/permissions/permission_request_manager.h"
+#include "components/permissions/test/mock_permission_request.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
@@ -745,6 +746,48 @@
   }
 }
 
+// Tests that showing a permission prompt bubble exits tab fullscreen.
+IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
+                       PermissionPromptExitsTabFullscreen) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  FullscreenController* fullscreen_controller = browser()
+                                                    ->GetFeatures()
+                                                    .exclusive_access_manager()
+                                                    ->fullscreen_controller();
+
+  // Enter tab fullscreen.
+  ToggleTabFullscreen(true);
+  ui_test_utils::FullscreenWaiter(browser(), {.tab_fullscreen = true}).Wait();
+
+  // Request a permission to show the bubble, which should exit fullscreen.
+  permissions::PermissionRequestManager* permission_request_manager =
+      permissions::PermissionRequestManager::FromWebContents(web_contents);
+  permission_request_manager->AddRequest(
+      web_contents->GetPrimaryMainFrame(),
+      std::make_unique<permissions::MockPermissionRequest>(
+          permissions::RequestType::kGeolocation));
+
+  ui_test_utils::FullscreenWaiter(browser(), {.tab_fullscreen = false}).Wait();
+  ASSERT_FALSE(fullscreen_controller->IsTabFullscreen());
+
+  // While bubble is showing, tab fullscreen cannot be entered.
+  EXPECT_THAT(content::EvalJs(web_contents,
+                              "document.documentElement.requestFullscreen()"),
+              content::EvalJsResult::IsError());
+  ASSERT_FALSE(fullscreen_controller->IsTabFullscreen());
+
+  // Accept the permission request to close the bubble.
+  permission_request_manager->Accept();
+
+  // Now we should be able to enter tab fullscreen again.
+  EXPECT_THAT(content::EvalJs(web_contents,
+                              "document.documentElement.requestFullscreen()"),
+              content::EvalJsResult::IsOk());
+  ASSERT_TRUE(fullscreen_controller->IsTabFullscreen());
+}
+
 // Tests ToggleFullscreenModeForTab always causes window to change.
 IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
                        ToggleFullscreenModeForTab) {
@@ -1276,11 +1319,15 @@
 
     auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
 
-    // Auto-accept Window Management permission prompts.
-    permissions::PermissionRequestManager* permission_request_manager =
-        permissions::PermissionRequestManager::FromWebContents(tab);
-    permission_request_manager->set_auto_response_for_test(
-        permissions::PermissionRequestManager::ACCEPT_ALL);
+    // Grant Window Management permission prompts.
+    // Don't use PermissionRequestManager::set_auto_response_for_test() because
+    // it shows the permission bubble before granting the permission, which will
+    // cause content fullscreen to exit due to security reason.
+    auto* content_settings =
+        HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+    content_settings->SetContentSettingDefaultScope(
+        url, GURL(), ContentSettingsType::WINDOW_MANAGEMENT,
+        CONTENT_SETTING_ALLOW);
 
     return tab;
   }
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
index b1286e5..8aa8c90 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
@@ -6,12 +6,16 @@
 
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
+#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_view_factory.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_style.h"
 #include "components/permissions/features.h"
 #include "content/public/browser/web_contents.h"
+#include "ui/display/types/display_constants.h"
 
 PermissionPromptBubble::PermissionPromptBubble(
     Browser* browser,
@@ -36,6 +40,16 @@
 }
 
 void PermissionPromptBubble::ShowBubble() {
+  FullscreenController* fullscreen_controller = browser()
+                                                    ->GetFeatures()
+                                                    .exclusive_access_manager()
+                                                    ->fullscreen_controller();
+  CHECK(fullscreen_controller);
+  if (fullscreen_controller->IsTabFullscreen()) {
+    fullscreen_blocker_ =
+        web_contents()->ForSecurityDropFullscreen(display::kInvalidDisplayId);
+  }
+
   raw_ptr<PermissionPromptBubbleBaseView> prompt_bubble =
       CreatePermissionPromptBubbleView(browser(), delegate()->GetWeakPtr(),
                                        PermissionPromptStyle::kBubbleOnly);
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble.h b/chrome/browser/ui/views/permissions/permission_prompt_bubble.h
index ba10ff62..3e60b72 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble.h
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble.h
@@ -53,6 +53,11 @@
 
   base::ScopedClosureRunner disallowed_custom_cursors_scope_;
 
+  // Used to prevent the tab from entering content fullscreen mode while the
+  // permission prompt bubble is visible. The content fullscreen mode has no
+  // browser toolbar UI and therefore is prone to spoofing attacks.
+  base::ScopedClosureRunner fullscreen_blocker_;
+
   base::WeakPtrFactory<PermissionPromptBubble> weak_factory_{this};
 };
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
index 40e862f9..35fb551 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
@@ -34,6 +34,7 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/metrics/content/subprocess_metrics_provider.h"
 #include "components/permissions/permission_request_manager.h"
+#include "components/permissions/test/mock_permission_request.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
@@ -745,6 +746,48 @@
   }
 }
 
+// Tests that showing a permission prompt bubble exits tab fullscreen.
+IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
+                       PermissionPromptExitsTabFullscreen) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  FullscreenController* fullscreen_controller = browser()
+                                                    ->GetFeatures()
+                                                    .exclusive_access_manager()
+                                                    ->fullscreen_controller();
+
+  // Enter tab fullscreen.
+  ToggleTabFullscreen(true);
+  ui_test_utils::FullscreenWaiter(browser(), {.tab_fullscreen = true}).Wait();
+
+  // Request a permission to show the bubble, which should exit fullscreen.
+  permissions::PermissionRequestManager* permission_request_manager =
+      permissions::PermissionRequestManager::FromWebContents(web_contents);
+  permission_request_manager->AddRequest(
+      web_contents->GetPrimaryMainFrame(),
+      std::make_unique<permissions::MockPermissionRequest>(
+          permissions::RequestType::kGeolocation));
+
+  ui_test_utils::FullscreenWaiter(browser(), {.tab_fullscreen = false}).Wait();
+  ASSERT_FALSE(fullscreen_controller->IsTabFullscreen());
+
+  // While bubble is showing, tab fullscreen cannot be entered.
+  EXPECT_THAT(content::EvalJs(web_contents,
+                              "document.documentElement.requestFullscreen()"),
+              content::EvalJsResult::IsError());
+  ASSERT_FALSE(fullscreen_controller->IsTabFullscreen());
+
+  // Accept the permission request to close the bubble.
+  permission_request_manager->Accept();
+
+  // Now we should be able to enter tab fullscreen again.
+  EXPECT_THAT(content::EvalJs(web_contents,
+                              "document.documentElement.requestFullscreen()"),
+              content::EvalJsResult::IsOk());
+  ASSERT_TRUE(fullscreen_controller->IsTabFullscreen());
+}
+
 // Tests ToggleFullscreenModeForTab always causes window to change.
 IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
                        ToggleFullscreenModeForTab) {
@@ -1276,11 +1319,15 @@
 
     auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
 
-    // Auto-accept Window Management permission prompts.
-    permissions::PermissionRequestManager* permission_request_manager =
-        permissions::PermissionRequestManager::FromWebContents(tab);
-    permission_request_manager->set_auto_response_for_test(
-        permissions::PermissionRequestManager::ACCEPT_ALL);
+    // Grant Window Management permission prompts.
+    // Don't use PermissionRequestManager::set_auto_response_for_test() because
+    // it shows the permission bubble before granting the permission, which will
+    // cause content fullscreen to exit due to security reason.
+    auto* content_settings =
+        HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+    content_settings->SetContentSettingDefaultScope(
+        url, GURL(), ContentSettingsType::WINDOW_MANAGEMENT,
+        CONTENT_SETTING_ALLOW);
 
     return tab;
   }
Loading diff…

Original Bug Report

reported by sy...@gmail.com

Misuse of Permission Dialog Dismiss to Deceive Users About Fullscreen Status

Steps to reproduce the problem

  1. Victim visit: https://syarifmsajjad.github.io/test-spoofing/fs-escape.html and click to enter fullscreen
  2. Press “Esc”. “Insert omnibox here” text should appear. An attacker can use this opportunity to trick the user into thinking that they have exited fullscreen and draw a fake omnibox once the user pressed “Esc”

Problem Description

In Chrome, the permission dialog can be closed using the “Esc” key, which also serves to exit fullscreen mode. When the permission dialog appears after a website takes the user into fullscreen mode, if the user presses “Esc” afterwards, the input will be redirected to the permission dialog instead of exiting fullscreen. This allows attackers to insert a fake omnibox and trick users into thinking that they have exited fullscreen mode. With this fake omnibox, the attacker can perform URL spoofing of other websites.

Expected result Pressing the “Esc” key while in fullscreen mode with a permission dialog open should exit fullscreen mode without redirecting input to the permission dialog. The browser should prioritize exiting fullscreen mode to ensure consistent and secure user experience, preventing potential UI spoofing vulnerabilities.

Actual result When the victim presses the “Esc” key while the permission dialog is displayed in fullscreen mode, instead of exiting fullscreen, the input is redirected to the permission dialog. This allows the attacker to insert a fake omnibox, tricking the user into believing they have exited fullscreen mode.

Summary

Misuse of Permission Dialog Dismiss to Deceive Users About Fullscreen Status

Custom Questions

Type of crash:

1

Crash state:

1

Reporter credit:

syrf

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A

View on issue tracker