Overview

Critical
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in ANGLE
DescriptionUse after free in ANGLE
ComponentANGLE
Sub-componentVulkan
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker512995785
Fix commitca3a627b7270 (angle/angle) +3/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-13 (per vendor advisory)
Fix landed2026-05-14 (public git)
Disclosed2026-06-30
Patch gap47 days — fix public in source before the advisory

Background

`RefCountedEvent`
ANGLE Vulkan wrapper that owns a VkEvent through a heap-allocated mHandle (a RefCounted<EventAndLayout> or similar), used to coordinate GPU synchronization.
`mHandle`
the heap pointer inside RefCountedEvent that must be either a valid reference-counted event object or nullptr; an intermediate dangling value violates the object’s invariant.
`UNREACHABLE()`
an ANGLE macro asserting a code path is impossible; in release builds it degrades to a no-op with undefined behavior rather than a hard abort, so the failure path silently continued.
`SafeDelete`
a helper that frees the pointed-to object and sets the pointer to nullptr in one step, guaranteeing no dangling reference remains.

Root Cause Analysis

RefCountedEvent::init() allocates mHandle and then attempts to create the underlying VkEvent. On the failure path after VkEvent creation/allocation fails, the old code invoked UNREACHABLE() and immediately return false, without releasing or nulling the already-allocated mHandle. Because UNREACHABLE() is a no-op in release builds, execution fell through and left the RefCountedEvent in a half-initialized state with a dangling/invalid mHandle. Callers treated the false return as a recoverable condition (falling back to the pipelineBarrier path) but the object retained the stale pointer, so a later use, destruction, or reuse of that RefCountedEvent dereferenced freed or invalid memory.

The fix replaces UNREACHABLE() with ASSERT(false) and adds SafeDelete(mHandle) so the failed event becomes cleanly invalid (mHandle == nullptr), and the intended pipelineBarrier fallback runs without touching dangling memory.

Key insight
A false return does not by itself restore an object’s invariant; the failure path must also undo the partial allocation it made. Relying on UNREACHABLE() to guard a genuinely reachable error path meant the cleanup never happened in release builds, leaving a dangling mHandle.

Attack Path

  1. Trigger event creation A renderer sends GPU work that causes ANGLE to construct a RefCountedEvent and call RefCountedEvent::init().
  2. Force init failure Under Vulkan resource pressure or an allocation/vkCreateEvent failure, init() takes the failure path and returns false while leaving mHandle allocated-but-invalid.
  3. Dangling handle persists The RefCountedEvent is retained with a stale mHandle instead of a clean nullptr invalid state.
  4. Stale dereference Later synchronization, reuse, or destruction of that RefCountedEvent dereferences the freed/invalid mHandle, producing a use-after-free in the GPU process.
  5. Exploit the UAF With heap grooming controlling the freed slot, the dangling access can be steered toward memory corruption and control-flow influence in the GPU process.

Impact Assessment

This is a use-after-free in the ANGLE Vulkan backend running in Chrome’s GPU process, reachable from renderer-driven graphics workloads. Exploitation requires provoking the init() failure path (e.g., resource exhaustion) but yields dangling-pointer access that can lead to memory corruption in the GPU process, a valuable target for sandbox-relevant escalation. The bug is release-build-specific because the UNREACHABLE() guard suppressed the crash that would otherwise surface in debug builds.

Files Changed

  • src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp

Audit Directions

  • `UNREACHABLE()` on reachable error paths
    Audit ANGLE and other release builds for UNREACHABLE() guarding paths that can actually fail (allocation, vkCreate*); these silently skip cleanup and leave partial state.
  • Partial-init cleanup
    Review init()/create() functions that allocate a member before a fallible step and return false; ensure every early return frees and nulls (SafeDelete) the allocated members, restoring the object’s invariant.
  • Invalid-object invariants
    Verify that objects representing a “failed but retained” resource (like an invalid RefCountedEvent feeding the pipelineBarrier fallback) hold nullptr, not dangling handles, wherever a false return is treated as recoverable.
From ca3a627b72709b6fd4b56a1517ff1a926f7c3fca Mon Sep 17 00:00:00 2001
From: Charlie Lao <cclao@google.com>
Date: Wed, 13 May 2026 17:18:28 -0700
Subject: [PATCH] Vulkan: Ensure we clean up mHandle when Event::init() fail

