Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in GPU
DescriptionImproper input validation in GPU
ComponentGPU
Bug ClassLogic Error
Tracker517382613
Fix commitbb5bb7d19cb8 (chromium/src) +31/-21
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/gles2_cmd_decoder.cc
modified
if
gpu/command_buffer/service/shader_translator.cc
modified
switch
gpu/command_buffer/service/shader_translator.cc
modified
TEST_F
gpu/command_buffer/service/shader_translator_unittest.cc
modified

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/shader_translator.cc
  • gpu/command_buffer/service/shader_translator_unittest.cc
From bb5bb7d19cb82801fe411263d9fd53a978511cd8 Mon Sep 17 00:00:00 2001
From: Zhenyao Mo <zmo@chromium.org>
Date: Fri, 17 Jul 2026 18:19:04 -0700
Subject: [PATCH] gpu: Map GLES contexts to WebGL shader specs in validating decoder

To prevent untrusted renderers from bypassing WebGL-specific shader
sanitization by requesting a CONTEXT_TYPE_OPENGLES2 or
CONTEXT_TYPE_OPENGLES3 context under the validating command decoder,
we map those context types to SH_WEBGL_SPEC and SH_WEBGL2_SPEC
respectively. This enforces safety mitigations (e.g. structure nesting
depth limits, loop validations).

Additionally, initOutputVariables is enabled unconditionally in
ShaderTranslator to guarantee that output variables are always zero-
initialized for all specifications, preventing stale graphics memory
leaks.

Fixed: 517382613
Change-Id: I9eb3bfd5db30a73b7e8e70aca283a21cc7351b3e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8120411
Reviewed-by: Kenneth Russell <kbr@chromium.org>
Commit-Queue: Zhenyao Mo <zmo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1664282}
---

diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 008d811..1ac9d0c0 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -3719,7 +3719,9 @@
               : 0;
       break;
     case CONTEXT_TYPE_OPENGLES2:
-      shader_spec = SH_GLES2_SPEC;
+      // Map OpenGLES contexts to WebGL shader specs to enforce WebGL-specific
+      // safety mitigations and shader sanitization.
+      shader_spec = SH_WEBGL_SPEC;
       resources.OES_standard_derivatives =
           features().oes_standard_derivatives ? 1 : 0;
       resources.ARB_texture_rectangle =
@@ -3740,7 +3742,9 @@
           features().ext_blend_func_extended ? 1 : 0;
       break;
     case CONTEXT_TYPE_OPENGLES3:
-      shader_spec = SH_GLES3_SPEC;
+      // Map OpenGLES contexts to WebGL shader specs to enforce WebGL-specific
+      // safety mitigations and shader sanitization.
+      shader_spec = SH_WEBGL2_SPEC;
       resources.ARB_texture_rectangle =
           features().arb_texture_rectangle ? 1 : 0;
       resources.OES_EGL_image_external =
@@ -3754,12 +3758,12 @@
       NOTREACHED();
   }
 
