High chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in ANGLE
DescriptionType Confusion in ANGLE
ComponentANGLE
Bug ClassType Confusion
Tracker499131214
Fix commit9015bc2851d8 (angle/angle) +335/-256
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Files Changed

  • src/compiler/translator/ParseContext.cpp
From 9015bc2851d8d67e7ede5e351b1cd99646e39aa9 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Tue, 07 Apr 2026 15:51:37 -0400
Subject: [PATCH] Translator: Fix redeclared built-in type checks

When a built-in is redeclared, it's type must match the built-in.  A
minor exception is that gl_ClipDistance and gl_CullDistance are allowed
to have a smaller array size.

The checks for various redeclared built-ins are consolidated in this
change, which automatically fixes missing validation for
gl_LastFragColor/Depth/StencilARM built-ins.

Bug: chromium:499131214
Change-Id: Idaea01c40ceced04ae3ab6357f1b5472dbb6ee36
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7736784
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
---

diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 418f9cf..62e78a5 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1186,19 +1186,14 @@
     static const char *reservedErrMsg = "reserved built-in name";
     if (gl::IsBuiltInName(identifier.data()))
     {
-        error(line, reservedErrMsg, "gl_");
+        error(line, reservedErrMsg, identifier);
         return false;
     }
     if (sh::IsWebGLBasedSpec(mShaderSpec))
     {
-        if (identifier.beginsWith("webgl_"))
+        if (identifier.beginsWith("webgl_") || identifier.beginsWith("_webgl_"))
         {
-            error(line, reservedErrMsg, "webgl_");
-            return false;
-        }
-        if (identifier.beginsWith("_webgl_"))
-        {
-            error(line, reservedErrMsg, "_webgl_");
+            error(line, reservedErrMsg, identifier);
             return false;
         }
     }
@@ -1980,150 +1975,159 @@
     checkBindingIsValid(line, *type);
 
     bool needsReservedCheck = true;
+    const TVariable *builtInSymbol =
+        static_cast<const TVariable *>(symbolTable.findBuiltIn(identifier, mShaderVersion));
 
