Medium chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Browser
DescriptionIncorrect authorization in Browser
ComponentBrowser
Sub-componentUI>Browser
Bug ClassAuthorization Error
Tracker514070956
Fix commitc3bfffdf1a5a (chromium/src) +80/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc
modified
IN_PROC_BROWSER_TEST_P
chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
modified

Files Changed

  • chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
  • chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc
  • chrome/browser/ui/exclusive_access/fullscreen_controller.h
From c3bfffdf1a5a09d2e1893eef51508b1cbd89d494 Mon Sep 17 00:00:00 2001
From: Thomas Lukaszewicz <tluk@chromium.org>
Date: Thu, 10 Sep 2026 10:17:39 -0700
Subject: [PATCH] Use requesting origin for automatic fullscreen in modal dialogs

When a tab is blocked by a modal dialog, content fullscreen mode is
exited unless the automatic fullscreen content setting is granted.

Previously, BrowserWindowModalDialogDelegate::SetWebContentsBlocked
evaluated this setting using the top-level WebContents URL, which
checked the primary page origin rather than the specific frame
origin that initiated fullscreen.

This CL exposes the requesting frame origin on FullscreenController
and updates BrowserWindowModalDialogDelegate to query the automatic
fullscreen setting against that origin, ensuring it is non-opaque
before evaluating the permission.

TAG=agy
CONV=95848c56-47d8-4e65-9ea7-5d0b3d57c6b1

Fixed: 514070956
Change-Id: I4d84e4e2cb71fd7219686c892f2f7831efcbff8b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8384657
Commit-Queue: Thomas Lukaszewicz <tluk@chromium.org>
Reviewed-by: David Yeung <dayeung@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1695483}
---
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller.h b/chrome/browser/ui/exclusive_access/fullscreen_controller.h
index 902872e..35b8dac 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller.h
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller.h
@@ -117,6 +117,10 @@
   content::FullscreenState GetFullscreenState(
       const content::WebContents* web_contents) const;
 
+  // Returns the origin of the frame currently in tab-initiated fullscreen.
+  // Returns an opaque origin if not in tab-initiated fullscreen.
+  const url::Origin& requesting_origin() const { return requesting_origin_; }
+
   // Returns true if |web_contents| is in fullscreen mode as a screen-captured
   // tab. See 'FullscreenWithinTab Note'.
   bool IsFullscreenWithinTab(const content::WebContents* web_contents) const;
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 bfcb028..6ef238d 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
@@ -22,7 +22,9 @@
 #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/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "url/origin.h"
 
 DEFINE_USER_DATA(BrowserWindowModalDialogDelegate);
 
