High chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in ANGLE
DescriptionInteger overflow in ANGLE
ComponentANGLE
Bug ClassInteger Overflow
Tracker487977696
Fix commit4b7aace91492 (angle/angle) +54/-4
CISA KEVNot listed
Creditedcinzinga
Disclosed2026-03-18

Files Changed

  • src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
  • src/tests/gl_tests/LineLoopTest.cpp
From 4b7aace914924f634c4148c41e6fc87198867fa5 Mon Sep 17 00:00:00 2001
From: Stephen White <senorblanco@chromium.org>
Date: Wed, 04 Mar 2026 15:44:30 -0500
Subject: [PATCH] D3D11: fix overflow in line loop and triangle fan

If an index buffer is created of element type less than 32bit (e.g.,
GL_UNSIGNED_BYTE) and used in a line loop or triangle fan, D3D11 must
widen it to 32-bit. This can cause an overflow if the widened size in
bytes is greater than INT_MAX. The fix is to detect the overflow and
abort.

Bug: chromium:487977696
Change-Id: I57b1dcc9b3d968da88282164a5f92386500c6205
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7637780
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Stephen White <senorblanco@chromium.org>
---

diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 60c00fe..ec644eb 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -2083,8 +2083,14 @@
     {
         return angle::Result::Continue;
     }
-    unsigned int spaceNeeded =
-        static_cast<unsigned int>(sizeof(GLuint) * mScratchIndexDataBuffer.size());
+
+    uint64_t spaceNeeded64 = sizeof(GLuint) * mScratchIndexDataBuffer.size();
+    ANGLE_CHECK(GetImplAs<Context11>(context), spaceNeeded64 <= std::numeric_limits<int>::max(),
+                "Failed to create a 32-bit looping index buffer for "
+                "a GL_LINE_LOOP of <32-bit element type; too many indices required.",
+                GL_OUT_OF_MEMORY);
+    int spaceNeeded = static_cast<int>(spaceNeeded64);
+
     ANGLE_TRY(
         mLineLoopIB->reserveBufferSpace(context, spaceNeeded, gl::DrawElementsType::UnsignedInt));
 
@@ -2171,8 +2177,12 @@
     GetTriFanIndices(indexPointer, type, count, glState.isPrimitiveRestartEnabled(),
                      &mScratchIndexDataBuffer);
 
-    const unsigned int spaceNeeded =
-        static_cast<unsigned int>(mScratchIndexDataBuffer.size() * sizeof(unsigned int));
+    uint64_t spaceNeeded64 = mScratchIndexDataBuffer.size() * sizeof(unsigned int);
+    ANGLE_CHECK(GetImplAs<Context11>(context), spaceNeeded64 <= std::numeric_limits<int>::max(),
+                "Failed to create a 32-bit looping index buffer for "
+                "a GL_TRIANGLE_FAN of <32-bit element type; too many indices required.",
+                GL_OUT_OF_MEMORY);
+    int spaceNeeded = static_cast<int>(spaceNeeded64);
     ANGLE_TRY(mTriangleFanIB->reserveBufferSpace(context, spaceNeeded,
                                                  gl::DrawElementsType::UnsignedInt));
 
diff --git a/src/tests/gl_tests/LineLoopTest.cpp b/src/tests/gl_tests/LineLoopTest.cpp
index 696dde3..2dc6551 100644
--- a/src/tests/gl_tests/LineLoopTest.cpp
+++ b/src/tests/gl_tests/LineLoopTest.cpp
@@ -546,6 +546,46 @@
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
 }
 
+// Test an edge case in the D3D11 backend where a large index array of GL_UNSIGNED_BYTE
+// drawn as a GL_LINE_LOOP is widened to 32-bits internally and overflows.
+// Disabled because it slow and triggers an internal error.
+TEST_P(LineLoopTestES3, DISABLED_LargeLoopUnsignedByte)
+{
+    size_t count = 805306368;
+
+    glLinkProgram(mProgram);
+    glUseProgram(mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    GLBuffer ebo;
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, count, nullptr, GL_STATIC_DRAW);
+    ASSERT_GL_NO_ERROR();
+
+    glDrawElements(GL_LINE_LOOP, count, GL_UNSIGNED_BYTE, 0);
+    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);
+}
+
+// Test an edge case in the D3D11 backend where a large index array of GL_UNSIGNED_BYTE
+// drawn as a GL_TRIANGLE_FAN is widened to 32-bits internally and overflows.
+// Disabled because it slow and triggers an internal error.
+TEST_P(LineLoopTestES3, DISABLED_LargeFanUnsignedByte)
+{
+    size_t count = 805306368;
+
+    glLinkProgram(mProgram);
+    glUseProgram(mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    GLBuffer ebo;
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, count, nullptr, GL_STATIC_DRAW);
+    ASSERT_GL_NO_ERROR();
+
+    glDrawElements(GL_TRIANGLE_FAN, count, GL_UNSIGNED_BYTE, 0);
+    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);
+}
+
 // Tests an edge case with a very large line loop element count.
 // Disabled because it is slow and triggers an internal error.
 TEST_P(LineLoopTest, DISABLED_DrawArraysWithLargeCount)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/LineLoopTest.cpp b/src/tests/gl_tests/LineLoopTest.cpp