-    // gl_LastFragData may be redeclared with a new precision qualifier
-    if (type->isArray() && identifier.beginsWith("gl_LastFragData"))
+    // Some built-ins may be redeclared with a new precision qualifier, but must otherwise match the
+    // built-in in type, array dimensions etc: gl_LastFragData, gl_LastFragColorARM,
+    // gl_LastFragDepthARM, gl_LastFragStencilARM, gl_ClipDistance, gl_CullDistance, gl_FragDepth,
+    // gl_Position, gl_PointSize.
+    //
+    // For gl_ClipDistance and gl_CullDistance, the array size can be less than the built-in's.
+    if (builtInSymbol != nullptr)
     {
-        const TVariable *maxDrawBuffers = static_cast<const TVariable *>(
-            symbolTable.findBuiltIn(ImmutableString("gl_MaxDrawBuffers"), mShaderVersion));
+        const TType &expectedType = builtInSymbol->getType();
+
+        uint32_t expectedArraySize         = 0;
+        bool canArraySizeBeLessThanBuiltIn = false;
+        const char *arraySizeCheckError    = nullptr;
+
+        switch (expectedType.getQualifier())
+        {
+            case EvqLastFragData:
+                expectedArraySize = static_cast<const TVariable *>(
+                                        symbolTable.findBuiltIn(
+                                            ImmutableString("gl_MaxDrawBuffers"), mShaderVersion))
+                                        ->getConstPointer()
+                                        ->getIConst();
+                arraySizeCheckError =
+                    "redeclaration of gl_LastFragData with size != gl_MaxDrawBuffers";
+                needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
+                break;
+            case EvqLastFragColor:
+            case EvqLastFragDepth:
+            case EvqLastFragStencil:
+                needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
+                break;
+            case EvqClipDistance:
+            {
+                const TVariable *maxClipDistances =
+                    static_cast<const TVariable *>(symbolTable.findBuiltIn(
+                        ImmutableString("gl_MaxClipDistances"), mShaderVersion));
+                if (maxClipDistances != nullptr)
+                {
+                    expectedArraySize = maxClipDistances->getConstPointer()->getIConst();
+                    canArraySizeBeLessThanBuiltIn = true;
+                    arraySizeCheckError =
+                        "redeclaration of gl_ClipDistance with size > gl_MaxClipDistances";
+                    needsReservedCheck =
+                        !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
+                }
+                else
+                {
+                    // Unsupported extension
+                    error(line, "redeclaration of built-in is not allowed", identifier);
+                    return false;
+                }
+                break;
+            }
+            case EvqCullDistance:
+            {
+                const TVariable *maxCullDistances =
+                    static_cast<const TVariable *>(symbolTable.findBuiltIn(
+                        ImmutableString("gl_MaxCullDistances"), mShaderVersion));
+                if (maxCullDistances != nullptr)
+                {
+                    expectedArraySize = maxCullDistances->getConstPointer()->getIConst();
+                    canArraySizeBeLessThanBuiltIn = true;
+                    arraySizeCheckError =
+                        "redeclaration of gl_CullDistance with size > gl_MaxCullDistances";
+                    needsReservedCheck =
+                        !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
+                }
+                else
+                {
+                    // Unsupported extension
+                    error(line, "redeclaration of built-in is not allowed", identifier);
+                    return false;
+                }
+                break;
+            }
+            case EvqFragDepth:
+                needsReservedCheck = !isExtensionEnabled(TExtension::EXT_conservative_depth) ||
+                                     mShaderType != GL_FRAGMENT_SHADER ||
+                                     symbolType == SymbolType::UserDefined;
+                break;
+            case EvqPosition:
+            case EvqPointSize:
+                if (isExtensionEnabled(TExtension::EXT_separate_shader_objects) &&
+                    mShaderType == GL_VERTEX_SHADER)
+                {
+                    needsReservedCheck = false;
+                    if (expectedType.getQualifier() == EvqPosition)
+                    {
+                        mPositionRedeclaredForSeparateShaderObject = true;
+                    }
+                    else
+                    {
+                        mPointSizeRedeclaredForSeparateShaderObject = true;
+                    }
+                    if (mPositionOrPointSizeUsedForSeparateShaderObject)
+                    {
+                        error(line,
+                              "When EXT_separate_shader_objects is enabled, both gl_Position and "
+                              "gl_PointSize must be redeclared before either is used",
+                              identifier);
+                    }
+                }
+                else
+                {
+                    error(line, "redeclaration of built-in is not allowed", identifier);
+                    return false;
+                }
+                break;
+            default:
+                error(line, "reserved built-in name", identifier);
+                return false;
+        }
+
+        // No built-in is an array of arrays.
         if (type->isArrayOfArrays())
         {
-            error(line, "redeclaration of gl_LastFragData as an array of arrays", identifier);
+            error(line, "redeclaration of built-in as an array of arrays", identifier);
             return false;
         }
-        else if (static_cast<int>(type->getOutermostArraySize()) ==
-                 maxDrawBuffers->getConstPointer()->getIConst())
+
+        if (type->getBasicType() != expectedType.getBasicType() ||
+            type->getNominalSize() != expectedType.getNominalSize() ||
+            type->getSecondarySize() != expectedType.getSecondarySize() ||
+            type->isArray() != expectedType.isArray())
         {
-            if (const TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion))
+            error(line, "redeclaration of built-in with a different type", identifier);
+            return false;
+        }
+
+        if (type->isArray())
+        {
+            unsigned int arraySize = type->getOutermostArraySize();
+            if (arraySize > expectedArraySize ||
+                (!canArraySizeBeLessThanBuiltIn && arraySize != expectedArraySize))
             {
-                needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
-            }
-        }
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 51d0c46..82c07b6 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -15738,110 +15738,10 @@
 })";
 
     ANGLE_GL_PROGRAM_WITH_GS(program, kVS, kGS, kFS);
+    EXPECT_NE(0u, program);
     EXPECT_GL_NO_ERROR();
 }
 