-  if (shader_spec == SH_WEBGL_SPEC || shader_spec == SH_WEBGL2_SPEC) {
+  if (feature_info_->IsWebGLContext()) {
     resources.ANGLE_multi_draw =
         multi_draw_explicitly_enabled_ && features().webgl_multi_draw;
   }
 
-  if (shader_spec == SH_WEBGL2_SPEC) {
+  if (feature_info_->context_type() == CONTEXT_TYPE_WEBGL2) {
     // The gl_BaseVertex/BaseInstance shader builtins is disabled in ANGLE for
     // WebGL As they are removed in
     // https://github.com/KhronosGroup/WebGL/pull/3278
@@ -3773,7 +3777,7 @@
          features().webgl_multi_draw_instanced_base_vertex_base_instance);
   }
 
-  if (((shader_spec == SH_WEBGL_SPEC || shader_spec == SH_WEBGL2_SPEC) &&
+  if ((feature_info_->IsWebGLContext() &&
        features().enable_shader_name_hashing) ||
       force_shader_name_hashing_for_test) {
     // TODO(crbug.com/40601370): In theory, it should be OK to change this
diff --git a/gpu/command_buffer/service/shader_translator.cc b/gpu/command_buffer/service/shader_translator.cc
index 746e6e3..805814f 100644
--- a/gpu/command_buffer/service/shader_translator.cc
+++ b/gpu/command_buffer/service/shader_translator.cc
@@ -127,32 +127,22 @@
   compile_options_.clampIndirectArrayBounds = true;
   compile_options_.emulateGLDrawID = true;
   compile_options_.emulateGLBaseVertexBaseInstance = true;
+  compile_options_.initOutputVariables = true;
 
   std::string compile_options_string =
       "objectCode:"
       "limitExpressionComplexity:limitCallStackDepth:clampIndirectArrayBounds:"
-      "emulateGLDrawID:emulateGLBaseVertexBaseInstance";
+      "emulateGLDrawID:emulateGLBaseVertexBaseInstance:initOutputVariables";
 
   if (gl_shader_interm_output) {
     compile_options_.intermediateTree = true;
     compile_options_string += ":intermediateTree";
   }
 
-  switch (shader_spec) {
-    case SH_WEBGL_SPEC:
-    case SH_WEBGL2_SPEC:
-      compile_options_.initOutputVariables = true;
-      break;
-    default:
-      break;
-  }
-
   // Build the options string for additional features that may be set by the
   // caller.  Note that this code is used by the validating command decoder,
   // which is deprecated.  No new features are expected to be enabled, neither
   // is it expected for there to be new users of this code.
-  if (compile_options_.initOutputVariables)
-    compile_options_string += ":initOutputVariables";
   if (compile_options_.initGLPosition)
     compile_options_string += ":initGLPosition";
   if (compile_options_.unfoldShortCircuit)
diff --git a/gpu/command_buffer/service/shader_translator_unittest.cc b/gpu/command_buffer/service/shader_translator_unittest.cc
index c9a17e9..08c2396 100644
--- a/gpu/command_buffer/service/shader_translator_unittest.cc
+++ b/gpu/command_buffer/service/shader_translator_unittest.cc
@@ -389,6 +389,22 @@
   EXPECT_TRUE(iter != interface_block_map.end());
 }
 
+TEST_F(ShaderTranslatorTest, OutputVariablesInitializedForAllSpecs) {
+  ShBuiltInResources resources;
+  sh::InitBuiltInResources(&resources);
+
+  for (ShShaderSpec spec :
+       {SH_GLES2_SPEC, SH_GLES3_SPEC, SH_WEBGL_SPEC, SH_WEBGL2_SPEC}) {
+    scoped_refptr<ShaderTranslator> translator = new ShaderTranslator();
+    ASSERT_TRUE(translator->Init(GL_FRAGMENT_SHADER, spec, &resources,
+                                 SH_ESSL_OUTPUT, {}, false));
+    std::string options(
+        translator->GetStringForOptionsThatWouldAffectCompilation()->data);
+    EXPECT_NE(options.find("initOutputVariables"), std::string::npos)
+        << "spec=" << spec;
+  }
+}
+
 TEST_F(ShaderTranslatorTest, OptionsString) {
   scoped_refptr<ShaderTranslator> translator_1 = new ShaderTranslator();
   scoped_refptr<ShaderTranslator> translator_2 = new ShaderTranslator();
@@ -397,14 +413,14 @@
   ShBuiltInResources resources;
   sh::InitBuiltInResources(&resources);
 
-  ShCompileOptions with_init_output_variables{};
-  with_init_output_variables.initOutputVariables = true;
+  ShCompileOptions with_init_gl_position{};
+  with_init_gl_position.initGLPosition = true;
 
   ASSERT_TRUE(translator_1->Init(GL_VERTEX_SHADER, SH_GLES2_SPEC, &resources,
                                  SH_GLSL_150_CORE_OUTPUT, {}, false));
   ASSERT_TRUE(translator_2->Init(GL_FRAGMENT_SHADER, SH_GLES2_SPEC, &resources,
-                                 SH_GLSL_150_CORE_OUTPUT,
-                                 with_init_output_variables, false));
+                                 SH_GLSL_150_CORE_OUTPUT, with_init_gl_position,
+                                 false));
   resources.EXT_draw_buffers = 1;
   ASSERT_TRUE(translator_3->Init(GL_VERTEX_SHADER, SH_GLES2_SPEC, &resources,
                                  SH_GLSL_150_CORE_OUTPUT, {}, false));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/service/shader_translator_unittest.cc b/gpu/command_buffer/service/shader_translator_unittest.cc
index c9a17e9..08c2396 100644
--- a/gpu/command_buffer/service/shader_translator_unittest.cc
+++ b/gpu/command_buffer/service/shader_translator_unittest.cc
@@ -389,6 +389,22 @@
   EXPECT_TRUE(iter != interface_block_map.end());
 }
 
+TEST_F(ShaderTranslatorTest, OutputVariablesInitializedForAllSpecs) {
+  ShBuiltInResources resources;
+  sh::InitBuiltInResources(&resources);
+
+  for (ShShaderSpec spec :
+       {SH_GLES2_SPEC, SH_GLES3_SPEC, SH_WEBGL_SPEC, SH_WEBGL2_SPEC}) {
+    scoped_refptr<ShaderTranslator> translator = new ShaderTranslator();
+    ASSERT_TRUE(translator->Init(GL_FRAGMENT_SHADER, spec, &resources,
+                                 SH_ESSL_OUTPUT, {}, false));
+    std::string options(
+        translator->GetStringForOptionsThatWouldAffectCompilation()->data);
+    EXPECT_NE(options.find("initOutputVariables"), std::string::npos)
+        << "spec=" << spec;
+  }
+}
+
 TEST_F(ShaderTranslatorTest, OptionsString) {
   scoped_refptr<ShaderTranslator> translator_1 = new ShaderTranslator();
   scoped_refptr<ShaderTranslator> translator_2 = new ShaderTranslator();
@@ -397,14 +413,14 @@
   ShBuiltInResources resources;
   sh::InitBuiltInResources(&resources);
 
-  ShCompileOptions with_init_output_variables{};
-  with_init_output_variables.initOutputVariables = true;
+  ShCompileOptions with_init_gl_position{};
+  with_init_gl_position.initGLPosition = true;
 
   ASSERT_TRUE(translator_1->Init(GL_VERTEX_SHADER, SH_GLES2_SPEC, &resources,
                                  SH_GLSL_150_CORE_OUTPUT, {}, false));
   ASSERT_TRUE(translator_2->Init(GL_FRAGMENT_SHADER, SH_GLES2_SPEC, &resources,
-                                 SH_GLSL_150_CORE_OUTPUT,
-                                 with_init_output_variables, false));
+                                 SH_GLSL_150_CORE_OUTPUT, with_init_gl_position,
+                                 false));
   resources.EXT_draw_buffers = 1;
   ASSERT_TRUE(translator_3->Init(GL_VERTEX_SHADER, SH_GLES2_SPEC, &resources,
                                  SH_GLSL_150_CORE_OUTPUT, {}, false));
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential bypass of WebGL translator sanitization via kOpenGLES2 context type

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: An untrusted renderer can potentially bypass WebGL-specific shader sanitization by requesting an kOpenGLES2 context type. On configurations utilizing the validating command decoder, this causes the ANGLE translator to compile shaders under the SH_GLES2_SPEC specification instead of WebGL specifications. As a result, critical safety mitigations such as output variable zero-initialization and structure nesting depth limits are disabled.

