← All issues

[5] ANGLE IndexRange integer truncation bypasses vertex bounds check

Severity: High | Component: ANGLE (WebGL) | 56c55dd

Rated High because the regression test confirms a maximal 0xFFFFFFFF index was accepted against an undersized buffer before the fix, producing an out-of-bounds vertex fetch in the GPU process; escalation past information disclosure is limited because no write primitive is evident and the truncating consumer arithmetic is not in this diff.

IndexRange marked up an index range with uint32_t start, uint32_t count, which cannot easily represent range [0, 0xFFFFFFFF]. Switch to start/end markup, with start > end marking an empty range.

Source/ThirdParty/ANGLE/src/common/mathutil.h

- IndexRange(uint32_t start, uint32_t end)
- : mStart(start), mEnd(end), mCount(static_cast<uint64_t>(end - start) + 1)
- { ASSERT(start <= end); }
- bool isEmpty() const { return mCount == 0; }
+ IndexRange(uint32_t start, uint32_t end) : mStart(start), mEnd(end) { ASSERT(mStart <= mEnd); }
+ bool isEmpty() const { return mStart > mEnd; }
...
- uint64_t vertexCount() const { return mCount; }
+ // Range: [0, 0xFFFFFFFF] == 0x100000000 (needs size_t).
+ size_t vertexCount() const
+ {
+ // Note: unsigned underflow ok on isEmpty() == true.
+ return static_cast<size_t>(mEnd) - mStart + 1u;
+ }
private:
- uint32_t mStart{0};
+ uint32_t mStart{1};
uint32_t mEnd{0};
- uint64_t mCount{0};
+ friend bool operator==(const IndexRange &a, const IndexRange &b) noexcept = default;

Source/ThirdParty/ANGLE/src/tests/gl_tests/WebGLCompatibilityTest.cpp

+ constexpr GLuint kIndexData2[] = { 0, std::numeric_limits<GLuint>::max() };
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndexData2), kIndexData2, GL_DYNAMIC_DRAW);
+ glDrawElements(GL_LINES, 2, GL_UNSIGNED_INT, 0);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION); // 0xFFFFFFFF index now rejected

gl::IndexRange is rewritten from a (mStart, mEnd, mCount) representation to a pure (mStart, mEnd) pair. The constructor no longer precomputes mCount = end - start + 1; isEmpty() becomes mStart > mEnd; default initializers become mStart{1}, mEnd{0} so a default range is empty. vertexCount() changes from uint64_t to a size_t computed on demand, with an explicit comment that [0, 0xFFFFFFFF] yields 0x100000000. The hand-written operator== is deleted in favour of a defaulted member-wise one. New unit tests exercise full-range values, and WebGLCompatibilityTest.cpp asserts that glDrawElements with 0xFFFFFFFF against a small buffer returns GL_INVALID_OPERATION.

Truncation of an inclusive full-width integer range's cardinality (which needs one extra bit) in downstream draw-bounds arithmetic, collapsing the maximal range to an apparently-empty/in-bounds value and defeating the vertex bounds check.

WebGL drawElements issues an indexed draw: an element-array buffer holds integer indices that select vertices from the bound vertex attribute arrays. Before dispatching, ANGLE computes the minimum and maximum index actually referenced and checks the maximum is within the number of vertices the bound buffers supply; an out-of-range maximum must produce GL_INVALID_OPERATION. gl::IndexRange carries that inclusive [min, max] span; its cardinality for an inclusive range is max - min + 1, so the full [0, 0xFFFFFFFF] range has 0x100000000 vertices — a value needing 33 bits that does not fit in a 32-bit integer. ComputeTypedIndexRange scans the index buffer to produce the range; with primitive restart disabled, 0xFFFFFFFF is a real index. In WebKit, ANGLE runs inside the sandboxed GPU process.

This is integer truncation/overflow leading to a bounds-check bypass and out-of-bounds read. The inclusive full-width range [0, 0xFFFFFFFF] has cardinality 0x100000000, requiring 33 bits. Notably, the IndexRange code removed by this diff did not itself wrap — the old mCount was a uint64_t that correctly held 0x100000000. The truncation-to-zero therefore occurs in consumer arithmetic that takes this cardinality (or reconstructs an upper bound) and stores it in a narrower type such as uint32_t/GLsizei.

🔒

Where the maximal-index range actually loses precision, and how far that propagates into the GPU draw pipeline, is traced in depth.

Subscribe to read more

🔒

Multiple reusable audit patterns identified for integer-cardinality truncation at bounds-check boundaries, with concrete ANGLE starting points for variant discovery.

Subscribe to read more