Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in ANGLE
DescriptionOut of bounds read in ANGLE
ComponentANGLE
Bug ClassOOB
Tracker515452019
Fix commit9464aca6502c (angle/angle) +91/-58
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • src/libANGLE/VertexArray.cpp
  • src/libANGLE/VertexArray.h
  • src/libANGLE/VertexAttribute.cpp
  • src/libANGLE/VertexAttribute.h
  • src/libANGLE/capture/FrameCapture.cpp
  • src/libANGLE/renderer/d3d/VertexBuffer.cpp
  • src/libANGLE/renderer/d3d/VertexDataManager.cpp
From 9464aca6502c2f4ba787f8a8ec9d1f82788454ac Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Wed, 10 Jun 2026 22:42:53 -0400
Subject: [PATCH] Use uintptr_t instead of GLintptr for vertex binding offsets

In glBindVertexBuffer, the offset is GLintptr, but is required to be
non-negative.  In glVertexAttribPointer, the offset is a pointer cast to
integer.  In the latter case, if the pointer value is large, the offset
shouldn't be interpreted as negative.

Bug: chromium:515452019
Change-Id: I9fc934b8723a8294593058a791e937f651a63546
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7921635
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Charlie Lao <cclao@google.com>
---

diff --git a/src/libANGLE/VertexArray.cpp b/src/libANGLE/VertexArray.cpp
index fcc2d72..d56d38a 100644
--- a/src/libANGLE/VertexArray.cpp
+++ b/src/libANGLE/VertexArray.cpp
@@ -475,7 +475,7 @@
 ANGLE_INLINE VertexArray::DirtyBindingBits VertexArray::bindVertexBufferImpl(const Context *context,
                                                                              size_t bindingIndex,
                                                                              Buffer *boundBuffer,
-                                                                             GLintptr offset,
+                                                                             uintptr_t offset,
                                                                              GLsizei stride)
 {
     ASSERT(bindingIndex < getMaxBindings());
@@ -556,8 +556,10 @@
                                    GLintptr offset,
                                    GLsizei stride)
 {
-    const VertexArray::DirtyBindingBits dirtyBindingBits =
-        bindVertexBufferImpl(context, bindingIndex, boundBuffer, offset, stride);
+    // |offset| must be non-negative per validation rules of glBindVertexBuffer.
+    ASSERT(offset >= 0);
+    const VertexArray::DirtyBindingBits dirtyBindingBits = bindVertexBufferImpl(
+        context, bindingIndex, boundBuffer, static_cast<uintptr_t>(offset), stride);
 
     if (!dirtyBindingBits.test(DIRTY_BINDING_BUFFER) && context->isSharedContext() &&
         boundBuffer != nullptr)
@@ -627,7 +629,7 @@
     // Change of attrib.pointer is not part of attribDirty. Pointer is actually the buffer offset
     // which is handled within bindVertexBufferImpl and reflected in bufferDirty.
     attrib.pointer  = pointer;
-    GLintptr offset = boundBuffer ? reinterpret_cast<GLintptr>(pointer) : 0;
+    uintptr_t offset = boundBuffer ? reinterpret_cast<uintptr_t>(pointer) : 0;
     const VertexArray::DirtyBindingBits dirtyBindingBits =
         bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride);
 
diff --git a/src/libANGLE/VertexArray.h b/src/libANGLE/VertexArray.h
index 413cbdd..78c6705 100644
--- a/src/libANGLE/VertexArray.h
+++ b/src/libANGLE/VertexArray.h
@@ -413,7 +413,7 @@
     DirtyBindingBits bindVertexBufferImpl(const Context *context,
                                           size_t bindingIndex,
                                           Buffer *boundBuffer,
-                                          GLintptr offset,
+                                          uintptr_t offset,
                                           GLsizei stride);
 
     void onBind(const Context *context);
diff --git a/src/libANGLE/VertexAttribute.cpp b/src/libANGLE/VertexAttribute.cpp
index faef8fe..49f6f54 100644
--- a/src/libANGLE/VertexAttribute.cpp
+++ b/src/libANGLE/VertexAttribute.cpp
@@ -135,7 +135,7 @@
 }
 
 // Warning: you should ensure binding really matches attrib.bindingIndex before using this function.
