CVE-2025-0440
Overview
Background
- `navigator.share`
- the Web Share API entry point that lets a page hand a title, text, or URL to the operating system’s native share UI.
- Fullscreen
- a browser mode, entered via
element.requestFullscreen(), in which the web contents occupy the entire display and browser chrome is hidden. - `ForSecurityDropFullscreen`
- a
WebContentsmethod that forces the tab out of fullscreen for security reasons and returns abase::ScopedClosureRunnerwhose destruction/reset lifts the accompanying fullscreen block. - `base::ScopedClosureRunner`
- a RAII helper that runs a stored closure when it is reset or destroyed, used here to keep fullscreen suppressed for the lifetime of the share operation.
Root Cause Analysis
On Windows, ShareServiceImpl::Share launched the native Windows Share UI directly from within the page’s fullscreen context via webshare::ShareOperation, without first leaving fullscreen. The Windows Share experience is designed as a windowed flow, so when it appears over a fullscreen tab the user cannot easily see or dismiss it, and interacting with the surrounding page can pull focus unintuitively — a spoofing/confusion hazard because the trustworthy native UI is layered over attacker-controlled full-screen content. The invariant being violated is that security- and trust-sensitive native OS surfaces should not be presented while the web contents own the entire screen.
The fix calls web_contents->ForSecurityDropFullscreen() before starting the share and holds the returned base::ScopedClosureRunner (fullscreen_block) for the duration of the operation, resetting it only in the completion callback. This guarantees the tab is windowed while the Share UI is active, restoring the invariant.
ForSecurityDropFullscreen and keeps it suppressed through the share’s lifetime with a base::ScopedClosureRunner.Attack Path
- Enter fullscreen
A malicious page calls
document.body.requestFullscreen()so the attacker-controlled content occupies the entire display and browser chrome is hidden. - Trigger share
The page invokes
navigator.share(...), reachingShareServiceImpl::Shareon Windows without any fullscreen exit. - Overlay native UI
The Windows
Shareflow starts throughwebshare::ShareOperationand renders its windowed UI on top of the still-fullscreen tab. - Confuse the user Because the tab remains fullscreen, the user cannot easily locate or dismiss the share dialog, and clicks can fall back into the attacker’s page, enabling UI confusion or misdirected interaction.
Impact Assessment
Share UI over full-screen, attacker-controlled web content in the browser (renderer-initiated, handled in the browser process), producing UI confusion or spoofing-friendly conditions rather than memory corruption. The precondition is that the victim is on Windows, is in fullscreen on the attacker’s page, and the page can call navigator.share (which requires a user gesture and a secure context).Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Fchrome/browser/webshare/share_service_browsertest.cc |
modified | |
SafeBrowsingShareServiceBrowserTestchrome/browser/webshare/share_service_browsertest.cc |
modified |
Files Changed
chrome/browser/webshare/share_service_browsertest.ccchrome/browser/webshare/share_service_impl.cc
Audit Directions
- Native UI launched from fullscreenReview other renderer-triggered flows that surface OS or browser-trusted dialogs (file pickers, permission prompts, print, payment) to confirm they drop fullscreen via
ForSecurityDropFullscreenbefore display. - Platform-guarded branchesAudit
#if BUILDFLAG(IS_WIN)(and other per-OS) code paths in shared services, since security mitigations added on one platform’s branch are easy to omit on others. - Scoped-lifetime mitigationsCheck uses of
base::ScopedClosureRunnerfor security blocks to ensure the runner is threaded through async callbacks and reset only after the protected operation completes, not dropped early.
Patch
From 92bebd7aaa776384c1a62b1814ce8de1ab90ea75 Mon Sep 17 00:00:00 2001
From: Hoch Hochkeppel <mhochk@microsoft.com>
Date: Fri, 18 Oct 2024 23:07:16 +0000
Subject: [PATCH] Exit fullscreen on Windows for navigator.share
The 'Share' experience on Windows is designed with a windowed
experience in mind, and results in unintuitive behaviors when invoked
from a fullscreen context. To mitigate any risks from these behaviors,
and to ensure the optimal user experience, this change ensures that
fullscreen is exited prior to starting the Windows portion of the
'Share' flow.
Bug: 40067914
Change-Id: I36af174544f738965e988e8d0fb4b29349cad8c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5938489
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Hoch Hochkeppel <mhochk@microsoft.com>
Reviewed-by: Amanda Baker <ambake@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1370877}
---
diff --git a/chrome/browser/webshare/share_service_browsertest.cc b/chrome/browser/webshare/share_service_browsertest.cc
index 1c4f45a..e6a131f 100644
--- a/chrome/browser/webshare/share_service_browsertest.cc
+++ b/chrome/browser/webshare/share_service_browsertest.cc
@@ -108,6 +108,27 @@
WebShareMethod::kShare, kRepeats);
}
+#if BUILDFLAG(IS_WIN)
+IN_PROC_BROWSER_TEST_F(ShareServiceBrowserTest, Fullscreen) {
+ base::HistogramTester histogram_tester;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/webshare/index.html")));
+ content::WebContents* const web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
+ EXPECT_TRUE(
+ content::ExecJs(web_contents, "document.body.requestFullscreen();"));
+ waiter.Wait();
+ ASSERT_TRUE(web_contents->IsFullscreen());
+
+ EXPECT_EQ("share succeeded",
+ content::EvalJs(web_contents, "share_text('hello')"));
+ EXPECT_FALSE(web_contents->IsFullscreen());
+}
+#endif // BUILDFLAG(IS_WIN)
+
class SafeBrowsingShareServiceBrowserTest : public ShareServiceBrowserTest {
public:
SafeBrowsingShareServiceBrowserTest()
diff --git a/chrome/browser/webshare/share_service_impl.cc b/chrome/browser/webshare/share_service_impl.cc
index 83195cd7..f174b4bb 100644
--- a/chrome/browser/webshare/share_service_impl.cc
+++ b/chrome/browser/webshare/share_service_impl.cc
@@ -282,6 +282,12 @@
blink::mojom::ShareError result) { std::move(callback).Run(result); },
std::move(sharing_service_operation), std::move(callback)));
#elif BUILDFLAG(IS_WIN)
+ // Drop fullscreen mode so the Share UI can be easily clicked away from,
+ // without clicking back into the web contents
+ base::ScopedClosureRunner fullscreen_block =
+ web_contents->ForSecurityDropFullscreen(
+ /*display_id=*/display::kInvalidDisplayId);
+
auto share_operation = std::make_unique<webshare::ShareOperation>(
title, text, share_url, web_contents);
auto* const share_operation_ptr = share_operation.get();
@@ -289,10 +295,13 @@
std::move(files),
base::BindOnce(
[](std::unique_ptr<webshare::ShareOperation> share_operation,
- ShareCallback callback, blink::mojom::ShareError result) {
+ base::ScopedClosureRunner fullscreen_block, ShareCallback callback,
+ blink::mojom::ShareError result) {
+ fullscreen_block.RunAndReset();
std::move(callback).Run(result);
},
- std::move(share_operation), std::move(callback)));
+ std::move(share_operation), std::move(fullscreen_block),
+ std::move(callback)));
#else
NOTREACHED_IN_MIGRATION();
std::move(callback).Run(blink::mojom::ShareError::INTERNAL_ERROR);
Regression Test / PoC
diff --git a/chrome/browser/webshare/share_service_browsertest.cc b/chrome/browser/webshare/share_service_browsertest.cc
index 1c4f45a..e6a131f 100644
--- a/chrome/browser/webshare/share_service_browsertest.cc
+++ b/chrome/browser/webshare/share_service_browsertest.cc
@@ -108,6 +108,27 @@
WebShareMethod::kShare, kRepeats);
}
+#if BUILDFLAG(IS_WIN)
+IN_PROC_BROWSER_TEST_F(ShareServiceBrowserTest, Fullscreen) {
+ base::HistogramTester histogram_tester;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/webshare/index.html")));
+ content::WebContents* const web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
+ EXPECT_TRUE(
+ content::ExecJs(web_contents, "document.body.requestFullscreen();"));
+ waiter.Wait();
+ ASSERT_TRUE(web_contents->IsFullscreen());
+
+ EXPECT_EQ("share succeeded",
+ content::EvalJs(web_contents, "share_text('hello')"));
+ EXPECT_FALSE(web_contents->IsFullscreen());
+}
+#endif // BUILDFLAG(IS_WIN)
+
class SafeBrowsingShareServiceBrowserTest : public ShareServiceBrowserTest {
public:
SafeBrowsingShareServiceBrowserTest()
Original Bug Report
Security: [Esc] KeyPress Does Not Work in FullScreen While navigator.share Is Active
VULNERABILITY DETAILS
We Can Prevent [Esc] KeyPress in Full Screen Mode Using navigator.share
VERSION
Chrome Version: [115.0.5790.99] + [stable]
Operating System: [Windows 10]
REPRODUCTION CASE
- Open PocIndex.html
- Double Click [Click Me!] button
- Done
CREDIT INFORMATION
Reporter credit: Umar Farooq