CVE-2026-9922
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forgpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc |
modified | |
ifgpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc |
modified |
Files Changed
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.ccgpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
Patch
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);
Original Bug Report
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.ccgpu/command_buffer/service/shared_image/iosurface_image_backing.mmgpu/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:
- 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.
- Import SharedImage: Create an
IOSurface-backed SharedImage and import it into Decoder A. This causesIOSurfaceBackingEGLStateto capture a strong reference to Decoder A’sgl::GLContext. - Share EGL State: Import the same SharedImage into Decoder B. Decoder B now holds a reference to the same
IOSurfaceBackingEGLState. - Simulate Context Loss: Delete the texture in Decoder A and call
WEBGL_lose_context.loseContext()on Decoder A. This marks Decoder A as lost. - Destroy Decoder A: Destroy Decoder A. The cleanup skips unregistering the ANGLE debug callback because
have_contextis false. The callbackuser_paramis now a dangling pointer to the freed Decoder A. - Heap Spray: Spray the GPU process heap to place a fake vtable at Decoder A’s old address.
- Induce Real Context Loss: Submit an expensive shader to induce a legitimate GPU hang, causing ANGLE to internally mark devices as lost.
- Trigger UAF: Delete the texture in Decoder B. Decoder B drops its reference, destroying
IOSurfaceBackingEGLState. Its destructor attempts to executeglDeleteTexturesusing Decoder A’s resurrected context. ANGLE generates aGL_CONTEXT_LOSTerror, 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.