High CVSS 7.5 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Sub-componentGPU
Bug ClassUAF
Tracker500187083
Fix commited956102b213 (chromium/src) +30/-4
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-07 (per vendor advisory)
Fix landed2026-05-06 (public git)
Disclosed2026-05-27
Patch gap21 days — fix public in source before the advisory

Background

Passthrough command decoder
GLES2DecoderPassthroughImpl forwards GL commands to ANGLE’s C API. Resources shared across decoders on a thread live in a PassthroughResources object (texture maps, shared-image representations).
`KHR_debug` callback
A GL debug-message callback registered via glDebugMessageCallbackKHRFn(fn, user_param), where user_param is the decoder pointer. ANGLE invokes it synchronously when it emits messages such as GL_CONTEXT_LOST.
Context loss
WEBGL_lose_context (or a real GPU reset) marks a decoder’s context lost; on destroy, the decoder historically skipped GL teardown work when it believed it had no current context.

Root Cause Analysis

When a passthrough decoder lost its context, it tore itself down but left stale references into its GL/ANGLE state. The KHR_debug callback’s user_param still pointed at the decoder, and underlying resources (textures, shared-image representations such as IOSurfaceBackingEGLState) were not told the context was lost. Because a shared IOSurfaceBackingEGLState holds a strong ref to the decoder’s gl::GLContext, that context — and the stale debug callback registered on it — could outlive the freed decoder. A later glDeleteTextures on that resurrected context (driven by another decoder releasing the shared image) could make ANGLE emit a GL_CONTEXT_LOST debug message, synchronously invoking the dangling callback and performing a virtual call on freed decoder memory. The dangling user_param crosses ANGLE’s C FFI boundary as const void*, so MiraclePtr/BackupRefPtr does not protect it.

The patch addresses the lifetime/state-propagation root cause rather than only clearing the callback. It adds PassthroughResources::MarkContextLost(), which iterates the texture object map calling texture->MarkContextLost() and calls representation()->OnContextLost() on each shared-image representation. In the decoder’s context-loss path it now, when the context is still current (context_ && context_->IsCurrent(nullptr)), marks all bound textures context-lost, calls resources_->MarkContextLost(), and clears the debug callback via glDebugMessageCallbackKHRFn(nullptr, nullptr). The bug class is UAF (CWE-416), matching the advisory. Worth noting: the advisory’s suggested fix was to unconditionally clear the callback regardless of have_context; the actual fix instead propagates the lost-context state into the underlying resources (so a shared IOSurfaceBackingEGLState handles the deferred delete safely) and still clears the callback, but only while the context is current.

Key insight
A lost-context decoder was destroyed without telling its shared GL resources, so a shared backing that kept the decoder’s GL context alive could later run glDeleteTextures and fire the decoder’s still-registered debug callback on freed memory.

Attack Path

  1. Two decoders From a compromised renderer, create two WebGL contexts mapping to passthrough decoders A and B (inferred renderer control).
  2. Shared IOSurface backing Import an IOSurface-backed SharedImage into A (capturing a strong ref to A’s gl::GLContext in IOSurfaceBackingEGLState), then import the same image into B so both share that EGL state.
  3. Lose A's context Call WEBGL_lose_context.loseContext() on A; A is marked lost while the shared EGL state keeps its GL context alive.
  4. Destroy A A is torn down; pre-patch the debug callback is not cleared and resources aren’t marked lost, so user_param dangles to freed A.
  5. Groom Spray the GPU heap to place a fake vtable at A’s former address.
  6. Trigger delete on resurrected context Release the shared image from B so ~IOSurfaceBackingEGLState runs glDeleteTextures on A’s context; with a GPU reset in flight, ANGLE emits GL_CONTEXT_LOST, firing the stale callback and performing a virtual call (GetLogger()) through the fake vtable (primitive: controlled virtual call / RCE in GPU process).

Impact Assessment

