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
Tracker499205491
Fix commit6044531d6116 (angle/angle) +3/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • src/libANGLE/renderer/gl/TextureGL.cpp
From 6044531d611644bf8399535180903c8892105cd8 Mon Sep 17 00:00:00 2001
From: Geoff Lang <[email protected]>
Date: Fri, 01 May 2026 11:03:14 -0400
Subject: [PATCH] GL: Use size_t for calculating lastRowOffset in TextureGL.

All the arguments are unsigned ints and the result is used as an offset.

The buffer has already been validated to be large enough for the offset.

No test case for this one because it requires >2gb PBOs to even have a
potential of overflow.

Fixed: chromium:499205491
Change-Id: I890ef851e4d2c8fcdeadb4f471cb3c96ba8b3089
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7807650
Reviewed-by: Shahbaz Youssefi <[email protected]>
Commit-Queue: Geoff Lang <[email protected]>
---

diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp
index 466fbe1..2816918 100644
--- a/src/libANGLE/renderer/gl/TextureGL.cpp
+++ b/src/libANGLE/renderer/gl/TextureGL.cpp
@@ -550,7 +550,7 @@
         {
             // Do not include skipBytes in the last image pixel start offset as it will be done by
             // the driver
-            GLint lastImageOffset          = (area.depth - 1) * imageBytes;
+            size_t lastImageOffset         = (area.depth - 1) * imageBytes;
             const GLubyte *lastImagePixels = pixels + lastImageOffset;
             ANGLE_GL_TRY(context, functions->texSubImage3D(
                                       ToGLenum(target), static_cast<GLint>(level), area.x, area.y,
@@ -561,7 +561,7 @@
         // Upload the last row of the last slice "manually"
         ANGLE_TRY(stateManager->setPixelUnpackState(context, directUnpack));
 
-        GLint lastRowOffset =
+        size_t lastRowOffset =
             skipBytes + (area.depth - 1) * imageBytes + (area.height - 1) * rowBytes;
         const GLubyte *lastRowPixels = pixels + lastRowOffset;
         ANGLE_GL_TRY(context,
@@ -584,7 +584,7 @@
         // Upload the last row "manually"
         ANGLE_TRY(stateManager->setPixelUnpackState(context, directUnpack));
 
-        GLint lastRowOffset          = skipBytes + (area.height - 1) * rowBytes;
+        size_t lastRowOffset         = skipBytes + (area.height - 1) * rowBytes;
         const GLubyte *lastRowPixels = pixels + lastRowOffset;
         ANGLE_GL_TRY(context, functions->texSubImage2D(ToGLenum(target), static_cast<GLint>(level),
                                                        area.x, area.y + area.height - 1, area.width,
Loading diff…

Original Bug Report

reported by [email protected]

Potential integer truncation in ANGLE TextureGL leads to OOB PBO read

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.

Overview: A potential vulnerability in ANGLE’s GL backend occurs when a zero-height texture update is processed with a large UNPACK_ROW_LENGTH. This triggers a driver workaround that calculates a negative PBO offset, which is passed to the native OpenGL driver, causing an out-of-bounds read and an info leak from the GPU process.

Affected files:

  • third_party/angle/src/libANGLE/renderer/gl/TextureGL.cpp

Estimated timestamp from git blame: 2018-10-03

Summary

A potential integer truncation vulnerability exists in ANGLE’s TextureGL.cpp within the setSubImagePaddingWorkaround function. When processing a texture update with an empty height (height = 0) and a large UNPACK_ROW_LENGTH, ANGLE calculates a negative byte offset for the Pixel Buffer Object (PBO). This negative offset is passed to the native OpenGL driver, resulting in an out-of-bounds memory read from the GPU process heap. This can be exploited to leak sensitive information back to the renderer process.

Technical Details

In WebGL 2, a texture update with height = 0 is valid and passes all frontend dimension and buffer size checks, because an update of 0 pixels requires 0 bytes of PBO memory.

When the command reaches ANGLE’s OpenGL backend (which is commonly used on Linux and macOS), it enters TextureGL::setSubImage. This function checks if a driver workaround is needed by calling ShouldApplyLastRowPaddingWorkaround. For height = 0, the calculated data size is correctly 0. However, the workaround logic incorrectly adds the padding expected by a driver based on the large UNPACK_ROW_LENGTH. This artificially inflates the expected end byte, causing ShouldApplyLastRowPaddingWorkaround to return true.

Consequently, ANGLE calls setSubImagePaddingWorkaround (found at third_party/angle/src/libANGLE/renderer/gl/TextureGL.cpp). This function calculates the byte offset for the “last row” to be uploaded using unchecked arithmetic:

GLint lastRowOffset = skipBytes + (area.height - 1) * rowBytes;

With area.height = 0, (area.height - 1) evaluates to -1. When multiplied by a large unsigned rowBytes (e.g., 1,000,000 bytes, derived from UNPACK_ROW_LENGTH), the result underflows as a 32-bit unsigned integer (e.g., 0xFFF0BDC0). This value is then truncated and stored in the signed GLint lastRowOffset as -1000000.

Next, the pointer math const GLubyte *lastRowPixels = pixels + lastRowOffset; creates a negative offset. Because a PBO is bound, the native OpenGL driver interprets this pointer as an offset into the PBO. Native drivers typically do not expect negative offsets since the frontend is supposed to validate bounds, leading to a raw out-of-bounds read of GPU process memory located before the start of the PBO mapping.

Suggested Steps to Trigger (Theoretical)

Note: These are suggested/potential steps as our tooling agent doesn’t yet have the ability to run code to confirm a working Proof of Concept.

  1. Create a WebGL 2.0 context.
  2. Create and bind a 2D texture, allocating at least 100x1 pixels.
  3. Create and bind a PIXEL_UNPACK_BUFFER with a small allocation (e.g., 16 bytes).
  4. Set a large row length to control the negative offset distance: gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 250000);
  5. Trigger the bug with a zero-height update: gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 1, 100, 0, gl.RGBA, gl.UNSIGNED_BYTE, 0);
  6. The native driver will read from the GPU process heap memory before the PBO and write it into the texture.
  7. Read back the texture contents using gl.readPixels() to retrieve the leaked data.

Suggested Fix

  1. Implement an early return in TextureGL::setImage and TextureGL::setSubImage if the update area is empty (e.g., area.width == 0 || area.height == 0 || area.depth == 0).
  2. Update ShouldApplyLastRowPaddingWorkaround in renderergl_utils.cpp to immediately return false if the dimensions are empty to prevent unnecessary workaround execution.
  3. Replace raw arithmetic with base::CheckedNumeric when calculating lastRowOffset in setSubImagePaddingWorkaround to prevent underflows and truncation.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker