Medium watch CVSS 8.8 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in ANGLE
DescriptionInsufficient validation of untrusted input in ANGLE
ComponentANGLE
Sub-componentGL
Bug ClassInput Validation
Action tierwatch — dos-class only — no demonstrated corruption path
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.
Tracker40073848
Fix commit42ae4b5eee73 (angle/angle) +88/-0
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
Creditedparkminchan, working for SSD Labs Korea
Reported2023-10-01 (per vendor advisory)
Fix landed2026-02-19 (public git)
Disclosed2026-04-07
Patch gap47 days — fix public in source before the advisory

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 bufferSizeLimit field 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.

Key insight
ANGLE forwarded arbitrarily large buffer 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

  1. Target vulnerable configuration The victim runs Chrome on an Intel Mac, whose GL driver cannot handle buffer allocations larger than ~1 GB.
  2. Issue an oversized allocation Web content (e.g. via WebGL) calls glBufferData / glBufferStorageEXT with a size exceeding 1 GB, which previously passed ANGLE validation.
  3. 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).
  4. 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

An attacker supplying an over-1 GB buffer size from web content on an Intel Mac could trigger a driver-level allocation/initialization failure, a robustness/denial-of-service class issue rather than a demonstrated memory-corruption primitive. Preconditions are specific: Apple hardware with an Intel GPU and the 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

FunctionChangeNotes
BufferDataTest1gbLimit
src/tests/gl_tests/BufferDataTest.cpp
modified

Files Changed

  • include/platform/autogen/FeaturesGL_autogen.h
  • include/platform/gl_features.json
  • src/libANGLE/Caps.h
  • src/libANGLE/ErrorStrings.h
  • src/libANGLE/renderer/gl/renderergl_utils.cpp
  • src/libANGLE/validationES2.cpp
  • src/libANGLE/validationES3.cpp
  • src/libANGLE/validationESEXT.cpp
  • src/tests/gl_tests/BufferDataTest.cpp

Audit Directions

  • Unbounded size forwarded to drivers
    Audit 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 gaps
    Review Limitations/FeaturesGL for driver quirks handled only by crash rather than by a validated cap, especially vendor/platform-specific combinations like isApple && isIntel.
  • Inconsistent error codes
    Note the mix of GL_INVALID_OPERATION and GL_INVALID_VALUE for the same limit across paths; audit for size/limit validations whose error semantics diverge between ES2/ES3/EXT entry points.
From 42ae4b5eee733709f37b9f7a50d227a6bc90af70 Mon Sep 17 00:00:00 2001
From: Geoff Lang <[email protected]>
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 <[email protected]>
Auto-Submit: Geoff Lang <[email protected]>
Reviewed-by: Shahbaz Youssefi <[email protected]>
---

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.
Loading diff…

Regression Test / PoC

shipped with the fix
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.
Loading diff…

Original Bug Report

reported by [email protected]

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.

--- 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

View on issue tracker