Executes in the GPU process (macOS passthrough decoder), which is privileged. Preconditions are significant: a cooperating renderer, a shared IOSurface backing holding the lost context alive, correct destroy ordering, a heap spray, and an induced GPU reset to make the delete emit GL_CONTEXT_LOST — reflected in the advisory’s AC:H. The primitive is a use-after-free virtual call on attacker-groomed memory, which the advisory escalates to GPU-process code execution. The freed-memory dereference is the confirmed sink; the control-flow hijack/RCE beyond the vtable call is (inferred) and depends on successful grooming not shown by the diff. MiraclePtr does not mitigate because the dangling pointer is held as const void* across ANGLE’s C API.

Changed Functions

FunctionChangeNotes
for
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
modified
if
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
modified

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h

Audit Directions

  • Callbacks across FFI
    Audit every glDebugMessageCallbackKHR (and similar C-API callback) registration for a teardown path that can skip unregistration when a context is deemed lost, leaving a dangling user_param MiraclePtr cannot cover.
  • Shared state outliving its owner
    Review egl_state_map_/IOSurfaceBackingEGLState and other cross-decoder caches that hold strong refs to a decoder’s gl::GLContext, for owners destroyed while the shared state still schedules GL work on that context.
  • Eager vs. lazy context-loss propagation
    Confirm other resource types (buffers, programs, framebuffers, non-IOSurface shared images) also receive OnContextLost()/MarkContextLost() promptly, not just textures, and on both WebGL-induced and real GPU resets.
From ed956102b21355072d78b9b24dddf39da8690ee0 Mon Sep 17 00:00:00 2001
From: Saifuddin Hitawala <[email protected]>
Date: Wed, 06 May 2026 12:07:18 -0700
Subject: [PATCH] [gpu] MarkContextLost on PassthroughResource on Decoder ContextLost

When passthrough decoder's MarkContextLost is triggered, we should
trigger context lost on underlying resources as well so that they
can handle future states properly instead of only doing so on
destruction.

This change adds PassthroughResources::MarkContextLost which calls
context loss methods on its underlying textures

Bug: 500187083
Change-Id: If698f41f5dadebb945f608692cb4adc811667fee
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7822399
Reviewed-by: Vasiliy Telezhnikov <[email protected]>
Commit-Queue: Saifuddin Hitawala <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1626365}
---

diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index 1a57d3b..c37effa 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -480,6 +480,16 @@
   return success;
 }
 
+void PassthroughResources::MarkContextLost() {
+  texture_object_map.ForEach(
+      [](GLuint client_id, scoped_refptr<TexturePassthrough> texture) {
+        texture->MarkContextLost();
+      });
+  for (auto& pair : texture_shared_image_map) {
+    pair.second.representation()->OnContextLost();
+  }
+}
+
 void PassthroughResources::Destroy(gl::GLApi* api,
                                    gl::ProgressReporter* progress_reporter) {
   bool have_context = !!api;
@@ -1644,10 +1654,25 @@
     return;
   }
 
