CVE-2026-117985
Overview
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.
Attack Path
- 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).
- Premature destroy
The offscreen
EndTransactionflushes 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. - Stale reference The compositor/scene-builder/render-backend continues building frames from a scene/command stream that still references the now-freed resources.
- Use A subsequent frame build or draw reads the freed external image / destroyed resource.
- 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgfx/layers/wr/WebRenderBridgeChild.cpp |
modified |
Files Changed
gfx/layers/wr/WebRenderBridgeChild.cppgfx/wr/webrender/src/scene_builder_thread.rs
Audit Directions
- Transaction-type-sensitive flushesAudit other WebRender client paths that flush or free resources without distinguishing offscreen vs. main transactions.
- Pipeline lifetime vs. scene rebuildReview other
remove_pipeline/ resource-release sites ingfx/wrto confirm they force a scene rebuild (or otherwise drop references) before releasing GPU resources. - External image lifetimeTrace external-image registration/release against scene references to find other “released but still referenced” windows.
- Destroy-command orderingExamine
mParentCommands/mDestroyedActorsordering for other cases where destroys outrun the consumer’s last use.
Patch
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);
}
}