CVE-2026-84333
Overview
Background
- Dawn
- Chromium’s implementation of the WebGPU standard that translates WebGPU calls into native GPU APIs such as Vulkan.
- `DescriptorSetAllocator`
- a Dawn Vulkan helper that owns a
VkDescriptorPooland hands outVkDescriptorSetobjects allocated from it. - `vkAllocateDescriptorSets`
- the Vulkan entry point that carves descriptor sets out of a descriptor pool and can fail with an out-of-memory result.
- UAF (use-after-free)
- a memory-safety bug where code dereferences or frees memory that has already been freed, producing a wild read or invalid free.
Root Cause Analysis
The vulnerable path is DescriptorSetAllocator::AllocateDescriptorSets, which calls mDevice->fn.AllocateDescriptorSets and, on any error, immediately called mDevice->fn.DestroyDescriptorPool to clean up the pool. The violated invariant is that a descriptor pool is safe to destroy after a failed allocation; on Imagination (PowerVR / IMG) drivers this does not hold, because the driver’s chunk-allocation path publishes the memory chunk before allocating and fails to unpublish it when allocation fails with VK_ERROR_OUT_OF_HOST_MEMORY or VK_ERROR_OUT_OF_DEVICE_MEMORY, leaving a dangling freed meminfo pointer inside the pool. The subsequent vkDestroyDescriptorPool call then dereferences that already-freed pointer, causing a UAF read and a wild free inside the driver.
The fix inspects the vendor via gpu_info::IsImgTec on the physical device and the concrete VkResult, and skips the DestroyDescriptorPool call precisely when the pool is in this corrupted state, avoiding the dangling-pointer dereference. Because the OOM failure is an internal error that leads to device loss anyway, leaking the pool by skipping destruction is acceptable and cannot propagate further.
DestroyDescriptorPool for exactly that vendor-and-error combination.Attack Path
- Reach the allocator
An attacker runs WebGPU content that repeatedly creates bind groups on a device backed by an Imagination (PowerVR) GPU, driving Dawn into
DescriptorSetAllocator::AllocateDescriptorSets. - Force an OOM failure
Content exhausts host or device memory so that
vkAllocateDescriptorSetsreturnsVK_ERROR_OUT_OF_HOST_MEMORYorVK_ERROR_OUT_OF_DEVICE_MEMORY. - Trigger the corrupted cleanup
The error branch calls
mDevice->fn.DestroyDescriptorPoolon a pool whosememinfochunk the IMG driver already freed but left published. - Dereference the dangling pointer
vkDestroyDescriptorPoolfollows the freedmeminfopointer, producing a use-after-free read and an invalid free inside the driver.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/dawn/native/vulkan/DescriptorSetAllocator.cpp |
modified |
Files Changed
src/dawn/native/vulkan/DescriptorSetAllocator.cpp
Audit Directions
- Error-path resource cleanupAudit every
Destroy/Freecall reached only after an API failure and confirm the object is still in a valid, destroyable state on that specific driver and error code. - Vendor-specific driver quirksLook for other Vulkan/GPU calls where a known buggy vendor driver leaves objects in an inconsistent state on OOM, and gate cleanup with
gpu_infovendor checks likeIsImgTec. - Discarded specific error resultsFlag places that collapse a detailed
VkResultinto a genericMaybeErrorbefore branching, since the exact code (e.g.VK_ERROR_OUT_OF_HOST_MEMORY) is needed to make safe cleanup decisions.
Patch
From daa0cc047acd570fbd5c127db4262e1a98637eb7 Mon Sep 17 00:00:00 2001 From: Lokbondo Kung <[email protected]> Date: Fri, 21 Aug 2026 11:13:48 -0700 Subject: [PATCH] [vulkan] Add workaround for UAF in DestroyDescriptorPool. - As per the bug and the related internal Android issue, this works around a temporary issue in the IMG driver that causes a UAF when we attempt to call AllocateDescriptorSets and run into an OOM. - The more "proper" fix would probably be to implement suballocations, but since that is a much larger effort, this is a band-aid fix to avoid the UAF issue for now. - Note that since the error that is generated is an internal error, this eventually leads to a device loss so it is acceptable to skip the Destroy call in this case since even if it would lead to a leak, it cannot propagate further. Bug: 549311485 Change-Id: I8c951e879bb405943698c179962275687e828168 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/334695 Reviewed-by: Kai Ninomiya <[email protected]> Commit-Queue: Loko Kung <[email protected]> --- diff --git a/src/dawn/native/vulkan/DescriptorSetAllocator.cpp b/src/dawn/native/vulkan/DescriptorSetAllocator.cpp index ed36582..d14b23e 100644 --- a/src/dawn/native/vulkan/DescriptorSetAllocator.cpp +++ b/src/dawn/native/vulkan/DescriptorSetAllocator.cpp @@ -30,6 +30,8 @@ #include <algorithm> #include <utility> +#include "src/dawn/common/GPUInfo.h" +#include "src/dawn/native/PhysicalDevice.h" #include "src/dawn/native/Queue.h" #include "src/dawn/native/vulkan/DeviceVk.h" #include "src/dawn/native/vulkan/FencedDeleter.h" @@ -184,15 +186,25 @@ allocateInfo.pSetLayouts = AsVkArray(layouts.data()); std::vector<VkDescriptorSet> sets(mMaxSets); - MaybeError result = - CheckVkSuccess(mDevice->fn.AllocateDescriptorSets(mDevice->GetVkDevice(), &allocateInfo, - AsVkArray(sets.data())), - "AllocateDescriptorSets"); + VkResult vkResult = VkResult::WrapUnsafe( + INJECT_ERROR_OR_RUN(mDevice->fn.AllocateDescriptorSets( + mDevice->GetVkDevice(), &allocateInfo, AsVkArray(sets.data())), + VK_FAKE_ERROR_FOR_TESTING)); + MaybeError result = CheckVkSuccessImpl(vkResult, "AllocateDescriptorSets"); if (result.IsError()) { - // On an error we can destroy the pool immediately because no command references it. - mDevice->fn.DestroyDescriptorPool(mDevice->GetVkDevice(), descriptorPool, nullptr); - DAWN_TRY(std::move(result)); + // TODO(crbug.com/549311485): On Imagination (PowerVR) drivers, when AllocateDescriptorSets + // fails with host or device OOM, the driver's chunk allocation path publishes the chunk + // before allocation and fails to unpublish on error. Calling vkDestroyDescriptorPool + // dereferences the dangling freed meminfo pointer and crashes (UAF read / wild free). + bool isCorruptedImaginationPool = + gpu_info::IsImgTec(mDevice->GetPhysicalDevice()->GetVendorId()) && + (vkResult == VK_ERROR_OUT_OF_HOST_MEMORY || vkResult == VK_ERROR_OUT_OF_DEVICE_MEMORY); + if (!isCorruptedImaginationPool) { + // On an error we can destroy the pool immediately because no command references it. + mDevice->fn.DestroyDescriptorPool(mDevice->GetVkDevice(), descriptorPool, nullptr); + } } + DAWN_TRY(std::move(result)); std::vector<SetIndex> freeSetIndices; freeSetIndices.reserve(mMaxSets);