-GLintptr ComputeVertexAttributeOffset(const VertexAttribute &attrib, const VertexBinding &binding)
+uintptr_t ComputeVertexAttributeOffset(const VertexAttribute &attrib, const VertexBinding &binding)
 {
     return attrib.relativeOffset + binding.getOffset();
 }
diff --git a/src/libANGLE/VertexAttribute.h b/src/libANGLE/VertexAttribute.h
index cbea773..9593169 100644
--- a/src/libANGLE/VertexAttribute.h
+++ b/src/libANGLE/VertexAttribute.h
@@ -37,8 +37,8 @@
     GLuint getDivisor() const { return mDivisor; }
     void setDivisor(GLuint divisorIn) { mDivisor = divisorIn; }
 
-    GLintptr getOffset() const { return mOffset; }
-    void setOffset(GLintptr offsetIn) { mOffset = offsetIn; }
+    uintptr_t getOffset() const { return mOffset; }
+    void setOffset(uintptr_t offsetIn) { mOffset = offsetIn; }
 
     const AttributesMask &getBoundAttributesMask() const { return mBoundAttributesMask; }
 
@@ -49,7 +49,7 @@
   private:
     GLuint mStride;
     GLuint mDivisor;
-    GLintptr mOffset;
+    uintptr_t mOffset;
 
     // Mapping from this binding to all of the attributes that are using this binding.
     AttributesMask mBoundAttributesMask;
@@ -98,7 +98,7 @@
 size_t ComputeVertexAttributeStride(const VertexAttribute &attrib, const VertexBinding &binding);
 
 // Warning: you should ensure binding really matches attrib.bindingIndex before using this function.
-GLintptr ComputeVertexAttributeOffset(const VertexAttribute &attrib, const VertexBinding &binding);
+uintptr_t ComputeVertexAttributeOffset(const VertexAttribute &attrib, const VertexBinding &binding);
 
 size_t ComputeVertexBindingElementCount(GLuint divisor,
                                         uint64_t drawCount,
diff --git a/src/libANGLE/capture/FrameCapture.cpp b/src/libANGLE/capture/FrameCapture.cpp
index dbc4f1e..206beaf 100644
--- a/src/libANGLE/capture/FrameCapture.cpp
+++ b/src/libANGLE/capture/FrameCapture.cpp
@@ -2962,7 +2962,8 @@
             }
             else if (attrib.bindingIndex == attribIndex &&
                      VertexBindingMatchesAttribStride(attrib, binding) &&
-                     (!buffer || binding.getOffset() == reinterpret_cast<GLintptr>(attrib.pointer)))
+                     (!buffer ||
+                      binding.getOffset() == reinterpret_cast<uintptr_t>(attrib.pointer)))
             {
                 // Check if we can use strictly ES2 semantics, and track indexes that do.
                 vertexPointerBindings.set(attribIndex);
@@ -3058,9 +3059,10 @@
 
         if (buffer)
         {
-            Capture(setupCalls, CaptureBindVertexBuffer(
-                                    *replayState, true, static_cast<GLuint>(bindingIndex),
-                                    buffer->id(), binding.getOffset(), binding.getStride()));
+            Capture(setupCalls,
+                    CaptureBindVertexBuffer(
+                        *replayState, true, static_cast<GLuint>(bindingIndex), buffer->id(),
+                        static_cast<GLintptr>(binding.getOffset()), binding.getStride()));
         }
 
         if (binding.getDivisor() != 0)
diff --git a/src/libANGLE/renderer/d3d/VertexBuffer.cpp b/src/libANGLE/renderer/d3d/VertexBuffer.cpp
index c8891f6..1262e14 100644
--- a/src/libANGLE/renderer/d3d/VertexBuffer.cpp
+++ b/src/libANGLE/renderer/d3d/VertexBuffer.cpp
@@ -249,7 +249,7 @@
     }
 
     size_t attribOffset =
