High watch CVSS 8.8 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in ANGLE
DescriptionInappropriate implementation in ANGLE
ComponentANGLE
Sub-componentSPIR-V
Bug ClassLogic Error
Action tierwatch — ub-class only — no demonstrated corruption path
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.
Tracker513921488
Fix commitf0d2ebc2635c (angle/angle) +96/-7
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-17 (per vendor advisory)
Fix landed2026-06-02 (public git)
Disclosed2026-07-29
Patch gap57 days — fix public in source before the advisory

Background

SPIR-V translator
ANGLE’s TranslatorSPIRV component that lowers ESSL shaders to SPIR-V for the Vulkan backend, including remapping certain fragment built-ins to emulated (flipped/rotated) variables.
`assignSpirvId`
assigns a fixed SPIR-V id (e.g. kIdSampleID, kIdFragCoord) to a built-in variable so later passes can reference it consistently.
Emulated built-ins
gl_SampleID, gl_PointCoord, and gl_FragCoord require special handling (id assignment and, for coordinates, flip/rotate emulation under pre-rotation) when a shader uses them.
`RotateAndFlipBuiltinVariable`
the transform that inserts a flipped/rotated replacement variable (named via constants like kFlippedFragCoordName) for a coordinate built-in.

Root Cause Analysis

Inside the input-varying scan loop in src/compiler/translator/spirv/TranslatorSPIRV.cpp (enclosing function not named in the hunk), each recognized built-in (gl_SampleID, gl_PointCoord, gl_FragCoord) ended its handling with break, which terminated the entire loop after the first matching built-in instead of proceeding to the remaining input varyings.

As a result, when a shader used more than one of these built-ins together, only the first was processed — the others never received their SPIR-V id assignment or usesPointCoord flag — so combinations such as gl_PointCoord before gl_FragCoord, or gl_SampleID before gl_FragCoord, were mistranslated.

The fix replaces each break with continue, so the loop keeps scanning and every used built-in is handled. A second, related copy-paste defect is fixed: the sample-position emulation passed kFlippedPointCoordName to RotateAndFlipBuiltinVariable instead of the correct kFlippedSamplePositionName, meaning the flipped sample-position variable collided with / reused the point-coord name.

Key insight
The single mistake was using break (which exits the loop) where continue (advance to the next varying) was intended, so only the first of several fragment built-ins was translated; changing the three breaks to continue — plus correcting the flipped-sample-position variable name — lets the built-ins work in combination.

Attack Path

  1. Author a multi-built-in shader Write a fragment shader that reads two or more of gl_SampleID, gl_PointCoord, gl_FragCoord (and/or gl_SamplePosition) in the same main, ordered so a later one follows an earlier one.
  2. Compile through the Vulkan/SPIR-V path ANGLE’s TranslatorSPIRV scans input varyings and, pre-patch, breaks after the first recognized built-in, leaving the rest without id assignment or emulation.
  3. Produce incorrect SPIR-V The generated shader references a built-in that was never properly set up (or a flipped variable under the wrong name), yielding inappropriate/incorrect rendering results.
  4. No memory-corruption primitive shown The patch and regression tests demonstrate only wrong pixel output; any behavior beyond miscompilation is (inferred) and not evidenced by the diff.

Impact Assessment

An attacker supplying a crafted shader (e.g. via WebGL) that combines these fragment built-ins could cause ANGLE to emit incorrect SPIR-V, producing wrong rendering output in the GPU process. The advisory classifies this as a logic error / inappropriate implementation in ANGLE; it reports no exploited-in-the-wild status and no memory-safety impact, so any escalation beyond incorrect rendering is (inferred). The precondition is use of the Vulkan/SPIR-V backend with these built-ins.

Files Changed

  • src/compiler/translator/spirv/TranslatorSPIRV.cpp
  • src/tests/gl_tests/GLSLTest.cpp

Audit Directions

  • `break` vs `continue` in matcher loops
    Audit loops that classify/handle multiple independent items (built-ins, attributes, varyings) for stray breaks that stop after the first match instead of continuing.
  • Copy-paste of emulation variable names
    Review calls to RotateAndFlipBuiltinVariable and similar helpers for reused kFlipped*Name constants that don’t match the built-in being transformed.
  • Combinatorial built-in coverage
    Ensure translator tests exercise fragment built-ins in combination and in varied source order, not only in isolation, to surface early-exit and name-collision bugs.
From f0d2ebc2635c41149ef9f686b0db095339dc48e1 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <[email protected]>
Date: Mon, 01 Jun 2026 15:24:45 -0400
Subject: [PATCH] SPIR-V: Fix combination of gl_SampleID/PointCoord/FragCoord

The loop that checked these `break`ed early instead of `continue`ing, so
these built-ins didn't really work together.

