High CVSS 6.5 chrome Info Disclosure 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in GPU
DescriptionInappropriate implementation in GPU
ComponentGPU
Bug ClassInfo Disclosure
Tracker499873765
Fix commit1cd84c139db0 (chromium/src) +5/-8
CWECWE-200 (Information exposure)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-06 (per vendor advisory)
Fix landed2026-04-23 (public git)
Disclosed2026-05-27
Patch gap34 days — fix public in source before the advisory

Background

GLES2 validating command decoder
GLES2DecoderImpl (in gpu/command_buffer/service/gles2_cmd_decoder.cc) is Chrome’s validating GPU command decoder (default on Android); it mirrors GL state to enforce security checks before draws.
Sampler uniforms / texture units
A sampler uniform names which texture unit a shader samples from; Chrome must know the real unit to run pre-draw safety checks.
`Program::SetSamplers`
Records, in the program’s UniformInfo::texture_units, which units a sampler uniform points at, and range-checks the supplied values against state_.texture_units.size().
`ClearUnclearedTextures()`
Pre-draw routine that lazily zero-initializes textures on the units a program samples, preventing disclosure of uninitialized/cross-origin GPU memory.
`DoUniform1iv` / `DoUniform1i`
Handlers for glUniform1iv/glUniform1i; DoUniform1i already calls SetSamplers unconditionally, while DoUniform1iv gated the call on a sampler-type whitelist.

Root Cause Analysis

GLES2DecoderImpl::DoUniform1iv only invoked state_.current_program->SetSamplers(...) when the uniform’s type was in a hardcoded list of ES2 sampler types: GL_SAMPLER_2D, GL_SAMPLER_2D_RECT_ANGLE, GL_SAMPLER_CUBE, GL_SAMPLER_EXTERNAL_OES. For ES3 sampler types (GL_SAMPLER_2D_ARRAY, GL_SAMPLER_3D, etc.) the condition was false, so SetSamplers was skipped entirely — yet the uniform was still forwarded to the driver via api()->glUniform1ivFn(real_location, count, safe_values). The effect is a state desynchronization: the driver binds the sampler to the real unit N, while Chrome’s UniformInfo::texture_units retains its default 0. Pre-draw validation (ClearUnclearedTextures, and the range check inside SetSamplers) therefore inspects the wrong texture unit, so an uncleared texture on unit N is never cleared while a cleared decoy on unit 0 satisfies the check.

The patch deletes the type whitelist and calls SetSamplers unconditionally: 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; } before forwarding to the driver. As the commit message notes, SetSamplers already contains a better internal check (and UniformInfo distinguishes samplers from non-samplers itself), and this aligns DoUniform1iv with DoUniform1i. Now every sampler uniform — ES2 or ES3 — updates Chrome’s tracker and is range-checked, so validation observes the true unit. The advisory labels this a logic error (CWE-200 info exposure); the diff confirms the logic error (incomplete sampler-type list), and the substantiated security consequence is disclosure of uninitialized GPU memory, hence the InfoDisclosure classification here.

Key insight
DoUniform1iv whitelisted only ES2 sampler types before updating Chrome’s sampler-to-unit tracker, so ES3 samplers were forwarded to the driver without recording their unit — desynchronizing validation state and letting pre-draw clearing/range checks inspect the wrong texture unit. The fix tracks all samplers unconditionally.

Attack Path

  1. Allocate uncleared texture From WebGL2 JS, create T as TEXTURE_2D_ARRAY with storage but no initialization (pass null to texImage3D).
  2. Cleared decoy Create and fully zero-initialize decoy D.
  3. Bind units Bind D to TEXTURE0, uninitialized T to TEXTURE1.
  4. ES3 sampler program Use a program with uniform sampler2DArray u_sampler;.
  5. Trigger desync gl.uniform1iv(loc, [1]) to point the sampler at unit 1; pre-patch, SetSamplers is skipped (ES3 type), so Chrome records unit 0 while the driver uses unit 1.
  6. Draw & exfiltrate Issue a draw; ClearUnclearedTextures inspects unit 0 (decoy, already clear) and permits it, so T on unit 1 is sampled uninitialized; read back the output to recover uninitialized GPU memory (potential cross-origin disclosure). A secondary consequence is bypass of the out-of-range unit check (inferred).

Impact Assessment

Runs in the GPU process, reachable from an unprivileged renderer via WebGL2 (decoder default on Android). The primitive is disclosure of uninitialized GPU memory — potentially cross-origin data — by evading lazy texture clearing, matching the advisory’s C:H/I:N/A:N. The diff also restores the per-call range check on the uniform’s unit value, so the “missing bounds check” vector the advisory mentions is closed too; however no direct memory-corruption primitive is shown by the diff, so any escalation beyond info disclosure is (inferred). Requires only standard WebGL2 capabilities; no elevated privilege needed.

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder.cc

Audit Directions

  • Hardcoded sampler-type lists
    Grep the decoder for other GL_SAMPLER_2D || ... enumerations that omit ES3 sampler types (GL_SAMPLER_2D_ARRAY, GL_SAMPLER_3D, shadow samplers); prefer UniformInfo::IsSampler().
  • State-tracking vs. driver-forwarding divergence
    Review other DoUniform*/state-setting handlers for paths that forward to api()->gl...Fn without updating the mirrored validation state.
  • Lazy-clear reliance on tracked units
    Audit ClearUnclearedTextures and related pre-draw checks for any assumption that UniformInfo::texture_units is always current.
From 1cd84c139db0d8b7d32dd631f5c431d72fb9f2b0 Mon Sep 17 00:00:00 2001
From: Vasiliy Telezhnikov <[email protected]>
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 <[email protected]>
Commit-Queue: Vasiliy Telezhnikov <[email protected]>
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 [email protected]

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