Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSandbox escape due to invalid pointer in the Graphics: WebRender component
ComponentGraphics
Bug ClassLogic Error
Tracker2076714
Fix commitf52ffc3cc6e8 (firefox) +4/-0
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

WebRender / SWGL
WebRender is Firefox’s GPU compositor; SWGL (“software GL”) is its software backend. RenderTextureHostSWGL wraps 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 a wr::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.”

Key insight
The secondary (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

  1. Drive the compositor Use content (WebGL/canvas/video to a WebRender SWGL path) to create a RenderTextureHostSWGL and cause it to be sampled. (web-reachability inferred; the diff shows only the host logic.)
  2. Reach the else branch unlocked Arrange the call sequence so LockSWGL/LockSWGLCompositeSurface is entered via the else branch while the surface is not actually locked (e.g. a secondary channel request without a successful primary lock, or after a failed lock).
  3. Invalid image produced The function skips the lock, indexes mPlanes, and returns a WrExternalImage built from uninitialized/stale buffer pointers.
  4. Compositor dereference WebRender samples the texture using those invalid pointers.
  5. 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

The code runs in the compositor/GPU process, and the advisory classifies it as a sandbox escape due to an invalid pointer — consistent with a content-driven path that gets the host to emit a 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 flags
    audit 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 frames
    review code that returns pointers from mPlanes-style arrays to ensure they are only exposed while the owning resource is locked and current.
  • Return-sentinel contracts
    the 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.
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++) {
Loading diff…