Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds write in GPU
DescriptionOut of bounds write in GPU
ComponentGPU
Bug ClassOOB
Tracker516794471
Fix commita78b216bb66b (chromium/src) +37/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/gles2_cmd_decoder.cc
modified

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
  • gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
From a78b216bb66b83b824a3a0a3d4c264d023e897f5 Mon Sep 17 00:00:00 2001
From: Ken Russell <[email protected]>
Date: Wed, 27 May 2026 19:00:04 -0700
Subject: [PATCH] Check GL errors in TexStorage2D impl before updating metadata.

Ensure the tracked data for the texture stays correct in case
allocation in the driver fails.

Co-authored with jetski-cli.

Fixed: 516794471
Change-Id: I58ac23f7cde78a4f79c5063950426c54b7909743
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7880444
Reviewed-by: Kai Ninomiya <[email protected]>
Commit-Queue: Kenneth Russell <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1637410}
---

diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 1c5abf4d..f356e218 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -16075,6 +16075,27 @@
     compatibility_internal_format = format_info->decompressed_internal_format;
   }
 
+  // TODO(zmo): We might need to emulate TexStorage using TexImage or
+  // CompressedTexImage on Mac OSX where we expose ES3 APIs when the underlying
+  // driver is lower than 4.2 and ARB_texture_storage extension doesn't exist.
+  LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(function_name);
+  if (dimension == ContextState::k2D) {
+    api()->glTexStorage2DEXTFn(target, levels, compatibility_internal_format,
+                               width, height);
+  } else {
+    api()->glTexStorage3DFn(target, levels, compatibility_internal_format,
+                            width, height, depth);
+  }
+  GLenum error = LOCAL_PEEK_GL_ERROR(function_name);
+  if (error != GL_NO_ERROR) {
+    // The driver rejected the allocation. Do NOT update the decoder-side
+    // LevelInfo / immutable flag, otherwise subsequent TexSubImage bounds
+    // checks (Texture::ValidForTexture) would validate against dimensions
+    // that the driver never allocated, allowing oversized writes to be
+    // forwarded to the native driver.
+    return;
+  }
+
   {
     GLsizei level_width = width;
     GLsizei level_height = height;
@@ -16103,17 +16124,6 @@
     texture->ApplyFormatWorkarounds(feature_info_.get());
     texture->SetImmutable(true, true);
   }
-
-  // TODO(zmo): We might need to emulate TexStorage using TexImage or
-  // CompressedTexImage on Mac OSX where we expose ES3 APIs when the underlying
-  // driver is lower than 4.2 and ARB_texture_storage extension doesn't exist.
-  if (dimension == ContextState::k2D) {
-    api()->glTexStorage2DEXTFn(target, levels, compatibility_internal_format,
-                               width, height);
-  } else {
-    api()->glTexStorage3DFn(target, levels, compatibility_internal_format,
-                            width, height, depth);
-  }
 }
 
 void GLES2DecoderImpl::DoTexStorage2DEXT(GLenum target,
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index abab007..60a1d33 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -1114,6 +1114,10 @@
   EXPECT_CALL(*gl_, TexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 4))
       .Times(1)
       .RetiresOnSaturation();
+  EXPECT_CALL(*gl_, GetError())
+      .WillOnce(Return(GL_NO_ERROR))
+      .WillOnce(Return(GL_NO_ERROR))
+      .RetiresOnSaturation();
   cmds::TexStorage2DEXT cmd;
   cmd.Init(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 4);
   EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
index 3ab35d3..826c27a8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
@@ -3609,6 +3609,10 @@
         *gl_, TexStorage2DEXT(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight))
         .Times(1)
         .RetiresOnSaturation();
+    EXPECT_CALL(*gl_, GetError())
+        .WillOnce(Return(GL_NO_ERROR))
+        .WillOnce(Return(GL_NO_ERROR))
+        .RetiresOnSaturation();
     cmds::TexStorage2DEXT cmd;
     cmd.Init(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight);
     EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
@@ -3671,6 +3675,10 @@
   EXPECT_CALL(*gl_, TexStorage3D(GL_TEXTURE_3D, 2, GL_RGB565, 4, 5, 6))
       .Times(1)
       .RetiresOnSaturation();
+  EXPECT_CALL(*gl_, GetError())
+      .WillOnce(Return(GL_NO_ERROR))
+      .WillOnce(Return(GL_NO_ERROR))
+      .RetiresOnSaturation();
   cmds::TexStorage3D cmd;
   cmd.Init(GL_TEXTURE_3D, 2, GL_RGB565, 4, 5, 6);
   EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
@@ -4091,6 +4099,10 @@
                                    kHeight, kDepth))
         .Times(1)
         .RetiresOnSaturation();
+    EXPECT_CALL(*gl_, GetError())
+        .WillOnce(Return(GL_NO_ERROR))
+        .WillOnce(Return(GL_NO_ERROR))
+        .RetiresOnSaturation();
     cmds::TexStorage3D cmd;
     cmd.Init(kTarget, kLevels, kInternalFormat, kWidth, kHeight, kDepth);
     EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index abab007..60a1d33 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -1114,6 +1114,10 @@
   EXPECT_CALL(*gl_, TexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 4))
       .Times(1)
       .RetiresOnSaturation();
+  EXPECT_CALL(*gl_, GetError())
+      .WillOnce(Return(GL_NO_ERROR))
+      .WillOnce(Return(GL_NO_ERROR))
+      .RetiresOnSaturation();
   cmds::TexStorage2DEXT cmd;
   cmd.Init(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 4);
   EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
