CVE-2026-7356
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/navigator.cc |
modified | |
ifcontent/browser/web_contents/web_contents_impl.cc |
modified |
Files Changed
content/browser/renderer_host/navigator.cccontent/browser/web_contents/web_contents_impl.cc
Patch
From 8e95aab414bfce3dd56947c3464b5d8465e4b2bb Mon Sep 17 00:00:00 2001 From: Jordan Bayles <[email protected]> Date: Wed, 15 Apr 2026 15:26:47 -0700 Subject: [PATCH] Fix UAF in fullscreen exit during cross-document navigation This change addresses a Use-After-Free (UAF) vulnerability that occurs when exiting fullscreen during a cross-document navigation, by introducing `base::WeakPtr` checks in `WebContentsImpl::DidNavigateAnyFramePreCommit` and `Navigator::DidNavigate` to ensure that early destruction of `WebContentsImpl` or `RenderFrameHostImpl` during nested message loops does not result in dereferencing freed memory. Bug: 497769116 Change-Id: Ife5e726df78beb2f1d51d8d782f3b87b53d65fdb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7750093 Commit-Queue: Jordan Bayles <[email protected]> Reviewed-by: Arthur Sonzogni <[email protected]> Cr-Commit-Position: refs/heads/main@{#1615462} --- diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc index 1819408..8fcff3f 100644 --- a/content/browser/renderer_host/navigator.cc +++ b/content/browser/renderer_host/navigator.cc @@ -525,9 +525,16 @@ #endif // BUILDFLAG(IS_ANDROID) // Run tasks that must execute just before the commit. + base::WeakPtr<RenderFrameHostImpl> weak_rfh = render_frame_host->GetWeakPtr(); delegate_->DidNavigateAnyFramePreCommit(navigation_request.get(), was_within_same_document); + // NOTE: the pre commit tasks may result in the destruction of the render + // frame host, in which case we should exit this method early. + if (!weak_rfh) { + return; + } + if (ui::PageTransitionIsMainFrame(params.transition)) { delegate_->DidNavigateMainFramePreCommit(navigation_request.get(), was_within_same_document); diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 736e23bf..76c3b0c5 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -7925,7 +7925,13 @@ } if (should_exit_fullscreen) { + base::WeakPtr<WebContentsImpl> weak_this = weak_factory_.GetWeakPtr(); ExitFullscreen(false); + + // If `this` gets destructed due to ExitFullscreen(), we need to exit early. + if (!weak_this) { + return; + } CancelKeyboardLock(keyboard_lock_widget_); } }
Original Bug Report
Use-After-Free in Navigator::DidNavigate via ExitFullscreenModeForTab
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential Use-After-Free exists when exiting fullscreen during a cross-document navigation. This can spin a nested message loop, during which the WebContents may be destroyed, leading to virtual function calls on a dangling this pointer and bypassing MiraclePtr.
Affected files:
content/browser/renderer_host/navigator.cccontent/browser/web_contents/web_contents_impl.cc
Estimated timestamp from git blame: 2026-03-06
Description
A potential Use-After-Free (UAF) vulnerability exists in the browser process, specifically in how cross-document navigations handle exiting fullscreen mode.
When a navigation commits, Navigator::DidNavigate invokes delegate_->DidNavigateAnyFramePreCommit(...) (content/browser/renderer_host/navigator.cc). The delegate is typically WebContentsImpl.
If the page is currently in fullscreen, WebContentsImpl::DidNavigateAnyFramePreCommit will attempt to exit fullscreen by calling ExitFullscreen(false), which eventually calls delegate_->ExitFullscreenModeForTab(this). As noted by inline comments in WebContentsImpl::ExitFullscreenMode (crbug.com/1506535), this delegate call may spin a nested message loop (e.g., on macOS for window transitions).
If the WebContentsImpl object is destroyed while this nested message loop is spinning (for example, if the fullscreen window is a popup and its opener closes it), the this pointers currently on the stack in Navigator::DidNavigate and WebContentsImpl::DidNavigateAnyFramePreCommit become dangling.
When the nested loop finishes, ExitFullscreenMode safely detects the destruction via a WeakPtr and returns early. However, the stack unwinds to DidNavigateAnyFramePreCommit, which immediately calls CancelKeyboardLock(keyboard_lock_widget_). This accesses the freed this pointer to read the keyboard_lock_widget_ member and perform a virtual method dispatch, leading to a UAF and potential vtable hijack.
Furthermore, the stack will unwind to Navigator::DidNavigate, which subsequently checks if the transition is a main frame and calls delegate_->DidNavigateMainFramePreCommit(...), reading the delegate_ from freed memory and performing another virtual call.
MiraclePtr (BRP) Bypass
This vulnerability naturally bypasses MiraclePtr (BackupRefPtr). WebContentsImpl embeds FrameTree directly, which in turn embeds Navigator directly. Therefore, all three reside in the exact same heap allocation. Navigator holds a raw_ptr<NavigatorDelegate> delegate_ which points to the WebContentsImpl.
When the WebContentsImpl is destroyed, the Navigator and its raw_ptr member are destroyed along with it. The raw_ptr destructor drops the BRP reference count before the memory is released to the allocator. Since the stack pointers waiting on the call stack are raw C++ pointers, the reference count drops to zero, and the memory is freed without being quarantined. An attacker can then use heap spraying to reclaim the memory with controlled data before the execution resumes.
Suggested Steps to Trigger
Note: These are potential steps as we have not verified them with a working Proof of Concept.
- An attacker creates a malicious webpage that opens a popup window.
- The user interacts with the popup, satisfying the user gesture requirement. The popup requests and enters fullscreen mode.
- The popup initiates a cross-document navigation to a different site.
- The navigation reaches the commit phase in the browser process, eventually calling
WebContentsImpl::DidNavigateAnyFramePreCommit, which spins a nested message loop to exit fullscreen. - While the browser is spinning the message loop, the attacker’s opener window calls
popup.close(), queuing a task that synchronously destroys the popup’sWebContentsImpl. - The opener window performs a heap spray via IPCs, allocating objects of the same size as
WebContentsImplto reclaim the freed memory with attacker-controlled bytes. - The nested message loop completes, and the stack unwinds back to
DidNavigateAnyFramePreCommitandNavigator::DidNavigate. - The code dereferences the dangling
thispointer (now pointing to sprayed memory) to call a virtual function, leading to arbitrary code execution in the browser process.
Proposed Fix
In WebContentsImpl::DidNavigateAnyFramePreCommit, acquire a base::WeakPtr to this before calling ExitFullscreen(false). If the WeakPtr is invalidated after the call returns, return early.
Similarly, in Navigator::DidNavigate, acquire a base::WeakPtr to the relevant object (e.g., via the RenderFrameHost or FrameTree) before the call to delegate_->DidNavigateAnyFramePreCommit. Check its validity before proceeding to call delegate_->DidNavigateMainFramePreCommit. Alternatively, delay the fullscreen exit logic and keyboard lock cancellation until after the synchronous commit sequence is complete.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.