High watch CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Fullscreen
DescriptionUse after free in Fullscreen
ComponentFullscreen
Sub-componentFullscreen
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker498752242
Fix commit91d5baaef742 (chromium/src) +15/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-02 (per vendor advisory)
Fix landed2026-04-03 (public git)
Disclosed2026-05-05
Patch gap32 days — fix public in source before the advisory

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 this was 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.

Key insight
The core mistake was entering fullscreen through a call that can synchronously destroy the owning object and then continuing to dereference this; the fix adds the same WeakPtr-and-early-return guard on the enter paths that already protected the exit path.

Attack Path

  1. Trigger a fullscreen request A page with a user-activated gesture requests fullscreen, driving the browser into EnterFullscreenMode / EnterFullscreenModeForTab.
  2. Spin the nested loop On Windows the ensuing ::SetWindowPos pumps a nested message loop while the synchronous delegate call is still on the stack.
  3. 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.
  4. Use the freed object Control returns and the pre-patch code touches this via FullscreenStateChanged / keyboard_lock_widget_, dereferencing freed memory (the UAF the guard now prevents).
  5. Escalation Turning the freed-object access into controlled code execution is not demonstrated by the diff and is (inferred).

Impact Assessment

An attacker-controlled page can cause a use-after-free in the browser process during a fullscreen transition, which is a high-severity memory-safety bug per the vendor advisory. Preconditions include the Windows platform (the message-loop spin is described as Windows-specific) and the ability to race a tab-destroying task against the fullscreen-enter call. Any escalation from the UAF to code execution or sandbox-relevant control is (inferred) and not shown by the patch; the advisory reports neither in-the-wild exploitation nor further impact.

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/render_frame_host_impl.cc
modified
if
content/browser/web_contents/web_contents_impl.cc
modified

Files Changed

  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/web_contents/web_contents_impl.cc

Audit Directions

  • Re-entrancy across delegate calls
    Audit every browser-process call into delegate_ (and other embedder callbacks) that can spin a nested message loop; any member or this access after such a call needs a WeakPtr guard.
  • Platform-specific message pumping
    Look for Windows window-manipulation calls (::SetWindowPos and similar) made synchronously from code holding object pointers, since they can re-enter and destroy those objects.
  • Asymmetric fixes
    When 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.
From 91d5baaef742d5b9e081914e2a373f3a79a60b41 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <[email protected]>
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 <[email protected]>
Reviewed-by: Rakina Zata Amni <[email protected]>
Auto-Submit: Arthur Sonzogni <[email protected]>
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;
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • content/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:

  1. WebContentsImpl::EnterFullscreenMode: After calling delegate_->EnterFullscreenModeForTab(...), the code accesses keyboard_lock_widget_, performs a virtual call on delegate_->RequestKeyboardLock, and accesses observers_.
  2. RenderFrameHostImpl::EnterFullscreen: After the WebContents returns, this method performs a virtual call on delegate_->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.

  1. Compromise a Renderer: Gain initial code execution in a renderer process.
  2. Transient Activation: Wait for a legitimate user interaction (e.g., a click) to satisfy the transient activation requirement for entering fullscreen.
  3. 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.
  4. Request Fullscreen: Synchronously send the Mojo IPC LocalFrameHost.EnterFullscreen(options).
  5. Nested Loop Triggered: The browser process enters WebContentsImpl::EnterFullscreenMode and ultimately calls the Win32 ::SetWindowPos API, spinning a nested message loop.
  6. Object Destruction: The queued tab destruction task executes within the nested loop, cleanly destroying the WebContents and RenderFrameHost objects.
  7. 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.
  8. 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.

View on issue tracker
Links in the report