Moderate firefox UAF 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionUse-after-free in the Graphics component
ComponentGraphics
Bug ClassUAF
Tracker2079065
Fix commitb7b512a5a194 (firefox) +5/-5
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`CanvasManagerChild`
The child-process (renderer) IPC actor that manages the lifetime of remote canvas plumbing. Its DestroyInternal() tears down the canvas subsystem when the actor is going away.
`mActiveResourceTracker`
An ActiveResourceTracker owned by CanvasManagerChild. It tracks resources with a generational aging scheme; AgeAllGenerations() advances all generations (expiring/evicting tracked resources) and reset() destroys the tracker object.
`mCanvasChild`
The CanvasChild actor that holds the actual remote-canvas state; mCanvasChild->Destroy() tears it down.
`CanvasShutdownManager::OnRemoteCanvasLost()`
A shutdown-notification hook invoked when the remote canvas connection is lost, so dependent subsystems can react.
Destruction ordering
In teardown code, the order in which owned members are released matters: a member destroyed early must not still be referenced by work done later in the same teardown function.

Root Cause Analysis

In CanvasManagerChild::DestroyInternal() (gfx/ipc/CanvasManagerChild.cpp) the old code aged and reset mActiveResourceTracker first, before calling mCanvasChild->Destroy() and CanvasShutdownManager::Get()->OnRemoteCanvasLost(). The resource tracker, however, is still needed by the work those later calls perform: destroying the canvas child and notifying the shutdown manager can age/expire or otherwise touch resources that are registered with mActiveResourceTracker. Because mActiveResourceTracker.reset() destroys the tracker, the subsequent mCanvasChild->Destroy() / OnRemoteCanvasLost() path operated against an already-freed ActiveResourceTracker, i.e. a use-after-free of the tracker (and of the resources whose teardown it coordinates).

The patch keeps all three operations but reorders them: mCanvasChild->Destroy() and OnRemoteCanvasLost() now run first, and the mActiveResourceTracker->AgeAllGenerations(); mActiveResourceTracker.reset(); block is moved to the very end of DestroyInternal(). This guarantees the tracker outlives every teardown step that depends on it, so it is destroyed only once nothing else will reference it.

Key insight
The teardown released mActiveResourceTracker before the canvas-child destruction and shutdown-notification steps that still consult it — a destruction-ordering mistake that left later teardown code dereferencing a freed ActiveResourceTracker.

Attack Path

  1. Setup Web content in the renderer drives the remote-canvas pipeline (e.g. an OffscreenCanvas / accelerated 2D canvas) so that CanvasManagerChild holds a live mActiveResourceTracker, a live mCanvasChild, and registered resources. (reachability inferred)
  2. Trigger teardown The attacker causes the canvas manager to be destroyed (navigation/tab teardown, context loss, or an IPC disconnect), invoking CanvasManagerChild::DestroyInternal().
  3. Premature free DestroyInternal() ages and reset()s mActiveResourceTracker, destroying it.
  4. Dangling use The still-executing mCanvasChild->Destroy() / OnRemoteCanvasLost() path references the now-freed tracker or its tracked resources.
  5. Primitive Use-after-free in the renderer/GPU process; with heap grooming of the freed tracker allocation this is the classic UAF primitive (controlled-write/call) rather than a mere crash. (exploit strength inferred)

Impact Assessment

The affected code runs in the content/compositor side of the canvas IPC (the *Child actors), i.e. the renderer (or GPU) process; no elevated privilege is needed beyond running script that uses canvas. The flaw yields a use-after-free of the ActiveResourceTracker during actor teardown. Exploitability depends on winning the race/timing of teardown and grooming the freed allocation; the advisory rates it moderate. By itself it is a memory-safety bug within the process; the diff does not demonstrate cross-process escalation, so any sandbox-escape value is inferred.

Changed Functions

FunctionChangeNotes
if
gfx/ipc/CanvasManagerChild.cpp
modified

Files Changed

  • gfx/ipc/CanvasManagerChild.cpp

Audit Directions

  • Teardown ordering of owned members
    Audit other *Child/*Parent actor DestroyInternal/ActorDestroy paths in gfx/ipc for members reset()/nullptr’d before sibling members whose destruction still references them.
  • `ActiveResourceTracker` consumers
    Find all code that registers with or reads from an ActiveResourceTracker and confirm none can run after the owning object’s tracker has been reset.
  • Shutdown-notification callbacks
    Review CanvasShutdownManager::OnRemoteCanvasLost and similar “on lost” callbacks for assumptions that owning objects’ members are still alive at callback time.
diff --git a/gfx/ipc/CanvasManagerChild.cpp b/gfx/ipc/CanvasManagerChild.cpp
index cbeefa5d6a2..24b1ca3c59d 100644
--- a/gfx/ipc/CanvasManagerChild.cpp
+++ b/gfx/ipc/CanvasManagerChild.cpp
@@ -44,11 +44,6 @@ void CanvasManagerChild::ActorDestroy(ActorDestroyReason aReason) {
 }
 
 void CanvasManagerChild::DestroyInternal() {
-  if (mActiveResourceTracker) {
-    mActiveResourceTracker->AgeAllGenerations();
-    mActiveResourceTracker.reset();
-  }
-
   if (mCanvasChild) {
     mCanvasChild->Destroy();
     mCanvasChild = nullptr;
@@ -57,6 +52,11 @@ void CanvasManagerChild::DestroyInternal() {
   if (auto* shutdownManager = CanvasShutdownManager::Get()) {
     shutdownManager->OnRemoteCanvasLost();
   }
+
+  if (mActiveResourceTracker) {
+    mActiveResourceTracker->AgeAllGenerations();
+    mActiveResourceTracker.reset();
+  }
 }
 
 void CanvasManagerChild::Destroy() {
Loading diff…