CVE-2026-117987
Overview
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
ActiveResourceTrackerowned byCanvasManagerChild. It tracks resources with a generational aging scheme;AgeAllGenerations()advances all generations (expiring/evicting tracked resources) andreset()destroys the tracker object. - `mCanvasChild`
- The
CanvasChildactor 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.
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
- Setup
Web content in the renderer drives the remote-canvas pipeline (e.g. an OffscreenCanvas / accelerated 2D canvas) so that
CanvasManagerChildholds a livemActiveResourceTracker, a livemCanvasChild, and registered resources. (reachability inferred) - Trigger teardown
The attacker causes the canvas manager to be destroyed (navigation/tab teardown, context loss, or an IPC disconnect), invoking
CanvasManagerChild::DestroyInternal(). - Premature free
DestroyInternal()ages andreset()smActiveResourceTracker, destroying it. - Dangling use
The still-executing
mCanvasChild->Destroy()/OnRemoteCanvasLost()path references the now-freed tracker or its tracked resources. - 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
*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
| Function | Change | Notes |
|---|---|---|
ifgfx/ipc/CanvasManagerChild.cpp |
modified |
Files Changed
gfx/ipc/CanvasManagerChild.cpp
Audit Directions
- Teardown ordering of owned membersAudit other
*Child/*ParentactorDestroyInternal/ActorDestroypaths in gfx/ipc for membersreset()/nullptr’d before sibling members whose destruction still references them. - `ActiveResourceTracker` consumersFind all code that registers with or reads from an
ActiveResourceTrackerand confirm none can run after the owning object’s tracker has been reset. - Shutdown-notification callbacksReview
CanvasShutdownManager::OnRemoteCanvasLostand similar “on lost” callbacks for assumptions that owning objects’ members are still alive at callback time.
Patch
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() {