CVE-2026-14397
Overview
Files Changed
src/libANGLE/renderer/gl/BufferGL.cppsrc/libANGLE/renderer/gl/VertexArrayGL.cpp
Patch
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; }
Original Bug Report
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.
- 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). - 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. - Draw Call: The attacker calls
gl.drawArraysInstanced(mode, first, count, instanceCount)with a heavily manipulatedfirstparameter close toINT32_MAX(e.g.,INT32_MAX - 1),count = 1, andinstanceCount = 1. - Bypassing Decoders & ANGLE Checks:
- Because only instanced attributes are active, the GPU process command decoder’s bounds check (
VertexAttrib::MaxVertexAccessed) completely ignores thefirstparameter. - Similarly, ANGLE’s
ValidateDrawAttribschecks the bounds againstcontext->getNonInstancedVertexElementLimit(). Because there are no non-instanced attributes, this limit remains atINT64_MAX, allowingfirst(up toINT32_MAX) to pass validation completely unchecked.
- Because only instanced attributes are active, the GPU process command decoder’s bounds check (
- Workaround Trigger: The draw call triggers
VertexArrayGL::syncDrawState. Becausefirst > 0and the hardware-specific workaround is enabled, the code callsstreamAttributes(third_party/angle/src/libANGLE/renderer/gl/VertexArrayGL.cpp:264). - Massive Allocation Failure:
streamAttributescalculatesrequiredBufferSize = streamingDataSize + (attribsToStream.count() * maxAttributeDataSize * first). WithfirstnearINT32_MAX, this requests a buffer up to ~34.3 GiB. The code callsANGLE_GL_TRYto allocate the buffer. In release builds,ANGLE_GL_TRYignores the resultingGL_OUT_OF_MEMORYerror. - Unchecked Mapping: The code calls
MapBufferRangeWithFallback, which returnsnullptrbecause the buffer allocation failed. - Absolute Write: The code calculates
curBufferOffset = maxAttributeDataSize * firstand executesmemcpy(bufferPointer + curBufferOffset, inputPointer + batchMemcpyInputOffset, destStride). SincebufferPointerisnullptr(0), this performs an absolute memory write of the attacker’s VBO data to the attacker-controlledcurBufferOffset.
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
- Pointer Validation: Ensure
MapBufferRangeWithFallbackhandlesnullptrsecurely inVertexArrayGL::streamAttributes. Do not rely solely onASSERT(inputBufferPointer). Return aGL_OUT_OF_MEMORYerror if mapping fails. - Sanity Bounds on
first: The validating command decoder and ANGLE’sValidateDrawAttribsshould enforce a reasonable upper bound onfirsteven when only instanced attributes are active. The parameter should not be allowed to reach sizes that guarantee integer overflows or absurd memory allocation requests. - Review
ANGLE_GL_TRYusage: Investigate instances whereANGLE_GL_TRYis 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.