Affected files:

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/shader_translator.cc
  • gpu/ipc/service/gpu_channel.cc

Estimated timestamp from git blame: 2015-09-22

Potential Security Impact

When a renderer requests a GLES command buffer, it can specify a context_type in mojom::GLESCreationAttribs (such as ContextType::kOpenGLES2). On configurations where the validating command decoder is active (e.g., certain Android devices running on blocklisted drivers), the GPU process initializes a GLES2DecoderImpl using CONTEXT_TYPE_OPENGLES2.

This selection propagates to GLES2DecoderImpl::InitializeShaderTranslator (in gpu/command_buffer/service/gles2_cmd_decoder.cc), mapping the shader compiler specification to SH_GLES2_SPEC instead of SH_WEBGL_SPEC or SH_WEBGL2_SPEC:

// gpu/command_buffer/service/gles2_cmd_decoder.cc
switch (feature_info_->context_type()) {
  case CONTEXT_TYPE_WEBGL1:  shader_spec = SH_WEBGL_SPEC;  ... break;
  case CONTEXT_TYPE_WEBGL2:  shader_spec = SH_WEBGL2_SPEC; ... break;
  case CONTEXT_TYPE_OPENGLES2:
    shader_spec = SH_GLES2_SPEC;
    ...
    break;

Compiling under SH_GLES2_SPEC disables multiple critical security mitigations enforced under WebGL specifications:

  1. Zero-Initialization of Outputs (initOutputVariables): In ShaderTranslator::Init (gpu/command_buffer/service/shader_translator.cc), output-variable zero-initialization is only configured for WebGL specs:

    switch (shader_spec) {
      case SH_WEBGL_SPEC:
      case SH_WEBGL2_SPEC:
        compile_options_.initOutputVariables = true;
        break;
      default:
        break;  // Bypassed under SH_GLES2_SPEC
    }
    

    Without zero-initialization, uninitialized fragment or varying outputs can potentially contain stale graphics memory, risking the exposure of cross-origin GPU frame data via glReadPixels or sampling operations.

  2. Bypass of Struct Nesting Limits: ANGLE’s nesting limits check in ParseContext.cpp is bypassed under SH_GLES2_SPEC:

    void TParseContext::checkIsBelowStructNestingLimit(const TSourceLoc &line, const TField &field) {
        if (!sh::IsWebGLBasedSpec(mShaderSpec)) {
            return; // Bypassed under SH_GLES2_SPEC
        }
        ...
    }
    

    An attacker can potentially construct deeply nested struct declarations that are parsed and forwarded directly to the native vendor GPU driver compiler. This bypasses the protections designed to prevent native compiler stack overflows and driver crashes.

  3. Loop Indexing and Identifier Validation Bypass: WebGL-specific validation checks, loop limitations, and identifier constraints are ignored because they are gated behind WebGL specifications.

Potential Attack Scenario

Note: Our analysis is based on static code review; we do not currently have a running proof-of-concept exploit.

  1. A compromised renderer process calls mojom::GpuChannel::CreateCommandBuffer with init_params->attribs set to mojom::ContextCreationAttribs::NewGles(mojom::GLESCreationAttribs{.context_type = mojom::ContextType::kOpenGLES2, ...}).
  2. The GPU process initializes a GLES2DecoderImpl with the CONTEXT_TYPE_OPENGLES2 specification.
  3. The attacker submits a custom fragment shader that declares but does not write to its output variables. Because initOutputVariables is false, compilation does not inject zero-initialization, potentially allowing stale GPU memory to be read back.
  4. Alternatively, the attacker compiles a shader containing deeply nested structures to attempt to trigger a crash or memory corruption in the native vendor GPU driver compiler.

Suggested Remediation

Restrict untrusted renderers from requesting native OpenGLES contexts. In gpu/ipc/service/gpu_channel.cc (within GpuChannel::CreateCommandBuffer) or in GLES2CommandBufferStub::Initialize, validate that attribs.context_type can only be CONTEXT_TYPE_OPENGLES2 or CONTEXT_TYPE_OPENGLES3 if the calling process is trusted (e.g., check is_gpu_host_ or a similar privilege flag). Otherwise, reject context creation or force the specification to fall back to a WebGL context type.

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