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 Graphics component
ComponentGraphics
Bug ClassUAF
Tracker2078982
Fix commit86b40fbd67bf (firefox) +12/-5
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

Parent commands / destroy commands
Queued operations, including resource-destruction commands, flushed via ProcessWebRenderParentCommands(). Destroying a resource too early frees something the compositor still references.
Offscreen transaction (`aRenderOffscreen`)
A transaction that renders to an offscreen target rather than the main display list.

Root Cause Analysis

Two coordinated lifetime bugs, both use-after-free of GPU resources. (1) In EndTransaction, the client unconditionally called ProcessWebRenderParentCommands(), flushing queued destroy commands even for an offscreen transaction. But after an offscreen transaction the main display list may still reference the resources those destroy commands target, so sending the destroys early frees resources still in use. The patch guards the flush: if (!aRenderOffscreen) { ProcessWebRenderParentCommands(); }, keeping destroy commands queued until the main display list stops referencing their resources. (2) In the Rust SceneBuilderThread, when a pipeline was reported removed, the old code removed it from dl_builders without setting rebuild_scene, with a comment acknowledging the render backend “goes on building frames from one that still references it.” The corrected comment states that a removed pipeline “has its external images released,” so the scene must be rebuilt without it; the patch sets rebuild_scene = true. Otherwise the backend keeps building frames from a scene that references a pipeline whose external images were already released — a use-after-free of those GPU images.

The advisory’s “sandbox escape due to use-after-free in Graphics” is consistent with both halves: resources (IPC-destroyed objects and WebRender external images) are freed while the renderer still references them.

Key insight
Resource-destruction was flushed/pipeline-removal was applied before the consumer stopped referencing those resources: offscreen transactions sent destroy commands too early, and removing a pipeline released its external images without forcing a scene rebuild, so the render backend kept drawing from a scene that still referenced freed GPU images.

Attack Path

  1. Drive Attacker content triggers WebRender transactions including an offscreen render and pipeline creation/removal (e.g. via nested/offscreen content or iframes; reachability inferred from the transaction/pipeline surface).
  2. Premature destroy The offscreen EndTransaction flushes queued destroy commands while the main display list still references those resources; and/or a pipeline removal releases its external images without rebuilding the scene.
  3. Stale reference The compositor/scene-builder/render-backend continues building frames from a scene/command stream that still references the now-freed resources.
  4. Use A subsequent frame build or draw reads the freed external image / destroyed resource.
  5. Primitive Use-after-free of GPU resources in the compositor/GPU process; escalation to sandbox escape is the advisory’s claim (inferred), not proven by the diff.

Impact Assessment

The affected code spans the content-side WebRender bridge and the GPU-process scene-builder/render-backend. The diff substantiates use-after-free of GPU resources: destroy commands flushed before the display list releases them, and a pipeline’s external images released while a still-referencing scene keeps being rendered. Because the sink is in the GPU process (a sandboxed-process-adjacent, lower-privilege-boundary target), the advisory frames exploitation as a sandbox escape; that escalation is inferred, not demonstrated by the diff. Preconditions: the attacker must orchestrate offscreen transactions and/or pipeline removal to create the stale reference. The advisory rates it moderate.

Changed Functions

FunctionChangeNotes
if
gfx/layers/wr/WebRenderBridgeChild.cpp
modified

Files Changed

  • gfx/layers/wr/WebRenderBridgeChild.cpp
  • gfx/wr/webrender/src/scene_builder_thread.rs

Audit Directions

  • Transaction-type-sensitive flushes
    Audit other WebRender client paths that flush or free resources without distinguishing offscreen vs. main transactions.
  • Pipeline lifetime vs. scene rebuild
    Review other remove_pipeline / resource-release sites in gfx/wr to confirm they force a scene rebuild (or otherwise drop references) before releasing GPU resources.
  • External image lifetime
    Trace external-image registration/release against scene references to find other “released but still referenced” windows.
  • Destroy-command ordering
    Examine mParentCommands/mDestroyedActors ordering for other cases where destroys outrun the consumer’s last use.
diff --git a/gfx/layers/wr/WebRenderBridgeChild.cpp b/gfx/layers/wr/WebRenderBridgeChild.cpp
index 5060717519f..9c4e22deec3 100644
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -141,8 +141,11 @@ bool WebRenderBridgeChild::EndTransaction(
 
   // With multiple render roots, we may not have sent all of our
   // mParentCommands, so go ahead and go through our mParentCommands and ensure
-  // they get sent.
-  ProcessWebRenderParentCommands();
+  // they get sent. Destroy commands must stay queued after an offscreen
+  // transaction, until the main display list stops referencing their resources.
+  if (!aRenderOffscreen) {
+    ProcessWebRenderParentCommands();
+  }
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 
diff --git a/gfx/wr/webrender/src/scene_builder_thread.rs b/gfx/wr/webrender/src/scene_builder_thread.rs
index d012c951528..8edf612b772 100644
--- a/gfx/wr/webrender/src/scene_builder_thread.rs
+++ b/gfx/wr/webrender/src/scene_builder_thread.rs
@@ -980,9 +980,13 @@ impl SceneBuilderThread {
                     self.removed_pipelines.insert(pipeline_id);
                     removed_pipelines.push((pipeline_id, txn.document_id));
 
-                    // Not released here: removing a pipeline does not rebuild
-                    // the scene, and the render backend goes on building frames
-                    // from one that still references it.
+                    // A pipeline reported as removed has its external images
+                    // released, so the scene must be rebuilt without it.
+                    rebuild_scene = true;
+
+                    // Not released here: the render backend goes on building
+                    // frames from a scene that still references it until the
+                    // rebuilt one is swapped in.
                     self.dl_builders.remove_pipeline(pipeline_id);
                 }
             }
Loading diff…