Overview

Critical
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Dawn
DescriptionUse after free in Dawn
ComponentDawn
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.
Tracker500417361
Fix commit292e993b4a84 (dawn) +24/-11
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
CreditedFlorian Schweitzer
Reported2026-04-08 (per vendor advisory)
Fix landed2026-06-22 (public git)
Disclosed2026-09-17
Patch gap87 days — fix public in source before the advisory

Background

Dawn
Chrome’s native implementation of the WebGPU API; src/dawn/native bridges JavaScript WebGPU calls onto backends such as Vulkan.
Device loss
a WebGPU GPUDevice becoming permanently unusable (DeviceLost), which can be triggered by driver errors, TDR, or unrecoverable validation faults.
`vkDeviceWaitIdle`/WaitIdle
the Vulkan operation that blocks until all outstanding GPU work on a device finishes; destroying device-owned objects (VkDevice children) while the GPU still references them is undefined behavior.
Device state machine
dawn::native::DeviceBase internal mState (Alive, BeingDisconnected, Disconnected) gating whether the app may submit new work and whether CPU/GPU resources may be torn down.

Root Cause Analysis

On a natural DeviceLost, dawn::native::DeviceBase::HandleError took an early deviceDefinitelyStopped shortcut that set mState = Disconnected immediately, treating the device as fully stopped the instant loss was observed. Because the state jumped straight to Disconnected, Dawn skipped the required drain of outstanding GPU work and proceeded to tear down VkDevice-owned objects while the Vulkan device — or an in-progress lost-device WaitIdle — could still reference them. Per the Vulkan spec, all queue and device work must be idle before device objects are destroyed, so freeing them mid-flight is a use-after-free in the GPU process.

The fix removes the shortcut and instead compare_exchanges mState from Alive to BeingDisconnected (acq_rel), so validation blocks any new submissions while the GPU timeline drains. Only after the WaitIdle completes does the device advance to Disconnected and destroy CPU-side structures, guaranteeing no object is freed while still in use. A new VulkanSleepAfterLostDeviceWait toggle (default on for the affected vendor) inserts a driver sleep after WaitIdle to paper over a driver race where WaitIdle returns before the GPU has truly quiesced.

Key insight
Treating a lost device as instantly “stopped” conflates the app can no longer submit with the GPU is finished touching these objects — two events separated in time. The intermediate BeingDisconnected state is essential: it blocks new work while still waiting for the GPU timeline to actually drain before any Vulkan object is destroyed.

Attack Path

  1. Trigger device loss malicious page issues WebGPU work that provokes a DeviceLost on the vulnerable Vulkan vendor.
  2. Immediate Disconnected old HandleError shortcut sets mState = Disconnected, declaring the device stopped without draining GPU work.
  3. Premature teardown Dawn destroys VkDevice-owned objects while outstanding GPU work / a lost-device WaitIdle still references them.
  4. Dangling GPU reference the freed Vulkan objects are used-after-free in the GPU process as the driver races to quiesce.
  5. Corruption/control the attacker grooms the freed allocations to steer the UAF toward memory corruption in the GPU process.

Impact Assessment

A use-after-free in the Dawn/Vulkan device teardown path corrupts memory inside the GPU process, which is reachable from ordinary web content via WebGPU. Successful exploitation can yield memory corruption and potential code execution within the sandboxed GPU process, a strong primitive toward a full sandbox-escape chain. The condition is timing-sensitive and vendor-specific, but a device loss is web-triggerable, making the race reachable from untrusted pages.

Changed Functions

FunctionChangeNotes
if
src/dawn/native/Device.cpp
modified

Files Changed

  • src/dawn/native/Device.cpp
  • src/dawn/native/Device.h
  • src/dawn/native/Toggles.h
  • src/dawn/native/vulkan/PhysicalDeviceVk.cpp
  • src/dawn/native/Toggles.cpp
  • src/dawn/native/vulkan/QueueVk.cpp

Audit Directions

  • Instant-terminal state transitions on loss
    flag any error/loss handler that jumps a resource to a final Disconnected/Destroyed state before outstanding async or GPU work is confirmed drained; require an intermediate quiescing state.
  • Object destruction ordering vs. GPU idle
    audit all backends (Metal, D3D12) for teardown paths that destroy device-owned objects without a completed WaitIdle/fence wait, especially on the error/lost path rather than the normal path.
  • Driver-race workarounds
    review WaitIdle-return assumptions; where a toggle like VulkanSleepAfterLostDeviceWait is needed, verify the drain is actually complete rather than relying on the API return alone.
From 292e993b4a84d9a4affba538643ad376a8f36064 Mon Sep 17 00:00:00 2001
From: Albin Bernhardsson <[email protected]>
Date: Mon, 22 Jun 2026 09:55:52 -0700
Subject: [PATCH] Vulkan: Fix device loss handling

Always wait for the device work to finish and destroy its objects before
destroying the device, even when the device is lost. This is required in
Vulkan according to spec.

Bug: 500417361
Change-Id: I89cedcd15b660a1a4e9a62099fc22e56c1c37a37
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/314796
Reviewed-by: Corentin Wallez <[email protected]>
Reviewed-by: David Neto <[email protected]>
Commit-Queue: David Neto <[email protected]>
---
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index dc2cda7..6c73110 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -668,14 +668,8 @@
         AppendDeviceLostMessage(error.get());
     }
 
