Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds write in ANGLE
DescriptionOut of bounds write in ANGLE
ComponentANGLE
Bug ClassOOB
Tracker517045394
Fix commit7d65fc5ed3e3 (angle/angle) +11/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • src/libANGLE/renderer/vulkan/ContextVk.cpp
  • src/libANGLE/renderer/vulkan/vk_helpers.cpp
From 7d65fc5ed3e3610c9156b9b9856eebeff57ff21d Mon Sep 17 00:00:00 2001
From: wangra <[email protected]>
Date: Sun, 19 Jul 2026 14:55:07 -0400
Subject: [PATCH] Vulkan: Reset DynamicBuffer state on allocation failure

- Reset DynamicBuffer state on map failure and harden
allocateFromCurrentBuffer in release builds.
- Clean up ContextVk active render pass tracking on error to prevent
command buffer state desynchronization.

Bug: b/517045394
Change-Id: Ic39e6e8d4b7d9bb49a5f5ff114ad37ae0bb827ad
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8118297
Reviewed-by: Charlie Lao <[email protected]>
Reviewed-by: Shahbaz Youssefi <[email protected]>
Commit-Queue: Shahbaz Youssefi <[email protected]>
---

diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 3684745..1154257 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -6987,6 +6987,7 @@
     {
         mLastFlushedQueueSerial = mRenderPassCommands->getQueueSerial();
         mRenderPassCommands->abandon(this, &collector);
+        mRenderPassCommandBuffer = nullptr;
     }
     collector.releaseCommandBuffers();
 
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index 46a7e0e..c80590c 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -2972,7 +2972,7 @@
 {
     // Allocate the buffer
     ASSERT(!mBuffer);
-    mBuffer = std::make_unique<BufferHelper>();
+    RendererScoped<BufferHelper> buffer(context->getRenderer());
 
     VkBufferCreateInfo createInfo    = {};
     createInfo.sType                 = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@@ -2983,11 +2983,19 @@
     createInfo.queueFamilyIndexCount = 0;
     createInfo.pQueueFamilyIndices   = nullptr;
 
-    return mBuffer->init(context, createInfo, mMemoryPropertyFlags);
+    ANGLE_TRY(buffer.get().init(context, createInfo, mMemoryPropertyFlags));
+
+    mBuffer = std::make_unique<BufferHelper>(buffer.release());
+    return angle::Result::Continue;
 }
 
 bool DynamicBuffer::allocateFromCurrentBuffer(size_t sizeInBytes, BufferHelper **bufferHelperOut)
 {
+    if (mBuffer == nullptr)
+    {
+        return false;
+    }
+
     mNextAllocationOffset =
         roundUp<uint32_t>(mNextAllocationOffset, static_cast<uint32_t>(mAlignment));
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential wild write in GPU process due to un-reset stale offset on vkMapMemory failure

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: A potential logic error in ANGLE’s Vulkan backend can cause DynamicBuffer to retain a stale mNextAllocationOffset and a partially-initialized mBuffer pointer if vkMapMemory fails. Because this failure translates to GL_INVALID_OPERATION rather than context loss, subsequent allocations can succeed using the stale state. This can lead to a wild memory write in the GPU process when downstream code attempts to copy uniform or vertex data.

Affected files:

  • third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/Suballocation.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp

Estimated timestamp from git blame: 2020-12-01

Root Cause Analysis

In third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp, DynamicBuffer::allocateNewBuffer assigns mBuffer before invoking BufferHelper::init:

angle::Result DynamicBuffer::allocateNewBuffer(ErrorContext *context)
{
    ...
    ASSERT(!mBuffer);
    mBuffer = std::make_unique<BufferHelper>();
    ...
    return mBuffer->init(context, createInfo, mMemoryPropertyFlags);
}

If mBuffer->init fails (for example, if vkMapMemory returns VK_ERROR_MEMORY_MAP_FAILED), the failure propagates immediately inside DynamicBuffer::allocate:

if (mBufferFreeList.empty() || ...)
{
    ANGLE_TRY(allocateNewBuffer(context));
}
...
mNextAllocationOffset = 0; // Bypassed on failure

Because the allocation failure propagates early, the following critical states are not rolled back:

  1. mBuffer remains pointing to the partially-initialized, unmapped BufferHelper instance.
  2. mNextAllocationOffset is not reset to 0, retaining its previous stale, high value.

Survival of the GL Context

When vkMapMemory fails with VK_ERROR_MEMORY_MAP_FAILED, it maps to GL_INVALID_OPERATION via DefaultGLErrorCode in ContextVk.cpp:

GLenum DefaultGLErrorCode(VkResult result) {
    switch (result) {
        ...
        default:
            return GL_INVALID_OPERATION;      // VK_ERROR_MEMORY_MAP_FAILED falls here
    }
}

Since ANGLE only triggers context loss on GL_OUT_OF_MEMORY or GL_CONTEXT_LOST, the WebGL context survives, records GL_INVALID_OPERATION, and continues processing GLES command buffers.

Downstream Wild Write in Release Builds

Upon a subsequent draw call requiring a smaller allocation, allocateFromCurrentBuffer checks if the requested size fits within the stale bounds:

bool DynamicBuffer::allocateFromCurrentBuffer(size_t sizeInBytes, BufferHelper **bufferHelperOut) {
    mNextAllocationOffset = roundUp<uint32_t>(mNextAllocationOffset, mAlignment);
    ...
    if (!checkedNextWriteOffset.IsValid() || checkedNextWriteOffset.ValueOrDie() > mSize)
        return false;
    ...
    mBuffer->setSuballocationOffsetAndSize(mNextAllocationOffset, sizeToAllocate);
    *bufferHelperOut = mBuffer.get();
}

Since ASSERT(mBuffer->getMappedMemory()) is compiled out in release builds, the function returns the half-initialized BufferHelper. When downstream code (such as default uniform copy in ProgramExecutableVk.cpp or vertex streamed buffer copy) writes data to the buffer, it calls getMappedMemory(), which evaluates to (spec-undefined mMappedMemory) + stale_offset:

uint8_t *bufferData = defaultUniformBuffer->getMappedMemory();
memcpy(&bufferData[offsets[shaderType]], uniformData.data(), uniformData.size()); // WILD WRITE

Potential Steps to Trigger (Hypothetical)

Note: These steps are based on static code analysis and have not been executed on a running system.

  1. From a compromised renderer, create a WebGL context backed by ANGLE/Vulkan.
  2. Issue drawing commands with default uniforms to advance mDefaultUniformStorage.mNextAllocationOffset near its limit (~56 KB out of 64 KB).
  3. Artificially exhaust host virtual-address (VA) space to force the next vkMapMemory call to return VK_ERROR_MEMORY_MAP_FAILED during a new buffer allocation.
  4. Submit a draw call that triggers the new allocation. The allocation fails, but because it is handled as GL_INVALID_OPERATION, the context survives and leaves mBuffer and mNextAllocationOffset un-reset.
  5. Release VA pressure.
  6. Issue a smaller draw command where the uniform requirements fit within the stale buffer boundaries (stale_offset + size <= mSize). allocateFromCurrentBuffer succeeds, returning the unmapped buffer helper.
  7. A memcpy copy of uniform data writes to an invalid address in the GPU process.

Suggested Fix

In DynamicBuffer::allocate (inside third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp), ensure that if allocateNewBuffer fails, mBuffer is explicitly reset to nullptr and mNextAllocationOffset is cleared or rolled back to a safe state to prevent subsequent draw calls from utilizing stale states.

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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