CVE-2026-3536
Overview
Background
- `ANGLE`
- Chrome’s graphics abstraction layer that translates OpenGL ES calls onto native backends such as Vulkan.
- `gl::Box`
- A rectangular region descriptor whose
width,height, anddepthmembers are signed 32-bit integers. - Staging buffer
- An intermediate CPU-visible allocation (
dstBufferSizebytes) used to hold pixel data during a texture copy or format conversion. - Integer overflow
- An arithmetic result that exceeds the range of its type, wrapping to a small or negative value instead of the true size.
Root Cause Analysis
In TextureVk.cpp, the size of the destination staging allocation was computed as dstBufferSize = sourceBox.width * sourceBox.height * sourceBox.depth * dstFormat.pixelBytes * layerCount. Because sourceBox.width, sourceBox.height, and sourceBox.depth are signed 32-bit int members of gl::Box, the entire multiplication chain was evaluated in 32-bit arithmetic before being assigned to the size_t result, so a large texture (for example a 16384 x 16384 RGB8 2D-array texture emulated as RGBA, at roughly 1GB per layer) overflowed the 32-bit intermediate and produced a truncated dstBufferSize. The violated invariant is that dstBufferSize must equal the true byte count of the region being staged; when it wraps, the buffer is under-allocated relative to the pixel data that is subsequently written into it.
The fix splits the calculation so sourceBox.width * sourceBox.height is stored into a size_t first, then multiplies the remaining factors into that size_t, forcing the wide-integer path; static_asserts prove that each individual sub-product is bounded by the Constants.h maximums and cannot itself overflow int32_t.
size_t, letting the product wrap for legal large textures. The fix promotes the accumulator to size_t after the first bounded sub-product so every later factor is multiplied in 64-bit width.Attack Path
- Allocate a giant texture
Create a 2D-array texture at the maximum 2D size with an RGB format that ANGLE emulates as RGBA, spanning enough layers that the total byte count exceeds
2^32. - Trigger a copy/conversion
Cause ANGLE’s Vulkan backend to stage the texture through the vulnerable path, so
dstBufferSizeis computed with the overflowing 32-bit multiplication. - Under-allocate the staging buffer
The wrapped
dstBufferSizeyields adstDataallocation far smaller than the real pixel footprint. - Write past the allocation
Subsequent pixel writes into
dstDatause the true dimensions, spilling beyond the undersized buffer and corrupting adjacent GPU-process memory.
Impact Assessment
IMPLEMENTATION_MAX_2D_TEXTURE_SIZE across enough array layers) and to trigger a copy or format conversion of it, plus a configuration that actually permits allocating multi-gigabyte textures; on typical configurations such allocation fails, which constrains but does not conceptually eliminate the corruption primitive.Files Changed
src/libANGLE/renderer/vulkan/TextureVk.cppsrc/tests/gl_tests/FramebufferTest.cppsrc/tests/gl_tests/VulkanImageTest.cpputil/shader_utils.cpp
Audit Directions
- Chained size multiplications in narrow typesFlag any
size_t/allocation size assigned from a product ofint/int32_toperands (such asgl::Boxmembers), since C++ evaluates the product in the operands’ width before the widening assignment. - Dimension-derived buffer sizesReview every allocation whose length comes from texture width/height/depth times bytes-per-pixel times layers, ensuring at least the first operand is widened to
size_tand that per-factor maximums are asserted. - Signed graphics descriptorsWatch for arithmetic on signed 32-bit graphics fields (box, viewport, mip dimensions) used to size unsigned buffers, where overflow can yield truncated or negative intermediates.
Patch
From a08731cf6d70c60fd74b1d75f2e8b94c52e18140 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Thu, 19 Feb 2026 14:42:08 -0500
Subject: [PATCH] Vulkan: Avoid overflow in texture size calculation
Bug: chromium:485622239
Change-Id: Idf9847afa0aa2e72b6433ac8348ae2820c1ad8c5
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7595734
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
---
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 9e208f9..e2185a4 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -3164,8 +3164,17 @@
// invalidate must be called after wait for finish.
ANGLE_TRY(srcBuffer->invalidate(renderer));
- size_t dstBufferSize = sourceBox.width * sourceBox.height * sourceBox.depth *
- dstFormat.pixelBytes * layerCount;
+ // Use size_t calculations to avoid 32-bit overflows. Note that the dimensions are bound by
+ // the maximums specified in Constants.h, and that gl::Box members are signed 32-bit
+ // integers.
+ static_assert(gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE *
+ gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE <
+ std::numeric_limits<int32_t>::max());
+ size_t dstBufferSize = sourceBox.width * sourceBox.height;
+ static_assert(gl::IMPLEMENTATION_MAX_3D_TEXTURE_SIZE *
+ gl::IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS * 16 <
+ std::numeric_limits<int32_t>::max());
+ dstBufferSize *= sourceBox.depth * dstFormat.pixelBytes * layerCount;
// Allocate memory in the destination texture for the copy/conversion.
uint8_t *dstData = nullptr;
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
index 020a041..f72f1a7 100644
--- a/src/tests/gl_tests/FramebufferTest.cpp
+++ b/src/tests/gl_tests/FramebufferTest.cpp
@@ -8894,6 +8894,62 @@
ASSERT_GL_NO_ERROR();
}
+// Test that 2D array texture size calculation doesn't overflow internally when rendering to it. An
+// RGB format is used which is often emualted with RGBA.
+//
+// Practically we cannot run this test. On most configurations, allocating a 4GB texture fails due
+// to internal driver limitations. On the few configs that the test actually runs, allocating such
+// large memory leads to instability.
+TEST_P(FramebufferTest_ES3, DISABLED_MaxSize2DArrayNoOverflow)
+{
+ GLint maxTexture2DSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexture2DSize);
+
+ maxTexture2DSize = std::min(maxTexture2DSize, 16384);
+
+ // Create a 2D array texture with RGB format. Every layer is going to take 1GB of memory (if
+ // emulated with RGBA), so only create 4 layers of it (for a total of 4GB of memory). If 32-bit
+ // math is involved when calculating sizes related to this texture, they will overflow.
+ constexpr uint32_t kLayers = 4;
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGB8, maxTexture2DSize, maxTexture2DSize, kLayers);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // Initialize the texture so its content is considered valid and worth preserving.
+ constexpr int kValidSubsectionWidth = 16;
+ constexpr int kValidSubsectionHeight = 20;
+ std::vector<GLColorRGB> data(kValidSubsectionWidth * kValidSubsectionHeight,
+ GLColorRGB(0, 255, 0));
+ for (uint32_t layer = 0; layer < kLayers; ++layer)
+ {
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kValidSubsectionWidth,
+ kValidSubsectionHeight, 1, GL_RGB, GL_UNSIGNED_BYTE, data.data());
+ }
+
+ // Draw with the texture, making sure it's initialized and data is flushed.
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
+ essl3_shaders::fs::Texture2DArray());
+ drawQuad(drawTex2DArray, essl3_shaders::PositionAttrib(), 0.5f);
+
+ // Bind a framebuffer to the texture and render into it. In some backends, the texture is
+ // recreated to RGBA to be renderable.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, 1);
+
+ ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
+ glViewport(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight);
+ drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);
+
+ EXPECT_PIXEL_RECT_EQ(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight, GLColor::red);
+ EXPECT_PIXEL_RECT_EQ(kValidSubsectionWidth / 2, 0,
+ kValidSubsectionWidth - kValidSubsectionWidth / 2, kValidSubsectionHeight,
+ GLColor::green);
+ ASSERT_GL_NO_ERROR();
+}
+
ANGLE_INSTANTIATE_TEST_ES2_AND(AddMockTextureNoRenderTargetTest,
ES2_D3D9().enable(Feature::AddMockTextureNoRenderTarget),
ES2_D3D11().enable(Feature::AddMockTextureNoRenderTarget));
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
index 2f06e2d..87e7482 100644
--- a/src/tests/gl_tests/VulkanImageTest.cpp
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
@@ -677,8 +677,8 @@
kTextureHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, textureColor.data());
}
- ANGLE_GL_PROGRAM(drawTex2DArray, essl1_shaders::vs::Texture2DArray(),
- essl1_shaders::fs::Texture2DArray());
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
+ essl3_shaders::fs::Texture2DArray());
drawQuad(drawTex2DArray, essl1_shaders::PositionAttrib(), 0.5f);
// Fill up the device memory until we start allocating on the system memory.
diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp
index 275e261..8994612 100644
--- a/util/shader_utils.cpp
+++ b/util/shader_utils.cpp
@@ -580,18 +580,6 @@
})";
}
-const char *Texture2DArray()
-{
- return R"(#version 300 es
-out vec2 v_texCoord;
-in vec4 a_position;
-void main()
-{
- gl_Position = vec4(a_position.xy, 0.0, 1.0);
- v_texCoord = (a_position.xy * 0.5) + 0.5;
-})";
-}
-
} // namespace vs
namespace fs
@@ -689,20 +677,6 @@
})";
}
-const char *Texture2DArray()
-{
- return R"(#version 300 es
-precision highp float;
-uniform highp sampler2DArray tex2DArray;
-uniform int slice;
-in vec2 v_texCoord;
-out vec4 fragColor;
-void main()
-{
- fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
-})";
-}
-
} // namespace fs
} // namespace essl1_shaders
@@ -787,6 +761,18 @@
})";
}
+const char *Texture2DArray()
+{
+ return R"(#version 300 es
+out vec2 v_texCoord;
+in vec4 a_position;
+void main()
+{
+ gl_Position = vec4(a_position.xy, 0.0, 1.0);
+ v_texCoord = (a_position.xy * 0.5) + 0.5;
+})";
+}
+
} // namespace vs
namespace fs
@@ -844,6 +830,20 @@
})";
}
+const char *Texture2DArray()
+{
+ return R"(#version 300 es
+precision highp float;
+uniform highp sampler2DArray tex2DArray;
+uniform int slice;
+in vec2 v_texCoord;
+out vec4 fragColor;
+void main()
+{
+ fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
+})";
+}
+
} // namespace fs
Regression Test / PoC
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
index 020a041..f72f1a7 100644
--- a/src/tests/gl_tests/FramebufferTest.cpp
+++ b/src/tests/gl_tests/FramebufferTest.cpp
@@ -8894,6 +8894,62 @@
ASSERT_GL_NO_ERROR();
}
+// Test that 2D array texture size calculation doesn't overflow internally when rendering to it. An
+// RGB format is used which is often emualted with RGBA.
+//
+// Practically we cannot run this test. On most configurations, allocating a 4GB texture fails due
+// to internal driver limitations. On the few configs that the test actually runs, allocating such
+// large memory leads to instability.
+TEST_P(FramebufferTest_ES3, DISABLED_MaxSize2DArrayNoOverflow)
+{
+ GLint maxTexture2DSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexture2DSize);
+
+ maxTexture2DSize = std::min(maxTexture2DSize, 16384);
+
+ // Create a 2D array texture with RGB format. Every layer is going to take 1GB of memory (if
+ // emulated with RGBA), so only create 4 layers of it (for a total of 4GB of memory). If 32-bit
+ // math is involved when calculating sizes related to this texture, they will overflow.
+ constexpr uint32_t kLayers = 4;
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGB8, maxTexture2DSize, maxTexture2DSize, kLayers);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // Initialize the texture so its content is considered valid and worth preserving.
+ constexpr int kValidSubsectionWidth = 16;
+ constexpr int kValidSubsectionHeight = 20;
+ std::vector<GLColorRGB> data(kValidSubsectionWidth * kValidSubsectionHeight,
+ GLColorRGB(0, 255, 0));
+ for (uint32_t layer = 0; layer < kLayers; ++layer)
+ {
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kValidSubsectionWidth,
+ kValidSubsectionHeight, 1, GL_RGB, GL_UNSIGNED_BYTE, data.data());
+ }
+
+ // Draw with the texture, making sure it's initialized and data is flushed.
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
+ essl3_shaders::fs::Texture2DArray());
+ drawQuad(drawTex2DArray, essl3_shaders::PositionAttrib(), 0.5f);
+
+ // Bind a framebuffer to the texture and render into it. In some backends, the texture is
+ // recreated to RGBA to be renderable.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, 1);
+
+ ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
+ glViewport(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight);
+ drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);
+
+ EXPECT_PIXEL_RECT_EQ(0, 0, kValidSubsectionWidth / 2, kValidSubsectionHeight, GLColor::red);
+ EXPECT_PIXEL_RECT_EQ(kValidSubsectionWidth / 2, 0,
+ kValidSubsectionWidth - kValidSubsectionWidth / 2, kValidSubsectionHeight,
+ GLColor::green);
+ ASSERT_GL_NO_ERROR();
+}
+
ANGLE_INSTANTIATE_TEST_ES2_AND(AddMockTextureNoRenderTargetTest,
ES2_D3D9().enable(Feature::AddMockTextureNoRenderTarget),
ES2_D3D11().enable(Feature::AddMockTextureNoRenderTarget));
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
index 2f06e2d..87e7482 100644
--- a/src/tests/gl_tests/VulkanImageTest.cpp
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
@@ -677,8 +677,8 @@
kTextureHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, textureColor.data());
}
- ANGLE_GL_PROGRAM(drawTex2DArray, essl1_shaders::vs::Texture2DArray(),
- essl1_shaders::fs::Texture2DArray());
+ ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
+ essl3_shaders::fs::Texture2DArray());
drawQuad(drawTex2DArray, essl1_shaders::PositionAttrib(), 0.5f);
// Fill up the device memory until we start allocating on the system memory.
Original Bug Report
ANGLE Vulkan reinitImageAsRenderable uint32 Overflow causes GPU OOB Write
Report description
ANGLE Vulkan reinitImageAsRenderable uint32 Overflow causes GPU OOB Write
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
The problem
Please describe the technical details of the vulnerability
TextureVk::reinitImageAsRenderable in the ANGLE Vulkan backend computes a staging buffer size using five operands that multiply in 32-bit arithmetic, overflowing before widening to size_t. When a WebGL2 page creates a non-renderable RGB8 TEXTURE_2D_ARRAY and calls copyTexSubImage3D, ANGLE detects that RGB8 is not natively renderable in Vulkan and converts the texture to RGBA8. The size calculation at TextureVk.cpp:3167 multiplies width, height, depth, pixelBytes, and layerCount in 32-bit arithmetic. For a 16384x16384x4-layer texture, the product is exactly 2^32 and wraps to 0. The staging buffer allocation receives the overflowed size (0). CopyImageCHROMIUM then writes 4GB of converted pixel data into it.
The attacker controls the source RGB8 pixel data. After conversion to RGBA8, the R, G, B channels are derived from the attacker’s data and the alpha channel is always 0xFF. The attacker controls 3 of every 4 bytes written during the overflow.
No compromised renderer is required. The overflow is triggered entirely from JavaScript via the WebGL2 API.
Affects Stable, Beta, and Dev. The vulnerable code has been present since September 2021. Tested on:
- 144.0.7559.133 (Stable, Windows)
- 146.0.7678.0 (ASan, Windows)
Affected Code
TextureVk.cpp, lines 3167-3168 in reinitImageAsRenderable:
size_t dstBufferSize = sourceBox.width * sourceBox.height * sourceBox.depth *
dstFormat.pixelBytes * layerCount;
All five operands (width, height, depth as int; pixelBytes, layerCount as uint32_t) multiply in 32-bit arithmetic. The result overflows before being stored into the 64-bit size_t. This value is passed to stageSubresourceUpdateAndGetData (line 3172), which calls initBufferForImageCopy to allocate a VMA staging buffer of the overflowed size. The CopyImageCHROMIUM loop at lines 3190-3197 writes the correct (non-overflowed) amount of data per layer, using pitch values from lines 3177-3184.
The pitch calculations at lines 3177-3184 use GLuint (uint32_t) and have the same overflow class, but they do not overflow at the dimensions required to trigger the size overflow.
Steps to Reproduce
- Launch Chrome with: –use-angle=vulkan
- Navigate to the attached poc.html
- The PoC auto-fires on page load
For ASan builds, also pass –in-process-gpu –disable-features=SkiaGraphite –disable-gpu-compositing. The latter two flags prevent an unrelated Dawn/D3D11 initialization failure in ASan builds when ANGLE is set to Vulkan; they are not related to the vulnerability, but were required in my testing.
ASan output (Chrome 146.0.7678.0, Windows, Intel HD 620):
==17556==ERROR: AddressSanitizer: access-violation on unknown address 0x120973c50000
==17556==The signal is caused by a WRITE memory access.
#0 angle::R8G8B8A8SRGB::writeColor imageformats.cpp:392
#1 rx::CopyImageCHROMIUM renderer_utils.cpp:795
#2 rx::TextureVk::reinitImageAsRenderable TextureVk.cpp:3192
#3 rx::TextureVk::respecifyImageStorage TextureVk.cpp:3260
#4 rx::TextureVk::ensureRenderableWithFormat TextureVk.cpp:4907
#5 rx::TextureVk::ensureRenderableIfCopyTexImage.. TextureVk.cpp:4968
#6 rx::TextureVk::copySubImage TextureVk.cpp:1479
#7 gl::Texture::copySubImage Texture.cpp:1613
#8 gl::Context::copyTexSubImage3D Context.cpp:5139
rax = 41 rcx = 120973c50000
RAX holds the attacker’s marker byte (0x41) at the point of the crash. RCX is the destination pointer. The faulting instruction is writeColor writing the attacker’s R channel byte to the overflow destination.
Chrome 144 stable (no –in-process-gpu) crashes the GPU process with STATUS_ACCESS_VIOLATION (0xC0000005), WRITE. The crashpad minidump confirms:
- RAX = 0x41 (attacker’s marker byte)
- The 128 bytes immediately before the crash address contain 32 consecutive 41 41 41 FF RGBA pixels
- The overflow wrote through a 4MB PAGE_READWRITE heap region and crashed at the adjacent PAGE_NOACCESS boundary
The PoC works as follows:
- Creates a 16384x16384x4-layer RGB8 TEXTURE_2D_ARRAY (non-renderable in Vulkan)
- Fills the first 512 rows with a controlled marker byte (0x41)
- Creates a 1x1 RGBA8 renderbuffer FBO as the copy source
- Calls copyTexSubImage3D from the RGBA8 FBO into the RGB8 texture
- ANGLE calls reinitImageAsRenderable, which computes dstBufferSize = 0 (uint32 overflow) and writes 4GB of RGBA8 converted data into the undersized staging buffer
The trigger path is:
gl.copyTexSubImage3D(TEXTURE_2D_ARRAY, ...)
-> TextureVk::copySubImage
-> ensureRenderableIfCopyTexImageCannotTransfer
-> ensureRenderableWithFormat (RGB8 not renderable, convert to RGBA8)
-> respecifyImageStorage
-> reinitImageAsRenderable (layerCount > 1, takes slow path)
-> dstBufferSize = width * height * depth * pixelBytes * layerCount overflows to 0
-> stageSubresourceUpdateAndGetData allocates staging buffer with overflowed size
-> CopyImageCHROMIUM writes 4GB into undersized buffer
Fix
Use CheckedNumeric for the size calculation, consistent with how block-compressed formats are already handled via ANGLE_VK_CHECK_MATH in vk_helpers.cpp stageSubresourceUpdateImpl (lines 8375-8390):
angle::CheckedNumeric<size_t> checkedSize = sourceBox.width;
checkedSize *= sourceBox.height;
checkedSize *= sourceBox.depth;
checkedSize *= dstFormat.pixelBytes;
checkedSize *= layerCount;
ANGLE_VK_CHECK_MATH(contextVk, checkedSize.IsValid());
size_t dstBufferSize = checkedSize.ValueOrDie();
The GLuint pitch calculations at lines 3177-3184 should also use CheckedNumeric.
Bisect
The vulnerable code was introduced in ANGLE commit 8ea87a6767 (“Vulkan: Avoid texture format fallback when possible”), committed 2021-09-08, CL https://chromium-review.googlesource.com/c/angle/angle/+/3104514. The entire reinitImageAsRenderable function, including the unchecked multiplication, was added as new code in this commit.
Impact analysis
Heap buffer overflow in the GPU process, reachable from JavaScript via WebGL2. The attacker controls 3 of every 4 bytes written during the 4GB overflow (the 4th byte is always 0xFF). The staging buffer is allocated in the VMA (Vulkan Memory Allocator) HOST_VISIBLE staging pool. The overflow corrupts adjacent heap allocations. In release Chrome on Windows, approximately 4MB of heap data is overwritten with attacker-controlled content before the write reaches an unmapped page.
On Android, ANGLE Vulkan is the default backend and no command-line flags are required. The Android GPU process has no sandbox (kAndroidGpuSandbox is FEATURE_DISABLED_BY_DEFAULT) and shares the same UID as the browser process, so this overflow corrupts memory in an unsandboxed process reachable from JavaScript with no user interaction. On Linux and ChromeOS, ANGLE Vulkan is the default on many configurations.
The cause
What version of Chrome have you found the security issue in?
144.0.7559.133 Stable
Is the security issue related to a crash?
Yes, it is related to a crash.
Choose the type of vulnerability
Memory Corruption (in a sandboxed process)
How would you like to be publicly acknowledged for your report?
cinzinga