-    // If ErrorInjectorEnabled() then this may be an injected DeviceLost, not a real one.
-    bool deviceDefinitelyStopped = type == InternalErrorType::DeviceLost && !ErrorInjectorEnabled();
-    if (deviceDefinitelyStopped) {
-        // If the device was lost naturally, the backend device has already stopped.
-        mState = State::Disconnected;
-    }
-
     InternalErrorType allowedErrors = InternalErrorType::Validation | additionalAllowedErrors;
+
     if (!(allowedErrors & type)) {
         // If we receive an error which we did not explicitly allow, assume the backend can't
         // recover and lose the device now. Cleanup for the device will be deferred until the last
@@ -684,9 +678,14 @@
                              allowedErrors);
 
         // Handle the remainder of this error as if it caused a device lost.
+        type = InternalErrorType::DeviceLost;
+    }
+
+    if (type == InternalErrorType::DeviceLost) {
+        // Transition to a non-alive state if we are currently alive so that the application can no
+        // longer use the device.
         State prev = State::Alive;
         mState.compare_exchange_strong(prev, State::BeingDisconnected, std::memory_order::acq_rel);
-        type = InternalErrorType::DeviceLost;
     }
 
     // Re-enable validation on device loss or OOM to avoid unpredictable behaviors afterwards.
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 5d4a5cb..1689ea2 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -344,7 +344,7 @@
     //   - Alive: the device is usable and might have work happening on the GPU timeline.
     //   - BeingDisconnected: the device is no longer usable because we are waiting for all
     //     work on the GPU timeline to finish. (this is to make validation prevent the
-    //     application from adding more work during the transition from Available to
+    //     application from adding more work during the transition from Alive to
     //     Disconnected)
     //   - Disconnected: there is no longer work happening on the GPU timeline and the CPU data
     //     structures can be safely destroyed without additional synchronization.
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp
index 5ade3d6..5db3805 100644
--- a/src/dawn/native/Toggles.cpp
+++ b/src/dawn/native/Toggles.cpp
@@ -804,6 +804,10 @@
      {"metal_fix_u32_div_mod",
       "Workaround a driver bug on Apple Silicon with u32 div and mod operations.",
       "https://crbug.com/517225032", ToggleStage::Device}},
+    {Toggle::VulkanSleepAfterLostDeviceWait,
+     {"vulkan_sleep_after_lost_device_wait",
+      "Insert an arbitrary sleep after WaitIdle functions on device loss.",
+      "https://crbug.com/500417361", ToggleStage::Device}},
     {Toggle::WaitIsThreadSafe,
      {"wait_is_thread_safe",
       "WaitFor* functions are thread-safe and can be called without the device-lock if implicit "
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h
index 2d1020e..9e71492 100644
--- a/src/dawn/native/Toggles.h
+++ b/src/dawn/native/Toggles.h
@@ -191,6 +191,7 @@
     VulkanForceStaticSamplersForExternalTextures,
     D3D12UseHLSL2021,
     MetalFixU32DivMod,
+    VulkanSleepAfterLostDeviceWait,
 
     // Once all backends have been updated to be thread safe for waiting, we can remove this toggle.
     WaitIsThreadSafe,
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index e38794b..aab9be9 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -1083,6 +1083,10 @@
         // instructions as matrix elements instead of a source/dest pointee elements.
         // See crbug.com/460209126
         deviceToggles->Default(Toggle::VulkanCooperativeMatrixStrideIsMatrixElements, true);
+
+        // dawn:500417361
+        // TODO: Add details once available.
+        deviceToggles->Default(Toggle::VulkanSleepAfterLostDeviceWait, true);
     }
 
     if (IsAndroidSamsung() || IsAndroidQualcomm() || IsAndroidHuawei()) {
diff --git a/src/dawn/native/vulkan/QueueVk.cpp b/src/dawn/native/vulkan/QueueVk.cpp
index 36c7433..642bf58 100644
--- a/src/dawn/native/vulkan/QueueVk.cpp
+++ b/src/dawn/native/vulkan/QueueVk.cpp
@@ -49,6 +49,7 @@
 #include "src/dawn/native/vulkan/VulkanError.h"
 #include "src/dawn/platform/metrics/HistogramMacros.h"
 #include "src/dawn/platform/tracing/TraceEvent.h"
+#include "src/dawn/utils/SystemUtils.h"
 #include "src/utils/compiler.h"
 
 namespace dawn::native::vulkan {
@@ -177,8 +178,12 @@
     // Ignore the result of QueueWaitIdle: it can return OOM which we can't really do anything
     // about, Device lost, which means workloads running on the GPU are no longer accessible
     // (so they are as good as waited on) or success.
-    [[maybe_unused]] VkResult waitIdleResult =
-        VkResult::WrapUnsafe(device->fn.QueueWaitIdle(mQueue));
+    VkResult waitIdleResult = VkResult::WrapUnsafe(device->fn.QueueWaitIdle(mQueue));
+
+    if (waitIdleResult == VK_ERROR_DEVICE_LOST &&
+        GetDevice()->IsToggleEnabled(Toggle::VulkanSleepAfterLostDeviceWait)) {
+        dawn::utils::USleep(1000);
+    }
 
     DAWN_TRY(WaitForQueueSerial(GetLastSubmittedCommandSerial(),
                                 std::numeric_limits<Nanoseconds>::max()));
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.