Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Tracker500187083
Fix commited956102b213 (chromium/src) +30/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

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
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