@@ -61,19 +63,24 @@
   // Skip browser-fullscreen, which is more expressly user-initiated.
   // Skip fullscreen-within-tab, which shows the browser frame.
   if (blocked) {
+    FullscreenController* const fullscreen_controller =
+        ExclusiveAccessManager::From(browser_)->fullscreen_controller();
     content::FullscreenState fullscreen_state =
-        ExclusiveAccessManager::From(browser_)
-            ->fullscreen_controller()
-            ->GetFullscreenState(web_contents);
+        fullscreen_controller->GetFullscreenState(web_contents);
     if (fullscreen_state.target_mode == content::FullscreenMode::kContent) {
-      // Skip URLs with the automatic fullscreen content setting granted.
-      const GURL& url = web_contents->GetLastCommittedURL();
+      // Skip origins with the automatic fullscreen content setting granted.
+      const url::Origin& requesting_origin =
+          fullscreen_controller->requesting_origin();
+      const GURL url = requesting_origin.GetURL();
       const HostContentSettingsMap* const content_settings =
           HostContentSettingsMapFactory::GetForProfile(
               web_contents->GetBrowserContext());
-      if (content_settings->GetContentSetting(
+      if (requesting_origin.opaque() ||
+          requesting_origin !=
+              web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin() ||
+          content_settings->GetContentSetting(
               url, url, ContentSettingsType::AUTOMATIC_FULLSCREEN) !=
-          CONTENT_SETTING_ALLOW) {
+              CONTENT_SETTING_ALLOW) {
         // 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
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 28e9884b..00dc22d 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
@@ -1321,6 +1321,67 @@
   EXPECT_TRUE(web_contents_->IsFullscreen());
 }
 
+IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest,
+                       BlockingContentsExitsForCrossOriginFrame) {
+#if BUILDFLAG(IS_MAC)
+  if (GetParam()) {
+    GTEST_SKIP() << "Flaky. See https://crbug.com/404887514";
+  }
+#endif
+
+  // Append a cross-origin iframe with the fullscreen permission policy.
+  const GURL src = embedded_https_test_server().GetURL("b.com", "/simple.html");
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
+  content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
+
+  EXPECT_TRUE(RequestFullscreen(/*gesture=*/false, child));
+  EXPECT_TRUE(web_contents_->IsFullscreen());
+
+  // Blocking the tab for a modal dialog exits fullscreen when a cross-origin
+  // iframe requested fullscreen, even if the top-level origin has the
+  // automatic fullscreen content setting granted.
+  BrowserWindowInterface* browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_);
+  ui_test_utils::FullscreenWaiter waiter(browser, {.tab_fullscreen = false});
+  BrowserWindowModalDialogDelegate::From(browser)->SetWebContentsBlocked(
+      web_contents_, true);
+  waiter.Wait();
+  EXPECT_FALSE(web_contents_->IsFullscreen());
+}
+
+IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest,
+                       BlockingContentsDoesNotExitForSameOriginFrame) {
+#if BUILDFLAG(IS_MAC)
+  if (GetParam()) {
+    GTEST_SKIP() << "Flaky. See https://crbug.com/404887514";
+  }
+#endif
+
+  if (GetParam()) {
+    // Isolated Web Apps in this test fixture do not serve subframe HTML files.
+    return;
+  }
+
+  // Append a same-origin iframe with the fullscreen permission policy.
+  const GURL src = embedded_https_test_server().GetURL("a.com", "/simple.html");
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
+  content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
+
+  EXPECT_TRUE(RequestFullscreen(/*gesture=*/false, child));
+  EXPECT_TRUE(web_contents_->IsFullscreen());
+
+  // Blocking the tab for a modal dialog preserves fullscreen for a same-origin
+  // iframe when the origin has the automatic fullscreen content setting
+  // granted.
+  BrowserWindowInterface* browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_);
+  BrowserWindowModalDialogDelegate::From(browser)->SetWebContentsBlocked(
+      web_contents_, true);
+  EXPECT_TRUE(web_contents_->IsFullscreen());
+}
+
 IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, QueryPermissionWithGesture) {
   // Expect an API TypeError when allowWithoutGesture is false or unspecified.
   EXPECT_EQ(
@@ -1367,7 +1428,7 @@
   web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
   content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
   EXPECT_EQ("granted", QueryPermission(child));
-  EXPECT_TRUE(RequestFullscreen(child));
+  EXPECT_TRUE(RequestFullscreen(/*gesture=*/false, child));
   EXPECT_TRUE(ExitFullscreen());
 }
 
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 28e9884b..00dc22d 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
@@ -1321,6 +1321,67 @@
   EXPECT_TRUE(web_contents_->IsFullscreen());
 }
 
+IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest,
+                       BlockingContentsExitsForCrossOriginFrame) {
+#if BUILDFLAG(IS_MAC)
+  if (GetParam()) {
+    GTEST_SKIP() << "Flaky. See https://crbug.com/404887514";
+  }
+#endif
+
+  // Append a cross-origin iframe with the fullscreen permission policy.
+  const GURL src = embedded_https_test_server().GetURL("b.com", "/simple.html");
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
+  content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
+
+  EXPECT_TRUE(RequestFullscreen(/*gesture=*/false, child));
+  EXPECT_TRUE(web_contents_->IsFullscreen());
+
+  // Blocking the tab for a modal dialog exits fullscreen when a cross-origin
+  // iframe requested fullscreen, even if the top-level origin has the
+  // automatic fullscreen content setting granted.
+  BrowserWindowInterface* browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_);
+  ui_test_utils::FullscreenWaiter waiter(browser, {.tab_fullscreen = false});
+  BrowserWindowModalDialogDelegate::From(browser)->SetWebContentsBlocked(
+      web_contents_, true);
+  waiter.Wait();
+  EXPECT_FALSE(web_contents_->IsFullscreen());
+}
+
+IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest,
+                       BlockingContentsDoesNotExitForSameOriginFrame) {
+#if BUILDFLAG(IS_MAC)
+  if (GetParam()) {
+    GTEST_SKIP() << "Flaky. See https://crbug.com/404887514";
+  }
+#endif
+
+  if (GetParam()) {
+    // Isolated Web Apps in this test fixture do not serve subframe HTML files.
+    return;
+  }
+
+  // Append a same-origin iframe with the fullscreen permission policy.
+  const GURL src = embedded_https_test_server().GetURL("a.com", "/simple.html");
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
+  content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
+
+  EXPECT_TRUE(RequestFullscreen(/*gesture=*/false, child));
+  EXPECT_TRUE(web_contents_->IsFullscreen());
+
+  // Blocking the tab for a modal dialog preserves fullscreen for a same-origin
+  // iframe when the origin has the automatic fullscreen content setting
+  // granted.
+  BrowserWindowInterface* browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_);
+  BrowserWindowModalDialogDelegate::From(browser)->SetWebContentsBlocked(
+      web_contents_, true);
+  EXPECT_TRUE(web_contents_->IsFullscreen());
+}
+
 IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, QueryPermissionWithGesture) {
   // Expect an API TypeError when allowWithoutGesture is false or unspecified.
   EXPECT_EQ(
@@ -1367,7 +1428,7 @@
   web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
   content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
   EXPECT_EQ("granted", QueryPermission(child));
-  EXPECT_TRUE(RequestFullscreen(child));
+  EXPECT_TRUE(RequestFullscreen(/*gesture=*/false, child));
   EXPECT_TRUE(ExitFullscreen());
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

Fullscreen persistence during modal dialogs via incorrect origin check

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: A potential logic error in BrowserWindowModalDialogDelegate incorrectly uses the top-level frame’s URL to check for the AUTOMATIC_FULLSCREEN permission when a tab-modal dialog is displayed. This allows cross-origin iframes with fullscreen delegation to bypass the mandatory fullscreen exit intended to reveal the omnibox. Consequently, an attacker can maintain control over the entire screen to spoof the browser environment around legitimate dialogs.

Affected files:

  • chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc

Estimated timestamp from git blame: 2024-03-14

Potential Vulnerability: Incorrect origin check for AUTOMATIC_FULLSCREEN exemption

When a tab-modal dialog (such as a PaymentRequest, WebAuthn prompt, or HTTP authentication dialog) is displayed, Chrome is designed to force the page out of HTML fullscreen mode. This is a critical security measure that ensures the omnibox (address bar) is revealed, providing the user with the necessary security context and site identity before they interact with the dialog.

Root Cause

A logic error exists in the implementation of the AUTOMATIC_FULLSCREEN exemption within BrowserWindowModalDialogDelegate::SetWebContentsBlocked (located in chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc).

When determining whether to skip the mandatory fullscreen exit, the code retrieves the AUTOMATIC_FULLSCREEN content setting using the top-level WebContents URL instead of the origin of the frame that is actually in fullscreen:

// chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc
if (fullscreen_state.target_mode == content::FullscreenMode::kContent) {
  // Potential Error: Retrieves top-level URL instead of the fullscreen frame's origin
  const GURL& url = web_contents->GetLastCommittedURL();
  const HostContentSettingsMap* const content_settings =
      HostContentSettingsMapFactory::GetForProfile(
          web_contents->GetBrowserContext());
  if (content_settings->GetContentSetting(
          url, url, ContentSettingsType::AUTOMATIC_FULLSCREEN) !=
      CONTENT_SETTING_ALLOW) {
    web_contents->ExitFullscreen(true);
  }
}

Because AUTOMATIC_FULLSCREEN is a TOP_ORIGIN_ONLY_SCOPE content setting, this lookup will return ALLOW whenever the top-level origin has been granted the permission, regardless of which frame holds the fullscreen state.

Potential Impact

If a trusted top-level origin (e.g., an Isolated Web App or a site with a manual permission grant) has the AUTOMATIC_FULLSCREEN permission, an embedded cross-origin iframe with fullscreen delegation (allow="fullscreen") can exploit this to remain in fullscreen while a modal dialog is shown. This allows the attacker to spoof the entire browser UI (fake address bar, fake security indicators) around a legitimate browser-rendered dialog, which can facilitate phishing or credential theft.

Potential Steps to Reproduce

  1. Grant the “Automatic Fullscreen” permission to https://trusted.example (e.g., via chrome://settings/content/automaticFullScreen).
  2. Host a page at https://trusted.example/index.html that embeds an attacker-controlled iframe: <iframe src="https://attacker.example/" allow="fullscreen; payment"></iframe>.
  3. As the attacker, have the iframe enter HTML fullscreen mode.
  4. Inside the iframe, trigger a tab-modal dialog, such as new PaymentRequest(methods, details).show().
  5. Observation: The iframe remains in fullscreen mode, and the omnibox is not revealed when the PaymentRequest dialog appears.

Suggested Fix

The check should validate the origin of the frame currently in fullscreen. The browser should query the FullscreenController for the requesting_origin of the current fullscreen state rather than relying on the WebContents top-level URL.

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