Bug: chromium:513921488
Change-Id: I14de6d5e3355a0630134bde1de78c69559ece51c
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7891093
Commit-Queue: Shahbaz Youssefi <[email protected]>
Reviewed-by: Yuxin Hu <[email protected]>
---

diff --git a/src/compiler/translator/spirv/TranslatorSPIRV.cpp b/src/compiler/translator/spirv/TranslatorSPIRV.cpp
index 76ad9db..dbaf91e 100644
--- a/src/compiler/translator/spirv/TranslatorSPIRV.cpp
+++ b/src/compiler/translator/spirv/TranslatorSPIRV.cpp
@@ -60,6 +60,7 @@
 namespace
 {
 constexpr ImmutableString kFlippedPointCoordName    = ImmutableString("flippedPointCoord");
+constexpr ImmutableString kFlippedSamplePositionName = ImmutableString("flippedSamplePosition");
 constexpr ImmutableString kFlippedFragCoordName     = ImmutableString("flippedFragCoord");
 constexpr ImmutableString kDefaultUniformsBlockName = ImmutableString("defaultUniforms");
 
@@ -898,13 +899,13 @@
                         static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
                             ImmutableString("gl_SampleID"), getShaderVersion()));
                     assignSpirvId(sampleID->uniqueId(), vk::spirv::kIdSampleID);
-                    break;
+                    continue;
                 }
 
                 if (inputVarying.name == "gl_PointCoord")
                 {
                     usesPointCoord = true;
-                    break;
+                    continue;
                 }
 
                 if (inputVarying.name == "gl_FragCoord")
@@ -914,7 +915,7 @@
                         static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
                             ImmutableString("gl_FragCoord"), getShaderVersion()));
                     assignSpirvId(fragCoord->uniqueId(), vk::spirv::kIdFragCoord);
-                    break;
+                    continue;
                 }
             }
 
@@ -977,7 +978,7 @@
                         ImmutableString("gl_SamplePosition"), getShaderVersion()));
                 if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), swapXY, flipXY,
                                                   &getSymbolTable(), samplePositionBuiltin,
-                                                  kFlippedPointCoordName, pivot))
+                                                  kFlippedSamplePositionName, pivot))
                 {
                     return false;
                 }
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 9723062..5060578 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -11231,6 +11231,32 @@
     EXPECT_EQ(userFBOData, backbufferData);
 }
 
+// Test gl_PointCoord used before gl_FragCoord when dithering might be emulated.
+TEST_P(GLSLTest_ES3, PointCoordBeforeFragCoord)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision mediump float;
+out vec4 colorOut;
+void main()
+{
+    vec2 p = gl_PointCoord;
+    colorOut = vec4(abs(p) + vec2(1, 1), gl_FragCoord.x * 0.000001, 1.0);
+})";
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+    GLTexture tex565;
+    glBindTexture(GL_TEXTURE_2D, tex565);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB565, 10, 20);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex565, 0);
+
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::yellow, 1);
+    ASSERT_GL_NO_ERROR();
+}
+
 bool SubrectEquals(const std::vector<GLColor> &bigArray,
                    const std::vector<GLColor> &smallArray,
                    int bigSize,
@@ -19447,7 +19473,63 @@
     color = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples));
 })";
 
-    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
+}
+
+// Test that gl_SampleID can be used before gl_SampleMaskIn.
+TEST_P(GLSLTest_ES3, SampleIDBeforeSampleMaskIn)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));
+
+    const char kFS[] = R"(#version 300 es
+#extension GL_OES_sample_variables : require
+precision highp float;
+out vec4 color;
+void main()
+{
+    int sampleId = gl_SampleID;
+    gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555;
+    color = vec4(gl_SamplePosition.yx, float(sampleId), float(gl_MaxSamples + gl_NumSamples));
+})";
+
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
+}
+
+// Test that gl_FragCoord can be used after gl_SampleID.
+TEST_P(GLSLTest_ES3, SampleIDBeforeFragCoord)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));
+
+    const char kFS[] = R"(#version 300 es
+#extension GL_OES_sample_variables : require
+precision highp float;
+uniform vec2 dim;
+out vec4 color;
+void main()
+{
+    vec2 useSampleIDFirst = vec2(gl_SampleID);
+    vec2 coords = gl_FragCoord.xy / dim + useSampleIDFirst;
+    color = vec4(coords.x > 0.5, coords.y > 0.5, 0, 1);
+})";
+
+    const int w  = getWindowWidth();
+    const int h  = getWindowHeight();
+    const int w2 = w / 2;
+    const int h2 = h / 2;
+
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    glUseProgram(program);
+    glUniform2f(glGetUniformLocation(program, "dim"), w, h);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+
+    EXPECT_PIXEL_RECT_EQ(0, 0, w2 - 1, h2 - 1, GLColor::black);
+    EXPECT_PIXEL_RECT_EQ(w2 + 1, 0, w - (w2 + 1), h2 - 1, GLColor::red);
+    EXPECT_PIXEL_RECT_EQ(0, h2 + 1, w2 - 1, h - (h2 + 1), GLColor::green);
+    EXPECT_PIXEL_RECT_EQ(w2 + 1, h2 + 1, w - (w2 + 1), h - (h2 + 1), GLColor::yellow);
 }
 
 // Test that shader caching maintains uniforms across compute shader compilations.
