CVE-2026-5879
Overview
Background
- ANGLE
- Chrome’s GL abstraction layer that translates WebGL/OpenGL ES calls onto the platform’s native graphics driver.
- `glBufferData` / `glBufferStorageEXT`
- GL entry points that allocate GPU buffer storage of a caller-supplied
size, validated in ANGLE before the native driver call. - `Limitations`
- an ANGLE per-context struct of driver-specific caps/workarounds; the patch adds a
bufferSizeLimitfield to it. - Driver workaround feature
- an
ANGLE_FEATURE_CONDITION-gated flag (limitMaxBufferSizeTo1gb) enabled only for matching GPUs, here Apple + Intel.
Root Cause Analysis
On Intel Macs, requesting a very large GL buffer allocation passed ANGLE’s validation and reached the native driver, which cannot handle allocations of this magnitude and fails internally when attempting to allocate and initialize buffers larger than ~1 GB. ANGLE had no upper bound on buffer size beyond generic overflow checks, so an untrusted-but-large size was forwarded to the driver (insufficient validation of untrusted input).
The fix adds a bufferSizeLimit field to Limitations (defaulting to std::numeric_limits<GLsizeiptr>::max(), i.e. effectively unlimited), sets it to 1 << 30 (1 GB) when the new limitMaxBufferSizeTo1gb feature is enabled, and enables that feature via ANGLE_FEATURE_CONDITION(... isApple && isIntel). The buffer-size validation routines for ES2, ES3, and the EXT buffer-storage path now compare size > limitations.bufferSizeLimit and, if exceeded, raise a GL error (GL_INVALID_OPERATION, or GL_INVALID_VALUE in one validationESEXT.cpp path) with message kBufferSizeLimitation and return false before the allocation proceeds. This is a tightening: the new guard rejects the oversized request in validation so the driver never receives it.
size values to Intel-Mac drivers that cannot service them; the fix caps allocations at 1 GB on those drivers via a Limitations::bufferSizeLimit check that fails validation before the native allocation.Attack Path
- Target vulnerable configuration The victim runs Chrome on an Intel Mac, whose GL driver cannot handle buffer allocations larger than ~1 GB.
- Issue an oversized allocation
Web content (e.g. via WebGL) calls
glBufferData/glBufferStorageEXTwith asizeexceeding 1 GB, which previously passed ANGLE validation. - Driver internal failure The oversized request reaches the native Intel driver, which fails internally during allocate/initialize (inferred — the diff documents driver “internal failures” but does not show the crash sink).
- Post-fix outcome
With the patch, the validation layer returns a GL error (
GL_INVALID_OPERATION/GL_INVALID_VALUE) and the allocation is never attempted.
Impact Assessment
limitMaxBufferSizeTo1gb workaround path; other platforms keep the effectively unlimited default. The vendor advisory rates this medium and reports no in-the-wild exploitation; any consequence beyond the driver’s own internal failure is not shown by the patch (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
BufferDataTest1gbLimitsrc/tests/gl_tests/BufferDataTest.cpp |
modified |
Files Changed
include/platform/autogen/FeaturesGL_autogen.hinclude/platform/gl_features.jsonsrc/libANGLE/Caps.hsrc/libANGLE/ErrorStrings.hsrc/libANGLE/renderer/gl/renderergl_utils.cppsrc/libANGLE/validationES2.cppsrc/libANGLE/validationES3.cppsrc/libANGLE/validationESEXT.cppsrc/tests/gl_tests/BufferDataTest.cpp
Audit Directions
- Unbounded size forwarded to driversAudit other ANGLE allocation entry points (textures, renderbuffers, transform-feedback,
glBufferSubData) for caller-supplied sizes that are range/overflow-checked but never capped against driver limits. - Per-driver capability gapsReview
Limitations/FeaturesGLfor driver quirks handled only by crash rather than by a validated cap, especially vendor/platform-specific combinations likeisApple && isIntel. - Inconsistent error codesNote the mix of
GL_INVALID_OPERATIONandGL_INVALID_VALUEfor the same limit across paths; audit for size/limit validations whose error semantics diverge between ES2/ES3/EXT entry points.
Patch
From 42ae4b5eee733709f37b9f7a50d227a6bc90af70 Mon Sep 17 00:00:00 2001
From: Geoff Lang <geofflang@chromium.org>
Date: Wed, 18 Feb 2026 12:09:39 -0500
Subject: [PATCH] GL: Limit buffer size to 1gb on Intel Macs.
These drivers cannot handle allocations this large so add a limitation
which generates GL_INVALID_OPERATION.
Bug: chromium:40073848
Change-Id: Ibeffb7507e72e4fb5e2a23ed9b873c726c351361
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7589937
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Auto-Submit: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
---
diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
index c0c00f1..f1aa71d 100644
--- a/include/platform/autogen/FeaturesGL_autogen.h
+++ b/include/platform/autogen/FeaturesGL_autogen.h
@@ -668,6 +668,12 @@
&members,
};
+ FeatureInfo limitMaxBufferSizeTo1gb = {
+ "limitMaxBufferSizeTo1gb",
+ FeatureCategory::OpenGLWorkarounds,
+ &members,
+ };
+
};
inline FeaturesGL::FeaturesGL() = default;
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
index a78b0d4..6d3c820 100644
--- a/include/platform/gl_features.json
+++ b/include/platform/gl_features.json
@@ -868,6 +868,14 @@
"Validate GL_MAX_*_UNIFORM_BLOCKS at compile time instead of link time to work around compiler bugs."
],
"issue": "http://crbug.com/475877320"
+ },
+ {
+ "name": "limit_max_buffer_size_to_1gb",
+ "category": "Workarounds",
+ "description": [
+ "Some drivers have internal failures when attempting to allocate and initialize buffers larger than 1gb."
+ ],
+ "issue": "http://crbug.com/475877320"
}
]
}
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index af19f52..9f05536 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -172,6 +172,10 @@
// GL_ANGLE_base_vertex_base_instance is emulated and should only be exposed to WebGL. Emulated
// by default in shared renderer code.
bool baseInstanceBaseVertexEmulated = true;
+
+ // Size limit for buffers. GL_INVALID_OPERATION should be generated if trying to allocate a
+ // buffer larger than this limit.
+ GLsizeiptr bufferSizeLimit = std::numeric_limits<GLsizeiptr>::max();
};
struct TypePrecision
diff --git a/src/libANGLE/ErrorStrings.h b/src/libANGLE/ErrorStrings.h
index 22a8ed0..16755fc 100644
--- a/src/libANGLE/ErrorStrings.h
+++ b/src/libANGLE/ErrorStrings.h
@@ -68,6 +68,7 @@
inline constexpr const char *kBufferNotMapped = "Buffer is not mapped.";
inline constexpr const char *kBufferNotUpdatable = "Buffer is not updatable.";
inline constexpr const char *kBufferOffsetOverflow = "Buffer offset overflow.";
+inline constexpr const char *kBufferSizeLimitation = "Buffer size is too large (artificial limitation).";
inline constexpr const char *kBufferTextureNotAllowed = "Buffer textures are not allowed.";
inline constexpr const char *kCannotPopDefaultDebugGroup = "Cannot pop the default debug group.";
inline constexpr const char *kClientBufferInvalid = "Size must not exceed the size of clientbuffer.";
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 29abdf4..06dc8b3 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -2221,6 +2221,11 @@
// Restore previous state
functions->blendColor(oldColor[0], oldColor[1], oldColor[2], oldColor[3]);
}
+
+ if (features.limitMaxBufferSizeTo1gb.enabled)
+ {
+ limitations->bufferSizeLimit = 1 << 30;
+ }
}
bool GetSystemInfoVendorIDAndDeviceID(const FunctionsGL *functions,
@@ -2755,6 +2760,9 @@
// IMG GL drivers crash while compiling shaders with more than the limit of uniform blocks.
ANGLE_FEATURE_CONDITION(features, validateMaxPerStageUniformBlocksAtCompileTime,
IsPowerVR(vendor));
+
+ // Mac Intel drivers are unable to allocate buffers larger than ~1gb
+ ANGLE_FEATURE_CONDITION(features, limitMaxBufferSizeTo1gb, isApple && isIntel);
}
void InitializeFrontendFeatures(const FunctionsGL *functions, angle::FrontendFeatures *features)
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index adaa73e..2b6a85e 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -3636,6 +3636,13 @@
return false;
}
+ const Limitations &limitations = context->getLimitations();
+ if (size > limitations.bufferSizeLimit)
+ {
+ ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kBufferSizeLimitation);
+ return false;
+ }
+
switch (usage)
{
case BufferUsage::StreamDraw:
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
index 173841a..170eeb2 100644
--- a/src/libANGLE/validationES3.cpp
+++ b/src/libANGLE/validationES3.cpp
@@ -3303,6 +3303,13 @@
return false;
}
+ const Limitations &limitations = context->getLimitations();
+ if (size > limitations.bufferSizeLimit)
+ {
+ ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kBufferSizeLimitation);
+ return false;
+ }
+
if (checkedReadSum.ValueOrDie() > readBuffer->getSize() ||
checkedWriteSum.ValueOrDie() > writeBuffer->getSize())
{
diff --git a/src/libANGLE/validationESEXT.cpp b/src/libANGLE/validationESEXT.cpp
index b9849fd..5e7055e 100644
--- a/src/libANGLE/validationESEXT.cpp
+++ b/src/libANGLE/validationESEXT.cpp
@@ -2681,6 +2681,13 @@
return false;
}
+ const Limitations &limitations = context->getLimitations();
+ if (size > limitations.bufferSizeLimit)
+ {
+ ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kBufferSizeLimitation);
+ return false;
+ }
+
constexpr GLbitfield kAllUsageFlags =
(GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_READ_BIT | GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT | GL_CLIENT_STORAGE_BIT_EXT);
@@ -2800,6 +2807,13 @@
return false;
}
+ const Limitations &limitations = context->getLimitations();
+ if (size > limitations.bufferSizeLimit)
+ {
+ ANGLE_VALIDATION_ERROR(GL_INVALID_VALUE, kBufferSizeLimitation);
+ return false;
+ }
+
return true;
}
diff --git a/src/tests/gl_tests/BufferDataTest.cpp b/src/tests/gl_tests/BufferDataTest.cpp
index f9960a4..97a8fba 100644
--- a/src/tests/gl_tests/BufferDataTest.cpp
+++ b/src/tests/gl_tests/BufferDataTest.cpp
@@ -2413,6 +2413,33 @@
glBindBuffer(GL_COPY_READ_BUFFER, 0);
}
+class BufferDataTest1gbLimit : public BufferDataTest
+{};
+
+// Allocating >1gb should generate an INVALID_OPERATION when LimitMaxBufferSizeTo1gb is enabled.
+TEST_P(BufferDataTest1gbLimit, ErrorGeneratedOnLargeAllocation)
+{
+ GLBuffer buffer;
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ EXPECT_GL_NO_ERROR();
+
+ // First make sure a small allocation works
+ glBufferData(GL_ARRAY_BUFFER, (1 << 10) + 1, nullptr, GL_STATIC_DRAW);
+ EXPECT_GL_NO_ERROR();
+
+ // >1gb should fail.
+ glBufferData(GL_ARRAY_BUFFER, (1 << 30) + 1, nullptr, GL_STATIC_DRAW);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ // glCopyBufferSubData can't be tested because a source buffer > 1gb cannot be created.
Regression Test / PoC
diff --git a/src/tests/gl_tests/BufferDataTest.cpp b/src/tests/gl_tests/BufferDataTest.cpp
index f9960a4..97a8fba 100644
--- a/src/tests/gl_tests/BufferDataTest.cpp
+++ b/src/tests/gl_tests/BufferDataTest.cpp
@@ -2413,6 +2413,33 @@
glBindBuffer(GL_COPY_READ_BUFFER, 0);
}
+class BufferDataTest1gbLimit : public BufferDataTest
+{};
+
+// Allocating >1gb should generate an INVALID_OPERATION when LimitMaxBufferSizeTo1gb is enabled.
+TEST_P(BufferDataTest1gbLimit, ErrorGeneratedOnLargeAllocation)
+{
+ GLBuffer buffer;
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ EXPECT_GL_NO_ERROR();
+
+ // First make sure a small allocation works
+ glBufferData(GL_ARRAY_BUFFER, (1 << 10) + 1, nullptr, GL_STATIC_DRAW);
+ EXPECT_GL_NO_ERROR();
+
+ // >1gb should fail.
+ glBufferData(GL_ARRAY_BUFFER, (1 << 30) + 1, nullptr, GL_STATIC_DRAW);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ // glCopyBufferSubData can't be tested because a source buffer > 1gb cannot be created.
+
+ if (EnsureGLExtensionEnabled("GL_EXT_buffer_storage"))
+ {
+ glBufferStorageEXT(GL_ARRAY_BUFFER, (1 << 30) + 1, nullptr, 0);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+ }
+}
+
ANGLE_INSTANTIATE_TEST_ES2(BufferDataTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BufferSubDataTest);
@@ -2437,6 +2464,10 @@
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BufferStorageTestES3Threaded);
ANGLE_INSTANTIATE_TEST_ES3(BufferStorageTestES3Threaded);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BufferDataTest1gbLimit);
+ANGLE_INSTANTIATE_TEST(BufferDataTest1gbLimit,
+ ES2_OPENGL().enable(Feature::LimitMaxBufferSizeTo1gb),
+ ES3_OPENGL().enable(Feature::LimitMaxBufferSizeTo1gb));
#ifdef _WIN64
// Test a bug where an integer overflow bug could trigger a crash in D3D.
Original Bug Report
ArrayBuffer OOR/W in glBufferData_Exec
Steps to reproduce the problem:
Insufficient input validation for large size ArrayBuffer results in an Out of Bound (R/W) vulnerability in the glBufferData_Exec function of the GL driver.
Problem Description:
ROOT CAUSE ANALYSIS
* Due to various characteristics, it will not be possible to reproduce in ClusterFuzzer.
* Because it occurs within the GL Driver, accurate root cause analysis is impossible.
* Since this vulnerability is reproduced when a page is loaded more than three times, it may be related to GPU task scheduling.
angle::Result Buffer::bufferDataImpl(Context \*context,
BufferBinding target,
const void \*data,
GLsizeiptr size,
BufferUsage usage,
GLbitfield flags)
{
...
if (context && context->isRobustResourceInitEnabled() && !data && size > 0)
{
angle::MemoryBuffer \*scratchBuffer = nullptr;
ANGLE_CHECK_GL_ALLOC(
context, context->getZeroFilledBuffer(static_cast<size_t>(size), &scratchBuffer));
dataForImpl = scratchBuffer->data();
}
...
}
If the size of the ArrayBuffer is close to the maximum value of the GLsizeiptr(signed long int), an integer overflow occurs.
bool ScratchBuffer::getImpl(size_t requestedSize,
MemoryBuffer \*\*memoryBufferOut,
Optional<uint8_t> initValue)
{
...
if (mScratchMemory.size() < requestedSize)
{
if (!mScratchMemory.resize(requestedSize))
{
return false;
}
mResetCounter = mLifetime;
if (initValue.valid())
{
mScratchMemory.fill(initValue.value());
}
}
...
}
The buffer is allocated by the size converted from the size in which the integer overflow has occurred to size_t.
angle::Result BufferGL::setData(const gl::Context \*context,
gl::BufferBinding target,
const void \*data,
size_t size,
gl::BufferUsage usage)
{
...
ANGLE_GL_TRY(context, functions->bufferData(gl::ToGLenum(DestBufferOperationTarget), size, data,
ToGLenum(usage)));
...
}
When handling with data in bufferData functions of GL driver, the actual size of data and size parameters are different, resulting in out of bound r/w memory corruptions.
RECOMMENDED PATCHES
--- a/third_party/angle/src/libANGLE/Buffer.cpp
+++ b/third_party/angle/src/libANGLE/Buffer.cpp
@@ -140,7 +140,7 @@
dataForImpl = scratchBuffer->data();
}
- if (mImpl->setDataWithUsageFlags(context, target, nullptr, dataForImpl, size, usage, flags) ==
+ if (mImpl->setDataWithUsageFlags(context, target, nullptr, dataForImpl, static_cast<size_t>(size), usage, flags) ==
angle::Result::Stop)
{
// If setData fails, the buffer contents are undefined. Set a zero size to indicate that.
When calling the setData function, if the size parameter is also converted to size_t, memory corruptions caused by different sizes from the data can be prevented.
VERSION
* Chrome Version: 116.0.5845.0 (Developer Build) (x86_64)
/ 118.0.5970.0 (Developer Build) (x86_64)
* Operating System: macOS Monterey Version 12.6.8 (21G725)
* Hardware: Intel Core i5, 8GB RAM
* GPU: Intel Iris Graphics 540 1536 MB
(**I have attached the contents of the chrome://gpu page.**)
REPRODUCE CASE
You can simply reproduce by opening the attached poc.html in Chromium.
./Chromium --no-sandbox poc.html
CREDIT
parkminchan, working for SSD Labs Korea.
Additional Comments:
**Chrome version: ** 117.0.0.0 **Channel: ** Stable
OS: Mac OS