Overview

High
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.
Tracker553928324
Fix commitb44c42c13a52 (chromium/src) +26/-5
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-08-28 (per vendor advisory)
Fix landed2026-08-29 (public git)
Disclosed2026-09-08
Patch gap10 days — fix public in source before the advisory

Background

Use-after-free (UAF)
dereferencing a heap pointer after the underlying object has been freed, which is undefined behavior that an attacker can steer toward disclosure or control-flow corruption.
`content::WebContents`
the browser-process object backing a tab’s page; here it is the print initiator whose lifetime the patch guards.
`ShowConstrainedWebDialog()`
the call that displays the modal print-preview dialog and, per the patch’s added comment, synchronously exits HTML fullscreen, which can re-enter code that destroys the initiator and its tab.
`base::WeakPtr`
a pointer that automatically becomes null once its pointee is destroyed, letting the caller test for destruction instead of dereferencing a dangling raw pointer.

Root Cause Analysis

In CreatePrintPreviewDialog(), the code called ShowConstrainedWebDialog() and then continued to use the raw initiator content::WebContents* (and the tab) without re-checking their validity; the added comment states that showing the dialog synchronously exits HTML fullscreen, which “can potentially destroy initiator and its tab.” The lifetime invariant that initiator outlives its use across this call was therefore not guaranteed, so any subsequent access to the freed WebContents was a UAF. The same violated assumption reached PrintPreview(), where the pre-patch code, on a failed GetOrCreatePreviewDialog(), immediately passed the raw initiator to PrintViewManager::FromWebContents(initiator) — a dereference of a pointer that GetOrCreatePreviewDialog() may have already freed.

The fix captures base::WeakPtr handles (weak_initiator, and weak_tab when a tab is present) before the dialog is shown and, after ShowConstrainedWebDialog(), aborts via web_dialog_delegate->OnDialogCloseFromWebUI() and returns nullptr if either was destroyed. In PrintPreview(), it likewise captures weak_initiator before GetOrCreatePreviewDialog() and only calls PrintViewManager::FromWebContents(weak_initiator.get()) after confirming weak_initiator is still live.

The patch only adds destruction checks; it does not change the dialog or printing mechanism itself.

Key insight
The single mistake was continuing to use the raw initiator (and tab) pointers across ShowConstrainedWebDialog() / GetOrCreatePreviewDialog(), calls that can synchronously free those objects via fullscreen exit; the fix converts those raw uses into base::WeakPtr liveness checks that bail out before any dereference of a freed object.

Attack Path

  1. Enter HTML fullscreen A page under attacker control enters HTML fullscreen so that a later print-preview action will trigger a synchronous fullscreen exit.
  2. Trigger print preview The page (or user) invokes print preview, reaching CreatePrintPreviewDialog(), which calls ShowConstrainedWebDialog(); that call synchronously exits fullscreen. (inferred: the exact fullscreen-exit handler that tears down the tab is not shown in the diff)
  3. Free the initiator during the call The fullscreen exit destroys the initiator content::WebContents and/or its tab while CreatePrintPreviewDialog() is still executing, per the patch’s own comment.
  4. Dangling dereference Control returns to the caller and the freed initiator is used: either the continued body of CreatePrintPreviewDialog() after ShowConstrainedWebDialog(), or PrintPreview() passing the stale initiator to PrintViewManager::FromWebContents() — a use-after-free read of the freed WebContents.
  5. Escalation Reclaiming the freed WebContents allocation with attacker-influenced data could turn the stale-pointer access into a stronger primitive. (inferred: the diff demonstrates the freed-pointer access but no specific read/write primitive or code execution)

Impact Assessment

An attacker who can drive a page into HTML fullscreen and then invoke print preview gains a use-after-free of a content::WebContents (and possibly tabs::TabInterface) object in the browser process, the higher-privilege process where printing runs. The precondition is reaching CreatePrintPreviewDialog() with a fullscreen-exit path that destroys the initiator mid-call. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. A severity rating are not provided in the advisory metadata beyond the stated “Use after free in Printing.”

