Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in GPU
DescriptionInappropriate implementation in GPU
ComponentGPU
Bug ClassLogic Error
Tracker499873765
Fix commit1cd84c139db0 (chromium/src) +5/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
From 1cd84c139db0d8b7d32dd631f5c431d72fb9f2b0 Mon Sep 17 00:00:00 2001
From: Vasiliy Telezhnikov <vasilyt@chromium.org>
Date: Thu, 23 Apr 2026 14:12:31 -0700
Subject: [PATCH] Remove unnecessary type check in DoUniform1iv

SetSamplers does have better check inside [1] and this is aligned with
what DoUniform1i does.

[1] https://source.chromium.org/chromium/chromium/src/+/main:gpu/command_buffer/service/program_manager.cc;drc=c0f23dc884b194c040601b8e4248919106bcbf20;l=1427

Bug: 499873765
Change-Id: Ifa938bf63f0a1b507e71d153ca3bca4506c9e801
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7789562
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1619760}
---

diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index f9c0990c..f43b3cd8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -8516,14 +8516,11 @@
   auto values_copy = std::make_unique<GLint[]>(count);
   GLint* safe_values = values_copy.get();
   std::copy(values, UNSAFE_TODO(values + count), safe_values);
-  if (type == GL_SAMPLER_2D || type == GL_SAMPLER_2D_RECT_ANGLE ||
-      type == GL_SAMPLER_CUBE || type == GL_SAMPLER_EXTERNAL_OES) {
-    if (!state_.current_program->SetSamplers(
-            state_.texture_units.size(), fake_location, count, safe_values)) {
-      LOCAL_SET_GL_ERROR(
-          GL_INVALID_VALUE, "glUniform1iv", "texture unit out of range");
-      return;
-    }
+  if (!state_.current_program->SetSamplers(state_.texture_units.size(),
+                                           fake_location, count, safe_values)) {
+    LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glUniform1iv",
+                       "texture unit out of range");
+    return;
   }
   api()->glUniform1ivFn(real_location, count, safe_values);
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential uninitialized GPU memory leak via ES3 sampler desync in validating command decoder

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 logic error in GLES2DecoderImpl::DoUniform1iv skips updating the internal state tracker for ES3-specific sampler types. This causes Chrome to lose track of which texture units are active, allowing an attacker to bypass pre-draw security checks like ClearUnclearedTextures(). This can lead to a potential leak of uninitialized GPU memory, disclosing sensitive cross-origin data.

Affected files:

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/program_manager.h
  • gpu/command_buffer/service/program_manager.cc

Estimated timestamp from git blame: 2025-05-26

Description

A state desynchronization vulnerability exists in the GLES2 validating command decoder (GLES2DecoderImpl), which is currently the default on Android.

When setting sampler uniforms via glUniform1iv, Chrome needs to track which texture unit the sampler points to in order to perform security validations before a draw call. However, in gpu/command_buffer/service/gles2_cmd_decoder.cc, the DoUniform1iv function explicitly restricts the state update (Program::SetSamplers()) to legacy ES2 sampler types:

  if (type == GL_SAMPLER_2D || type == GL_SAMPLER_2D_RECT_ANGLE ||
      type == GL_SAMPLER_CUBE || type == GL_SAMPLER_EXTERNAL_OES) {
    if (!state_.current_program->SetSamplers(
            state_.texture_units.size(), fake_location, count, safe_values)) {
      // ... error handling ...
      return;
    }
  }
  api()->glUniform1ivFn(real_location, count, safe_values);

If an ES3 sampler type is used (e.g., GL_SAMPLER_2D_ARRAY, GL_SAMPLER_3D), the if condition evaluates to false, and SetSamplers() is skipped. As a result, Chrome’s internal tracker (uniform_info->texture_units) retains its default value of 0. However, the uniform is still forwarded to the underlying GPU driver via api()->glUniform1ivFn.

This creates a critical desynchronization: the GPU driver correctly uses texture unit N for the sampler, while Chrome’s validation logic incorrectly believes the program is sampling from unit 0.

Security Impact

This desynchronization allows an attacker to bypass critical security-enforcement routines:

  1. Bypass of ClearUnclearedTextures() (Information Leak): Chrome lazily clears uninitialized textures before they are sampled to prevent leaking sensitive cross-origin GPU memory. Because the validator checks the state of the wrong texture unit (unit 0), an attacker can easily bypass this by placing a cleared decoy texture on unit 0, while placing an uncleared texture on unit N.
  2. Missing Bounds Checks: Because SetSamplers() is skipped, the check ensuring the uniform value does not exceed state_.texture_units.size() is bypassed, allowing out-of-bounds uniform indices to be sent directly to the underlying driver.

Suggested Exploitation Steps

These are potential steps an attacker could follow from malicious WebGL2 JavaScript (tooling limitations prevent runtime verification):

  1. Create a texture T (e.g., TEXTURE_2D_ARRAY), allocate its storage, but leave it uninitialized (e.g., pass null to texImage3D).
  2. Create a decoy texture D and fully initialize it with safe zeros.
  3. Bind the decoy texture D to TEXTURE0.
  4. Bind the uninitialized texture T to TEXTURE1.
  5. Compile a shader program with an ES3 sampler: uniform sampler2DArray u_sampler;.
  6. Trigger the bug by using gl.uniform1iv to set u_sampler to point to unit 1. Chrome records the sampler as pointing to unit 0, but the driver points it to unit 1.
  7. Issue a draw call. ClearUnclearedTextures inspects unit 0, sees the safe decoy D, and allows the draw. The GPU executes the shader, sampling from the uninitialized texture T on unit 1.
  8. Read back the rendered pixels to exfiltrate uninitialized GPU memory.

Suggested Fix

Instead of a hardcoded list of ES2 sampler types, DoUniform1iv should rely on a generalized check to determine if the uniform is a sampler. The UniformInfo struct already possesses an IsSampler() method that covers all ES2 and ES3 sampler types. Alternatively, an explicit check matching the one in IsSampler() can be used to ensure SetSamplers() is correctly called for all samplers.

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