Low watch CVSS 5.3 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in GPU
DescriptionUninitialized Use in GPU
ComponentGPU
Bug ClassUninitialized Memory
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker498382925
Fix commit09708b952e81 (chromium/src) +7/-3
CWECWE-457 (Use of uninitialized)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-01 (per vendor advisory)
Fix landed2026-04-01 (public git)
Disclosed2026-05-05
Patch gap34 days — fix public in source before the advisory

Background

Transform feedback varyings reflection
The GLES2 command decoder reports back to the client the names/sizes/types of a program’s transform-feedback output varyings, serialized into a shared buffer.
`TransformFeedbackVaryingsHeader` / varying info block
Fixed-size header and per-varying info records are written into the front of the buffer, while variable-length name strings are appended at the end and referenced by offset.
`name_offset` vs `name_length`
name_offset is the byte offset of a varying’s name string within the buffer; name_length is that string’s length, so a reader must have both to locate and bound the name.
Passthrough decoder
gles2_cmd_decoder_passthrough_doers.cc implements the “passthrough” command decoder that forwards GLES2 calls to the native driver and marshals results back into client-visible buffers.

Root Cause Analysis

In the reflection path (the enclosing function name is not shown in the @@ -4357 hunk header), the per-varying info record stored the buffer size into the wrong field: varying_info.name_length = data->size(); was assigned and then immediately overwritten by varying_info.name_length = length + 1;, so the first assignment was dead code and varying_info.name_offset was never written at all. Because the info record is copied into the buffer via InsertValueIntoBuffer with name_offset left at its default/uninitialized value, a client reading the reflection buffer would use a bogus offset to locate the name string.

The fix changes the first assignment to varying_info.name_offset = data->size();, recording the true offset at which AppendStringToBuffer places the string, so the info record now correctly points at its name. This restores the invariant that every varying info record carries both a valid name_offset and name_length before being committed to the buffer.

Key insight
The single mistake was a copy-paste/field-name error that assigned the buffer offset to name_length (then clobbered) instead of to name_offset, leaving name_offset uninitialized; the fix assigns it to name_offset.

Attack Path

  1. Trigger reflection A renderer/client issues the GLES2 command that drives transform-feedback varying reflection for a program with at least one varying, reaching the code that builds the info records.
  2. Uninitialized offset committed Because only name_length was set (twice) and name_offset was never assigned, the info record is serialized into the reflection buffer with an uninitialized name_offset.
  3. Client reads at bad offset The consumer of the reflection buffer uses the uninitialized name_offset to locate the name string, producing an uninitialized-memory use / out-of-range read within the buffer (inferred downstream consequence; the diff shows only the incorrect offset being produced).

Impact Assessment

An attacker driving the GPU command buffer obtains use of an uninitialized name_offset value when reflecting transform-feedback varyings, which the advisory classifies as “Uninitialized Use in GPU” at low severity. The flaw manifests in the GPU process reflection buffer; any read-primitive or information disclosure beyond the uninitialized field itself is (inferred) and not demonstrated by the diff. The advisory states no exploited-in-the-wild status.

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc

Audit Directions

  • Dead/duplicate assignments to struct fields
    Grep for a field assigned twice in a row (the first value overwritten), which often signals the intended target was a different, now-uninitialized field.
  • Offset/length pairs serialized into client buffers
    Audit reflection and marshaling code where variable-length data is appended and referenced by {offset, length}; verify both members are set before InsertValueIntoBuffer/copy-out.
  • Uninitialized POD records in GPU marshaling
    Look for info/record structs committed to shared or client-visible buffers without a full field initialization, especially in *_passthrough_doers.cc.
From 09708b952e816617db04d5f42b7ca88f233ebe10 Mon Sep 17 00:00:00 2001
From: Corentin Wallez <[email protected]>
Date: Wed, 01 Apr 2026 09:06:53 -0700
Subject: [PATCH] gles2_cmd_decoder_passthrough: Fix incorrect offset for TF varying reflection

Bug: 498382925
Change-Id: Id1b066667b4c6cf0a67d7d4eb6c680757818f3c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7719959
Auto-Submit: Corentin Wallez <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
Commit-Queue: Geoff Lang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1608576}
---

diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index 2c8508d..6d640db0 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -4319,8 +4319,9 @@
   api()->glGetProgramivFn(service_program, GL_TRANSFORM_FEEDBACK_VARYINGS,
                           &num_transform_feedback_varyings);
 
