CVE-2026-117975
Overview
Background
- WebRender / SWGL
- WebRender is Firefox’s GPU compositor; SWGL (“software GL”) is its software backend.
RenderTextureHostSWGLwraps an external texture (e.g. a video/image buffer) so SWGL can sample from it. - `LockSWGL` / `LockSWGLCompositeSurface`
- called before sampling; they acquire/lock the underlying texture, populate per-channel plane descriptors (
mPlanes), and return awr::WrExternalImage(containing buffer pointers) for a given plane/aChannelIndex. - `mLockedSWGL` / `mLockedSWGLCompositeSurface`
- boolean state flags recording whether the texture has actually been locked and its planes made valid for the current frame.
- `InvalidToWrExternalImage()`
- the sentinel returned to signal “no valid image,” causing the caller to skip sampling.
Root Cause Analysis
Both LockSWGL and LockSWGLCompositeSurface have an initial branch that performs the real lock and, on success, sets the state flag (mLockedSWGL = true / mLockedSWGLCompositeSurface = true). The else branch is taken for subsequent/secondary calls (for example additional channels of the same already-locked surface) and assumed the surface was already locked — it fell straight through to building a WrExternalImage from mPlanes. The bug is that nothing verified the surface was actually locked on that else path: if the function is entered via the else branch while mLockedSWGL/mLockedSWGLCompositeSurface is still false (the real lock never succeeded or was never performed), the code proceeds to index mPlanes and hand back buffer pointers that are stale/uninitialized — an invalid pointer delivered to the compositor.
The patch adds the missing state check to each else branch: } else if (!mLockedSWGL) { return InvalidToWrExternalImage(); } in LockSWGL, and } else if (!mLockedSWGLCompositeSurface) { return false; } in LockSWGLCompositeSurface. Now, if the non-locking path is reached without a prior successful lock, the function bails out with the invalid/false sentinel instead of dereferencing/returning plane pointers that were never validated. This enforces the invariant “do not produce a WrExternalImage from mPlanes unless the surface is currently locked.”
else) path of the SWGL lock functions trusted that the surface had already been locked without checking the lock-state flag; when it had not, the code served up uninitialized/stale plane pointers as a valid external image. The fix is the one missing guard: return invalid when mLockedSWGL/mLockedSWGLCompositeSurface is false.Attack Path
- Drive the compositor
Use content (WebGL/canvas/video to a WebRender SWGL path) to create a
RenderTextureHostSWGLand cause it to be sampled. (web-reachability inferred; the diff shows only the host logic.) - Reach the else branch unlocked
Arrange the call sequence so
LockSWGL/LockSWGLCompositeSurfaceis entered via theelsebranch while the surface is not actually locked (e.g. a secondary channel request without a successful primary lock, or after a failed lock). - Invalid image produced
The function skips the lock, indexes
mPlanes, and returns aWrExternalImagebuilt from uninitialized/stale buffer pointers. - Compositor dereference WebRender samples the texture using those invalid pointers.
- Primitive Use of an invalid/dangling pointer in the GPU/compositor process; the advisory frames this as a sandbox escape. (memory-safety consequence beyond the invalid pointer is inferred)
Impact Assessment
WrExternalImage referencing unvalidated plane memory. The precondition is reaching the non-locking else branch without a prior successful lock, which depends on the surface/channel call ordering; whether and how reliably content can force that ordering is not established by the diff (inferred). The concrete defect is a missing lock-state validation yielding an invalid pointer; promotion to a controllable read/write or RCE is inferred, not shown.Files Changed
gfx/webrender_bindings/RenderTextureHostSWGL.cpp
Audit Directions
- Lock/use state flagsaudit other
RenderTextureHost*and SWGL/compositor surfaces for functions whose “already handled” branch assumes a lock/init flag is set without checking it. - Plane/buffer descriptors reused across framesreview code that returns pointers from
mPlanes-style arrays to ensure they are only exposed while the owning resource is locked and current. - Return-sentinel contractsthe comment “Never returns nullptr” style assumptions (as also seen in the Cairo pattern bug) are worth checking; verify every caller handles the invalid/false sentinel these lock paths can now return.
Patch
diff --git a/gfx/webrender_bindings/RenderTextureHostSWGL.cpp b/gfx/webrender_bindings/RenderTextureHostSWGL.cpp
index 4501eee2c06..8744701ba38 100644
--- a/gfx/webrender_bindings/RenderTextureHostSWGL.cpp
+++ b/gfx/webrender_bindings/RenderTextureHostSWGL.cpp
@@ -116,6 +116,8 @@ wr::WrExternalImage RenderTextureHostSWGL::LockSWGL(
return InvalidToWrExternalImage();
}
mLockedSWGL = true;
+ } else if (!mLockedSWGL) {
+ return InvalidToWrExternalImage();
}
if (aChannelIndex >= mPlanes.size()) {
return InvalidToWrExternalImage();
@@ -169,6 +171,8 @@ bool RenderTextureHostSWGL::LockSWGLCompositeSurface(
return false;
}
mLockedSWGLCompositeSurface = true;
+ } else if (!mLockedSWGLCompositeSurface) {
+ return false;
}
MOZ_ASSERT(mPlanes.size() <= 3);
for (size_t i = 0; i < mPlanes.size(); i++) {