index 696dde3..2dc6551 100644
--- a/src/tests/gl_tests/LineLoopTest.cpp
+++ b/src/tests/gl_tests/LineLoopTest.cpp
@@ -546,6 +546,46 @@
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
 }
 
+// Test an edge case in the D3D11 backend where a large index array of GL_UNSIGNED_BYTE
+// drawn as a GL_LINE_LOOP is widened to 32-bits internally and overflows.
+// Disabled because it slow and triggers an internal error.
+TEST_P(LineLoopTestES3, DISABLED_LargeLoopUnsignedByte)
+{
+    size_t count = 805306368;
+
+    glLinkProgram(mProgram);
+    glUseProgram(mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    GLBuffer ebo;
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, count, nullptr, GL_STATIC_DRAW);
+    ASSERT_GL_NO_ERROR();
+
+    glDrawElements(GL_LINE_LOOP, count, GL_UNSIGNED_BYTE, 0);
+    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);
+}
+
+// Test an edge case in the D3D11 backend where a large index array of GL_UNSIGNED_BYTE
+// drawn as a GL_TRIANGLE_FAN is widened to 32-bits internally and overflows.
+// Disabled because it slow and triggers an internal error.
+TEST_P(LineLoopTestES3, DISABLED_LargeFanUnsignedByte)
+{
+    size_t count = 805306368;
+
+    glLinkProgram(mProgram);
+    glUseProgram(mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    GLBuffer ebo;
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, count, nullptr, GL_STATIC_DRAW);
+    ASSERT_GL_NO_ERROR();
+
+    glDrawElements(GL_TRIANGLE_FAN, count, GL_UNSIGNED_BYTE, 0);
+    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);
+}
+
 // Tests an edge case with a very large line loop element count.
 // Disabled because it is slow and triggers an internal error.
 TEST_P(LineLoopTest, DISABLED_DrawArraysWithLargeCount)
Loading diff…

Original Bug Report

reported by ci...@gmail.com

ANGLE D3D11 drawLineLoop Integer Overflow causes GPU OOB Write


Report description

ANGLE D3D11 drawLineLoop Integer Overflow causes GPU OOB Write


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://chromium.googlesource.com/angle/angle/+/refs/heads/main/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp


The problem

Please describe the technical details of the vulnerability

Renderer11::drawLineLoop has an integer overflow when computing the size of a mapped index buffer. The overflow check at line 2073 only validates the non-primitive-restart case (count+1 elements). When primitive restart is enabled, GetLineLoopIndices calls GetLineLoopWithRestartIndexCount which can return a significantly larger count due to index expansion at each restart boundary. The expanded output count multiplied by sizeof(GLuint) overflows unsigned int at line 2086-2087, truncating spaceNeeded to zero. The subsequent memcpy at line 2096 uses the full untruncated size, writing approximately 4GB of attacker-influenced data into a 16KB streaming index buffer.

Affected Code

Renderer11.cpp, drawLineLoop():

// Line 2073-2078: overflow check only validates (count+1), not primitive restart expansion
bool indexCheck = static_cast<unsigned int>(count) + 1 >
                  (std::numeric_limits<unsigned int>::max() / sizeof(unsigned int));

// Line 2080-2081: GetLineLoopIndices expands indices beyond what the check allows
GetLineLoopIndices(indices, type, static_cast<GLuint>(count),
                   glState.isPrimitiveRestartEnabled(), &mScratchIndexDataBuffer);

// Line 2086-2087: unsigned int truncation (e.g. 1073741824 * 4 = 0x100000000 -> 0)
unsigned int spaceNeeded =
    static_cast<unsigned int>(sizeof(GLuint) * mScratchIndexDataBuffer.size());

// Line 2096-2097: memcpy uses full untruncated size, not spaceNeeded
memcpy(mappedMemory, &mScratchIndexDataBuffer[0],
       sizeof(GLuint) * mScratchIndexDataBuffer.size());