-        (static_cast<size_t>(ComputeVertexAttributeOffset(attrib, binding)) % attribStride);
+        static_cast<size_t>(ComputeVertexAttributeOffset(attrib, binding) % attribStride);
     return (offset == attribOffset);
 }
 
@@ -257,9 +257,9 @@
                                                           const gl::VertexBinding &binding)
 {
     formatID = attrib.format->id;
-    offset = stride = static_cast<GLuint>(ComputeVertexAttributeStride(attrib, binding));
-    offset          = static_cast<size_t>(ComputeVertexAttributeOffset(attrib, binding)) %
-             ComputeVertexAttributeStride(attrib, binding);
+    stride   = static_cast<GLuint>(ComputeVertexAttributeStride(attrib, binding));
+    offset   = static_cast<size_t>(ComputeVertexAttributeOffset(attrib, binding) %
+                                   ComputeVertexAttributeStride(attrib, binding));
 }
 
 StaticVertexBufferInterface::StaticVertexBufferInterface(BufferFactoryD3D *factory)
diff --git a/src/libANGLE/renderer/d3d/VertexDataManager.cpp b/src/libANGLE/renderer/d3d/VertexDataManager.cpp
index 729e1eb..a2181a7 100644
--- a/src/libANGLE/renderer/d3d/VertexDataManager.cpp
+++ b/src/libANGLE/renderer/d3d/VertexDataManager.cpp
@@ -122,10 +122,10 @@
         alignment = std::min<size_t>(elementSize, 4);
     }
 
-    GLintptr offset = ComputeVertexAttributeOffset(attrib, binding);
+    uintptr_t offset = ComputeVertexAttributeOffset(attrib, binding);
     // Final alignment check - unaligned data must be converted.
-    return (static_cast<size_t>(ComputeVertexAttributeStride(attrib, binding)) % alignment == 0) &&
-           (static_cast<size_t>(offset) % alignment == 0);
+    return (ComputeVertexAttributeStride(attrib, binding) % alignment == 0) &&
+           (offset % alignment == 0);
 }
 }  // anonymous namespace
 
@@ -370,13 +370,13 @@
     // Compute source data pointer
     const uint8_t *sourceData = nullptr;
 
