CVE-2026-28917
Overview
Background
- Provoking vertex
- The vertex whose attributes are used for flat-shaded primitives; ANGLE rewrites index buffers on Metal to emulate GL provoking-vertex semantics.
- Index vs primitive count
- For each primitive mode (points/lines/strips/triangles/fans) there is a fixed relation between index count and primitive count used to size buffers.
- Index-buffer rewrite
- ANGLE builds a new Metal index buffer; its size must match how it is later indexed, or accesses go out of bounds.
- ANGLE Metal backend
- The translation layer that implements WebGL/GLES on Apple’s Metal API.
Root Cause Analysis
ANGLE’s Metal backend rewrites index buffers for provoking-vertex handling (ProvokingVertexHelper). The pre-patch code converted between index counts and primitive counts with two hand-rolled per-mode helpers, primCountForIndexCount() and indexCountForPrimCount(), that switched on a packed fixIndexBufferKey mode and did unchecked integer arithmetic (indexCount/2, indexCount-1, primCount*3, casts through int/uint). Those computations size and index the rewritten Metal index buffer, so an inconsistent or wrapped count (from a crafted draw with an unusual mode/count, or the two directions disagreeing) yields a buffer sized differently from how it is later indexed – an out-of-bounds access during the index-buffer fixup, surfacing as a GPU/WebContent process crash.
The fix replaces both helpers with a single resolveIndexedDrawRewriteInfo(gl::PrimitiveMode mode, GLsizei count) that returns a coherent {primitiveCount, newIndexCount, newPrimitiveMode} struct computed once from the GL draw parameters, so the primitive count, the rewritten index count, and the mode are derived together and cannot disagree. [Inference] The exact overflow/mismatch input is not spelled out by the diff; what the patch establishes is that the fragile dual-direction count arithmetic is consolidated into one bounded computation, removing the count-mismatch that led to the OOB. This is reached through WebGL draw calls that hit the Metal provoking-vertex index-rewrite path.
Attack Path
- Use WebGL on Metal Run WebGL content on an Apple/Metal ANGLE backend that uses ProvokingVertexHelper to rewrite index buffers.
- Issue a crafted indexed draw Call an indexed draw with a primitive mode and index count chosen so the per-mode index<->primitive count arithmetic miscomputes or the two directions disagree.
- Mis-size the rewrite buffer [inference] The rewritten Metal index buffer is allocated/indexed from an inconsistent count, so writes/reads fall outside it.
- Out-of-bounds / crash The index-buffer fixup accesses out of bounds, corrupting memory or crashing the GPU/WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
resolveIndexedDrawRewriteInfo (replaces primCountForIndexCount / indexCountForPrimCount)Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/ProvokingVertexHelper.mm |
modified | Consolidates the index<->primitive count conversions into one function returning a coherent {primitiveCount, newIndexCount, newPrimitiveMode}, so the counts used to size and index the rewritten Metal index buffer are derived together and cannot disagree/overflow. |
Files Changed
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/ProvokingVertexHelper.mm
Audit Directions
- Other count arithmetic in the Metal backendgrep ANGLE metal/ for per-primitive-mode count math (count/2, count-1, *3) that sizes buffers without a single coherent computation or overflow check.
- Index-buffer sizing vs indexingAudit index-rewrite/line-loop/triangle-fan expansion paths to ensure the allocated size and the write/read loop use the same derived count.
- Draw-parameter validationReview where GL draw count/mode reach the Metal rewrite to confirm bounds/mode validation before the conversion.