-  // SECURITY: crbug.com/500187083. Unconditionally clear the debug callback if
-  // current context IsCurrent before it gets lost to prevent UAF.
-  if (context_ && context_->IsCurrent(nullptr) && api()) {
-    api()->glDebugMessageCallbackKHRFn(nullptr, nullptr);
+  bool have_context = context_ && context_->IsCurrent(nullptr);
+  if (have_context) {
+    for (auto& bound_texture_type : bound_textures_) {
+      for (auto& bound_texture : bound_texture_type) {
+        if (bound_texture.texture) {
+          bound_texture.texture->MarkContextLost();
+        }
+      }
+    }
+
+    if (resources_) {
+      resources_->MarkContextLost();
+    }
+
+    // SECURITY: crbug.com/500187083. Unconditionally clear the debug callback
+    // if current context IsCurrent before it gets lost to prevent UAF.
+    if (api()) {
+      api()->glDebugMessageCallbackKHRFn(nullptr, nullptr);
+    }
   }
 
   // Don't make GL calls in here, the context might not be current.
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
index 49a8a0e..7a0af5af 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
@@ -66,6 +66,7 @@
   PassthroughResources();
   ~PassthroughResources();
 
+  void MarkContextLost();
   // api is null if we don't have a context (e.g. lost).
   void Destroy(gl::GLApi* api, gl::ProgressReporter* progress_reporter);
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF via stale KHR_debug callback in Passthrough Decoder due to EGLState context retention

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 security team.

Overview: A Use-After-Free vulnerability exists in the macOS GPU process where a WebGL context can be destroyed without unregistering its GL debug callback if the context is lost. A shared IOSurfaceBackingEGLState can keep the underlying GL context alive, allowing a subsequent glDeleteTextures call on the lost context to trigger the stale callback, leading to RCE.

Affected files:

  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
  • gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc

Estimated timestamp from git blame: 2026-02-17

Description

A potential Use-After-Free (UAF) vulnerability has been identified in the GLES2 passthrough decoder, specifically affecting macOS via IOSurfaceBackingEGLState. The issue stems from a combination of GL context retention across different decoders and a failure to clear the KHR_debug callback during decoder destruction when a context is lost.

When a passthrough decoder imports a macOS IOSurface-backed SharedImage, IOSurfaceImageBacking::RetainGLTexture creates an IOSurfaceBackingEGLState object that holds a strong scoped_refptr to the decoder’s gl::GLContext. This EGL state is cached in egl_state_map_ and shared across decoders on the same thread.

When a decoder is destroyed, GLES2DecoderPassthroughImpl::Destroy(bool have_context) skips unregistering the glDebugMessageCallbackKHR if have_context is false (e.g., if WEBGL_lose_context was previously called). This leaves a stale pointer to the freed decoder in ANGLE’s C API.

If another decoder keeps the IOSurfaceBackingEGLState (and thus the underlying GL context) alive, releasing the SharedImage later will cause ~IOSurfaceBackingEGLState to call glDeleteTextures via ui::ScopedMakeCurrent. If a GPU reset is induced before this release, ANGLE emits a GL_CONTEXT_LOST error debug message during the delete call, synchronously invoking the stale callback and leading to a virtual call (GetLogger()) on the freed decoder memory.

Potential Attack Steps

These are suggested steps based on static analysis:

  1. Setup Contexts: In a compromised renderer, create two WebGL contexts (Decoder A and Decoder B) that map to separate passthrough decoders in the GPU process.
  2. Import SharedImage: Create an IOSurface-backed SharedImage and import it into Decoder A. This causes IOSurfaceBackingEGLState to capture a strong reference to Decoder A’s gl::GLContext.
  3. Share EGL State: Import the same SharedImage into Decoder B. Decoder B now holds a reference to the same IOSurfaceBackingEGLState.
  4. Simulate Context Loss: Delete the texture in Decoder A and call WEBGL_lose_context.loseContext() on Decoder A. This marks Decoder A as lost.
  5. Destroy Decoder A: Destroy Decoder A. The cleanup skips unregistering the ANGLE debug callback because have_context is false. The callback user_param is now a dangling pointer to the freed Decoder A.
  6. Heap Spray: Spray the GPU process heap to place a fake vtable at Decoder A’s old address.
  7. Induce Real Context Loss: Submit an expensive shader to induce a legitimate GPU hang, causing ANGLE to internally mark devices as lost.
  8. Trigger UAF: Delete the texture in Decoder B. Decoder B drops its reference, destroying IOSurfaceBackingEGLState. Its destructor attempts to execute glDeleteTextures using Decoder A’s resurrected context. ANGLE generates a GL_CONTEXT_LOST error, firing the stale callback with the dangling pointer and executing the fake vtable.

MiraclePtr bypass

The vulnerability bypasses MiraclePtr/BackupRefPtr because the user_param is registered as a const void* within ANGLE’s C API structure, crossing an FFI boundary where C++ smart pointers cannot protect it.

Suggested Fix

Ensure that the GL debug callback is unconditionally cleared during the destruction of the passthrough decoder, regardless of whether the context is considered lost.

In gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc, modify GLES2DecoderPassthroughImpl::Destroy:

  // Unconditionally clear the debug callback to prevent UAF.
  if (api()) {
      api()->glDebugMessageCallbackKHRFn(nullptr, nullptr);
  }

Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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