-    angle::CheckedNumeric<GLintptr> offset = ComputeVertexAttributeOffset(attrib, binding);
+    angle::CheckedNumeric<uintptr_t> offset = ComputeVertexAttributeOffset(attrib, binding);
 
     ANGLE_TRY(bufferD3D->getData(context, &sourceData));
 
     if (sourceData)
     {
-        sourceData += GLintptr{offset.ValueOrDie()};
+        sourceData += uintptr_t{offset.ValueOrDie()};
     }
 
     translated->storage = nullptr;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index 84f0c4c..edb1d10 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -101,7 +101,7 @@
 42266900 VULKAN : EGLSurfacelessContextTest.*/*ForceDelayedDeviceCreationForTesting* = SKIP
 510376884 VULKAN : ComputeShaderTest.DeleteTextureBoundToImageUnit/* = SKIP
 
-500721361 VULKAN SWIFTSHADER : VertexAttributeTestES3.storeStaticAttribWithLargeOffset/* = SKIP
+500721361 VULKAN SWIFTSHADER : VertexAttributeTestES3.StoreStaticAttribWithLargeOffset/* = SKIP
 
 // Causes a write-after-write hazard on Linux/Vk
 524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP
@@ -394,7 +394,7 @@
 42265138 MAC AMD OPENGL : TransformFeedbackTest.TransformFeedbackQueryPausedDrawThenResume/* = SKIP
 42265138 MAC AMD OPENGL : TransformFeedbackTest.TransformFeedbackPausedDrawThenResume/* = SKIP
 40050013 MAC AMD OPENGL : Texture3DTestES3.PixelUnpackStateTex* = SKIP
-1296467 MAC OPENGL : VertexAttributeTestES3.emptyBuffer/* = SKIP
+1296467 MAC OPENGL : VertexAttributeTestES3.EmptyBuffer/* = SKIP
 42265677 MAC INTEL OPENGL : CopyTextureTest.CopyToMipmap/* = SKIP
 42265680 MAC NVIDIA OPENGL : CopyTextureTest.CubeMap*/* = SKIP
 42265680 MAC NVIDIA OPENGL : CopyTextureTest.CopyToMipmap/* = SKIP
@@ -508,6 +508,7 @@
 515709506 MAC METAL : DrawBaseVertexBaseInstanceTest_ES3.BaseInstanceSmallDivisorClientMemory/* = SKIP
 518849408 MAC OPENGL : GLSLTest.EmulateGLFragColorBroadcastInvariantFragColor/* = SKIP
 518849408 MAC OPENGL : GLSLTest.EmulateGLFragColorBroadcastInvariantFragColorUnused/* = SKIP
+524008572 MAC METAL : VertexAttributeTestES3.LargeAttribPointerOffsetNoCrash/* = SKIP
 
 // The workaround is not intended to be enabled in this configuration so
 // skip it as the failure is likely a driver bug.
@@ -768,7 +769,7 @@
 42264692 PIXEL4ORXL GLES : DepthStencilTestES3.FramebufferClearThenStencilAttachedThenStencilTestState/* = SKIP
 42264692 PIXEL4ORXL GLES : DepthStencilTestES3.FramebufferClearThenStencilTestStateThenStencilAttached/* = SKIP
 42264692 PIXEL4ORXL GLES : DepthStencilTestES3.StencilTestStateThenFramebufferClearThenStencilAttached/* = SKIP
-1296467 PIXEL4ORXL GLES : VertexAttributeTestES3.emptyBuffer/* = SKIP
+1296467 PIXEL4ORXL GLES : VertexAttributeTestES3.EmptyBuffer/* = SKIP
 
 42265757 PIXEL4ORXL GLES : TextureBufferTestES31.TextureBufferThenBufferData/* = SKIP
diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt
index ff95017..52af888 100644
--- a/src/tests/capture_replay_tests/capture_replay_expectations.txt
+++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt
@@ -83,6 +83,7 @@
 42264831 : FramebufferTest_ES3.RenderAndInvalidateImmutableTextureWithBeyondMaxLevel/* = SKIP_FOR_CAPTURE
 490170083 : CopyTextureTestES3.SRGBWithPackParameters/* = SKIP_FOR_CAPTURE
 498904293 : CopyTextureTestES3.PBOSynchronization/* = SKIP_FOR_CAPTURE
+42264706 : VertexAttributeTestES3.LargeAttribPointerOffsetNoCrash/* = SKIP_FOR_CAPTURE
 
 # The following tests fail with forceRobustResourceInit
 # They were accidentally passing until http://crrev/c/5588816
diff --git a/src/tests/gl_tests/VertexAttributeTest.cpp b/src/tests/gl_tests/VertexAttributeTest.cpp
index 5b337c1..446a51b 100644
--- a/src/tests/gl_tests/VertexAttributeTest.cpp
+++ b/src/tests/gl_tests/VertexAttributeTest.cpp
@@ -5284,7 +5284,7 @@
 }
 
 // Test maxinum attribs full of Client buffers and then switch to mixed.
-TEST_P(VertexAttributeTestES3, fullClientBuffersSwitchToMixed)
+TEST_P(VertexAttributeTestES3, FullClientBuffersSwitchToMixed)
 {
     GLint maxAttribs;
     glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs);
@@ -5403,16 +5403,16 @@
 }
 
 // Test bind an empty buffer for vertex attribute does not crash
-TEST_P(VertexAttributeTestES3, emptyBuffer)
+TEST_P(VertexAttributeTestES3, EmptyBuffer)
 {
-    constexpr char vs2[] =
+    constexpr char kVS[] =
         R"(#version 300 es
             in uvec4 attr0;
             void main()
             {
                 gl_Position = vec4(attr0.x, 0.0, 0.0, 0.0);
             })";
-    constexpr char fs[] =
+    constexpr char kFS[] =
         R"(#version 300 es
             precision highp float;
             out vec4 color;
@@ -5420,13 +5420,13 @@
             {
                 color = vec4(1.0, 0.0, 0.0, 1.0);
             })";
-    GLuint program2 = CompileProgram(vs2, fs);
+    ANGLE_GL_PROGRAM(program, kVS, kFS);
     GLBuffer buf;
     glBindBuffer(GL_ARRAY_BUFFER, buf);
     glEnableVertexAttribArray(0);
     glVertexAttribIPointer(0, 4, GL_UNSIGNED_BYTE, 0, 0);
     glVertexAttribDivisor(0, 2);
-    glUseProgram(program2);
+    glUseProgram(program);
     glDrawArrays(GL_POINTS, 0, 1);
 
     swapBuffers();
@@ -5434,16 +5434,16 @@
 
 // Test that setting a large offset on glVertexAttribPointer doesn't OOB when going
 // through StoreStaticAttrib. See http://crbug.com/489369089
-TEST_P(VertexAttributeTestES3, storeStaticAttribWithLargeOffset)
+TEST_P(VertexAttributeTestES3, StoreStaticAttribWithLargeOffset)
 {
-    constexpr char vs2[] =
+    constexpr char kVS[] =
         R"(#version 300 es
             layout(location = 0) in vec3 a;
             void main() {
                 gl_Position = vec4(a, 1.0);
                 gl_PointSize = 1.0;
             })";
-    constexpr char fs[] =
+    constexpr char kFS[] =
         R"(#version 300 es
             precision mediump float;
             layout(location = 0) out vec4 FragColor;
@@ -5451,7 +5451,7 @@
                 FragColor = vec4(1.0, 0.0, 0.0, 1.0);
             })";
 
-    GLuint program2 = CompileProgram(vs2, fs);
+    ANGLE_GL_PROGRAM(program, kVS, kFS);
     GLBuffer buf;
     glBindBuffer(GL_ARRAY_BUFFER, buf);
     std::array<uint8_t, 256> data;
@@ -5466,7 +5466,7 @@
     glVertexAttribPointer(0, 3, GL_BYTE, GL_TRUE, 3, reinterpret_cast<void *>(0x80000000));
     glEnableVertexAttribArray(0);
 
-    glUseProgram(program2);
+    glUseProgram(program);
     glDrawArrays(GL_POINTS, 0, 1);
 
     swapBuffers();
@@ -6485,6 +6485,31 @@
     EXPECT_PIXEL_COLOR_EQ(54, 54, GLColor::green);
 }
 
+// Ensure a large offset is not interpreted as negative.
+TEST_P(VertexAttributeTestES3, LargeAttribPointerOffsetNoCrash)
+{
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
+    glUseProgram(program);
+
+    GLBuffer position;
+    constexpr std::array<float, 6> kTriangle = {-1, -1, 3, -1, -1, 3};
+    glBindBuffer(GL_ARRAY_BUFFER, position);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(kTriangle), kTriangle.data(), GL_STATIC_DRAW);
+
+    GLint posLoc = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
+    ASSERT_NE(-1, posLoc);
+    glEnableVertexAttribArray(posLoc);
+    glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0,
+                          reinterpret_cast<const void *>(0x80000000));
+    glVertexAttribDivisor(posLoc, 256);
+
+    glDrawArrays(GL_TRIANGLES, 0, 3);
+    // Nothing that can be validated.  The test shouldn't crash.
+    ASSERT_GL_NO_ERROR();
+
+    swapBuffers();
+}
+
 ANGLE_INSTANTIATE_TEST_ES3(VertexAttributeResizeDefaultTest);
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VertexAttributeUint8Test);
Loading diff…

Original Bug Report

reported by vm...@google.com

CPU OOB Read in ANGLE-Vulkan via Sign-Extended VertexAttribPointer Offset

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

Overview: The GLES2 passthrough decoder sign-extends the VertexAttribPointer offset on 64-bit platforms, allowing a compromised renderer to specify a negative offset. In ANGLE’s Vulkan backend, CPU-side vertex divisor emulation can use this negative offset to perform an out-of-bounds read from mapped GPU memory. This vulnerability potentially allows for cross-origin information disclosure within the GPU process.

Affected files:

  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc
  • third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
  • third_party/angle/src/libANGLE/VertexArray.cpp

Estimated timestamp from git blame: 2019-07-17

Summary

A potential sign-extension vulnerability exists in the GLES2 passthrough command decoder’s handling of vertex attribute offsets. When combined with ANGLE’s vertex divisor emulation in the Vulkan backend, this can lead to a CPU-side out-of-bounds (OOB) read. On 64-bit systems, a large unsigned 32-bit offset is incorrectly treated as a signed value, resulting in a negative 64-bit pointer/offset that bypasses standard bounds checks and points before the allocated buffer memory.

Root Cause Analysis

1. Sign-Extension in Passthrough Decoder

In gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc, the handler for VertexAttribPointer (and VertexAttribIPointer) takes a renderer-controlled uint32_t c.offset and narrows it to a signed 32-bit GLsizei before converting it to a pointer:

// gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc
GLsizei offset = static_cast<GLsizei>(c.offset);          // e.g., 0x80000000 -> -2147483648
const void* ptr = reinterpret_cast<const void*>(offset);   // Sign-extended on 64-bit systems

On 64-bit platforms, reinterpret_cast<const void*>(offset) performs sign-extension if the most significant bit of the 32-bit offset is set. For example, 0x80000000 becomes 0xFFFFFFFF80000000.

2. Negative Offset in ANGLE

ANGLE receives this sign-extended pointer and, when a buffer is bound, stores it as a signed 64-bit GLintptr binding offset in third_party/angle/src/libANGLE/VertexArray.cpp. There is currently no front-end validation to ensure this offset is non-negative when a buffer is bound.

3. OOB Read in Vulkan Backend

In ANGLE’s Vulkan backend, if a vertex attribute divisor exceeds the hardware limit (e.g., 255), the renderer falls back to CPU-side emulation in VertexArrayVk::updateStreamedAttribs. This path maps the source vertex buffer and calculates the source pointer by adding the stored offset:

// third_party/angle/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
ANGLE_TRY(bufferVk->mapForReadAccessOnly(contextVk, &buffSrc));
// buffSrc points to the start of the suballocation
src = reinterpret_cast<const uint8_t *>(buffSrc) + binding.getOffset(); 

Since binding.getOffset() can be negative (e.g., -2GB), the resulting src pointer points to memory significantly before the intended buffer suballocation, leading to an OOB read during the subsequent data copy in StreamVertexDataWithDivisor.

Potential Impact

A compromised renderer could potentially leak up to 2GB of memory from the GPU process’s address space. In the Vulkan backend, buffers are often suballocated from large shared BufferBlock pools. This OOB read could allow an attacker to disclose cross-origin vertex data, pixel data, or other sensitive information belonging to different WebGL contexts or origins sharing the GPU process. This is particularly relevant on Android, where the GPU process is often unsandboxed and uses the Vulkan backend.

Potential Steps to Reproduce

  1. From a compromised renderer, create a large GL_ARRAY_BUFFER and populate it with data.
  2. Call glVertexAttribPointer with an offset that has the 31st bit set (e.g., 0x80000000).
  3. Set a vertex divisor for the same attribute that is greater than 255 (e.g., glVertexAttribDivisor(index, 256)).
  4. Perform a draw call while capturing output via Transform Feedback.
  5. Read back the Transform Feedback buffer to inspect the leaked memory contents from the GPU process.

Suggested Fix

  1. Decoder Level: In gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc, treat the offset as an unsigned value (uintptr_t) during the conversion to void* to avoid sign-extension.
  2. ANGLE Level: Update ValidateVertexAttribPointer and ValidateVertexAttribIPointer in third_party/angle/src/libANGLE/validationES.cpp to explicitly disallow negative offsets when a buffer is bound to the attribute.

Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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