Changed Functions

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

Files Changed

  • chrome/browser/printing/print_preview_dialog_controller.cc

Audit Directions

  • Raw pointer held across a synchronous UI/dialog call
    Audit any browser-process code that keeps a content::WebContents*, tabs::TabInterface*, or similar raw pointer alive across calls like ShowConstrainedWebDialog() that can re-enter and destroy objects, and require a base::WeakPtr re-check afterward.
  • Fullscreen-exit side effects
    Review call sites that synchronously exit HTML fullscreen (or otherwise fire observer/event callbacks) mid-operation, since those callbacks can tear down the initiating tab or WebContents before the caller resumes.
  • Failure/early-exit cleanup paths
    Check error paths that touch the initiator after a create/show helper returns (e.g. calling FromWebContents() or invoking managers), as these often assume the object survived the helper when it may have been freed inside it.
From b44c42c13a5272c0ade24e338f0d3a5cc98d3844 Mon Sep 17 00:00:00 2001
From: Lei Zhang <thestig@chromium.org>
Date: Fri, 28 Aug 2026 17:48:36 -0700
Subject: [PATCH] printing: Handle initiator destruction in PrintPreviewDialogController

When creating a print preview dialog in CreatePrintPreviewDialog(),
showing the constrained web dialog synchronously exits HTML fullscreen.
This can potentially trigger the destruction of the print initiator.

To avoid UAF:
- Track both the initiator WebContents and TabInterface using
  base::WeakPtr across the ShowConstrainedWebDialog() call.
- If either gets destroyed, abort dialog creation by calling
  OnDialogCloseFromWebUI() and return nullptr.
- In PrintPreview(), check if the initiator WebContents was destroyed
  before accessing its PrintViewManager.

This is a cleaned up version of the AI-suggested fix from the bug
report.

Bug: 553928324
Change-Id: I1e36651b9a43d0da8e1963277294ddb7f85f8e2b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8308419
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1688449}
---
diff --git a/chrome/browser/printing/print_preview_dialog_controller.cc b/chrome/browser/printing/print_preview_dialog_controller.cc
index 4745f22..2767b3c 100644
--- a/chrome/browser/printing/print_preview_dialog_controller.cc
+++ b/chrome/browser/printing/print_preview_dialog_controller.cc
@@ -201,12 +201,22 @@
     return;
   }
 
-  if (!GetOrCreatePreviewDialog(initiator, params, is_pdf)) {
-    auto* print_view_manager = PrintViewManager::FromWebContents(initiator);
-    if (print_view_manager) {
-      print_view_manager->PrintPreviewDone();
-    }
+  // `initiator` can be destroyed inside GetOrCreatePreviewDialog().
+  base::WeakPtr<content::WebContents> weak_initiator = initiator->GetWeakPtr();
+  if (GetOrCreatePreviewDialog(initiator, params, is_pdf)) {
+    return;
   }
+  if (!weak_initiator) {
+    return;
+  }
+
+  auto* print_view_manager =
+      PrintViewManager::FromWebContents(weak_initiator.get());
+  if (!print_view_manager) {
+    return;
+  }
+
+  print_view_manager->PrintPreviewDone();
 }
 
 // static
