Moderate firefox UAF 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSandbox escape due to use-after-free in the Print Preview component
ComponentCore
Bug ClassUAF
Tracker2076399
Fix commit2b6f847a46f4 (firefox) +5/-2
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`CanonicalBrowsingContext::PrintWithNoContentAnalysis`
Parent-process method (docshell/base/CanonicalBrowsingContext.cpp) that initiates printing for a browsing context, returning a RefPtr<PrintPromise>; it constructs a remote print job and wires up a listener.
`RemotePrintJobParent`
The parent-side IPDL actor for a remote (content-process) print job, constructed with PrintSettings and bound to the protocol via SendPRemotePrintJobConstructor.
`SendPRemotePrintJobConstructor(actor)`
The IPDL “constructor send” that binds the parent actor to the protocol and returns the actor pointer on success, or a null/failure indication if the message cannot be sent (e.g. the content process/manager is gone). On failure the actor is not successfully managed.
`MakeRefPtr` / reference-counted actors
MakeRefPtr<T>(...) creates an object already owned by a RefPtr. The patch switches the actor from a raw new to MakeRefPtr, giving the local code a strong reference independent of IPDL’s management.
`RegisterListener`
Called on the remotePrintJob after construction to attach the print listener; dereferences the actor.

Root Cause Analysis

Pre-fix, PrintWithNoContentAnalysis created the actor with a raw new:

layout::RemotePrintJobParent* remotePrintJob = new layout::RemotePrintJobParent(printSettings);
printData.remotePrintJob() = browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
remotePrintJob->RegisterListener(listener);

The method then unconditionally dereferenced remotePrintJob->RegisterListener(listener) regardless of whether the constructor send succeeded. When SendPRemotePrintJobConstructor fails — which can happen if the managing content process/actor (the browserParent->Manager()) has gone away — the IPDL machinery tears down and frees the just-passed actor, leaving remotePrintJob dangling. The subsequent RegisterListener call then operates on freed memory: a use-after-free of the RemotePrintJobParent in the parent process. The patch fixes this two ways. First, it constructs the actor with auto remotePrintJob = MakeRefPtr<layout::RemotePrintJobParent>(printSettings);, so local code holds a strong reference and the object is not freed out from under the subsequent dereference. Second, it checks the constructor result and bails out before using the actor: if (NS_WARN_IF(!printData.remotePrintJob())) { promise->Reject(NS_ERROR_FAILURE, __func__); return promise; }. Together these ensure RegisterListener is only reached when the actor is live and strongly held. The advisory describes a “sandbox escape due to use-after-free in Print Preview”; the diff substantiates the use-after-free (raw actor used after a possibly-failed constructor send, now RefPtr + failure check). The sandbox-escape framing is the escalation (a content process that can force the constructor to fail provokes a UAF in the parent), which is plausible but not directly shown by the diff — inferred.

Key insight
PrintWithNoContentAnalysis dereferenced a raw RemotePrintJobParent with RegisterListener even when SendPRemotePrintJobConstructor failed and freed the actor, a parent-process use-after-free; the fix holds the actor in a RefPtr via MakeRefPtr and returns early when the constructor send fails.

Attack Path

  1. Trigger remote printing A content process initiates a print that reaches CanonicalBrowsingContext::PrintWithNoContentAnalysis in the parent (print preview / printing path; web-triggerability of printing is inferred).
  2. Cause the constructor send to fail Arrange for the managing content-process actor to be gone or the IPDL send to fail at the moment SendPRemotePrintJobConstructor is invoked (e.g. racing process teardown), so the just-passed actor is freed.
  3. Dangling actor Pre-fix, remotePrintJob now points at freed memory while the code continues.
  4. Use freed actor remotePrintJob->RegisterListener(listener) dereferences and mutates the freed RemotePrintJobParent.
  5. Groom the freed allocation With control over parent-process heap timing, the freed slot could be reclaimed with attacker-influenced data (inferred).
  6. Primitive achieved Use-after-free of a parent-process IPDL actor, the basis for a sandbox escape (escalation inferred, not shown by the diff).

Impact Assessment

The use-after-free occurs in the parent/browser process on a RemotePrintJobParent actor, driven from the printing path. Because the parent is the sandbox boundary, a content process that can force the constructor send to fail and then win the heap-grooming race would corrupt parent-process memory — hence Mozilla’s “sandbox escape due to use-after-free” label. The diff proves the UAF (raw actor dereferenced after a failed/freed constructor send) and the lifetime fix; it does not demonstrate the full escape, so the escalation to code execution/sandbox escape is inferred. Mozilla rates it moderate.

Files Changed

  • docshell/base/CanonicalBrowsingContext.cpp

Audit Directions

  • Raw actors after constructor sends
    Audit other SendP*Constructor call sites that store the actor in a raw pointer and dereference it without checking the send result; these are the same UAF pattern.
  • Printing/IPDL lifetimes
    Review RemotePrintJobParent and related print IPDL actors for other spots assuming successful construction before use.
  • MakeRefPtr migration
    Look for remaining new SomeActor(...) immediately followed by a SendP...Constructor in the parent where failure leaves a dangling raw pointer; prefer MakeRefPtr plus a result check.
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
index aa25126b9a9..a2fef661169 100644
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -1119,10 +1119,13 @@ RefPtr<PrintPromise> CanonicalBrowsingContext::PrintWithNoContentAnalysis(
     return promise;
   }
 
-  layout::RemotePrintJobParent* remotePrintJob =
-      new layout::RemotePrintJobParent(printSettings);
+  auto remotePrintJob = MakeRefPtr<layout::RemotePrintJobParent>(printSettings);
   printData.remotePrintJob() =
       browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
+  if (NS_WARN_IF(!printData.remotePrintJob())) {
+    promise->Reject(NS_ERROR_FAILURE, __func__);
+    return promise;
+  }
 
   remotePrintJob->RegisterListener(listener);
 
Loading diff…