[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);
Patch Details
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.
Background
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.
Analysis
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.
Aaaa Aaaaaaaaaaaaaaaa Aa Aaaaaaa Aaaa a Aaaaaa Aaaaa Aaaaaa Aaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaa Aaa Aaaaaaaaa Aaaaaa Aaaaa Aaaaaa Aaaaaaaa Aaaaaaaa Aaaa Aaaaaaa Aaaaaaaa Aaaaa Aaa Aaaaaaaaaa Aaaa Aaaaaaaa Aaa a Aaaa Aaaaaaaa Aaa Aaaaa Aaaaaa Aaaaa Aaaa Aaaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaa Aa Aaaaaa Aaaaaaa a Aaaaaaa a Aaa Aaaa Aaa Aaaaa Aaaaaaa Aaaa Aaaa Aaaaa a Aaaa Aaaaaaaa Aaaaaaaaa Aa Aaa Aaa Aaaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaaa Aaaaaaaaa Aa Aaa Aaaaa Aaaaaa Aaaaaaa Aaa Aaaaaaaaa Aaa Aaa Aaaaaa Aaa Aaaaaa Aa Aaaaaaaaa Aaaaaaa Aaaaaa Aaa Aaa Aaa Aaaaaaaaa Aaa Aa Aaaaa Aaaaaaaaa Aaa Aaa Aaaaaaaaaaaaa Aaaa Aaaaaaa Aaaaaaaaa Aa Aaaaaa Aaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aa Aaaaa Aa Aaaa Aa Aaaaaaaa Aaaaaaaaaaaa Aaaaaaa Aaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaa Aaa Aaaa Aa Aaaaaa Aaa Aaa Aaaaaaa Aaaaa Aaaa Aaaa Aaa Aaaaaa Aaaaaaaa Aaaaaaa Aa Aaaaa Aaaaaa Aaaaa a Aaaaaaaa Aaaaaa Aaaaaa Aaaaa Aaa a Aaaaaa Aaaaaaaaa Aaaa Aaaaa Aaaaaaa Aa Aaaaaaaaaaa Aaaaaa Aaaaaaaa Aaaa Aa Aaa Aaaaaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaa Aaaaa a Aaaaaa Aaaa Aaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaa Aaaaaa Aaaa Aaa Aaaaaaaa Aaaaa Aaaa Aa Aaa Aaa Aaaaaaaa Aaaaa Aaaaa Aaaaaaaaa Aaaaaaaa Aaa Aaa Aaaaaaaaa Aaa Aa Aaaaaa a Aaaaaaaaaa Aaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaaa Aaaa a Aaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaa Aaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaa Aa a Aaaaaaaa Aaaaa Aaa a Aaaaaaaa Aaaaa Aa Aaaaaaaaaa Aaaaaaaaaaa
🔒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
Audit directions
a Aaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaaaa Aaa Aaa Aaaaaa Aaaaaaa Aaa Aaaaaa Aaa Aaaaaaaa Aaaaaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaa Aaaaa Aaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aa Aaaaa Aaaaaaaaa Aaaa Aa Aaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaa Aa Aaaaaaaa Aaaa Aaa Aaaa Aaaaaaaaaaaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaaaaaaa Aaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaa
a Aaaaaaaaaaa Aaaa Aaaaaaa Aaaaaaaaaaaaaa Aaaaaa Aaaa a Aaaaaa Aaaa Aaa Aaa Aaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaa Aaaaaa Aa Aaaaa Aaaaaaaaaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aa Aaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaaa Aaaaaaaaa Aaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaa Aaaa Aaaa Aaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaa Aaaaaaaaa
a Aaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaa a Aaaaaaaa Aaaaaaa Aaaaaa Aaa Aaa Aaaaaaaaaa Aaaaaaa Aaaaaaa Aa Aaa Aaaaaa Aaaaa a Aaaaaaaaa Aaaaaaa Aaaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaa Aaaaaaa Aaaaaa Aaaaaaaaa Aaaaa Aaaaa Aaaaa Aaaaa Aaaaaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaa Aaaaaaaa Aa a Aaaaa Aaaa a Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaa Aaaaaaaaa Aaaaa Aaaaaaaaaaa Aa Aaaaaaaa Aaaaaaaaaa Aaaaaaa Aaaa Aaaaaaa Aaaa Aaa Aaaa Aaaaaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaa Aaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaaaaa Aaaaaaa a Aaaaaaaaaa a Aaaaaaaaa Aaaaaaaa a Aaaaaaaaa Aaaaaaaaa Aaaaaa Aaa Aaaaa Aaaaaa Aaa Aaaaaaa Aaaaaaaa Aaaaa Aaaaaaaa
🔒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