CVE-2026-11648
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/file_system_access/chrome_file_system_access_permission_context.cc |
modified | |
ifchrome/browser/renderer_context_menu/render_view_context_menu.cc |
modified | |
ifchrome/browser/ui/browser.cc |
modified | |
ifchrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc |
modified | |
ifchrome/browser/ui/views/permissions/chooser_bubble_ui.cc |
modified | |
ifchrome/browser/ui/views/permissions/permission_prompt_bubble.cc |
modified | |
ifchrome/browser/webshare/share_service_impl.cc |
modified |
Files Changed
chrome/browser/file_system_access/chrome_file_system_access_permission_context.ccchrome/browser/renderer_context_menu/render_view_context_menu.ccchrome/browser/ui/browser.ccchrome/browser/ui/views/media_router/media_router_dialog_controller_views.ccchrome/browser/ui/views/permissions/chooser_bubble_ui.ccchrome/browser/ui/views/permissions/permission_prompt_bubble.ccchrome/browser/webshare/share_service_impl.cccontent/browser/file_system_access/file_system_access_manager_impl.cc
Patch
From 058c2d6ea49cf80034fe78cfdb88cf24be7435de Mon Sep 17 00:00:00 2001
From: Jordan Bayles <jophba@chromium.org>
Date: Thu, 07 May 2026 14:39:57 -0700
Subject: [PATCH] content: Prevent UAF in WebContentsImpl related to drop fullscreen
ForSecurityDropFullscreen can synchronously destroy the WebContentsImpl
object. This CL updates ForSecurityDropFullscreen() to return a
std::optional<base::ScopedClosureRunner> that is std::nullopt if `this`
was destroyed, forcing callers to handle it explicitly.
Affected methods:
- ShowCreatedWindow
- ViewSource
- EnumerateDirectory
- RunJavaScriptDialog
- RunBeforeUnloadConfirm
- RunFileChooser
- SetWindowRect
- DidCallFocus
Bug: 506684534
Change-Id: Ieb700f4d5f3785adc6360f8c6c2f637be769be5d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7818866
Commit-Queue: Jordan Bayles <jophba@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1627248}
---
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
index 05a3746..779aac2 100644
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
@@ -987,9 +987,13 @@
}
// Drop fullscreen mode so that the user sees the URL bar.
- base::ScopedClosureRunner fullscreen_block =
- web_contents->ForSecurityDropFullscreen(
- /*display_id=*/display::kInvalidDisplayId);
+ auto blocker = web_contents->ForSecurityDropFullscreen(
+ /*display_id=*/display::kInvalidDisplayId);
+ if (!blocker) {
+ RunCallbackAndRecordPermissionRequestOutcome(
+ std::move(callback), PermissionRequestOutcome::kRequestAborted);
+ return;
+ }
if (context_->IsEligibleToUpgradePermissionRequestToRestorePrompt(
origin_, path_info_.path, handle_type_, user_action_, type_)) {
@@ -1001,7 +1005,7 @@
origin_, request_data_list},
base::BindOnce(&PermissionGrantImpl::OnRestorePermissionRequestResult,
this, std::move(callback)),
- std::move(fullscreen_block));
+ std::move(*blocker));
return;
}
@@ -1019,7 +1023,7 @@
{file_request_data}},
base::BindOnce(&PermissionGrantImpl::OnPermissionRequestResult, this,
std::move(callback)),
- std::move(fullscreen_block));
+ std::move(*blocker));
}
const url::Origin& origin() const {
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 2023197..e353113 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -3693,9 +3693,10 @@
// so drop fullscreen when it is shown. https://crbug.com/40054574
// TODO(avi): Do we need to attach the fullscreen block to the emoji
// panel?
- source_web_contents_
- ->ForSecurityDropFullscreen(/*display_id=*/display::kInvalidDisplayId)
- .RunAndReset();
+ if (!source_web_contents_->ForSecurityDropFullscreen(
+ /*display_id=*/display::kInvalidDisplayId)) {
+ return;
+ }
Browser* browser = GetBrowser();
if (browser) {
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index e7e0bd4..8ad020f 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -2660,17 +2660,17 @@
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (permission_request_manager) {
- // At this point, there will be UI presented, and running a dialog causes an
- // exit to webpage-initiated fullscreen. http://crbug.com/41322524
- base::ScopedClosureRunner fullscreen_block =
- web_contents->ForSecurityDropFullscreen(
- /*display_id=*/display::kInvalidDisplayId);
+ auto blocker = web_contents->ForSecurityDropFullscreen(
+ /*display_id=*/display::kInvalidDisplayId);
+ if (!blocker) {
+ return;
+ }
permission_request_manager->AddRequest(
requesting_frame,
std::make_unique<
custom_handlers::RegisterProtocolHandlerPermissionRequest>(
- registry, handler, url, std::move(fullscreen_block)));
+ registry, handler, url, std::move(*blocker)));
}
}
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 f7aa2cc..e6a5839c 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
@@ -78,8 +78,12 @@
FullscreenController* fullscreen_controller =
exclusive_access_manager->fullscreen_controller();
if (fullscreen_controller->IsTabFullscreen()) {
- fullscreen_blocker_ =
+ auto blocker =
initiator()->ForSecurityDropFullscreen(display::kInvalidDisplayId);
+ if (!blocker) {
+ return;
+ }
+ fullscreen_blocker_ = std::move(*blocker);
}
}
diff --git a/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc b/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
index 06bebdf..30a9f227 100644
--- a/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
+++ b/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
@@ -158,8 +158,11 @@
// Drop fullscreen mode for the current webcontent so that the user sees the
// URL.
if (fullscreen_controller->IsTabFullscreen()) {
- fullscreen_blocker_ =
+ auto blocker =
contents->ForSecurityDropFullscreen(display::kInvalidDisplayId);
+ if (blocker) {
+ fullscreen_blocker_ = std::move(*blocker);
+ }
}
}
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
index bf1ddf2..f176aab 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
@@ -46,8 +46,12 @@
->fullscreen_controller();
CHECK(fullscreen_controller);
if (fullscreen_controller->IsTabFullscreen()) {
- fullscreen_blocker_ =
+ auto blocker =
web_contents()->ForSecurityDropFullscreen(display::kInvalidDisplayId);
+ if (!blocker) {
+ return;
+ }
+ fullscreen_blocker_ = std::move(*blocker);
}
raw_ptr<PermissionPromptBubbleBaseView> prompt_bubble =
diff --git a/chrome/browser/webshare/share_service_impl.cc b/chrome/browser/webshare/share_service_impl.cc
index 158d69f6..8804fd9 100644
--- a/chrome/browser/webshare/share_service_impl.cc
+++ b/chrome/browser/webshare/share_service_impl.cc
@@ -305,11 +305,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 blocker = web_contents->ForSecurityDropFullscreen(
+ /*display_id=*/display::kInvalidDisplayId);
+ if (!blocker) {
+ std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED);
+ return;
+ }
auto share_operation = std::make_unique<webshare::ShareOperation>(
title, text, share_url, web_contents);
@@ -323,7 +324,7 @@
fullscreen_block.RunAndReset();
std::move(callback).Run(result);
},
- std::move(share_operation), std::move(fullscreen_block),
+ std::move(share_operation), std::move(*blocker),
std::move(callback)));
#else
NOTREACHED();
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.cc b/content/browser/file_system_access/file_system_access_manager_impl.cc
index 12dabda..d027b6ad 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl.cc
Regression Test / PoC
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index fccff6d..a4749d3 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -4018,9 +4018,9 @@
// While the |fullscreen_block| is in scope, fullscreen should fail with an
// error.
- base::ScopedClosureRunner fullscreen_block =
- web_contents->ForSecurityDropFullscreen(
- /*display_id=*/display::kInvalidDisplayId);
+ auto blocker = web_contents->ForSecurityDropFullscreen(
+ /*display_id=*/display::kInvalidDisplayId);
+ ASSERT_TRUE(blocker.has_value());
EXPECT_TRUE(ExecJs(main_frame, "document.body.requestFullscreen();",
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
Original Bug Report
Use-After-Free in WebContentsImpl::SetWindowRect
Use-After-Free in WebContentsImpl::SetWindowRect via ForSecurityDropFullscreen
Summary
WebContentsImpl::SetWindowRect() calls ForSecurityDropFullscreen() which internally calls ExitFullscreen() on other WebContents. On Windows, this can spin a nested message loop via ::SetWindowPos. During this nested loop, the calling WebContents can be destroyed (e.g., via window.close() from a popup’s fullscreenchange handler). The subsequent call to delegate_->SetContentsBounds(this, bounds) then reads from the freed WebContentsImpl object.
This is a variant of my earlier report, Bug 505045913 (UAF in RenderFrameHostImpl::ExitFullscreen). That fix added a WeakPtr guard in ExitFullscreen, but the same nested message loop race exists in SetWindowRect via ForSecurityDropFullscreen and was not addressed.
Version
Chrome 149.0.7805.0, Windows x64, ASAN build (is_asan=true is_debug=false symbol_level=1)
Steps to Reproduce
- Build Chrome with ASAN
- Apply
patch.diff(RunLoop simulating::SetWindowPosnested message loop) - Launch:
chrome --no-sandbox poc.html - Click Start — a popup opens
- Click the popup body — it enters fullscreen, opener auto-resizes
- ASAN reports heap-use-after-free in
SetWindowRect
Root Cause
content/browser/web_contents/web_contents_impl.cc:
void WebContentsImpl::SetWindowRect(const gfx::Rect& new_bounds) {
// ...
ForSecurityDropFullscreen(display_id).RunAndReset(); // [1]
delegate_->SetContentsBounds(this, bounds); // [2]
}
[1] ForSecurityDropFullscreen iterates fullscreen WebContents and calls ExitFullscreen() on each. On Windows, ExitFullscreen → ExitFullscreenMode → ::SetWindowPos spins a nested message loop. During this loop, Mojo IPCs are processed — including RequestClose from the popup’s fullscreenchange handler calling opener.close() — destroying this WebContents.
[2] delegate_->SetContentsBounds(this, bounds) reads this->delegate_ from the freed 5592-byte WebContentsImpl allocation.
The ExitFullscreen fix (Bug 505045913) added a WeakPtr guard in RenderFrameHostImpl::ExitFullscreen(), but SetWindowRect reaches the same nested message loop via ForSecurityDropFullscreen without any guard.
ASAN Trace
==17584==ERROR: AddressSanitizer: heap-use-after-free on address 0x1218f580c210
READ of size 8 at 0x1218f580c210 thread T0
#0 in content::WebContentsImpl::SetWindowRect web_contents_impl.cc:9737
#1 in content::RenderFrameHostImpl::SetWindowRect render_frame_host_impl.cc:7809
freed by thread T0 here:
#0 in operator delete
#1 in content::WebContentsImpl::~WebContentsImpl web_contents_impl.cc:1370
#7 in content::WebContentsImpl::Close web_contents_impl.cc:9699
#8 in content::RenderFrameHostImpl::ClosePageIgnoringUnloadEvents render_frame_host_impl.cc:7769
MiraclePtr Status: NOT PROTECTED
This crash is still exploitable with MiraclePtr.
Patch Explanation
patch.diff adds a base::RunLoop pump between ForSecurityDropFullscreen() and SetContentsBounds(). This simulates the natural race from the ::SetWindowPos nested message loop on Windows, allowing the pending RequestClose Mojo IPC to execute and destroy the WebContents.
Suggested Fix
fix.diff adds a base::WeakPtr guard after ForSecurityDropFullscreen(), matching the pattern from the ExitFullscreen fix (Bug 505045913):
base::WeakPtr<WebContentsImpl> weak_this = weak_factory_.GetWeakPtr();
ForSecurityDropFullscreen(display_id).RunAndReset();
if (!weak_this) {
return;
}
delegate_->SetContentsBounds(this, bounds);
Reporter Credit: Mihnea Nicolau