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
Tracker2037323
Fix commit00fa289fdf52 (firefox) +16/-0
CISA KEVNot listed
CreditedMihalis Haatainen
Disclosed2026-06-16

Background

`WebGL2Context::CopyBufferSubData`
the WebGL 2 entry point that copies a byte range from one GPU buffer object into another via gl->fCopyBufferSubData.
`mIndexCache`
a CPU-side mirror of an element-array buffer’s contents that WebGL keeps so it can validate draw-call indices without reading back from the GPU.
Index validation
the check WebGL performs before an indexed draw to ensure every element index stays within the bounds of the bound vertex attribute arrays.
`InvalidateCacheRange`
a WebGLBuffer method that marks a region of the cached index data as needing revalidation on the next indexed draw.

Root Cause Analysis

WebGL2Context::CopyBufferSubData copied a byte range directly on the GPU through gl->fCopyBufferSubData but never updated the destination buffer’s CPU-side mIndexCache, so if the write buffer was an element-array (index) buffer with an allocated cache, that cache retained its pre-copy contents. This violated the invariant that mIndexCache always mirrors the buffer’s real bytes, which WebGL’s index-validation path relies on to bound-check indexed draws. A page could seed the write buffer’s cache with small, in-range indices, then overwrite the GPU bytes with larger out-of-range indices via CopyBufferSubData while the stale cache still reported the old, safe values.

The fix copies the matching range from readBuffer->mIndexCache into writeBuffer->mIndexCache (both being mByteLength-sized when allocated) with memcpy and then calls InvalidateCacheRange(writeOffset, size) so the affected range is revalidated. This keeps the cache consistent with the GPU buffer and forces the bounds check to run against the real, updated index values.

Key insight
The single core mistake is that a GPU-side buffer copy bypassed the CPU-side index cache, leaving mIndexCache stale so draw-call bounds checks trusted outdated indices; the fix propagates the copied bytes into the destination cache and invalidates the written range so validation reflects the buffer’s true contents.

Attack Path

  1. Prepare a validated index buffer The attacker’s WebGL 2 page creates an element-array buffer and populates it with small, in-bounds indices, causing WebGL to build and validate its mIndexCache.
  2. Stage malicious indices in a source buffer A second buffer is filled with larger indices that point beyond the bound vertex attribute arrays.
  3. Overwrite via `CopyBufferSubData` copyBufferSubData copies the malicious range from the source buffer into the validated index buffer, changing the GPU bytes while mIndexCache still holds the old safe values.
  4. Issue an indexed draw An indexed draw call passes the stale-cache bounds check but executes against the out-of-range indices now resident in the GPU buffer.
  5. Trigger out-of-bounds access Vertex fetch reads past the attribute arrays, producing the memory-safety violation.

Impact Assessment

An attacker gains an out-of-bounds read via unvalidated indexed draws, driven entirely from untrusted JavaScript/WebGL 2 in a web content context and executed in the GPU/compositor path that handles WebGL buffer contents. The only precondition is that the target has WebGL 2 enabled and the page can use copyBufferSubData into an index buffer that owns an allocated mIndexCache. Rated moderate/medium; the diff supports a stale-cache validation bypass but does not itself establish write primitives or specific disclosure beyond the out-of-bounds access.

Changed Functions

FunctionChangeNotes
if
dom/canvas/WebGL2ContextBuffers.cpp
modified

Files Changed

  • dom/canvas/WebGL2ContextBuffers.cpp

Audit Directions

  • Cache-mirror desync
    Audit every GPU-side buffer mutation path (fCopyBufferSubData, fBufferSubData, getBufferSubData, mapped-buffer writes) to confirm each one updates or invalidates any CPU-side mIndexCache mirror it can affect.
  • Validation on stale state
    Flag security checks that read from a cached copy (mIndexCache and similar) without ensuring the cache was refreshed after the underlying data could change.
  • Invalidate-after-write
    Verify that byte-range writes into cached buffers pair a data update with an InvalidateCacheRange-style call so no partially-updated range escapes revalidation.
diff --git a/dom/canvas/WebGL2ContextBuffers.cpp b/dom/canvas/WebGL2ContextBuffers.cpp
index 5012c70ff3e..761a3ee3d0d 100644
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -73,6 +73,22 @@ void WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
   gl->fCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset,
                          size);
 
+  // Update the destination index cache if needed.
+  if (writeBuffer->mIndexCache) {
+    MOZ_ASSERT(readBuffer->mIndexCache);
+    if (readBuffer->mIndexCache) {
+      // The read and write ranges have been validated above by fnValidateOffsetSize.
+      // If allocated, the size of mIndexCache is always mByteLength.
+      const auto* src =
+          static_cast<const uint8_t*>(readBuffer->mIndexCache.get()) +
+          readOffset;
+      auto* dst =
+          static_cast<uint8_t*>(writeBuffer->mIndexCache.get()) + writeOffset;
+      memcpy(dst, src, size);
+    }
+    writeBuffer->InvalidateCacheRange(writeOffset, size);
+  }
+
   writeBuffer->ResetLastUpdateFenceId();
 }
 
Loading diff…