CVE-2026-4448
Overview
Files Changed
src/libANGLE/renderer/vulkan/TextureVk.cppsrc/libANGLE/renderer/vulkan/vk_helpers.cppsrc/libANGLE/renderer/vulkan/vk_renderer.cppsrc/libANGLE/renderer/vulkan/vk_renderer.hsrc/tests/gl_tests/TextureTest.cpp
Patch
From 4de47461e45248eeaf8fd0ef04ca3949f98029da Mon Sep 17 00:00:00 2001 From: Amirali Abdolrashidi <[email protected]> Date: Thu, 26 Feb 2026 15:17:40 -0800 Subject: [PATCH] Vulkan: Cap memory allocation size to 1GB Currently the maximum memory allocation size for a single object can be inquired from the Vulkan driver via KHR_maintenance3 (promoted to core in Vulkan 1.1). However, the reported limit can still be quite large for common usage. In addition, it can increase the risk of memory size calculations overflowing if the operands are not 64 bits. As an example, if the limit is 4GB and an image is defined with 32-bit dims and format that theoretically require 4GB, the multiplication of these values can result in overflow, even if it is to be assigned to a 64-bit value. * (One way to avoid such overflow cases is to split multiplication into several steps, e.g., CL:7595734) This change aims to reduce the maximum size allowed for allocation in order to reduce the risk of such overflow issues. * Added kMemoryAllocationSizeLimit to vk_renderer. * Currently set to 1GB. * Added to vk::Renderer: mMaxMemoryAllocationSize * Set to the minimum of kMemoryAllocationSizeLimit and the maxMemoryAllocationSize reported from the driver. * Cast the values used in some buffer size calculations to size_t. * (The leftmost value being size_t should be enough to propagate this type for the result of each multiplication.) * Updated the following test to use a smaller width and height to avoid error due to the new cap: TextureCubeTestES32.MaxArrayTextureLayersVerify Bug: chromium:486972661 Bug: chromium:487208468 Change-Id: I98bde71ede153324f524bb579b19043a474823d5 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7615650 Reviewed-by: Shahbaz Youssefi <[email protected]> Commit-Queue: Amirali Abdolrashidi <[email protected]> --- diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp index 2dd5f8a..84c51d6 100644 --- a/src/libANGLE/renderer/vulkan/TextureVk.cpp +++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp @@ -3161,17 +3161,9 @@ // invalidate must be called after wait for finish. ANGLE_TRY(srcBuffer->invalidate(renderer)); - // 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; + size_t dstBufferSize = + static_cast<size_t>(sourceBox.width) * static_cast<size_t>(sourceBox.height) * + static_cast<size_t>(sourceBox.depth) * dstFormat.pixelBytes * layerCount; // Allocate memory in the destination texture for the copy/conversion. uint8_t *dstData = nullptr; diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp index 4f9c83d..f32774f 100644 --- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp +++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp @@ -10783,9 +10783,10 @@ // used in this function to be of some combined depth and stencil format. ASSERT(getAspectFlags() == VK_IMAGE_ASPECT_COLOR_BIT); - uint32_t pixelBytes = imageFormat.pixelBytes; - size_t bufferSize = - sourceArea.width * sourceArea.height * sourceArea.depth * pixelBytes * layerCount; + size_t pixelBytes = imageFormat.pixelBytes; + size_t bufferSize = static_cast<size_t>(sourceArea.width) * + static_cast<size_t>(sourceArea.height) * + static_cast<size_t>(sourceArea.depth) * pixelBytes * layerCount; const VkImageAspectFlags aspectFlags = getAspectFlags(); diff --git a/src/libANGLE/renderer/vulkan/vk_renderer.cpp b/src/libANGLE/renderer/vulkan/vk_renderer.cpp index 62c43ae..612c044 100644 --- a/src/libANGLE/renderer/vulkan/vk_renderer.cpp +++ b/src/libANGLE/renderer/vulkan/vk_renderer.cpp @@ -166,6 +166,9 @@ // value will use a dedicated VkDeviceMemory. constexpr size_t kImageSizeThresholdForDedicatedMemoryAllocation = 8 * 1024 * 1024; +// Maximum size for an allocated memory for a single object. +constexpr VkDeviceSize kMemoryAllocationSizeLimit = 1 * 1024 * 1024 * 1024; + // Pipeline cache header version. It should be incremented any time there is an update to the cache // header or data structure. constexpr uint32_t kPipelineCacheVersion = 3; @@ -2091,6 +2094,7 @@ mSupportedBufferWritePipelineStageMask(0), mSupportedVulkanShaderStageMask(0), mMemoryAllocationTracker(MemoryAllocationTracker(this)), + mMaxMemoryAllocationSize(0), mMaxBufferMemorySizeLimit(0), mNativeVectorWidthDouble(0), mNativeVectorWidthHalf(0), @@ -5726,13 +5730,20 @@ mPhysicalDeviceProperties.limits.maxVertexInputBindingStride) : 0; + // Although the maximum memory allocation size for a single object can be derived from the + // Vulkan driver, it could still be too large for common use (e.g., ~4GB on some platforms) and + // increase the risk of overflow if the object dimensions used for size calculations are 32-bit. + // The limit can be restricted to a specific fixed value to reduce this risk. + mMaxMemoryAllocationSize = + std::min(mMaintenance3Properties.maxMemoryAllocationSize, kMemoryAllocationSizeLimit); + // The limits related to buffer size should also take the max memory allocation size and padding // (if applicable) into account. - mMaxBufferMemorySizeLimit = getMaxMemoryAllocationSize() - mMaxVertexAttribStride; + mMaxBufferMemorySizeLimit = mMaxMemoryAllocationSize - mMaxVertexAttribStride; ANGLE_FEATURE_CONDITION(&mFeatures, forceD16TexFilter, IsAndroid() && isQualcommProprietary); - // Allocation sanitization disabled by default because of a heaveyweight implementation + // Allocation sanitization disabled by default because of a heavyweight implementation // that can cause OOM and timeouts. ANGLE_FEATURE_CONDITION(&mFeatures, allocateNonZeroMemory, false); diff --git a/src/libANGLE/renderer/vulkan/vk_renderer.h b/src/libANGLE/renderer/vulkan/vk_renderer.h index 41f4119..3e7d131 100644 --- a/src/libANGLE/renderer/vulkan/vk_renderer.h +++ b/src/libANGLE/renderer/vulkan/vk_renderer.h @@ -659,10 +659,7 @@ void requestAsyncCommandsAndGarbageCleanup(vk::ErrorContext *context); - VkDeviceSize getMaxMemoryAllocationSize() const - { - return mMaintenance3Properties.maxMemoryAllocationSize; - } + VkDeviceSize getMaxMemoryAllocationSize() const { return mMaxMemoryAllocationSize; } // Cleanup garbage and finish command batches from the queue if necessary in the event of an OOM // error. @@ -1113,6 +1110,9 @@ // A placeholder descriptor set layout handle for layouts with no bindings. vk::DescriptorSetLayoutPtr mPlaceHolderDescriptorSetLayout; + // Allocation size limit for a single object. + VkDeviceSize mMaxMemoryAllocationSize; + // Cached value for the buffer memory size limit. VkDeviceSize mMaxBufferMemorySizeLimit; diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index 2beb301..d87b33c 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -14088,27 +14088,28 @@ // Test that the maximum texture layer can allocate enough memory. TEST_P(TextureCubeTestES32, MaxArrayTextureLayersVerify) { - GLint maxTextureLayers = 0; - GLTexture texture; + constexpr uint32_t kSize = 128; + GLTexture texture; glBindTexture(GL_TEXTURE_2D_ARRAY, texture); ASSERT_GL_NO_ERROR(); + GLint maxTextureLayers = -1; glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers); ASSERT_GL_NO_ERROR(); - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 256, 256, maxTextureLayers, 0, GL_RGBA, + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, maxTextureLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); ASSERT_GL_NO_ERROR(); - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 256, 256, maxTextureLayers + 1, 0, GL_RGBA, + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, maxTextureLayers + 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); EXPECT_GL_ERROR(GL_INVALID_VALUE); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 256, 256, maxTextureLayers); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kSize, kSize, maxTextureLayers); ASSERT_GL_NO_ERROR(); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 256, 256, maxTextureLayers + 1); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kSize, kSize, maxTextureLayers + 1); EXPECT_GL_ERROR(GL_INVALID_VALUE); }
Regression Test / PoC
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 2beb301..d87b33c 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -14088,27 +14088,28 @@
// Test that the maximum texture layer can allocate enough memory.
TEST_P(TextureCubeTestES32, MaxArrayTextureLayersVerify)
{
- GLint maxTextureLayers = 0;
- GLTexture texture;
+ constexpr uint32_t kSize = 128;
+ GLTexture texture;
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
ASSERT_GL_NO_ERROR();
+ GLint maxTextureLayers = -1;
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers);
ASSERT_GL_NO_ERROR();
- glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 256, 256, maxTextureLayers, 0, GL_RGBA,
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, maxTextureLayers, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
- glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 256, 256, maxTextureLayers + 1, 0, GL_RGBA,
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, maxTextureLayers + 1, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
- glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 256, 256, maxTextureLayers);
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kSize, kSize, maxTextureLayers);
ASSERT_GL_NO_ERROR();
- glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 256, 256, maxTextureLayers + 1);
+ glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kSize, kSize, maxTextureLayers + 1);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
}
Original Bug Report
Heap buffer overflow in ANGLE copyImageDataToBuffer
Report description
Heap buffer overflow in ANGLE copyImageDataToBuffer
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?
https://chromium.googlesource.com/chromium
The problem
Please describe the technical details of the vulnerability
Description
Commit a08731cf (Bug: chromium:485622239) fixed a 32-bit integer overflow in the destination buffer size calculation in TextureVk::reinitImageAsRenderable() at TextureVk.cpp:3170 by splitting the multiplication into two stages with static_assert guards.
That same function also calls ImageHelper::copyImageDataToBuffer() at TextureVk.cpp:3155, which does its own staging buffer allocation with the same kind of unchecked 32-bit multiply at vk_helpers.cpp:10788:
uint32_t pixelBytes = imageFormat.pixelBytes;
size_t bufferSize =
sourceArea.width * sourceArea.height * sourceArea.depth * pixelBytes * layerCount;
Every operand here is 32-bit (int or uint32_t), so the product wraps before it gets assigned to the 64-bit size_t. When the true result hits 2^32 the value becomes zero, a zero-byte staging buffer gets allocated, and the following vkCmdCopyImageToBuffer writes the full image data into it, overflowing the heap.
In short: the fix patched the destination buffer (caller) but missed the source buffer (callee).
Steps to Reproduce
Tested on Windows 11 with Chrome 145 Stable. The PoC allocates a 4 GiB texture, so the Vulkan driver must support a single allocation of that size. Most discrete GPUs (e.g. NVIDIA RTX 4050, 6 GB VRAM) cap maxMemoryAllocationSize just under 4 GiB, causing texStorage3D to return GL_OUT_OF_MEMORY before the vulnerable path is reached. On laptops with an Intel Iris Xe iGPU, the Vulkan driver allocates from system RAM and supports allocations up to ~16 GiB, which is enough.
- Place
poc.htmlin a directory and start a local web server (PowerShell):
cd path\to\poc
python -m http.server 8765
- Open a second PowerShell window. Force Chrome to use the Intel Vulkan driver and launch with logging to file:
$env:VK_ICD_FILENAMES = "C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_9741ef1f4093481f\igvk64.json"
New-Item -ItemType Directory -Force "$env:TEMP\chrome-vulkan-test" | Out-Null
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --use-angle=vulkan --disable-gpu-sandbox --no-sandbox --user-data-dir="$env:TEMP\chrome-vulkan-test" --no-first-run --enable-logging --log-file="$env:TEMP\chrome-vulkan-test\chrome.log" http://localhost:8765/poc.html
The VK_ICD_FILENAMES path is for the Intel driver on the test system. On other machines, find it under C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_*\igvk64.json.
- The page will go blank within a few seconds (GPU process crash). Wait ~10 seconds, then check the log:
Select-String "GPU process exited unexpectedly" "$env:TEMP\chrome-vulkan-test\chrome.log"
Expected output:
[ERROR:gpu_process_host.cc:996] GPU process exited unexpectedly: exit_code=-1073741819
-1073741819 is 0xC0000005 (STATUS_ACCESS_VIOLATION).
- Crashpad also writes a minidump to the user-data-dir:
ls "$env:TEMP\chrome-vulkan-test\Crashpad\reports\*.dmp"
Minidump from test system:
ExceptionCode: EXCEPTION_ACCESS_VIOLATION (0xC0000005)
Access type: WRITE
Faulting addr: libglesv2.dll + 0x0044A120
Write target: 0x1B1D28C0000 (unmapped, past end of staging buffer)
What the PoC does: it creates a TEXTURE_2D_ARRAY (RGB8, 2048x2048, 256 layers). ANGLE stores RGB8 as RGBA8 (4 bytes/pixel) on Vulkan since RGB8 is not natively renderable. Attaching layer 0 to an FBO and calling gl.clear() forces reinitImageAsRenderable, which calls copyImageDataToBuffer. The buffer size wraps: 2048 * 2048 * 1 * 4 * 256 = 2^32 = 0.
Control tests
| Test | Result |
|---|---|
poc.html (2048x2048x256, overflows) |
GPU crash, exit_code=-1073741819 |
about:blank (no WebGL) |
No crash |
poc_safe.html (256x256x4, no overflow) |
No crash |
Root Cause
Looking at the commit diff, the fix only touches TextureVk.cpp (the destination buffer allocation). ImageHelper::copyImageDataToBuffer in vk_helpers.cpp (the source buffer allocation) was left as-is and still does the whole multiplication in one 32-bit expression.
GPU memory note
The PoC needs a single Vulkan allocation of exactly 4 GiB (2^32 bytes). Many discrete GPUs cap maxMemoryAllocationSize just below that (the RTX 4050 caps at 0xffe00000, ~2 MiB short). The Intel Iris Xe iGPU allocates from system RAM with a limit of ~16 GiB, so it works on any machine with enough free RAM. On Linux or systems without an Intel iGPU, lowering IMPLEMENTATION_MAX_2D_TEXTURE_SIZE in src/libANGLE/Constants.h to 256 and rebuilding ANGLE makes the overflow trigger with only a few hundred KiB.
Impact analysis
A web page can cause a heap buffer overflow in Chrome’s GPU process through WebGL 2. The data written past the buffer comes from texture contents, which the page controls through texSubImage3D. The GPU process runs at higher privilege than the renderer.
The cause
What version of Chrome have you found the security issue in?
Chrome 145.0.7632.110 Stable, Windows 11 x86_64
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?
M. Fauzan Wijaya (Gh05t666nero)
- http://localhost:8765/poc.html
- https://bughunters.google.com/about/rules/5745167867576320/chrome-vulnerability-reward-program-rules
- https://chromium-review.googlesource.com/c/angle/angle/+/7595734
- https://chromium.googlesource.com/chromium
- https://github.com/google/angle/blob/main/src/libANGLE/renderer/vulkan/TextureVk.cpp#L3155
- https://github.com/google/angle/blob/main/src/libANGLE/renderer/vulkan/TextureVk.cpp#L3170
- https://github.com/google/angle/blob/main/src/libANGLE/renderer/vulkan/vk_helpers.cpp#L10788
- https://github.com/google/angle/commit/a08731cf6d70c60fd74b1d75f2e8b94c52e18140