-  // Resize the data to fit the headers and info objects so that strings can be
-  // appended.
+  // Resize the data to fit the headers and info objects. Strings will be
+  // appended at the end of the buffer and info objects will point to the
+  // strings using their offset in the buffer.
   const base::CheckedNumeric<size_t> buffer_header_size(
       sizeof(TransformFeedbackVaryingsHeader));
   const base::CheckedNumeric<size_t> buffer_block_size(
@@ -4357,12 +4358,15 @@
     varying_info.size = size;
     varying_info.type = type;
 
+    // Add the string at the end and make the info object point at it using its
+    // offset.
     DCHECK(length + 1 <= max_transform_feedback_varying_length);
-    varying_info.name_length = data->size();
+    varying_info.name_offset = data->size();
     varying_info.name_length = length + 1;
     AppendStringToBuffer(data, transform_feedback_varying_name_buf.data(),
                          length + 1);
 
+    // Put the header info in the previously reserved space in the buffer.
     InsertValueIntoBuffer(
         data, varying_info,
         (buffer_header_size +
Loading diff…

Original Bug Report

reported by [email protected]

GPU stack memory leak in DoGetTransformFeedbackVaryingsCHROMIUM

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A copy-paste typo in the GLES2 passthrough decoder leaves the name_offset field of the TransformFeedbackVaryingInfo struct uninitialized. This struct is subsequently copied to a command buffer bucket returned to the renderer. On Android official builds where stack variables are not auto-initialized, this potentially leaks 4 bytes of GPU process stack memory per varying, providing an ASLR bypass primitive.

Affected files:

  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc

Estimated timestamp from git blame: 2017-03-24

Description

In the GPU process’s passthrough command buffer decoder, there is a copy-paste typo when populating transform feedback varying information.

In gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc, the function GLES2DecoderPassthroughImpl::DoGetTransformFeedbackVaryingsCHROMIUM iterates through a program’s varyings and populates a stack-allocated TransformFeedbackVaryingInfo struct:

    TransformFeedbackVaryingInfo varying_info;
    varying_info.size = size;
    varying_info.type = type;

    DCHECK(length + 1 <= max_transform_feedback_varying_length);
    varying_info.name_length = data->size();  // <-- TYPO HERE
    varying_info.name_length = length + 1;

The line varying_info.name_length = data->size(); was clearly intended to be varying_info.name_offset = data->size();. Because it incorrectly assigns to name_length (which is immediately overwritten on the next line), the 4-byte name_offset field of the 16-byte struct is never initialized.

The uninitialized struct is then copied into the response vector data via InsertValueIntoBuffer, which uses memcpy. Finally, the vector is placed into a command buffer bucket and returned to the renderer.

On Android official builds, the compiler flag init_stack_vars is set to false (in build/config/compiler/BUILD.gn) to optimize binary size and performance. As a result, the uninitialized name_offset field will contain 4 bytes of leftover data from the GPU process’s stack. Leaking this data to a compromised renderer provides a reliable ASLR bypass primitive against the GPU process.

Potential Attacker Steps

Note: These are suggested steps based on static analysis, as our tooling agent does not yet have the ability to run code to verify a full exploit chain.

  1. Compromise the Renderer: The attacker first gains arbitrary code execution in the sandboxed renderer process.
  2. Context Creation: The attacker establishes a WebGL2 or OpenGL ES 3.0 context.
  3. Program Setup: The attacker compiles and links a shader program containing one or more transform feedback varyings.
  4. Command Execution: The attacker crafts and sends the GetTransformFeedbackVaryingsCHROMIUM command via the GPU command buffer, providing the valid program ID and a target bucket ID.
  5. Data Retrieval: The attacker issues GetBucketStart / GetBucketData commands to read the populated bucket from shared memory.
  6. Information Leak: The attacker parses the returned binary structure and extracts the 4 bytes at the name_offset position for each varying. These 4 bytes contain leaked stack memory from the GPU process, which can be used to defeat ASLR.

Proposed Fix

Correct the typo by assigning the offset to the correct field. Additionally, explicitly zero-initialize the struct at declaration to protect against any future changes to the struct definition.

    TransformFeedbackVaryingInfo varying_info = {}; // Explicitly zero-initialize
    varying_info.size = size;
    varying_info.type = type;

    DCHECK(length + 1 <= max_transform_feedback_varying_length);
    varying_info.name_offset = data->size();        // Fix typo
    varying_info.name_length = length + 1;

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results from 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