Steps to Reproduce

  1. Open Chrome on Windows (D3D11 is the default ANGLE backend, no flags needed)
  2. Navigate to the attached poc_lineloop_d3d11.html
  3. The page auto-fires: creates a WebGL2 context, allocates an element array buffer with 805306368 UNSIGNED_BYTE indices in the pattern [0, 1, 0xFF] repeating (0xFF = primitive restart), then calls gl.drawElements(gl.LINE_LOOP, 805306368, gl.UNSIGNED_BYTE, 0)
  4. GPU process crashes with access violation (WRITE of 4GB)

Note: the PoC allocates ~805MB for the element array buffer. Systems with less than ~2GB of available GPU process memory may fail the allocation before reaching the overflow.

Tested on:

  • Chrome 145.0.7632.110 (stable, Windows 11) – GPU process crash
  • Chromium 146.0.7678.0 ASan (Windows 11, –in-process-gpu) – heap-buffer-overflow WRITE of size 4294967296 at Renderer11.cpp:2096

With –in-process-gpu, the crash occurs in the browser process address space.

ASan Output

==21376==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x12722fe50000 at pc 0x7ffcdf71b4fc bp 0x00ed077fdb70 sp 0x00ed077fdbb8
WRITE of size 4294967296 at 0x12722fe50000 thread T23
    #0 0x7ffcdf71b4fb  (clang_rt.asan_dynamic-x86_64.dll+0x18004b4fb)
    #1 0x7ffcdb3fe543 in rx::Renderer11::drawLineLoop Renderer11.cpp:2096:5
    #2 0x7ffcdb401b6d in rx::Renderer11::drawElements Renderer11.cpp:1970:16
    #3 0x7ffcdb3b0a49 in rx::Context11::drawElements Context11.cpp:361:12
    #4 0x7ffcdabbacfb in GL_DrawElements entry_points_gles_2_0_autogen.cpp:1862:22
    #5 0x7ffc7d294f7b in gl::RealGLApi::glDrawElementsFn gl_gl_api_implementation.cc:401:16
    #6 0x7ffc7dae946b in gpu::gles2::GLES2DecoderPassthroughImpl::DoDrawElements gles2_cmd_decoder_passthrough_doers.cc:1163:10
    #7 0x7ffc7db338ea in gpu::gles2::GLES2DecoderPassthroughImpl::HandleDrawElements gles2_cmd_decoder_passthrough_handlers.cc:138:10
    #8 0x7ffc7db51cbc in gpu::gles2::GLES2DecoderPassthroughImpl::DoCommandsImpl gles2_cmd_decoder_passthrough.cc:742:20
    #9 0x7ffc67ce5e7b in gpu::CommandBufferService::Flush command_buffer_service.cc:267:35
    #10 0x7ffc7dcfd071 in gpu::CommandBufferStub::OnAsyncFlush command_buffer_stub.cc:504:22

0x12722fe50000 is located 6144 bytes before 359576-byte region [0x12722fe51800,0x12722fea9498)

SUMMARY: AddressSanitizer: heap-buffer-overflow Renderer11.cpp:2096:5 in rx::Renderer11::drawLineLoop

Full unabridged ASan output is attached as asan_output.txt.

Fix

Replace the unsigned int spaceNeeded with size_t, or validate mScratchIndexDataBuffer.size() after GetLineLoopIndices returns (accounting for primitive restart expansion) rather than only validating count+1 before the call:

size_t spaceNeeded = sizeof(GLuint) * mScratchIndexDataBuffer.size();
ANGLE_CHECK(GetImplAs<Context11>(context),
            spaceNeeded <= std::numeric_limits<unsigned int>::max(),
            "Line loop index buffer too large", GL_OUT_OF_MEMORY);

The same fix should be applied to drawTriangleFan (line 2168).

Bisect

The vulnerable code was introduced in ANGLE commit 0d3537c224c7df5b90a2cf77678eb0987b6b7a30 (“D3D11: Implement ES3 primitive restart with line loops”, 2015-11-06). This commit introduced unsigned int spaceNeeded = static_cast<unsigned int>(sizeof(GLuint) * mScratchIndexDataBuffer.size()) (truncation), and added a memcpy using the full untruncated sizeof(GLuint) * mScratchIndexDataBuffer.size() — but did not update the pre-existing overflow check to account for the expanded index count.

Impact analysis

Heap buffer overflow in the GPU process, reachable from JavaScript via WebGL2 on any webpage. No flags or special configuration required. D3D11 is the default ANGLE backend on all Windows Chrome installations. The overflow writes approximately 4GB of partially attacker-controlled data (vertex index values from the element array buffer) into a 16KB D3D11 streaming index buffer. With –in-process-gpu the corruption occurs in the browser process address space.


The cause

What version of Chrome have you found the security issue in?

145.0.7632.110 Stable

Yes, it is related to a crash.

Choose the type of vulnerability

Memory Corruption (in a sandboxed process)

How would you like to be publicly acknowledged for your report?

cinzinga

View on issue tracker