Otherwise we may leave a dangling mHandle in RefCountedEvent and cause
potential UAF later.

Bug: b/512995785
Change-Id: Id9148c7d05fbca4abd9eda0e7f1c73edc372c224
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7846935
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Charlie Lao <cclao@google.com>
---

diff --git a/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp b/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp
index 1948b6c..3d855ae 100644
--- a/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp
@@ -60,7 +60,9 @@
                 // that many VkEvents under normal situation. If we failed to allocate, there is a
                 // high chance that we may have a leak somewhere. This macro should help us catch
                 // such potential bugs in the bots if that happens.
-                UNREACHABLE();
+                ASSERT(false);
+                // Ensure memory is freed and pointer is nulled
+                SafeDelete(mHandle);
                 // If still fail to create, we just return. An invalid event will trigger
                 // pipelineBarrier code path
                 return false;
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in ANGLE Vulkan via RefCountedEvent initialization 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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in ANGLE’s Vulkan backend when vkCreateEvent fails during synchronization initialization. A leaked heap-allocated handle persists in recycled command buffers, leading to premature destruction and subsequent dangling pointer access.

Affected files:

  • third_party/angle/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/vk_ref_counted_event.h
  • third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/vk_renderer.cpp

Estimated timestamp from git blame: 2024-05-10

Summary

A Use-After-Free (UAF) vulnerability has been identified in ANGLE’s Vulkan backend, specifically within the lifecycle management of RefCountedEvent. The issue stems from an inconsistent state created when vkCreateEvent fails, allowing a heap-allocated pointer to survive command buffer recycling. This can lead to memory corruption when the stale pointer is later reused and prematurely freed.

Root Cause Analysis

In third_party/angle/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp, the RefCountedEvent::init() function handles the allocation and initialization of Vulkan events. When a VkEvent fails to initialize (e.g., due to driver handle exhaustion), the function returns false but fails to clean up the newly allocated mHandle member:

// third_party/angle/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp:43
mHandle = new RefCounted<EventAndStage>; // Refcount starts at 0
// ... initialization attempts ...
if (result != VK_SUCCESS)
{
    UNREACHABLE();
    return false; // mHandle remains non-null, pointing to a RefCounted object with refcount 0
}

The calling function, RefCountedEventArray::initEventAtStage(), receives the false return and does not set the corresponding bit in its mBitMask. Because the CommandBufferHelper recycling logic relies on this bitmask to identify and release active events, the “zombie” mHandle pointer remains in the mEvents array when the helper is returned to a pool.

Potential Exploitation Path

  1. Triggering the Poisoned State: An attacker uses WebGL to exhaust VkEvent handles (a common limitation on some drivers) or creates memory pressure, causing vkCreateEvent to fail during a synchronization operation. This leaves a recycled CommandBufferHelper with a non-null mHandle but a zero reference count in its event array.
  2. Incorrect Adoption: When the CommandBufferHelper is reused, a subsequent image operation may check if an event is already valid for its stage. Since mHandle is non-null, RefCountedEvent::valid() returns true. ImageHelper::setCurrentRefCountedEvent then adopts this zombie event via copy assignment, which increments the reference count from 0 to 1.
  3. Premature Free: When the adopting ImageHelper later releases its reference (e.g., during a layout transition), the reference count drops from 1 back to 0. On hardware drivers (where recycleVkEvent is typically disabled), this triggers SafeDelete(mHandle), freeing the memory.
  4. Use-After-Free: The CommandBufferHelper still holds the now-freed pointer. Subsequent attempts to use that event stage on the same helper—either by another ImageHelper or during command buffer submission—will result in a UAF write (refcount increment/decrement) or a UAF read (retrieving the VkEvent handle from freed memory).

In ANGLE, mHandle is a bare pointer to a RefCounted object, meaning this vulnerability is not mitigated by MiraclePtr. On Android, where the GPU process is often unsandboxed, this could potentially lead to platform-level arbitrary code execution.

Suggested Fix

Ensure that mHandle is properly deleted and set to nullptr if initialization fails in RefCountedEvent::init():

if (result != VK_SUCCESS)
{
    UNREACHABLE();
    SafeDelete(mHandle); // Ensure memory is freed and pointer is nulled
    return false;
}

Reproducibility Note

These steps are based on a source code analysis of the Vulkan backend synchronization logic. Tooling constraints currently prevent the execution of a proof-of-concept to confirm reachability under specific driver conditions.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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