@@ -24090,7 +24172,10 @@
     ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
     ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
     ES3_VULKAN().disable(Feature::SupportsSPIRV14),
-    ES2_VULKAN().enable(Feature::VaryingsRequireMatchingPrecisionInSpirv));
+    ES2_VULKAN().enable(Feature::VaryingsRequireMatchingPrecisionInSpirv),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation270));
 
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLTestNoValidation);
 
@@ -24102,7 +24187,10 @@
     ES3_OPENGLES().enable(Feature::ScalarizeVecAndMatConstructorArgs),
     ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
     ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
-    ES3_VULKAN().disable(Feature::SupportsSPIRV14));
+    ES3_VULKAN().disable(Feature::SupportsSPIRV14),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation270));
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLPrecisionTest_ES3);
 ANGLE_INSTANTIATE_TEST_ES3_AND(
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 9723062..5060578 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -11231,6 +11231,32 @@
     EXPECT_EQ(userFBOData, backbufferData);
 }
 
+// Test gl_PointCoord used before gl_FragCoord when dithering might be emulated.
+TEST_P(GLSLTest_ES3, PointCoordBeforeFragCoord)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision mediump float;
+out vec4 colorOut;
+void main()
+{
+    vec2 p = gl_PointCoord;
+    colorOut = vec4(abs(p) + vec2(1, 1), gl_FragCoord.x * 0.000001, 1.0);
+})";
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+    GLTexture tex565;
+    glBindTexture(GL_TEXTURE_2D, tex565);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB565, 10, 20);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex565, 0);
+
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::yellow, 1);
+    ASSERT_GL_NO_ERROR();
+}
+
 bool SubrectEquals(const std::vector<GLColor> &bigArray,
                    const std::vector<GLColor> &smallArray,
                    int bigSize,
@@ -19447,7 +19473,63 @@
     color = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples));
 })";
 
-    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
+}
+
+// Test that gl_SampleID can be used before gl_SampleMaskIn.
+TEST_P(GLSLTest_ES3, SampleIDBeforeSampleMaskIn)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));
+
+    const char kFS[] = R"(#version 300 es
+#extension GL_OES_sample_variables : require
+precision highp float;
+out vec4 color;
+void main()
+{
+    int sampleId = gl_SampleID;
+    gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555;
+    color = vec4(gl_SamplePosition.yx, float(sampleId), float(gl_MaxSamples + gl_NumSamples));
+})";
+
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
+}
+
+// Test that gl_FragCoord can be used after gl_SampleID.
+TEST_P(GLSLTest_ES3, SampleIDBeforeFragCoord)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));
+
+    const char kFS[] = R"(#version 300 es
+#extension GL_OES_sample_variables : require
+precision highp float;
+uniform vec2 dim;
+out vec4 color;
+void main()
+{
+    vec2 useSampleIDFirst = vec2(gl_SampleID);
+    vec2 coords = gl_FragCoord.xy / dim + useSampleIDFirst;
+    color = vec4(coords.x > 0.5, coords.y > 0.5, 0, 1);
+})";
+
+    const int w  = getWindowWidth();
+    const int h  = getWindowHeight();
+    const int w2 = w / 2;
+    const int h2 = h / 2;
+
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
+    glUseProgram(program);
+    glUniform2f(glGetUniformLocation(program, "dim"), w, h);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+
+    EXPECT_PIXEL_RECT_EQ(0, 0, w2 - 1, h2 - 1, GLColor::black);
+    EXPECT_PIXEL_RECT_EQ(w2 + 1, 0, w - (w2 + 1), h2 - 1, GLColor::red);
+    EXPECT_PIXEL_RECT_EQ(0, h2 + 1, w2 - 1, h - (h2 + 1), GLColor::green);
+    EXPECT_PIXEL_RECT_EQ(w2 + 1, h2 + 1, w - (w2 + 1), h - (h2 + 1), GLColor::yellow);
 }
 
 // Test that shader caching maintains uniforms across compute shader compilations.
@@ -24090,7 +24172,10 @@
     ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
     ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
     ES3_VULKAN().disable(Feature::SupportsSPIRV14),
