CVE-2024-11691
Overview
Background
- `WebGLVertexArray`
- The WebGL object (VAO) that records, per attribute index, whether the attribute is fed from a bound buffer (an “array” attribute) or from a constant generic value, along with pointer/stride state.
- Vertex attribute array
- A per-index switch, toggled by
EnableVertexAttribArray/DisableVertexAttribArray, that tells the GPU pipeline to read attributeindexfrom a buffer during a draw rather than from a constant. - `DoVertexAttrib`
- The
WebGLVertexArraymethod that (re)applies the driver-level attribute state for a given index so the tracked WebGL state and the actual GL/driver state stay consistent. - Compatibility profile
- A legacy OpenGL context mode in which attribute index
0has special, non-disableable semantics, historically requiringDisableVertexAttribArrayto skip index0.
Root Cause Analysis
The vulnerable path was in WebGLContext::EnableVertexAttribArray and WebGLContext::DisableVertexAttribArray, which immediately issued the raw driver calls gl->fEnableVertexAttribArray(index) and gl->fDisableVertexAttribArray(index) and only then updated the WebGLVertexArray bookkeeping via SetAttribIsArray. On Apple silicon M-series hardware, driving the underlying GPU driver directly in this way — including the special-cased skip of index 0 in the compatibility-profile branch — could leave the driver’s notion of which attributes are array-backed inconsistent with the buffer/stride state that Firefox tracked, so a subsequent draw could read or write vertex attribute memory out of bounds due to a flaw in Apple’s GPU driver. The invariant violated is that the driver’s enabled-attribute state must always be reapplied together with, and derived from, the WebGL-tracked attribute state rather than being set eagerly and independently.
The fix removes the direct fEnableVertexAttribArray/fDisableVertexAttribArray calls (and the fragile compatibility-profile conditional) and instead updates SetAttribIsArray and then calls mBoundVertexArray->DoVertexAttrib(index), which reapplies the full, validated attribute state in one consistent step. This routes all attribute enabling/disabling through a single code path that cannot desynchronize the tracked state from what is programmed into the driver.
WebGLVertexArray tracking, which on Apple M-series drivers could produce an out-of-bounds access; the fix routes the change through DoVertexAttrib, so the driver is only ever programmed from the fully-updated, validated WebGL attribute state.Attack Path
- Create a malicious WebGL context The attacker loads a page that obtains a WebGL context and a vertex array object on an Apple M-series device.
- Toggle attribute arrays
JavaScript calls
enableVertexAttribArray/disableVertexAttribArrayin a sequence that exercises the eagerfEnableVertexAttribArray/fDisableVertexAttribArraypath, including the compatibility-profile index-0special case. - Desynchronize driver state
Because the driver call precedes and diverges from the
SetAttribIsArraybookkeeping, the GPU driver’s enabled-attribute/stride view becomes inconsistent with the buffers actually bound. - Trigger a draw A subsequent draw call causes Apple’s GPU driver to read vertex data using the inconsistent attribute state, indexing past the intended buffer bounds.
- Achieve memory corruption The out-of-bounds vertex fetch/write corrupts adjacent memory, which the attacker can shape toward controlled corruption.
Impact Assessment
Files Changed
dom/canvas/WebGLContextVertices.cpp
Audit Directions
- Eager driver calls before state trackingLook for WebGL entry points that call
gl->f*driver functions before updating the correspondingWebGLVertexArray/WebGLContextbookkeeping, since the driver may then act on state that Firefox has not validated. - Profile-specific special casesScrutinize conditionals keyed on
IsCompatibilityProfileor attribute index0, which introduce divergent paths where tracked and driver state can drift apart. - State reapplication coverageVerify that every place mutating per-attribute array state funnels through
DoVertexAttrib(or an equivalent single reapply point) rather than programming the driver ad hoc.
Patch
diff --git a/dom/canvas/WebGLContextVertices.cpp b/dom/canvas/WebGLContextVertices.cpp
index cd2fbc51b2d..2d27dcfcb4e 100644
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -90,10 +90,9 @@ void WebGLContext::EnableVertexAttribArray(GLuint index) {
if (!ValidateAttribIndex(*this, index)) return;
- gl->fEnableVertexAttribArray(index);
-
MOZ_ASSERT(mBoundVertexArray);
mBoundVertexArray->SetAttribIsArray(index, true);
+ mBoundVertexArray->DoVertexAttrib(index);
}
void WebGLContext::DisableVertexAttribArray(GLuint index) {
@@ -102,12 +101,9 @@ void WebGLContext::DisableVertexAttribArray(GLuint index) {
if (!ValidateAttribIndex(*this, index)) return;
- if (index || !gl->IsCompatibilityProfile()) {
- gl->fDisableVertexAttribArray(index);
- }
-
MOZ_ASSERT(mBoundVertexArray);
mBoundVertexArray->SetAttribIsArray(index, false);
+ mBoundVertexArray->DoVertexAttrib(index);
}
Maybe<double> WebGLContext::GetVertexAttrib(GLuint index, GLenum pname) {