CVE-2026-12030
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/shared_image/gl_texture_holder.cc |
modified |
Files Changed
gpu/command_buffer/service/shared_image/gl_texture_holder.cc
Patch
From 7d1c92a1029e0fb7ade776374de1275ccf22e6c5 Mon Sep 17 00:00:00 2001 From: Ken Russell <[email protected]> Date: Thu, 04 Jun 2026 08:29:54 -0700 Subject: [PATCH] Clear and check GL errors around GLTextureHolder allocations. Keep state in sync between the texture object and its metadata. A unit test isn't feasible. Fixed: 518007423 Change-Id: I774d803f3c2f11d75e853b117ddf3b104386907d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7900975 Reviewed-by: Vasiliy Telezhnikov <[email protected]> Auto-Submit: Kenneth Russell <[email protected]> Commit-Queue: Vasiliy Telezhnikov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1641661} --- diff --git a/gpu/command_buffer/service/shared_image/gl_texture_holder.cc b/gpu/command_buffer/service/shared_image/gl_texture_holder.cc index a5dd82c7..a601dc1 100644 --- a/gpu/command_buffer/service/shared_image/gl_texture_holder.cc +++ b/gpu/command_buffer/service/shared_image/gl_texture_holder.cc @@ -160,22 +160,19 @@ if (is_passthrough_) { passthrough_texture_->SetEstimatedSize(format_.EstimatedSizeInBytes(size_)); - } else { - // TODO(piman): We pretend the texture was created in an ES2 context, so - // that it can be used in other ES2 contexts, and so we have to pass - // gl_format as the internal format in the LevelInfo. - // https://crbug.com/628064 - texture_->SetLevelInfo(format_desc_.target, 0, format_desc_.data_format, - size_.width(), size_.height(), /*depth=*/1, 0, - format_desc_.data_format, format_desc_.data_type, - /*cleared_rect=*/gfx::Rect()); - texture_->SetImmutable(true, format_info.supports_storage); } gl::GLApi* api = gl::g_current_gl_context; gl::ScopedRestoreTexture scoped_restore(api, format_desc_.target, GetServiceId()); + // Drain any pre-existing GL errors so the post-allocation check + // below is attributable to the storage call. Silently squelching + // these errors is unfortunate, but is done in order to mirror other + // allocation checks done in the command decoder. + while (api->glGetErrorFn() != GL_NO_ERROR) { + } + // Initialize the texture storage/image parameters and upload initial pixels // if available. if (format_info.supports_storage) { @@ -224,6 +221,25 @@ } if (!is_passthrough_) { + // Only commit decoder-side LevelInfo / immutable state once the native + // allocation has succeeded. If the driver rejected the allocation (e.g. + // GL_OUT_OF_MEMORY), leaving LevelInfo at {0,0,0} ensures + // Texture::ValidForTexture rejects subsequent TexSubImage calls instead of + // forwarding oversized writes to a zero-storage native texture. This + // mirrors the fix in GLES2DecoderImpl::TexStorageImpl. + if (api->glGetErrorFn() == GL_NO_ERROR) { + // TODO(piman): We pretend the texture was created in an ES2 context, so + // that it can be used in other ES2 contexts, and so we have to pass + // gl_format as the internal format in the LevelInfo. + // https://crbug.com/628064 + texture_->SetLevelInfo(format_desc_.target, 0, format_desc_.data_format, + size_.width(), size_.height(), /*depth=*/1, 0, + format_desc_.data_format, format_desc_.data_type, + /*cleared_rect=*/gfx::Rect()); + texture_->SetImmutable(true, format_info.supports_storage); + } else { + LOG(ERROR) << "GLTextureHolder: native storage allocation failed"; + } // Must be set after initial pixel upload. texture_->SetCompatibilitySwizzle(format_info.swizzle); }
Original Bug Report
Potential validating decoder state desync in GLTextureHolder::Initialize allows OOB GPU write
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: GLTextureHolder::Initialize updates the validating command decoder’s texture state (SetLevelInfo/SetImmutable) before invoking native GLES allocation APIs. If the subsequent driver allocation fails, the decoder-side metadata remains permanently desynchronized with the driver state. This desynchronization can potentially allow a compromised renderer to bypass bounds checks and trigger an out-of-bounds GPU write.
Affected files:
gpu/command_buffer/service/shared_image/gl_texture_holder.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Root Cause Analysis
In GLTextureHolder::Initialize() (located in gpu/command_buffer/service/shared_image/gl_texture_holder.cc around line 139), the validating decoder’s metadata state is committed via SetLevelInfo() and SetImmutable() before performing the native driver-level allocation:
if (is_passthrough_) {
passthrough_texture_->SetEstimatedSize(format_.EstimatedSizeInBytes(size_));
} else {
// (1) Validate decoder metadata updated first:
texture_->SetLevelInfo(format_desc_.target, 0, format_desc_.data_format,
size_.width(), size_.height(), /*depth=*/1, 0,
format_desc_.data_format, format_desc_.data_type,
/*cleared_rect=*/gfx::Rect());
texture_->SetImmutable(true, format_info.supports_storage);
}
...
if (format_info.supports_storage) {
...
// (2) Native allocation performed without post-allocation error checking:
api->glTexStorage2DEXTFn(format_desc_.target, /*levels=*/1,
format_info.adjusted_storage_internal_format,
size_.width(), size_.height());
If the underlying driver’s allocation fails—such as generating a GL_OUT_OF_MEMORY error due to renderer-induced GPU memory pressure or a GL_INVALID_VALUE error due to driver-specific rejection—no physical storage is allocated. However, since the function does not query the native GL error state nor implement a rollback mechanism, the gles2::Texture instance permanently registers that a valid level-0 buffer has been allocated and marks it as immutable.
Potential Attack Path / Exploitation Scenario
Note: The following steps represent potential/suggested exploitation mechanics, as our testing tools do not currently have the capability to execute code or run a live proof of concept.
- Memory Preparation: A compromised sandboxed renderer exhausts or fragments GPU memory to prepare for a native allocation failure.
- SharedImage Allocation: The renderer requests a new GLES2-compatible SharedImage (e.g., via
CreateSharedImage) with a large width and height nearGL_MAX_TEXTURE_SIZE. - Desynchronization Trigger: In the GPU process,
GLTextureHolder::Initialize()executes. It successfully writes the target dimensions into the validating decoder’sLevelInfoand sets the immutable flag totrue. However, the nativeglTexStorage2DEXTcall fails due to memory constraints, leaving the physical texture unallocated. - Importing the Texture: The renderer imports the desynchronized SharedImage into its GLES2 command buffer as a client-side texture ID via
CreateAndTexStorage2DSharedImageINTERNAL. - Validation Bypass: The renderer issues a
TexSubImage2Dcommand with a malicious payload targeting the imported texture ID. - OOB Write: Inside
TextureManager::ValidateAndDoTexSubImage(), the bounds checkValidForTexture()evaluates totruebecause it reads the stale metadata dimensions. SinceIsImmutable()istrue, the validating decoder skips reallocation logic and passes the rawglTexSubImage2Dcommand to the native GL driver. The driver attempts to write data to unallocated native memory, resulting in an out-of-bounds heap write in the unsandboxed GPU process (on Android).
Suggested Remediation
Modify GLTextureHolder::Initialize to perform the driver allocation before updating the validation state metadata, or query the GL error state post-allocation to verify success.
If the driver allocation fails (e.g., glGetError() returns an error), do not call SetLevelInfo or SetImmutable. Instead, roll back any changes, log the failure, and return an initialization error to prevent subsequent commands from operating on the unallocated texture.
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.