← All issues

[4] WebGL provoking vertex with uint16 primitive restart causes GPU OOB access

Severity: High | Component: ANGLE Metal backend (fixIndexBuffer kernel) | e298509

Rated High because the observable effect is a deterministic GPU-process OOB read of vertex-attribute memory driven by an attacker-controlled 0xFFFF index marker, the Metal Shader Validation regression test confirms the crash, and projection to a stride-engineered tight relative read primitive is bounded by GPU-heap groomability.

Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/shaders/rewrite_indices.metal

+constant uint restartIndex = indexBufferIsUint16 ? 0xFFFF : 0xFFFFFFFF;
 
static inline uint readIdx(
const device ushort *indexBufferUint16,
const device uint *indexBufferUint32,
- const uint restartIndex,
const uint indexCount,
uint idx, ...)
{
...
if(doPrimRestart && !foundRestart && inIndex == restartIndex)
{ foundRestart = true; ... }
}
 
kernel void fixIndexBuffer(...)
{
- constexpr uint restartIndex = 0xFFFFFFFF;
uint baseIndex = 0;
...
- outputPrimitive(..., restartIndex, indexCount, ...);
+ outputPrimitive(..., indexCount, ...);
}

LayoutTests/webgl/webgl2-provoking-vertex-primitive-restart-uint16-nocrash.html

+ext.provokingVertexWEBGL(0x8E4E); // FIRST_VERTEX_CONVENTION
+const indices = new Uint16Array([
+ 0, 1, 2, 3,
+ 0xFFFF,
+ 4, 5, 6
+]);
+gl.drawElements(gl.TRIANGLE_STRIP, 8, gl.UNSIGNED_SHORT, 0);

The Metal compute kernel fixIndexBuffer, invoked when ANGLE rewrites an index buffer for WEBGL_provoking_vertex emulation, previously declared constexpr uint restartIndex = 0xFFFFFFFF locally. The fix removes the local hardcoded constant and adds a file-scope constant uint restartIndex = indexBufferIsUint16 ? 0xFFFF : 0xFFFFFFFF; next to the existing indexBufferIsUint16 / indexBufferIsUint32 function constants. The restartIndex parameter is dropped from readIdx and outputPrimitive (and the READ_IDX macro) so the kernel always references the correctly-typed compile-time constant. Identical changes are applied across rewrite_indices.metal, mtl_internal_shaders_src_autogen.metal, and the embedded C++ string in mtl_internal_shaders_src_autogen.h.

Sentinel-value mismatch with operand width — primitive-restart marker hardcoded for the wider index type, so the narrower-type marker is silently interpreted as a real index.

Primitive restart is an OpenGL/WebGL2 feature where a sentinel index value breaks the current strip/fan into a new primitive instead of indexing a vertex; the sentinel is type-dependent — 0xFFFF for GL_UNSIGNED_SHORT, 0xFFFFFFFF for GL_UNSIGNED_INT. WEBGL_provoking_vertex is a WebGL2 extension that lets script select which vertex of a primitive supplies flat-qualified varyings. ANGLE is WebKit's Metal-backed WebGL implementation; because Metal does not natively expose the GL provoking-vertex semantics, ANGLE rewrites index buffers using the fixIndexBuffer compute kernel so that the resulting indexed draw produces the GL-required behaviour. Metal function constants are values supplied at pipeline-state-object creation time that the Metal compiler specialises on; indexBufferIsUint16 is one such constant. Metal Shader Validation is an opt-in runtime mode that traps shader-side memory violations and is enabled in the regression test so the OOB surfaces as a deterministic crash rather than silent corruption.

The kernel passed 0xFFFFFFFF into readIdx, which compares inIndex == restartIndex after reading either indexBufferUint16[inIndex] (zero-extended to uint) or indexBufferUint32[inIndex]. For uint16 input, every element lies in [0, 0xFFFF], so the comparison can never be true and the legitimate 0xFFFF restart marker is treated as a regular vertex index.

🔒

A close look at how a single hardcoded sentinel in a Metal compute kernel turns an attacker-controlled index value into a GPU-process out-of-bounds access — and how far that primitive can plausibly be pushed.

Subscribe to read more

🔒

Several reusable audit patterns identified across ANGLE's Metal shaders and host-side index handling, with concrete files and grep targets for variant discovery.

Subscribe to read more