High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionSite isolation issue in the Graphics: CanvasWebGL component
ComponentDOM
Bug ClassLogic Error
Tracker2050584
Fix commite9eb24c96103 (firefox) +6/-0
CISA KEVNot listed
Creditedsatyamasd
Disclosed2026-08-18

Background

`WebGLFramebuffer`
Gecko’s object backing a WebGL framebuffer, tracking its color/depth/stencil attachments and their initialization state.
`ResolveAttachmentData`
A method that lazily zero-initializes any attachment slices still marked uninitialized before they can be read, so uninitialized GPU memory is never exposed.
`fDrawBuffers` / `RefreshDrawBuffers`
GL calls that set the framebuffer’s draw-buffer routing table; RefreshDrawBuffers restores the framebuffer’s real configuration after a temporary override.

Root Cause Analysis

When ResolveAttachmentData zero-initialized an uninitialized color attachment, it called gl->fClearBufferiv(LOCAL_GL_COLOR, drawBuffer, ...) (or the uiv/fv variants) using drawBuffer derived from mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0, but it never guaranteed that the framebuffer’s current draw-buffer mapping actually routed that index to the intended attachment. Per the GL spec, glClearBuffer*(GL_COLOR, drawBuffer, ...) clears whatever attachment slot drawBuffer maps to under the active glDrawBuffers state, so when that state mapped the index to LOCAL_GL_NONE or to a different attachment, the target attachment was left un-cleared. The invariant violated is that every uninitialized attachment slice must be fully zeroed before it becomes readable; instead the clear silently missed, leaving stale GPU memory in place.

The fix builds an explicit drawBuffersForClear vector whose drawBuffer slot is set to the real mAttachmentPoint, applies it with fDrawBuffers before clearing so the clear provably targets the intended attachment, and calls RefreshDrawBuffers() afterward to restore the framebuffer’s normal routing. This closes the gap between the assumed and actual clear target.

Key insight
The single mistake was treating the glClearBuffer* drawBuffer argument as if it named a color attachment when it actually indexes the mutable glDrawBuffers routing table, so initialization could be routed away from the real attachment; the fix pins the routing with an explicit fDrawBuffers call so the zeroing clear always lands on the intended attachment.

Attack Path

  1. Set up a mismatched draw-buffer mapping Attacker JavaScript configures a WebGL framebuffer with multiple color attachments and a drawBuffers state that maps the relevant index to NONE or another slot.
  2. Leave an attachment uninitialized The page attaches or resizes a texture/renderbuffer so a color attachment slice is marked uninitialized but never explicitly cleared by content.
  3. Trigger lazy resolution A read-style operation (draw, readPixels, blit, or sampling) causes ResolveAttachmentData to run, whose clear misses the mis-routed attachment and leaves it uninitialized.
  4. Read back stale contents The attacker reads the now-“resolved” attachment (e.g. readPixels or rendering it back), retrieving whatever prior GPU memory occupied that region.
  5. Exfiltrate cross-origin pixels Repeating the pattern lets the page harvest leftover framebuffer memory that may contain another origin’s or process’s rendered content.

Impact Assessment

An attacker running WebGL in a content process gains an information-disclosure primitive that reads uninitialized GPU memory, potentially containing pixel data from other origins or the GPU process, defeating site isolation. It requires only script execution on an attacker-controlled page with WebGL2 (multiple draw buffers) available. Severity is high because leaked cross-origin graphics data undermines the origin-isolation guarantee for the Graphics: CanvasWebGL component.

Changed Functions

FunctionChangeNotes
switch
dom/canvas/WebGLFramebuffer.cpp
modified

Files Changed

  • dom/canvas/WebGLFramebuffer.cpp

Audit Directions

  • Lazy zero-initialization paths
    Review all code that clears “uninitialized” GPU resources (framebuffers, textures, renderbuffers) to verify the clear actually reaches the resource under the current GL state, not just that a clear call was issued.
  • Transient GL state overrides
    Check that any temporary override of draw-buffer, viewport, or scissor state around a security-critical clear is both correctly applied beforehand and reliably restored afterward.
diff --git a/dom/canvas/WebGLFramebuffer.cpp b/dom/canvas/WebGLFramebuffer.cpp
index b07bad434c8..7afe36ab0d3 100644
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -890,6 +890,11 @@ void WebGLFramebuffer::ResolveAttachmentData() const {
             const uint32_t drawBuffer =
                 cur->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
             MOZ_ASSERT(drawBuffer <= 100);
+            std::vector<GLenum> drawBuffersForClear(drawBuffer + 1,
+                                                    LOCAL_GL_ONE);
+            drawBuffersForClear[drawBuffer] = cur->mAttachmentPoint;
+            gl->fDrawBuffers(drawBuffersForClear.size(),
+                             drawBuffersForClear.data());
             switch (format->componentType) {
               case webgl::ComponentType::Int:
                 gl->fClearBufferiv(LOCAL_GL_COLOR, drawBuffer, iZeros);
@@ -921,6 +926,7 @@ void WebGLFramebuffer::ResolveAttachmentData() const {
       }
       imageInfo->mUninitializedSlices.reset();
     }
+    RefreshDrawBuffers();
     return;
   }
 
Loading diff…