High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bug fixed in Firefox 152
ComponentDOM
Bug ClassLogic Error
Tracker2024852
Fix commit9c071e3bcf3b (firefox) +14/-2
CISA KEVNot listed
Creditedjayjayjazz
Disclosed2026-06-16

Background

WebGL2
The browser API exposing OpenGL ES 3.0 rendering to untrusted web content through the WebGLContext class.
Shader validator
The webgl::ShaderValidator (ANGLE-derived) that translates and validates GLSL before it is handed to the real GL driver.
Uniform block
A GLSL construct grouping uniforms into a buffer-backed block, limited per-stage by GL_MAX_VERTEX_UNIFORM_BLOCKS and GL_MAX_FRAGMENT_UNIFORM_BLOCKS.
`ShBuiltInResources`
The resources struct that tells the validator the driver’s real capability limits so it can reject over-limit shaders.

Root Cause Analysis

The vulnerable path was WebGLContext::CreateShaderValidator, which built the validator’s resources limits for WebGL2 but never populated the per-stage uniform-block maxima (MaxVertexUniformBlocks / MaxFragmentUniformBlocks), and never enabled the validator option that enforces them. As a result the compile options left validatePerStageMaxUniformBlocks unset, so a shader declaring more uniform blocks than a single stage actually supports could pass validation and reach the underlying GL driver. The violated invariant is that every resource limit the driver enforces must also be enforced by the validator, so that content accepted by the browser is guaranteed to be within driver bounds.

The fix queries the real limits during WebGLContext::InitWebGL2, stores them in the new mGLMaxVertexUniformBlocks / mGLMaxFragmentUniformBlocks fields, copies them into resources, and sets compileOptions.validatePerStageMaxUniformBlocks = true for WebGL2. This makes the validator reject over-limit shaders in the content process before they can drive the GL backend past its bounds.

Key insight
The single mistake was omitting the per-stage uniform-block limits (and their enforcement flag) from WebGL2 shader validation, allowing content to exceed driver capacity; the fix supplies the real limits and turns on validatePerStageMaxUniformBlocks so the validator enforces them.

Attack Path

  1. Obtain a WebGL2 context A malicious page creates a WebGL2RenderingContext, entering the IsWebGL2() code path that skipped per-stage block validation.
  2. Craft an over-limit shader The attacker writes GLSL declaring more uniform blocks in a single stage than GL_MAX_VERTEX_UNIFORM_BLOCKS or GL_MAX_FRAGMENT_UNIFORM_BLOCKS permits.
  3. Pass validation Because MaxVertexUniformBlocks / MaxFragmentUniformBlocks were never set and enforcement was off, the shader validates successfully.
  4. Reach the driver The validated but out-of-bounds shader is compiled and linked by the real GL driver, exceeding its per-stage capacity.
  5. Trigger memory unsafety The driver mishandles the over-limit block count, producing the memory-safety condition described in the advisory.

Impact Assessment

An attacker who can run script gains a memory-safety bug via out-of-limit WebGL2 shader resources, exercised in the process that hosts WebGL rendering (the content/GPU path). Preconditions are minimal: hardware-accelerated WebGL2 must be available and the victim need only load an attacker-controlled page. The advisory rates this high severity, consistent with a driver-level memory-safety fault reachable from untrusted content.

Files Changed

  • dom/canvas/WebGL2Context.cpp
  • dom/canvas/WebGLContext.h
  • dom/canvas/WebGLShaderValidator.cpp

Audit Directions

  • Validator-vs-driver limit parity
    For every driver limit the GL backend enforces, verify the shader validator’s resources struct is populated with the same value and that the corresponding enforcement flag is enabled.
  • Feature-gated resource fields
    Audit IsWebGL2()-conditional blocks for capability fields that are declared but left at their 0 defaults, since an unset limit silently disables its check.
  • New validator options
    When ANGLE adds a validate* compile option, confirm Firefox opts in wherever the corresponding limit is meaningful rather than relying on defaults.
diff --git a/dom/canvas/WebGL2Context.cpp b/dom/canvas/WebGL2Context.cpp
index bb4abd4abff..ee7c2845c1c 100644
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -116,6 +116,10 @@ bool WebGLContext::InitWebGL2(FailureReason* const out_failReason) {
       gl->GetIntAs<uint32_t>(LOCAL_GL_MIN_PROGRAM_TEXEL_OFFSET);
   mGLMaxProgramTexelOffset =
       gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_PROGRAM_TEXEL_OFFSET);
+  mGLMaxVertexUniformBlocks =
+      gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_VERTEX_UNIFORM_BLOCKS);
+  mGLMaxFragmentUniformBlocks =
+      gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_FRAGMENT_UNIFORM_BLOCKS);
 
   mIndexedUniformBufferBindings.resize(mLimits->maxUniformBufferBindings);
 
diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h
index 2116e3834e0..8327f63d89a 100644
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -970,6 +970,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
   // ES3:
   uint32_t mGLMinProgramTexelOffset = 0;
   uint32_t mGLMaxProgramTexelOffset = 0;
+  uint32_t mGLMaxVertexUniformBlocks = 0;
+  uint32_t mGLMaxFragmentUniformBlocks = 0;
 
  public:
   auto GLMaxDrawBuffers() const { return mLimits->maxColorDrawBuffers; }
diff --git a/dom/canvas/WebGLShaderValidator.cpp b/dom/canvas/WebGLShaderValidator.cpp
index 562a172c135..016f86e7e7e 100644
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -150,6 +150,8 @@ std::unique_ptr<webgl::ShaderValidator> WebGLContext::CreateShaderValidator(
   if (IsWebGL2()) {
     resources.MinProgramTexelOffset = mGLMinProgramTexelOffset;
     resources.MaxProgramTexelOffset = mGLMaxProgramTexelOffset;
+    resources.MaxVertexUniformBlocks = mGLMaxVertexUniformBlocks;
+    resources.MaxFragmentUniformBlocks = mGLMaxFragmentUniformBlocks;
   }
 
   resources.MaxDrawBuffers = MaxValidDrawBuffers();
@@ -212,8 +214,12 @@ std::unique_ptr<webgl::ShaderValidator> WebGLContext::CreateShaderValidator(
 
   // -
 
-  const auto compileOptions =
-      webgl::ChooseValidatorCompileOptions(resources, gl);
+  auto compileOptions = webgl::ChooseValidatorCompileOptions(resources, gl);
+
+  if (IsWebGL2()) {
+    compileOptions.validatePerStageMaxUniformBlocks = true;
+  }
+
   auto ret = webgl::ShaderValidator::Create(shaderType, spec, outputLanguage,
                                             resources, compileOptions);
   if (!ret) return ret;
Loading diff…