CVE-2026-7917
Overview
Background
- `RenderFrameHostImpl` / `WebContentsImpl`
- browser-process objects representing a frame and its tab; both own fullscreen-transition logic and can be destroyed when their tab goes away.
- Nested message loop
- a re-entrant run of the task queue that can execute arbitrary pending tasks (including tab teardown) in the middle of a synchronous call that spun it.
- `base::WeakPtr`
- a pointer that automatically becomes null when the object it references is destroyed, letting a caller detect that
thiswas freed after re-entrancy. - `::SetWindowPos`
- a Windows API invoked while entering fullscreen that can pump the message loop synchronously, per the patch description.
Root Cause Analysis
On Windows, the call into delegate_->EnterFullscreenMode(this, *options) (in render_frame_host_impl.cc) and delegate_->EnterFullscreenModeForTab(requesting_frame, options) (in web_contents_impl.cc) can spin a nested message loop via ::SetWindowPos, and that nested loop may run a pending task that destroys the current RenderFrameHostImpl / WebContentsImpl. Before the patch, control returned from the delegate call and immediately continued to touch this — calling delegate_->FullscreenStateChanged(this, ...) and accessing members such as keyboard_lock_widget_ / esc_key_locked_ — on an object that may already have been freed, a use-after-free. The invariant violated is that this must remain valid across a re-entrant call that can pump the loop.
The fix captures a base::WeakPtr before each EnterFullscreenMode* call and returns early if the pointer is invalidated, so no post-free member access occurs; the comments and the parallel ExitFullscreenMode branch (which already had this guard per crbug.com/1506535) confirm this is the intended pattern.
The patch strictly TIGHTENS behavior by adding guards; it also harmonizes the exit-path guard to use GetWeakPtr() and the WebContents type.
this; the fix adds the same WeakPtr-and-early-return guard on the enter paths that already protected the exit path.Attack Path
- Trigger a fullscreen request
A page with a user-activated gesture requests fullscreen, driving the browser into
EnterFullscreenMode/EnterFullscreenModeForTab. - Spin the nested loop
On Windows the ensuing
::SetWindowPospumps a nested message loop while the synchronous delegate call is still on the stack. - Destroy the tab mid-call
A pending task (e.g., a tab/frame teardown already queued) runs inside that nested loop and frees the
RenderFrameHostImpl/WebContentsImpl. - Use the freed object
Control returns and the pre-patch code touches
thisviaFullscreenStateChanged/keyboard_lock_widget_, dereferencing freed memory (the UAF the guard now prevents). - Escalation
Turning the freed-object access into controlled code execution is not demonstrated by the diff and is
(inferred).
Impact Assessment
(inferred) and not shown by the patch; the advisory reports neither in-the-wild exploitation nor further impact.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/render_frame_host_impl.cc |
modified | |
ifcontent/browser/web_contents/web_contents_impl.cc |
modified |
Files Changed
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/web_contents/web_contents_impl.cc
Audit Directions
- Re-entrancy across delegate callsAudit every browser-process call into
delegate_(and other embedder callbacks) that can spin a nested message loop; any member orthisaccess after such a call needs aWeakPtrguard. - Platform-specific message pumpingLook for Windows window-manipulation calls (
::SetWindowPosand similar) made synchronously from code holding object pointers, since they can re-enter and destroy those objects. - Asymmetric fixesWhen one direction of a paired operation (here, exit) gains a safety guard, check that the mirror operation (enter) received the same protection; this bug existed precisely because only exit was guarded.
Patch
From 91d5baaef742d5b9e081914e2a373f3a79a60b41 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <arthursonzogni@chromium.org>
Date: Thu, 02 Apr 2026 18:28:34 -0700
Subject: [PATCH] [Fullscreen] Guard against UAF during EnterFullscreenMode
Entering fullscreen on Windows can spin a nested message loop via
::SetWindowPos, allowing a pending task to destroy the tab. This adds
WeakPtr guards to prevent UAF upon return, matching the existing fix
for ExitFullscreenMode (crbug.com/1506535).
I haven't added tests because testing the precise timing of this
synchronous Windows-specific destruction is difficult to get right,
especially from a Linux workstation.
Fixed: 498752242
Change-Id: I90c926214e9548208de1dbe54c0201a5393a512d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7722343
Commit-Queue: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Auto-Submit: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1609608}
---
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 9c95b15..ac474e22 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9264,7 +9264,13 @@
if (had_fullscreen_token && !GetView()->HasFocus()) {
GetView()->Focus();
}
+ // This may spin the message loop and destroy this object.
+ // See crbug.com/1506535, crbug.com/498752242.
+ base::WeakPtr<RenderFrameHostImpl> weak_ptr = GetWeakPtr();
delegate_->EnterFullscreenMode(this, *options);
+ if (!weak_ptr) {
+ return;
+ }
delegate_->FullscreenStateChanged(this, /*is_fullscreen=*/true,
std::move(options));
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index b0f2bd329..3e0c8bd 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4806,7 +4806,13 @@
}
if (delegate_) {
+ // This may spin the message loop and destroy this object.
+ // See crbug.com/1506535, crbug.com/498752242.
+ base::WeakPtr<WebContents> weak_ptr = GetWeakPtr();
delegate_->EnterFullscreenModeForTab(requesting_frame, options);
+ if (!weak_ptr) {
+ return;
+ }
if (keyboard_lock_widget_) {
delegate_->RequestKeyboardLock(this, esc_key_locked_);
@@ -4834,8 +4840,9 @@
base::TimeTicks::Now());
if (delegate_) {
- // This may spin the message loop and destroy this object crbug.com/1506535
- base::WeakPtr<WebContentsImpl> weak_ptr = weak_factory_.GetWeakPtr();
+ // This may spin the message loop and destroy this object.
+ // See crbug.com/1506535, crbug.com/498752242.
+ base::WeakPtr<WebContents> weak_ptr = GetWeakPtr();
delegate_->ExitFullscreenModeForTab(this);
if (!weak_ptr) {
return;
Original Bug Report
Browser Process UAF in WebContentsImpl::EnterFullscreenMode
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 without the security team.
Overview: A potential Use-After-Free (UAF) vulnerability exists in the browser process on Windows when a renderer requests fullscreen mode. The transition synchronously calls a Win32 API that can spin a nested message loop, allowing pending tab-destruction tasks to execute and free the WebContents and RenderFrameHost. Upon returning from the nested loop, the code unsafely dereferences these destroyed objects, potentially leading to sandbox escape and Remote Code Execution.
Affected files:
content/browser/web_contents/web_contents_impl.cccontent/browser/renderer_host/render_frame_host_impl.cc
Estimated timestamp from git blame: 2024-07-17
Summary
A Use-After-Free (UAF) vulnerability exists in the browser process when entering fullscreen mode on Windows. The issue arises because the fullscreen entry path lacks the WeakPtr guards that were previously implemented for the corresponding fullscreen exit path (see crbug.com/1506535).
Technical Details
When a renderer requests fullscreen, the browser-side call stack reaches RenderFrameHostImpl::EnterFullscreen and WebContentsImpl::EnterFullscreenMode. On Windows, this execution path eventually routes to ui/views/win/fullscreen_handler.cc, which performs a synchronous window message dispatch via ::SetWindowPos with SWP_FRAMECHANGED.
As explicitly documented in Chromium’s UI code (e.g., ui/views/win/hwnd_message_handler.cc), the ::SetWindowPos API can spin a nested message loop to process synchronous window messages. This allows pending tasks in the UI thread’s message pump—such as an IPC command to destroy the tab—to execute immediately.
If the WebContentsImpl or RenderFrameHostImpl is cleanly destroyed during this nested loop, the raw_ptr reference counts in standard holding structures will drop to zero, fully releasing the memory to the allocator (circumventing MiraclePtr/BackupRefPtr protections). However, the implicit this pointers on the executing C++ stack remain as raw, un-instrumented pointers.
When the stack unwinds back to the calling methods, the code continues to dereference this:
WebContentsImpl::EnterFullscreenMode: After callingdelegate_->EnterFullscreenModeForTab(...), the code accesseskeyboard_lock_widget_, performs a virtual call ondelegate_->RequestKeyboardLock, and accessesobservers_.RenderFrameHostImpl::EnterFullscreen: After theWebContentsreturns, this method performs a virtual call ondelegate_->FullscreenStateChanged(...).
An attacker who reallocates the freed memory (heap spraying) can control these delegate_ and observers_ pointers, hijacking virtual method calls to achieve Remote Code Execution (RCE) in the unsandboxed browser process.
Potential Reproduction Steps
Note: These are suggested steps based on static analysis by an AI agent; a functional Proof of Concept has not yet been executed to confirm exploitability.
- Compromise a Renderer: Gain initial code execution in a renderer process.
- Transient Activation: Wait for a legitimate user interaction (e.g., a click) to satisfy the transient activation requirement for entering fullscreen.
- Queue Tab Destruction: Execute an IPC or DOM operation (like
window.close()or cross-process frame navigation) that queues an asynchronous tab destruction task on the browser’s UI thread. - Request Fullscreen: Synchronously send the Mojo IPC
LocalFrameHost.EnterFullscreen(options). - Nested Loop Triggered: The browser process enters
WebContentsImpl::EnterFullscreenModeand ultimately calls the Win32::SetWindowPosAPI, spinning a nested message loop. - Object Destruction: The queued tab destruction task executes within the nested loop, cleanly destroying the
WebContentsandRenderFrameHostobjects. - Heap Spray: The compromised renderer sprays the browser heap via IPC to reclaim the exact memory region of the freed objects with a forged vtable structure.
- UAF and RCE: The Win32 API returns, the stack unwinds, and the browser process dereferences the attacker-controlled memory (e.g.,
delegate_->RequestKeyboardLock), hijacking the instruction pointer.
Suggested Fix
Implement the same protection used in WebContentsImpl::ExitFullscreenMode (fixed in crbug.com/1506535).
In WebContentsImpl::EnterFullscreenMode, capture a WeakPtr before calling the delegate, and check its validity before proceeding:
if (delegate_) {
base::WeakPtr<WebContentsImpl> weak_ptr = weak_factory_.GetWeakPtr();
delegate_->EnterFullscreenModeForTab(requesting_frame, options);
if (!weak_ptr) {
return;
}
if (keyboard_lock_widget_) {
delegate_->RequestKeyboardLock(this, esc_key_locked_);
}
}
A similar WeakPtr check should be applied in RenderFrameHostImpl::EnterFullscreen around the delegate_->EnterFullscreenMode(this, *options); call to prevent the subsequent delegate_->FullscreenStateChanged(...) call on a destroyed frame.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.