Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bug fixed in Firefox 152
ComponentDOM
Bug ClassLogic Error
Tracker2038302
Fix commita32cb067287d (firefox) +1/-1
CISA KEVNot listed
CreditedMihalis Haatainen
Disclosed2026-06-16

Background

`WebGLBuffer`
Gecko’s GPU-buffer wrapper backing WebGL gl.bufferData/gl.bindBuffer calls in the content process.
`WebGLBuffer::Kind::ElementArray`
the buffer’s persistent kind, fixed when the buffer is first bound, marking it as an index (element-array) buffer.
Index cache
a CPU-side copy (newIndexCache/mIndexCache) of an element-array buffer’s contents used to validate index values before a draw call.
`target`
the transient binding-point argument passed to BufferData (e.g. LOCAL_GL_ELEMENT_ARRAY_BUFFER), which does not necessarily match the buffer’s own kind.

Root Cause Analysis

WebGLBuffer::BufferData decided whether to allocate and populate the shadow index cache by testing the caller-supplied target == LOCAL_GL_ELEMENT_ARRAY_BUFFER rather than the buffer’s own mContent kind. Because a WebGLBuffer’s kind is fixed at first bind while target merely names the binding point used for this particular upload, the two can diverge, so an element-array buffer uploaded through a mismatched target would skip building its index cache while mNeedsIndexValidation still expected one to exist. The violated invariant is that every buffer of kind ElementArray must carry an index cache consistent with its device contents whenever index validation is enabled. Later index-validation logic then reads a stale or absent cache that no longer reflects the buffer’s real size and data, producing an out-of-bounds read.

The fix keys the allocation on the buffer’s stable identity, mContent == WebGLBuffer::Kind::ElementArray, so the cache is built exactly when the buffer actually is an index buffer, restoring the invariant regardless of which target the upload used.

Key insight
The single mistake was gating a per-buffer, persistent state decision on the transient, caller-controlled target binding point instead of the buffer’s own kind; the fix substitutes the authoritative mContent field so cache creation tracks what the buffer really is.

Attack Path

  1. Create a mismatched index buffer Script creates a WebGLBuffer, binds it so its mContent becomes Kind::ElementArray, in a context where mNeedsIndexValidation is set.
  2. Upload via a divergent target Call bufferData in a way where the target argument does not equal LOCAL_GL_ELEMENT_ARRAY_BUFFER, so the old check skips allocating the index cache.
  3. Leave the cache absent or stale The element-array buffer now has device data but no matching CPU-side index cache, breaking the size/contents invariant.
  4. Issue an indexed draw A draw call triggers index validation, which reads the missing or outdated cache instead of the buffer’s true bounds.
  5. Trigger the out-of-bounds access Validation and the subsequent draw operate on unvalidated indices, causing an out-of-bounds memory access in the content process.

Impact Assessment

An attacker running WebGL-capable JavaScript on a page gains an out-of-bounds read tied to WebGL index validation within the content (renderer) process, a memory-safety flaw rated moderate. Preconditions are that the context has mNeedsIndexValidation (or legacy vertex-attrib-0 handling) enabled and that the attacker can drive a buffer to kind ElementArray while uploading through a mismatched target. No elevated privileges are required beyond loading attacker-controlled script.

Changed Functions

FunctionChangeNotes
if
dom/canvas/WebGLBuffer.cpp
modified

Files Changed

  • dom/canvas/WebGLBuffer.cpp

Audit Directions

  • Transient argument vs. persistent state
    Flag any code that makes a per-object durable decision from a caller-supplied binding target or parameter instead of the object’s own stored kind/state field.
  • Cache-invariant coupling
    Audit paths where a validation cache is conditionally allocated and later unconditionally consumed; ensure the allocation condition exactly matches the consumption condition.
  • WebGL buffer kind divergence
    Review other WebGLBuffer operations that branch on target to confirm they use mContent wherever the buffer’s fixed kind, not the current binding point, is what matters.
diff --git a/dom/canvas/WebGLBuffer.cpp b/dom/canvas/WebGLBuffer.cpp
index e08211851bc..f80490f90a3 100644
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -119,7 +119,7 @@ void WebGLBuffer::BufferData(const GLenum target, const uint64_t size,
   UniqueBuffer newIndexCache;
   const bool needsIndexCache = mContext->mNeedsIndexValidation ||
                                mContext->mMaybeNeedsLegacyVertexAttrib0Handling;
-  if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER && needsIndexCache) {
+  if (mContent == WebGLBuffer::Kind::ElementArray && needsIndexCache) {
     newIndexCache = UniqueBuffer::Take(malloc(AssertedCast<size_t>(size)));
     if (!newIndexCache) {
       mContext->ErrorOutOfMemory("Failed to alloc index cache.");
Loading diff…