index 3ab35d3..826c27a8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
@@ -3609,6 +3609,10 @@
         *gl_, TexStorage2DEXT(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight))
         .Times(1)
         .RetiresOnSaturation();
+    EXPECT_CALL(*gl_, GetError())
+        .WillOnce(Return(GL_NO_ERROR))
+        .WillOnce(Return(GL_NO_ERROR))
+        .RetiresOnSaturation();
     cmds::TexStorage2DEXT cmd;
     cmd.Init(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight);
     EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
@@ -3671,6 +3675,10 @@
   EXPECT_CALL(*gl_, TexStorage3D(GL_TEXTURE_3D, 2, GL_RGB565, 4, 5, 6))
       .Times(1)
       .RetiresOnSaturation();
+  EXPECT_CALL(*gl_, GetError())
+      .WillOnce(Return(GL_NO_ERROR))
+      .WillOnce(Return(GL_NO_ERROR))
+      .RetiresOnSaturation();
   cmds::TexStorage3D cmd;
   cmd.Init(GL_TEXTURE_3D, 2, GL_RGB565, 4, 5, 6);
   EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
@@ -4091,6 +4099,10 @@
                                    kHeight, kDepth))
         .Times(1)
         .RetiresOnSaturation();
+    EXPECT_CALL(*gl_, GetError())
+        .WillOnce(Return(GL_NO_ERROR))
+        .WillOnce(Return(GL_NO_ERROR))
+        .RetiresOnSaturation();
     cmds::TexStorage3D cmd;
     cmd.Init(kTarget, kLevels, kInternalFormat, kWidth, kHeight, kDepth);
     EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
Loading diff…

Original Bug Report

reported by [email protected]

Potential state desynchronization in GLES2DecoderImpl::TexStorageImpl leads to heap OOB 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: A potential state desynchronization vulnerability in the validating command decoder’s texture storage implementation can allow a texture’s metadata to mismatch the actual driver state. If a native storage allocation call fails, the decoder continues to track the texture with oversized dimensions and immutable status. This allows subsequent sub-image writes to bypass validation and trigger a heap out-of-bounds write in the GPU process.

Affected files:

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/texture_manager.cc

Estimated timestamp from git blame: 2016-02-16

Description

A potential state desynchronization vulnerability exists in the GLES2 validating command decoder’s texture storage implementation.

Unlike other allocation paths in the validating decoder (e.g., DoTexImage via ValidateAndDoTexImage) which verify that the underlying allocation succeeded using ERRORSTATE_PEEK_GL_ERROR before updating the metadata, GLES2DecoderImpl::TexStorageImpl in gpu/command_buffer/service/gles2_cmd_decoder.cc updates the decoder’s internal texture level metadata and sets the texture as immutable before invoking the native GL driver, without any trailing error checking or rollback mechanism.

    for (int ii = 0; ii < levels; ++ii) {
      ...
      } else {
        texture_manager()->SetLevelInfo(
            texture_ref, target, ii, adjusted_internal_format, level_width,
            level_height, level_depth, 0, format, type, gfx::Rect());
      }
      ...
    }
    texture->ApplyFormatWorkarounds(feature_info_.get());
    texture->SetImmutable(true, true); // Overwrites metadata before the GL call
  }

  if (dimension == ContextState::k2D) {
    api()->glTexStorage2DEXTFn(target, levels, compatibility_internal_format,
                               width, height);
  } else { ... }
  // No trailing GL error check or rollback

If the native driver allocation fails (for example, generating GL_OUT_OF_MEMORY or GL_INVALID_OPERATION), the actual driver-side texture dimensions remain unchanged (or unallocated). However, the validating decoder’s internal metadata permanently records the new oversized dimensions and flags the texture as immutable.

Potential Impact & Exploitation Vector

This desynchronization allows subsequent TexSubImage2D operations to potentially bypass bounds checks:

  1. A texture is created and allocated with safe, small dimensions (e.g., 64x64).
  2. TexStorage2DEXT is invoked with oversized dimensions (e.g., 16384x16384) designed to cause native allocation failure.
  3. The validating decoder updates its tracking metadata (SetLevelInfo and SetImmutable(true, true)) to the new dimensions and state, but the subsequent native glTexStorage2DEXTFn call fails.
  4. The native driver-side allocation remains 64x64, while the decoder tracks the texture as 16384x16384 and immutable.
  5. When TexSubImage2D is called with larger dimensions, ValidateTexSubImage checks bounds against the decoder’s recorded dimensions (16384x16384) and succeeds.
  6. Because texture->IsImmutable() is true, the redefinition paths are skipped, forcing execution into the raw glTexSubImage2D forwarding branch.
  7. The native GLES driver receives a sub-image update of 16384x16384 against the actual 64x64 allocation, potentially resulting in a heap out-of-bounds write in the GPU process.

Since the validating command decoder is used on specific blocklisted Android models, and since the GPU process is unsandboxed on Android, this could potentially lead to GPU process compromise/RCE.

Note: These are potential steps and analysis based on source code auditing; our tooling agent does not have the ability to run or verify functional exploit code on active hardware.

Suggested Fix

Defer updating the decoder’s texture metadata and immutability flags until after verifying that the native storage call succeeded with no GL errors, mimicking the robust behavior of DoTexImage. Alternatively, perform a trailing GL error check after the native glTexStorage2DEXTFn / glTexStorage3DFn calls, and rollback the level info metadata and immutability flags if an error is detected.

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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