← All issues

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

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

High로 평가된 근거는 다음과 같습니다. 공격자가 제어하는 0xFFFF index marker를 통해 GPU process에서 vertex-attribute 메모리에 대한 매번 재현 가능한 OOB read가 발생하며, Metal Shader Validation regression test에서 crash가 확인되었습니다. stride를 조작한 tight relative read primitive로의 확장 가능성은 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);

ANGLE이 WEBGL_provoking_vertex 에뮬레이션을 위해 index buffer를 재작성할 때 fixIndexBuffer kernel이 호출됩니다. 이 kernel은 기존에 constexpr uint restartIndex = 0xFFFFFFFF를 지역 상수로 선언하고 있었습니다. 수정에서는 이 하드코딩된 지역 상수를 제거하고, 기존 indexBufferIsUint16 / indexBufferIsUint32 function constant 옆에 파일 스코프의 constant uint restartIndex = indexBufferIsUint16 ? 0xFFFF : 0xFFFFFFFF;를 새로 배치했습니다. restartIndex 매개변수는 readIdxoutputPrimitive(READ_IDX macro 포함)에서 제거되었으며, 이제 kernel은 항상 올바른 타입의 compile-time constant를 직접 참조하게 됩니다. 동일한 변경이 rewrite_indices.metal, mtl_internal_shaders_src_autogen.metal, 그리고 mtl_internal_shaders_src_autogen.h 내 C++ string에도 적용되었습니다.

operand 너비와 sentinel 값 불일치 — 더 넓은 index type 기준으로 하드코딩된 primitive-restart marker가 더 좁은 타입에서 실제 index로 묵시적으로 해석되는 패턴.

Primitive restart는 OpenGL/WebGL2의 기능으로, sentinel index 값이 vertex를 참조하는 대신 현재 strip/fan을 끊고 새로운 primitive를 시작하는 데 사용됩니다. sentinel 값은 타입에 따라 결정되며, GL_UNSIGNED_SHORT의 경우 0xFFFF, GL_UNSIGNED_INT의 경우 0xFFFFFFFF입니다. WEBGL_provoking_vertex는 WebGL2 extension으로, 스크립트가 primitive의 어느 vertex가 flat 한정자가 붙은 varying 값을 제공할지 선택할 수 있게 합니다. ANGLE은 WebKit의 Metal 기반 WebGL 구현입니다. Metal이 GL provoking-vertex semantics를 기본적으로 지원하지 않기 때문에, ANGLE은 fixIndexBuffer compute kernel로 index buffer를 재작성하여 indexed draw가 GL 사양에 맞게 동작하도록 합니다. Metal function constants는 pipeline-state-object 생성 시 제공되는 값으로, Metal 컴파일러가 이를 기반으로 특수화를 수행합니다. indexBufferIsUint16이 이 중 하나입니다. Metal Shader Validation은 선택적으로 활성화하는 런타임 모드로, shader 측 메모리 위반을 감지합니다. regression test에서 이를 활성화했기 때문에 OOB 접근이 조용한 메모리 오염 대신 매번 재현 가능한 crash로 드러납니다.

kernel은 readIdx0xFFFFFFFF를 전달했습니다. 이 함수는 indexBufferUint16[inIndex](uint로 zero-extend됨) 또는 indexBufferUint32[inIndex]를 읽은 뒤 inIndex == restartIndex를 비교하는데, uint16 입력에서는 모든 원소가 [0, 0xFFFF] 범위에 속하므로 이 비교가 true가 되는 경우는 없습니다. 결과적으로 정상적인 0xFFFF restart marker도 일반 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.

더 확인하려면 구독해 주세요

🔒

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

더 확인하려면 구독해 주세요