← All issues

[2] WebGPU vertex buffer OOB read via flipped comparison in index validation

Severity: High | Component: WebGPU Buffer | 15ddef0

Rated High because the observable effect is a GPU-side out-of-bounds read from vertex buffer memory reachable from any web page via the WebGPU API, and the bypass mechanism (a flipped comparison operator in a security-critical validation function) is confirmed at confidence 0.95 from the single-line diff — unverified claims concern GPU memory exfiltration via shader output and cross-context data leakage, which depend on platform-specific GPU memory models.

The comparison m_maxUshortIndex > maxUshortIndex in Buffer::needsIndexValidation was flipped — it should have been maxUshortIndex > m_maxUshortIndex. The fix replaces both branches with a single needsUpdate expression using || of the two comparisons (both in the correct direction) and updates both cached values via std::max.

Source/WebGPU/WebGPU/Buffer.mm

bool Buffer::needsIndexValidation(uint32_t maxUnsignedIndex, uint16_t maxUshortIndex)
{
- bool needsUpdate = false;
- if (maxUnsignedIndex > m_maxUnsignedIndex) {
- m_maxUnsignedIndex = maxUnsignedIndex;
- needsUpdate = true;
- }
- if (m_maxUshortIndex > maxUshortIndex) {
- m_maxUshortIndex = maxUshortIndex;
- needsUpdate = true;
- }
+ const bool needsUpdate = maxUnsignedIndex > m_maxUnsignedIndex || maxUshortIndex > m_maxUshortIndex;
+ m_maxUnsignedIndex = std::max(m_maxUnsignedIndex, maxUnsignedIndex);
+ m_maxUshortIndex = std::max(m_maxUshortIndex, maxUshortIndex);
 
return needsUpdate;
}

The patch replaces a two-branch update-and-check pattern with a single boolean expression. The old code had two if-blocks: the first for maxUnsignedIndex (correct: maxUnsignedIndex > m_maxUnsignedIndex) and the second for maxUshortIndex (flipped: m_maxUshortIndex > maxUshortIndex). The flipped branch had two effects: it triggered only when the cached max exceeded the new max (the opposite of what's needed), and it then assigned the smaller new value downward into m_maxUshortIndex. The fix uses a single const bool needsUpdate with both comparisons in the correct direction, then unconditionally updates both cached values via std::max.

WebGPU's drawIndexed call uses an index buffer to select which vertices to process. The index buffer contains integer indices into the vertex buffer. Before issuing the draw, the implementation must validate that the maximum index in the index buffer does not exceed the vertex buffer's bounds — otherwise the GPU would read past the end of the vertex data.

Buffer::needsIndexValidation is an optimization: it caches the previously validated maximum index (m_maxUnsignedIndex for uint32 indices, m_maxUshortIndex for uint16 indices) and returns whether a new validation pass is needed — i.e., whether the current draw's maximum index exceeds the previously validated maximum. A true return means the caller must re-validate; false means the previously validated bound still covers the current draw.

Flipped comparison operands in an index-validation guard, causing the validation gate to open when it should close and vice versa.

The ushort comparison m_maxUshortIndex > maxUshortIndex fires only when the cached max exceeds the new max — the opposite of the safety-relevant direction. When a draw call presents a larger ushort index than previously seen (the dangerous case), the condition is false, needsUpdate stays false, and m_maxUshortIndex is not updated. The function tells the caller no re-validation is needed when it absolutely is. Conversely, when a draw call presents a smaller index (already validated), the condition is true, the cached max is downgraded to the smaller value, and the function spuriously reports that re-validation is needed. The uint32 path was correct while the uint16 path was flipped — a classic copy-paste error where the operand order was reversed in the second branch.

🔒

Explores the exploitation mechanics of bypassing GPU index validation and what GPU memory could be disclosed

Subscribe to read more

🔒

Multiple audit patterns identified for WebGPU validation caching and index-type handling, with concrete search targets

Subscribe to read more