-    ES2_VULKAN().enable(Feature::VaryingsRequireMatchingPrecisionInSpirv));
+    ES2_VULKAN().enable(Feature::VaryingsRequireMatchingPrecisionInSpirv),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation270));
 
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLTestNoValidation);
 
@@ -24102,7 +24187,10 @@
     ES3_OPENGLES().enable(Feature::ScalarizeVecAndMatConstructorArgs),
     ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
     ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
-    ES3_VULKAN().disable(Feature::SupportsSPIRV14));
+    ES3_VULKAN().disable(Feature::SupportsSPIRV14),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
+    ES3_VULKAN().enable(Feature::EmulatedPrerotation270));
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLPrecisionTest_ES3);
 ANGLE_INSTANTIATE_TEST_ES3_AND(
Loading diff…

Original Bug Report

reported by [email protected]

Potential memory corruption in unsandboxed GPU process via duplicate SPIR-V built-in variables

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 logic error in ANGLE’s SPIR-V translator causes it to prematurely terminate its scan of input variables, leading to incorrect SPIR-V metadata. On Android, this triggers the injection of duplicate built-in variables into the shader module, violating the Vulkan specification. Providing such malformed SPIR-V to production graphics drivers in the unsandboxed GPU process could lead to memory corruption and remote code execution.

Affected files:

  • third_party/angle/src/compiler/translator/spirv/TranslatorSPIRV.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/spv_utils.cpp
  • third_party/angle/src/compiler/translator/spirv/BuildSPIRV.cpp
  • third_party/angle/src/compiler/translator/spirv/OutputSPIRV.cpp

Estimated timestamp from git blame: Unknown (Google3 checkout)

Description

A logic error exists in ANGLE’s TranslatorSPIRV::translateImpl within third_party/angle/src/compiler/translator/spirv/TranslatorSPIRV.cpp. When scanning the shader’s input varyings to assign reserved SPIR-V IDs and set metadata flags, the implementation uses break instead of continue for several built-in variables. This causes the scanner to stop processing the remaining variables in the list if it encounters certain built-ins early.

Specifically, the loop at lines 878-921 handles built-ins like gl_SampleID, gl_PointCoord, and gl_FragCoord. If gl_SampleID or gl_PointCoord appears in the mInputVaryings list before gl_FragCoord, the scanner executes a break and never reaches the code that handles gl_FragCoord (lines 912-920).

Impact on SPIR-V Generation

When the scanner is cut short:

  1. assignSpirvId is not called for gl_FragCoord, meaning it is not mapped to its reserved SPIR-V ID (kIdFragCoord).
  2. The usesFragCoord flag remains false, preventing the metadata flag HasFragCoord from being set in mMetadataFlags (line 995).
  3. During SPIR-V emission in BuildSPIRV.cpp, gl_FragCoord is assigned a generic ID instead of the reserved one.
  4. Consequently, the kOverviewHasFragCoordMask bit in the non-semantic overview marker is not set.

On Android, when the emulateDithering feature is active, ANGLE’s SpirvDitherEmulationTransformer (in third_party/angle/src/libANGLE/renderer/vulkan/spv_utils.cpp) inspects these flags. If hasFragCoord() returns false, the transformer proceeds to inject a second OpVariable decorated with BuiltIn FragCoord using the reserved ID. This results in a SPIR-V module with two distinct variables both decorated as the FragCoord built-in, violating Vulkan standalone SPIR-V requirements (VUID-StandaloneSpirv-OpEntryPoint-08721).

Security Impact

This malformed SPIR-V is passed directly to the production Vulkan driver’s vkCreateShaderModule call. In Chrome on Android, the GPU process is currently unsandboxed. Driver shader compilers are highly complex and typically assume SPIR-V validity; providing a module that violates fundamental uniqueness constraints for built-in variables can lead to undefined behavior, including memory corruption (such as out-of-bounds writes or use-after-frees) within the driver’s JIT compiler. This allows for a potential sandbox escape from a compromised renderer to the privileged GPU process.

Potential Reproduction Steps

Note: These are potential steps based on code analysis.

  1. From a web page, create a WebGL 2.0 context.
  2. Create a fragment shader that references gl_PointCoord before gl_FragCoord in its execution path (or simply by lexical order if the compiler preserves it). Example:
    #version 300 es
    precision mediump float;
    out vec4 color;
    void main() {
        float x = gl_PointCoord.x;
        color = vec4(gl_FragCoord.xyz, x);
    }
    
  3. Trigger a draw call using this shader on an Android device where dithering emulation is required (typically devices lacking hardware dithering support).
  4. The resulting invalid SPIR-V module will be submitted to the driver in the GPU process.

Suggested Fix

In third_party/angle/src/compiler/translator/spirv/TranslatorSPIRV.cpp, change the break statements to continue at lines 903, 909, and 919 within the mInputVaryings loop in TranslatorSPIRV::translateImpl.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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