@@ -441,11 +451,22 @@
     bool is_pdf) {
   base::AutoReset<bool> auto_reset(&is_creating_print_preview_dialog_, true);
 
+  // Showing the dialog synchronously exits HTML fullscreen, which can
+  // potentially destroy `initiator` and its `tab`.
+  base::WeakPtr<content::WebContents> weak_initiator = initiator->GetWeakPtr();
+  base::WeakPtr<tabs::TabInterface> weak_tab =
+      tab ? tab->GetWeakPtr() : nullptr;
+
   // The dialog delegates are deleted when the dialog is closed.
   ConstrainedWebDialogDelegate* web_dialog_delegate = ShowConstrainedWebDialog(
       initiator->GetBrowserContext(),
       std::make_unique<PrintPreviewDialogDelegate>(initiator), initiator);
 
+  if (!weak_initiator || (tab && !weak_tab)) {
+    web_dialog_delegate->OnDialogCloseFromWebUI();
+    return nullptr;
+  }
+
   WebContents* preview_dialog = web_dialog_delegate->GetWebContents();
 
   // Clear the zoom level for the print preview dialog so it isn't affected by
Loading diff…

Original Bug Report

reported by vm...@google.com

Browser process UAF in PrintPreviewDialogController::CreatePrintPreviewDialog

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 potential use-after-free vulnerability exists in the browser process within PrintPreviewDialogController::CreatePrintPreviewDialog. When initiating print preview from a webpage in HTML fullscreen mode, modal dialog initialization triggers a synchronous fullscreen exit whose platform event processing can destroy the initiator tab and its WebContents. The function subsequently dereferences the freed TabInterface and WebContents raw pointers without liveness verification, leading to virtual method dispatch and heap writes on freed objects.

Affected files:

  • chrome/browser/printing/print_preview_dialog_controller.cc
  • chrome/browser/printing/print_view_manager.cc

Estimated timestamp from git blame: 2024-10-08

Summary

PrintPreviewDialogController::CreatePrintPreviewDialog retains raw stack pointers (tabs::TabInterface* tab and content::WebContents* initiator) across a call to ShowConstrainedWebDialog(). If the initiator tab is currently in HTML element fullscreen, ShowConstrainedWebDialog() synchronously initiates a fullscreen exit (web_contents->ExitFullscreen(true)). On platforms where native fullscreen transitions process UI-thread window messages (e.g., Windows or macOS), a tab close event occurring during this transition synchronously destroys the TabModel and WebContentsImpl. Upon return, CreatePrintPreviewDialog() continues execution and accesses both pointers, leading to virtual dispatch (tab->ShowModalUI()) and memory writes (StartObserving(initiator)) on freed memory.

Vulnerability Details

  1. Unguarded Stack Pointers in CreatePrintPreviewDialog: In chrome/browser/printing/print_preview_dialog_controller.cc:437-472:
WebContents* PrintPreviewDialogController::CreatePrintPreviewDialog(
    tabs::TabInterface* tab,
    content::WebContents* initiator,
    const mojom::RequestPrintPreviewParams& params,
    bool is_pdf) {
  base::AutoReset<bool> auto_reset(&is_creating_print_preview_dialog_, true);

  ConstrainedWebDialogDelegate* web_dialog_delegate = ShowConstrainedWebDialog(
      initiator->GetBrowserContext(),
      std::make_unique<PrintPreviewDialogDelegate>(initiator), initiator);
  ...
  InitiatorData data(initiator, params, is_pdf,
                     tab ? tab->ShowModalUI() : nullptr);     // (1) Virtual call on freed TabModel
  preview_dialog_map_.emplace(preview_dialog, std::move(data));
  ...
  web_contents_collection_.StartObserving(initiator);         // (2) Observer write on freed WebContentsImpl
  1. Synchronous Fullscreen Exit Flow: ShowConstrainedWebDialog() (chrome/browser/ui/views/constrained_web_dialog_delegate_views.cc:525-534) creates the dialog widget and invokes constrained_window::ShowWebModalDialogViews() -> WebContentsModalDialogManager::ShowDialogWithManager() (components/web_modal/web_contents_modal_dialog_manager.cc:48-64). This calls BlockWebContentsInteraction(true), which invokes BrowserWindowModalDialogDelegate::SetWebContentsBlocked() (chrome/browser/ui/web_modal/browser_window_modal_dialog_delegate.cc:40-92):
    if (fullscreen_state.target_mode == content::FullscreenMode::kContent) {
      if (content_settings->GetContentSetting(url, url,
              ContentSettingsType::AUTOMATIC_FULLSCREEN) != CONTENT_SETTING_ALLOW) {
        web_contents->ExitFullscreen(true);
      }
    }
  1. Tab Destruction During Native Message Processing: WebContentsImpl::ExitFullscreenMode() (content/browser/web_contents/web_contents_impl.cc:4984-4991) calls delegate_->ExitFullscreenModeForTab(this) to perform the native window transition. As noted in comments (// This may spin the message loop and destroy this object. See crbug.com/1506535, crbug.com/498752242), processing window messages can run a pending tab-close task (TabStripModel::CloseTabs -> dt->tab.reset()), synchronously freeing both the TabModel and WebContentsImpl.

While the scripted print preview flow (PrintViewManager::OnScriptedPrintPreviewCallback, chrome/browser/printing/print_view_manager.cc:569-579) was previously guarded against this exact scenario (crbug.com/517047197) by pre-exiting fullscreen with a weak_this check before creating the dialog, the non-scripted IPC handler (OnRequestPrintPreviewCallback, print_view_manager.cc:607-645) lacks this pre-exit.

  1. Use-After-Free Sites:
  • Site 1: tab->ShowModalUI() (line 462) performs a virtual function call through tab’s vtable. Inside TabModel::ShowModalUI(), ScopedTabModalUIImpl executes tab_->showing_modal_ui_ = true; and notifies modal_ui_changed_callback_list_, performing writes and callback iteration on freed memory.
  • Site 2: web_contents_collection_.StartObserving(initiator) (line 468) constructs a ForwardingWebContentsObserver, calling static_cast<WebContentsImpl*>(initiator)->AddObserver(this), mutating the observer list in freed memory.
  • Site 3: A dangling pointer is stored in preview_dialog_map_, triggering subsequent UAF calls during cleanup in RemovePreviewDialog().

Because tab and initiator in CreatePrintPreviewDialog are bare stack pointers, MiraclePtr (BackupRefPtr) does not protect them.

Potential Reproduction Steps

(Note: These are suggested theoretical steps as our automated analysis tooling cannot execute live binaries.)

  1. Tab B opens Tab A via const a = window.open(...). Tab A opens Tab C so that a tab exists at a higher index (preventing an index out-of-bounds check during tab strip unwind).
  2. Tab A enters HTML element fullscreen (document.documentElement.requestFullscreen()).
  3. From a compromised renderer (or via user interaction such as Ctrl+P), Tab A sends printing::mojom::PrintManagerHost::RequestPrintPreview and signals Tab B via BroadcastChannel.
  4. The browser processes PrintViewManager::OnRequestPrintPreviewCallback and calls PrintPreviewDialogController::CreatePrintPreviewDialog.
  5. During ShowConstrainedWebDialog() -> ExitFullscreen(true), the native window manager spins message processing.
  6. Tab B’s a.close() task executes during message processing, synchronously destroying Tab A’s TabModel and WebContentsImpl.
  7. ShowConstrainedWebDialog() returns, and CreatePrintPreviewDialog executes tab->ShowModalUI() and StartObserving(initiator) on the freed objects.

Suggested Fix

  1. In PrintViewManager::OnRequestPrintPreviewCallback, pre-exit fullscreen before calling dialog_controller->PrintPreview(), matching the fix applied to OnScriptedPrintPreviewCallback:
if (web_contents()->IsFullscreen()) {
  auto weak_this = weak_factory_.GetWeakPtr();
  web_contents()->ExitFullscreen(true);
  if (!weak_this) {
    return;
  }
}
  1. In PrintPreviewDialogController::CreatePrintPreviewDialog, hold base::WeakPtr<tabs::TabInterface> and base::WeakPtr<content::WebContents> across ShowConstrainedWebDialog(), and verify both pointers remain valid before using them.

Evaluated with Chrome root at commit: 796d95f11afe18561709217227313319d6660322


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
Links in the report