-// Negative test using builtins that can only be used when redefining gl_PerVertex
-TEST_P(GLSLTest_ES31, PerVertexNegativeTest)
-{
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));
-
-    constexpr char kVS[] = R"(#version 310 es
-void main()
-{
-    gl_Position = vec4(1.0, 0.0, 0.0, 1.0);
-})";
-
-    constexpr char kGS[] = R"(#version 310 es
-#extension GL_EXT_geometry_shader : require
-#extension GL_EXT_clip_cull_distance : require
-
-layout(lines_adjacency, invocations = 3) in;
-layout(points, max_vertices = 16) out;
-
-vec4 gl_Position;
-float gl_ClipDistance[4];
-float gl_CullDistance[4];
-
-void main()
-{
-    for (int n = 0; n < 16; ++n)
-    {
-        gl_Position = vec4(n, 0.0, 0.0, 1.0);
-        EmitVertex();
-    }
-
-    EndPrimitive();
-})";
-
-    constexpr char kFS[] = R"(#version 310 es
-precision highp float;
-
-out vec4 result;
-
-void main()
-{
-    result = vec4(1.0);
-})";
-
-    GLuint program = CompileProgramWithGS(kVS, kGS, kFS);
-    EXPECT_EQ(0u, program);
-    glDeleteProgram(program);
-}
-
-// Negative test using builtins that can only be used when redefining gl_PerVertex
-// but have the builtins in a differently named struct
-TEST_P(GLSLTest_ES31, PerVertexRenamedNegativeTest)
-{
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));
-
-    constexpr char kVS[] = R"(#version 310 es
-void main()
-{
-    gl_Position = vec4(1.0, 0.0, 0.0, 1.0);
-})";
-
-    constexpr char kGS[] = R"(#version 310 es
-#extension GL_EXT_geometry_shader : require
-#extension GL_EXT_clip_cull_distance : require
-
-layout(lines_adjacency, invocations = 3) in;
-layout(points, max_vertices = 16) out;
-
-out Block {
-    vec4 gl_Position;
-    float gl_ClipDistance[4];
-    float gl_CullDistance[4];
-};
-
-void main()
-{
-    for (int n = 0; n < 16; ++n)
-    {
-        gl_Position = vec4(n, 0.0, 0.0, 1.0);
-        EmitVertex();
-    }
-
-    EndPrimitive();
-})";
-
-    constexpr char kFS[] = R"(#version 310 es
-precision highp float;
-
-out vec4 result;
-
-void main()
-{
-    result = vec4(1.0);
-})";
-
-    GLuint program = CompileProgramWithGS(kVS, kGS, kFS);
-    EXPECT_EQ(0u, program);
-    glDeleteProgram(program);
-}
-
 // Test pragma STDGL invariant all with I/O blocks
 TEST_P(GLSLTest_ES31, IOBlockInvariantAll)
 {
@@ -22452,12 +22352,12 @@
 }
 
 // Test that an unused gl_LastFragDepthARM does not lead to errors
-TEST_P(GLSLTest_ES31, UnsedLastFragDepth)
+TEST_P(GLSLTest_ES31, UnusedLastFragDepth)
 {
     ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));
 
     const char kFS[] = R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil:require
-int gl_LastFragDepthARM;
+mediump float gl_LastFragDepthARM;
 void main()
 {
     gl_FragColor = vec4(0, 1, 0, 1);
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 0003b95..8e64731 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -3165,6 +3165,73 @@
     }
 }
 
+// Negative test using builtins that can only be used when redefining gl_PerVertex
+TEST_P(GLSLValidationTest_ES31, RedefinePerVertexMembersSeparately)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));
+
+    constexpr char kGS[] = R"(#version 310 es
+#extension GL_EXT_geometry_shader : require
+#extension GL_EXT_clip_cull_distance : require
+
+layout(lines_adjacency, invocations = 3) in;
+layout(points, max_vertices = 16) out;
+
+vec4 gl_Position;
+float gl_ClipDistance[4];
+float gl_CullDistance[4];
+
+void main()
+{
+    for (int n = 0; n < 16; ++n)
+    {
+        gl_Position = vec4(n, 0.0, 0.0, 1.0);
+        EmitVertex();
+    }
+
+    EndPrimitive();
+})";
+
+    validateError(GL_GEOMETRY_SHADER, kGS,
+                  "'gl_Position' : redeclaration of built-in is not allowed");
+}
+
+// Negative test using builtins that can only be used when redefining gl_PerVertex but have the
+// builtins in a differently named struct
+TEST_P(GLSLValidationTest_ES31, RedefinePerVertexMembersInOtherBlock)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));
+
+    constexpr char kGS[] = R"(#version 310 es
+#extension GL_EXT_geometry_shader : require
+#extension GL_EXT_clip_cull_distance : require
+
+layout(lines_adjacency, invocations = 3) in;
+layout(points, max_vertices = 16) out;
+
+out Block {
+    vec4 gl_Position;
+    float gl_ClipDistance[4];
+    float gl_CullDistance[4];
+};
+
+void main()
+{
+    for (int n = 0; n < 16; ++n)
+    {
+        gl_Position = vec4(n, 0.0, 0.0, 1.0);
+        EmitVertex();
+    }
+
+    EndPrimitive();
+})";
+
+    validateError(GL_GEOMETRY_SHADER, kGS,
+                  "'gl_Position' : redefinition in an invalid interface block");
+}
+
 // Regression test case of unary + constant folding of a void struct member.
 TEST_P(GLSLValidationTest, UnaryPlusOnVoidStructMemory)
 {
@@ -5025,6 +5092,114 @@
                   "GL_EXT_shader_framebuffer_fetch_non_coherent extension is used");
 }
 
+// Redeclare gl_LastFragColorARM with unexpected basic type
+TEST_P(GLSLValidationTest, FramebufferFetchLastFragColorWrongType)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch"));
+
+    constexpr char kFS[] =
+        R"(#extension GL_ARM_shader_framebuffer_fetch : require
+highp int gl_LastFragColorARM;
+
+void main (void)
+{
+    gl_FragColor = vec4(gl_LastFragColorARM);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'gl_LastFragColorARM' : redeclaration of built-in with a different type");
+}
+
+// Redeclare gl_LastFragColorARM with unexpected arrayness
+TEST_P(GLSLValidationTest, FramebufferFetchLastFragColorArrayed)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch"));
+
+    constexpr char kFS[] =
+        R"(#extension GL_ARM_shader_framebuffer_fetch : require
+highp vec4 gl_LastFragColorARM[4];
+
+void main (void)
+{
+    gl_FragColor = vec4(gl_LastFragColorARM[0]);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'gl_LastFragColorARM' : redeclaration of built-in with a different type");
+}
+
+// Redeclare gl_LastFragDepthARM with unexpected basic type
+TEST_P(GLSLValidationTest, FramebufferFetchLastFragDepthWrongType)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));
+
+    constexpr char kFS[] =
+        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
+highp int gl_LastFragDepthARM;
+
+void main (void)
+{
+    gl_FragColor = vec4(gl_LastFragDepthARM);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'gl_LastFragDepthARM' : redeclaration of built-in with a different type");
+}
+
+// Redeclare gl_LastFragDepthARM with unexpected arrayness
+TEST_P(GLSLValidationTest, FramebufferFetchLastFragDepthArrayed)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));
+
+    constexpr char kFS[] =
+        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
+highp float gl_LastFragDepthARM[4];
+
+void main (void)
+{
+    gl_FragColor = vec4(gl_LastFragDepthARM[0]);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'gl_LastFragDepthARM' : redeclaration of built-in with a different type");
+}
+
+// Redeclare gl_LastFragStencilARM with unexpected basic type
+TEST_P(GLSLValidationTest, FramebufferFetchLastFragStencilWrongType)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));
+
+    constexpr char kFS[] =
+        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
+highp float gl_LastFragStencilARM;
+
+void main (void)
+{
+    gl_FragColor = vec4(gl_LastFragStencilARM);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
... (truncated)
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Type Confusion in ANGLE SPIR-V Compiler via Built-in Array Redeclaration

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 vulnerability in ANGLE allows a malicious renderer to submit GLSL that redeclares gl_LastFragDepthARM as an array, bypassing necessary type checks. During the framebuffer fetch emulation pass, this causes type confusion in the AST, resulting in the generation of structurally invalid SPIR-V. This malformed SPIR-V is passed directly to the Vulkan driver without validation, potentially triggering memory corruption and a GPU process sandbox escape.

Affected files:

  • third_party/angle/src/compiler/translator/ParseContext.cpp
  • third_party/angle/src/compiler/translator/tree_ops/spirv/EmulateFramebufferFetch.cpp
  • third_party/angle/src/compiler/translator/tree_util/ReplaceVariable.cpp
  • third_party/angle/src/compiler/translator/IntermNode.cpp
  • third_party/angle/src/compiler/translator/spirv/OutputSPIRV.cpp

Estimated timestamp from git blame: 2025-09-17

Overview

There is a potential type confusion vulnerability in ANGLE’s shader translator when generating SPIR-V for Vulkan backends. By explicitly requesting the GL_ARM_shader_framebuffer_fetch_depth_stencil extension, an attacker can redeclare specific built-in variables (like gl_LastFragDepthARM) as arrays. This bypasses validation and corrupts the Abstract Syntax Tree (AST) during emulation passes, causing the compiler to emit structurally invalid SPIR-V.

Because SPIR-V validation is disabled in Chromium release builds, this malformed shader is passed directly to the proprietary Vulkan driver. Compiling structurally invalid SPIR-V can cause memory corruption within the driver, leading to a potential GPU process sandbox escape.

Note: The steps and exploitation path outlined below are based on detailed code analysis. A working proof-of-concept has not been actively run to verify the specific driver behavior.

Root Cause & Vulnerability Flow

1. Missing Type Validation: In third_party/angle/src/compiler/translator/ParseContext.cpp, the function TParseContext::declareVariable handles the redeclaration of built-in variables. For gl_FragDepth, the code explicitly checks if (type->isArray()) and throws an error. However, for the ARM framebuffer fetch built-ins like gl_LastFragDepthARM (handled around line 2007), this array check is completely missing.

2. AST Type Confusion: If an attacker provides GLSL such as:

#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : enable
highp float gl_LastFragDepthARM[10];
float f = gl_LastFragDepthARM[5];

The parser accepts the array and constructs a TIntermBinary node (EOpIndexDirect) representing the indexing operation. The type of this node is correctly inferred as a scalar float.

During the EmulateFramebufferFetch pass, the compiler replaces the array symbol gl_LastFragDepthARM with a scalar swizzle of a subpass load (e.g., subpassLoad(depth).x). The ReplaceVariables utility swaps the child node but does not recalculate the type of the parent TIntermBinary node. The AST is now in an invalid state: a TIntermBinary node is attempting to perform an array index operation on a child that is now a scalar float.

3. Malformed SPIR-V Generation: When OutputSPIRVTraverser processes this malformed AST:

  • visitSwizzle sees the .x component and appends index 0 to the current access chain’s idList.
  • visitBinary processes the direct index (5) and appends it to the same idList, resulting in [0, 5].
  • The traverser calls accessChainOnPush, which contains a debug-only ASSERT(parentType.isVector()). Since the parent is a scalar, this assertion fails in debug builds, but in release builds, it is compiled out and execution continues blindly.
  • Finally, accessChainLoad emits an OpCompositeExtract instruction with a vec4 base (from the subpassLoad) and the indices [0, 5].

This is a violation of the SPIR-V specification. The first index (0) extracts a scalar float from the vec4, but the second index (5) illegally attempts to extract an element from that scalar.

Impact

In release builds, ANGLE skips running spvValidate before sending the SPIR-V to the driver via vkCreateShaderModule. Proprietary Vulkan driver compilers typically assume SPIR-V is structurally valid. Processing an OpCompositeExtract that indexes beyond the composite type’s depth and into scalars can cause the driver compiler to perform out-of-bounds reads or writes against its internal AST/IR buffers. This leads to memory corruption inside the privileged GPU process.

Suggested Attacker Steps (Potential)

  1. Compromise Renderer: Gain arbitrary code execution within the unprivileged renderer process.
  2. Create Context: Communicate with the GPU process via IPC to create a GLES context using the passthrough command decoder (which passes GLSL directly to ANGLE without strict pre-validation).
  3. Submit Payload: Submit the malicious GLSL shader that enables the target extension and redeclares gl_LastFragDepthARM as an array, performing out-of-bounds indexing.
  4. Trigger Driver Bug: The renderer triggers a draw call or shader compilation, forcing the Vulkan driver to process the malformed SPIR-V, potentially triggering memory corruption and a sandbox escape into the GPU process.

Suggested Fix

In third_party/angle/src/compiler/translator/ParseContext.cpp, update TParseContext::declareVariable to enforce type constraints on ARM framebuffer fetch built-ins. Specifically, reject the redeclaration if the variable is an array or if the basic type does not match the expected scalar/vector type, mirroring the existing logic for variables like gl_FragDepth.

// In TParseContext::declareVariable, around the ARM built-ins block:
if (type->isArray())
{
    error(line, "built-in variable cannot be redeclared as an array", identifier);
    return false;
}

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


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