Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds write in ANGLE
DescriptionOut of bounds write in ANGLE
ComponentANGLE
Bug ClassOOB
Tracker511772608
Fix commit3aec5c7a15c4 (angle/angle) +8/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • src/libANGLE/renderer/gl/BufferGL.cpp
  • src/libANGLE/renderer/gl/VertexArrayGL.cpp
From 3aec5c7a15c41d14ae3e272e6f72a51545fa703b Mon Sep 17 00:00:00 2001
From: Geoff Lang <[email protected]>
Date: Mon, 11 May 2026 12:15:42 -0400
Subject: [PATCH] GL: Always check for GL errors after buffer/texture allocs

Texture allocation functions already did this, but it was missing for
some buffer allocations.

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

diff --git a/src/libANGLE/renderer/gl/BufferGL.cpp b/src/libANGLE/renderer/gl/BufferGL.cpp
index 28f2da5..4e3fca4 100644
--- a/src/libANGLE/renderer/gl/BufferGL.cpp
+++ b/src/libANGLE/renderer/gl/BufferGL.cpp
@@ -118,8 +118,9 @@
         }
     }
 
-    ANGLE_GL_TRY(context, functions->bufferData(gl::ToGLenum(DestBufferOperationTarget), size,
-                                                uploadData, ToGLenum(usage)));
+    ANGLE_GL_TRY_ALWAYS_CHECK(
+        context, functions->bufferData(gl::ToGLenum(DestBufferOperationTarget), size, uploadData,
+                                       ToGLenum(usage)));
 
     mBufferSize = size;
 
diff --git a/src/libANGLE/renderer/gl/VertexArrayGL.cpp b/src/libANGLE/renderer/gl/VertexArrayGL.cpp
index f4774a5..5f56328 100644
--- a/src/libANGLE/renderer/gl/VertexArrayGL.cpp
+++ b/src/libANGLE/renderer/gl/VertexArrayGL.cpp
@@ -347,8 +347,8 @@
         if (requiredStreamingBufferSize > mStreamingElementArrayBufferSize)
         {
             // Copy the indices in while resizing the buffer
-            ANGLE_GL_TRY(context,
-                         functions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize,
+            ANGLE_GL_TRY_ALWAYS_CHECK(
+                context, functions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize,
                                                indices, GL_DYNAMIC_DRAW));
             mStreamingElementArrayBufferSize = requiredStreamingBufferSize;
         }
@@ -443,8 +443,9 @@
     stateManager->bindBuffer(gl::BufferBinding::Array, mStreamingArrayBuffer);
     if (requiredBufferSize > mStreamingArrayBufferSize)
     {
-        ANGLE_GL_TRY(context, functions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr,
-                                                    GL_DYNAMIC_DRAW));
+        ANGLE_GL_TRY_ALWAYS_CHECK(
+            context,
+            functions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr, GL_DYNAMIC_DRAW));
         mStreamingArrayBufferSize = requiredBufferSize;
     }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Absolute Memory Write in ANGLE GL Backend on macOS Intel via Unchecked Buffer Mapping

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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: ANGLE’s OpenGL backend on macOS Intel systems may allow a renderer-controlled absolute memory write in the GPU process. By passing an exceptionally large first parameter to glDrawArraysInstanced with only instanced attributes enabled, an attacker can bypass bounds validation, trigger a massive buffer allocation failure, and write controlled data to an absolute memory address via a missing nullptr check.

Affected files:

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

Estimated timestamp from git blame: 2025-08-01

Description

A vulnerability exists in the ANGLE OpenGL backend’s handling of instanced vertex attributes on macOS systems with Intel GPUs. When the shiftInstancedArrayDataWithOffset feature workaround is active, the code attempts to pad a dynamically allocated VBO based on the first parameter passed to glDrawArraysInstanced.

The code fails to properly validate the result of this massive memory allocation. In release builds, the ANGLE_GL_TRY macro drops underlying OpenGL errors. A subsequent call to MapBufferRangeWithFallback returns nullptr due to the lack of a backing store, but the pointer is used unchecked in a memcpy operation alongside an attacker-controlled offset, leading to an absolute memory write.

Potential Trigger Steps

Note: These are suggested steps based on static analysis. Our tooling has not yet executed a working proof of concept.

  1. Environment: The victim must be running Chrome on a macOS Intel device (excluding Haswell) where the OpenGL ANGLE backend is used (e.g., if Metal fails to initialize or via --use-angle=gl).
  2. Validation Bypass: A malicious WebGL2 site binds a Vertex Buffer Object containing attacker-controlled data and enables a single vertex attribute. The attacker calls gl.vertexAttribDivisor(index, 1) to configure it strictly as an instanced attribute.
  3. Draw Call: The attacker calls gl.drawArraysInstanced(mode, first, count, instanceCount) with a heavily manipulated first parameter close to INT32_MAX (e.g., INT32_MAX - 1), count = 1, and instanceCount = 1.
  4. Bypassing Decoders & ANGLE Checks:
    • Because only instanced attributes are active, the GPU process command decoder’s bounds check (VertexAttrib::MaxVertexAccessed) completely ignores the first parameter.
    • Similarly, ANGLE’s ValidateDrawAttribs checks the bounds against context->getNonInstancedVertexElementLimit(). Because there are no non-instanced attributes, this limit remains at INT64_MAX, allowing first (up to INT32_MAX) to pass validation completely unchecked.
  5. Workaround Trigger: The draw call triggers VertexArrayGL::syncDrawState. Because first > 0 and the hardware-specific workaround is enabled, the code calls streamAttributes (third_party/angle/src/libANGLE/renderer/gl/VertexArrayGL.cpp:264).
  6. Massive Allocation Failure: streamAttributes calculates requiredBufferSize = streamingDataSize + (attribsToStream.count() * maxAttributeDataSize * first). With first near INT32_MAX, this requests a buffer up to ~34.3 GiB. The code calls ANGLE_GL_TRY to allocate the buffer. In release builds, ANGLE_GL_TRY ignores the resulting GL_OUT_OF_MEMORY error.
  7. Unchecked Mapping: The code calls MapBufferRangeWithFallback, which returns nullptr because the buffer allocation failed.
  8. Absolute Write: The code calculates curBufferOffset = maxAttributeDataSize * first and executes memcpy(bufferPointer + curBufferOffset, inputPointer + batchMemcpyInputOffset, destStride). Since bufferPointer is nullptr (0), this performs an absolute memory write of the attacker’s VBO data to the attacker-controlled curBufferOffset.

Impact

This vulnerability provides a highly reliable primitive to write attacker-controlled data to an arbitrary, absolute memory address within a massive ~34.3 GiB range in the unsandboxed GPU process. An attacker could use this to overwrite function pointers, vtables, or executable memory pages, leading to Remote Code Execution (RCE) and Sandbox Escape.

Suggested Fix

  1. Pointer Validation: Ensure MapBufferRangeWithFallback handles nullptr securely in VertexArrayGL::streamAttributes. Do not rely solely on ASSERT(inputBufferPointer). Return a GL_OUT_OF_MEMORY error if mapping fails.
  2. Sanity Bounds on first: The validating command decoder and ANGLE’s ValidateDrawAttribs should enforce a reasonable upper bound on first even when only instanced attributes are active. The parameter should not be allowed to reach sizes that guarantee integer overflows or absurd memory allocation requests.
  3. Review ANGLE_GL_TRY usage: Investigate instances where ANGLE_GL_TRY is used for resource allocation (glBufferData) that subsequently map memory. Swallowing OOM errors on allocations that are immediately mapped is a dangerous pattern.

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


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