Overview

Low
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Printing
DescriptionUse after free in Printing
ComponentPrinting
Sub-componentPrinting
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.
Tracker523765972
Fix commit16142cbd5000 (chromium/src) +8/-0
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-06-14 (per vendor advisory)
Fix landed2026-08-27 (public git)
Disclosed2026-09-22
Patch gap26 days — fix public in source before the advisory

Background

`PrintViewManager`
The WebContents-scoped browser-process object that coordinates print and Print Preview requests for a tab.
`PrintPreviewDialogController::PrintPreview()`
The call that opens the Print Preview dialog, and which may exit fullscreen as a side effect.
Use-after-free (UAF)
A memory-safety bug where code continues to touch an object (here this) after it has been destroyed.
`weak_factory_`
A WeakPtrFactory member whose WeakPtr becomes null once the owning object is destroyed, letting callers detect that destruction.

Root Cause Analysis

In PrintViewManager::OnRequestPrintPreviewCallback(), the code obtained the PrintPreviewDialogController and called dialog_controller->PrintPreview(), then continued executing on this (for example calling PrintPreviewAllowedForTesting()). The problem is that PrintPreview() may exit fullscreen, and exiting fullscreen can synchronously destroy the PrintViewManager that owns the current stack frame, violating the invariant that this remains valid for the remainder of the method. Because the initiator could be closed mid-call, any subsequent access to this was a use-after-free.

The fix captures auto weak_this = weak_factory_.GetWeakPtr(); before the PrintPreview() call and returns early when !weak_this, so no further work touches the freed object.

The patch also documents why the sibling OnScriptedPrintPreviewCallback() path is safe: it already exited fullscreen earlier, so its PrintPreview() cannot trigger the same re-entrant destruction.

Key insight
The single mistake was assuming this survives the call to PrintPreviewDialogController::PrintPreview(), when that call can re-entrantly exit fullscreen and destroy the PrintViewManager; the fix detects that destruction with a WeakPtr and bails out before touching freed memory.

Attack Path

  1. Trigger a print preview request An attacker-controlled page issues a request that reaches PrintViewManager::OnRequestPrintPreviewCallback() in the browser process.
  2. Enter a fullscreen state The tab is in a fullscreen mode such that the ensuing PrintPreview() will need to exit fullscreen.
  3. Force re-entrant teardown The call to dialog_controller->PrintPreview() exits fullscreen, which synchronously closes the initiator and destroys the PrintViewManager (this).
  4. Reach freed-object access Control returns into OnRequestPrintPreviewCallback(), which continues to use the now-freed this, producing a use-after-free that a crafted heap layout can steer toward controlled corruption.

Impact Assessment

An attacker gains a use-after-free on a PrintViewManager in the privileged browser process, which can lead to memory corruption and potentially code execution outside the renderer sandbox. Exploitation requires driving the tab into a fullscreen state and issuing a print preview request whose PrintPreview() call exits fullscreen and destroys the manager mid-callback.

Changed Functions

FunctionChangeNotes
if
chrome/browser/printing/print_view_manager.cc
modified

Files Changed

  • chrome/browser/printing/print_view_manager.cc

Audit Directions

  • Re-entrant destruction after callbacks
    Audit browser-process methods that call into UI or fullscreen-exiting code and then keep using this; guard them with a WeakPtr check.
  • Fullscreen side effects
    Trace any call path where exiting fullscreen can synchronously destroy WebContents-scoped observers like PrintViewManager, and verify no member access follows.
  • Asymmetric sibling handlers
    Compare paired callbacks such as OnRequestPrintPreviewCallback and OnScriptedPrintPreviewCallback, confirming that a safety check applied to one is either replicated or provably unnecessary in the other.
From 16142cbd5000e21d08d6140f0c19d38c03231a7b Mon Sep 17 00:00:00 2001
From: Lei Zhang <thestig@chromium.org>
Date: Thu, 27 Aug 2026 11:32:16 -0700
Subject: [PATCH] printing: Handle initiator being closed during PrintPreview()

PrintViewManager::OnRequestPrintPreviewCallback() calls
PrintPreviewDialogController::PrintPreview(), which may result in
PrintViewManager destruction. Check for that and exit early if needed.

Add comments to explain why this is different from
OnScriptedPrintPreviewCallback(), which also called PrintPreview().

Bug: 523765972
Change-Id: Iee0a91c2a698de147f706ff66871c7e2dc578851
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8301136
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1687427}
---
diff --git a/chrome/browser/printing/print_view_manager.cc b/chrome/browser/printing/print_view_manager.cc
index 82a478d..1b70189 100644
--- a/chrome/browser/printing/print_view_manager.cc
+++ b/chrome/browser/printing/print_view_manager.cc
@@ -581,6 +581,8 @@
   auto* dialog_controller = PrintPreviewDialogController::GetInstance();
   CHECK(dialog_controller);
   mojom::RequestPrintPreviewParams params;
+  // Note that this PrintPreview() call will not exit fullscreen, since the code
+  // above just did that. So no need to check if `this` got destroyed.
   dialog_controller->PrintPreview(web_contents(), params,
                                   print_preview_rfh_->GetProcess()->IsPdf());
 
@@ -632,8 +634,14 @@
 
   auto* dialog_controller = PrintPreviewDialogController::GetInstance();
   CHECK(dialog_controller);
+  // Note that this PrintPreview() call may exit fullscreen, so `this` may get
+  // destroyed.
+  auto weak_this = weak_factory_.GetWeakPtr();
   dialog_controller->PrintPreview(web_contents(), *params,
                                   render_frame_host->GetProcess()->IsPdf());
+  if (!weak_this) {
+    return;
+  }
 
   PrintPreviewAllowedForTesting();
 }
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free in PrintViewManager via ExitFullscreen nested loop

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A Use-After-Free vulnerability exists in PrintViewManager when initiating a print preview. Showing the preview dialog forces the tab out of HTML fullscreen, which on Windows and macOS spins a nested message loop; closing the tab during this loop destroys the PrintViewManager, leading to a virtual function call on the freed object upon return.

Affected files:

  • chrome/browser/printing/print_view_manager.cc

Estimated timestamp from git blame: 2021-09-27

Description

A potential Use-After-Free (UAF) vulnerability has been identified in PrintViewManager::OnRequestPrintPreviewCallback (chrome/browser/printing/print_view_manager.cc).

When a renderer requests a print preview, PrintViewManager calls PrintPreviewDialogController::PrintPreview to display the modal preview dialog. Showing a modal dialog requires dropping HTML Fullscreen mode to ensure the user has context. To do this, BrowserWindowModalDialogDelegate::SetWebContentsBlocked calls web_contents->ExitFullscreen(true).

On Windows (via ::SetWindowPos) and macOS (via [NSWindow toggleFullScreen:]), exiting fullscreen spins a native nested message loop. If an attacker closes the tab while this nested loop is running, the WebContents and its associated WebContentsUserData (including the PrintViewManager instance) are synchronously destroyed.

When the nested loop finishes, the stack unwinds back to PrintViewManager::OnRequestPrintPreviewCallback. The function then calls PrintPreviewAllowedForTesting() using the implicit this pointer. Because this is a raw pointer on the execution stack, it is not protected by MiraclePtr (BackupRefPtr). Since the object has been destroyed, this results in a virtual method call on a freed object, which can be exploited for Control-Flow Hijacking and Remote Code Execution (RCE) in the Browser Process.

Potential Steps to Trigger

  1. An attacker-controlled renderer opens two tabs (Tab A and Tab B) in the same window.
  2. In Tab A, the attacker uses JavaScript to enter HTML Fullscreen (document.documentElement.requestFullscreen()).
  3. Tab A sends a mojom::PrintManagerHost::RequestPrintPreview IPC. The browser binds a WeakPtr and invokes PrintViewManager::OnRequestPrintPreviewCallback.
  4. Inside the callback, the raw this pointer is extracted from the WeakPtr to execute the method. The code calls dialog_controller->PrintPreview().
  5. The UI framework attempts to show the modal dialog, which triggers web_contents->ExitFullscreen(true).
  6. The platform-specific ExitFullscreen implementation spins a nested message loop.
  7. During the nested loop, Tab B sends a ClosePage IPC to close Tab A. Tab A’s WebContents and PrintViewManager are destroyed.
  8. The attacker performs heap spraying (e.g., via other IPCs) to replace the freed PrintViewManager object in memory with a fake vtable.
  9. The nested loop finishes. The stack unwinds.
    • Note: The code bypasses a CHECK in TabStripModel::SetTabBlocked during unwinding because Tab B is still open, keeping the stale tab index within bounds.
    • Note: The code bypasses a CHECK(weak_widget) in ShowWebModalDialogViews because the native dialog widget is closed asynchronously and still exists during the unwind.
  10. Execution returns to OnRequestPrintPreviewCallback. The code calls PrintPreviewAllowedForTesting() on the freed this pointer.
  11. The virtual call uses the attacker’s fake vtable. On Windows, Control Flow Guard (CFG) only verifies that the target is a valid function start, allowing the attacker to jump to an arbitrary ROP gadget and achieve RCE.

(Note: These are suggested steps based on code analysis; our tooling agent does not have the ability to run code to verify an exploit end-to-end.)

Suggested Fix

Unlike OnScriptedPrintPreviewCallback, which checks print_preview_state_ and explicitly handles ExitFullscreen with a WeakPtr guard, OnRequestPrintPreviewCallback lacks these protections.

The fix should guard the execution after PrintPreview() returns by checking if this is still valid. You can acquire a WeakPtr before the call and check it afterward:

  auto weak_this = weak_factory_.GetWeakPtr();
  dialog_controller->PrintPreview(web_contents(), *params);

  if (!weak_this) {
    return;
  }

  PrintPreviewAllowedForTesting();

Alternatively, verify print_preview_state_ before proceeding, similar to how OnScriptedPrintPreviewCallback operates.

Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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