High watch CVSS 8.8 chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in ANGLE
DescriptionInteger overflow in ANGLE
ComponentANGLE
Sub-componentD3D11
Bug ClassInteger Overflow
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker487977696
Fix commit4b7aace91492 (angle/angle) +54/-4
CWECWE-472, CWE-190 (Integer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
Creditedcinzinga
Reported2026-02-26 (per vendor advisory)
Fix landed2026-03-06 (public git)
Disclosed2026-03-18
Patch gap12 days — fix public in source before the advisory

Background

ANGLE D3D11 backend
ANGLE’s translation of OpenGL ES/WebGL calls to Direct3D 11; on Windows it is the default backend Chrome uses for WebGL in the GPU process.
`GL_LINE_LOOP` / `GL_TRIANGLE_FAN`
primitive topologies D3D11 does not support natively, so Renderer11 builds a new 32-bit index buffer (mLineLoopIB, mTriangleFanIB) from the app’s indices in mScratchIndexDataBuffer to emulate them.
Index widening
when the source element type is narrower than 32 bits (e.g. GL_UNSIGNED_BYTE), each index becomes a 4-byte GLuint, so the byte size of the emulated buffer is about 4x the source buffer.
`reserveBufferSpace`
the index-buffer helper that reserves the requested number of bytes before ANGLE copies the widened indices into it.

Root Cause Analysis

In the D3D11 line-loop and triangle-fan index paths in Renderer11.cpp (the enclosing function names are not shown in the hunks), the byte size of the widened index buffer was computed as sizeof(GLuint) * mScratchIndexDataBuffer.size() and narrowed with static_cast<unsigned int>. If a large GL_UNSIGNED_BYTE index buffer is used, the widened byte count can go past 32 bits, so the cast truncates it (or it exceeds INT_MAX when later read as a signed size). That passes a wrong, much smaller size to mLineLoopIB->reserveBufferSpace/mTriangleFanIB->reserveBufferSpace while the full scratch buffer still holds the larger data. The regression tests use count = 805306368 (0x30000000), which gives 0xC0000000 bytes, above INT_MAX.

The fix computes the size in uint64_t spaceNeeded64. Before the reservation, ANGLE_CHECK rejects any value above std::numeric_limits<int>::max() with GL_OUT_OF_MEMORY and returns early. Only then is the value narrowed to int spaceNeeded.

The patch does not show what happens after the reservation (the copy of the widened indices), but an undersized reservation followed by a full-length copy would be a heap buffer overflow (inferred).

Key insight
The size of the 32-bit emulation buffer was computed and truncated in 32-bit arithmetic with no overflow check, so a huge narrow-type index buffer could produce a size that did not match the real data. The fix computes the size in 64 bits and returns GL_OUT_OF_MEMORY before reserving whenever it exceeds INT_MAX.

Attack Path

  1. Create a huge narrow index buffer WebGL/GLES content allocates an element array buffer of roughly 805 million GL_UNSIGNED_BYTE indices (as in DISABLED_LargeLoopUnsignedByte/DISABLED_LargeFanUnsignedByte).
  2. Draw with an emulated topology The content calls glDrawElements with GL_LINE_LOOP or GL_TRIANGLE_FAN, which on the D3D11 backend forces Renderer11 to widen the indices to 32 bits in mScratchIndexDataBuffer.
  3. Size computation wraps sizeof(GLuint) * size() (or size() * sizeof(unsigned int)) is cast to unsigned int, so values above INT_MAX or 32 bits produce a wrong size for reserveBufferSpace.
  4. Undersized buffer is written The widened indices are then copied into a buffer reserved with the wrong size, which could give out-of-bounds writes in the GPU process (inferred; the copy is not in the diff).

Impact Assessment

The bug could corrupt memory in the GPU process, where ANGLE runs (inferred; the diff shows only the unchecked size). Using it for code execution in the GPU process would also be (inferred). Reaching it requires Windows with ANGLE on the D3D11 backend (the patched code is in Renderer11.cpp) and a draw with GL_LINE_LOOP or GL_TRIANGLE_FAN from a very large index buffer of an element type narrower than 32 bits. That plausibly makes it reachable from ordinary web content through WebGL in a stock build, provided the GPU process can allocate the multi-gigabyte buffers involved (inferred). The vendor advisory rates it high severity (“Integer overflow in ANGLE”) and does not state in-the-wild exploitation.

Files Changed

  • src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
  • src/tests/gl_tests/LineLoopTest.cpp

Audit Directions

  • Widening and expansion multipliers
    Look for other places where ANGLE backends convert narrow index or vertex data to wider formats (index conversion, primitive restart, vertex format conversion in D3D9/Vulkan/Metal) and confirm the size * elementSize product is checked before being narrowed to unsigned int or int.
  • Emulated topologies with extra elements
    Line-loop emulation adds a closing index and fan emulation expands to 3 * (n - 2) indices; check every helper that sizes those outputs (for example GetTriFanIndices, line-loop helpers for draw arrays) for multiplication overflow.
  • `static_cast` narrowing of byte counts
    Search ANGLE for static_cast<unsigned int>(... * sizeof(...)) and static_cast<GLuint>/int applied to size_t byte counts on the way into allocation or reserveBufferSpace-style APIs.
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