Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow GPU
DescriptionHeap buffer overflow GPU
ComponentChromium
Bug ClassOOB
Tracker518007423
Fix commit7d1c92a1029e (chromium/src) +26/-10
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-11

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/shared_image/gl_texture_holder.cc
modified

Files Changed

  • gpu/command_buffer/service/shared_image/gl_texture_holder.cc
From 7d1c92a1029e0fb7ade776374de1275ccf22e6c5 Mon Sep 17 00:00:00 2001
From: Ken Russell <kbr@chromium.org>
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 <vasilyt@chromium.org>
Auto-Submit: Kenneth Russell <kbr@chromium.org>
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
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);
   }
Loading diff…

Original Bug Report

reported by vm...@google.com

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.

  1. Memory Preparation: A compromised sandboxed renderer exhausts or fragments GPU memory to prepare for a native allocation failure.
  2. SharedImage Allocation: The renderer requests a new GLES2-compatible SharedImage (e.g., via CreateSharedImage) with a large width and height near GL_MAX_TEXTURE_SIZE.
  3. Desynchronization Trigger: In the GPU process, GLTextureHolder::Initialize() executes. It successfully writes the target dimensions into the validating decoder’s LevelInfo and sets the immutable flag to true. However, the native glTexStorage2DEXT call fails due to memory constraints, leaving the physical texture unallocated.
  4. Importing the Texture: The renderer imports the desynchronized SharedImage into its GLES2 command buffer as a client-side texture ID via CreateAndTexStorage2DSharedImageINTERNAL.
  5. Validation Bypass: The renderer issues a TexSubImage2D command with a malicious payload targeting the imported texture ID.
  6. OOB Write: Inside TextureManager::ValidateAndDoTexSubImage(), the bounds check ValidForTexture() evaluates to true because it reads the stale metadata dimensions. Since IsImmutable() is true, the validating decoder skips reallocation logic and passes the raw glTexSubImage2D command 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.

View on issue tracker