Skip to content

preprocessor: guard against OOB read in handlePragma for #pragma STDGL#4342

Merged
arcady-lunarg merged 1 commit into
KhronosGroup:mainfrom
ashketchumwashere:fix/pragma-stdgl-oob-read
Jul 23, 2026
Merged

preprocessor: guard against OOB read in handlePragma for #pragma STDGL#4342
arcady-lunarg merged 1 commit into
KhronosGroup:mainfrom
ashketchumwashere:fix/pragma-stdgl-oob-read

Conversation

@ashketchumwashere

@ashketchumwashere ashketchumwashere commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes an out-of-bounds heap read (SEGV) in glslang::TParseContext::handlePragma() at ParseHelper.cpp:422 when a shader contains #pragma STDGL with fewer than 4 tokens.

Confirmed crash in production (non-ASan) builds — not a sanitizer-only artifact. The _M_p field of the out-of-bounds std::string object always resolves to a non-readable address (NULL under standard allocator; code-segment address under the pool allocator), causing memcmp() to SEGV without any sanitizer involvement.

Also reproducible via the OSS-Fuzz target shaderc_general_fuzzer (google/shaderc) with a 34-byte input.

Root Cause

The condition at line 422 accesses tokens[1] and tokens[3] without first checking tokens.size():

// BEFORE (vulnerable):
} else if (spvVersion.spv > 0 && tokens[0].compare("STDGL") == 0 &&
           tokens[1].compare("invariant") == 0 && tokens[3].compare("all") == 0) {

Every other tokens[N] access in handlePragma() has an explicit size check (lines 340, 366). This branch was the only exception.

For input #pragma STDGL with no trailing tokens:

  • tokens = {"STDGL"}tokens.size() == 1
  • tokens[1] → OOB heap read (32 bytes past vector end)
  • tokens[3] → OOB heap read (96 bytes past vector end)

Fix

// AFTER (fixed):
} else if (spvVersion.spv > 0 && tokens[0].compare("STDGL") == 0 &&
           tokens.size() >= 4 &&
           tokens[1].compare("invariant") == 0 && tokens[3].compare("all") == 0) {

Reproducer

Via glslang directly (requires setEnvClient(EShClientVulkan))

#pragma STDGL

Via shaderc / OSS-Fuzz shaderc_general_fuzzer (34-byte input)

#version 450
#pragma STDGL
void main(){}
# Reproduce via OSS-Fuzz:
git clone https://github.com/google/oss-fuzz /tmp/oss-fuzz-repro
cd /tmp/oss-fuzz-repro
python3 infra/helper.py build_image shaderc
python3 infra/helper.py build_fuzzers --sanitizer address shaderc
printf '#version 450
#pragma STDGL
void main(){}' > /tmp/crash.glsl
python3 infra/helper.py reproduce shaderc shaderc_general_fuzzer /tmp/crash.glsl

ASan output

AddressSanitizer: SEGV on unknown address (READ)
  #0 memcmp
  #1 std::basic_string::compare(char const*)
  #2 glslang::TParseContext::handlePragma()  ParseHelper.cpp:422
  #3 glslang::TPpContext::CPPpragma()        Pp.cpp:884
  #4 glslang::TShader::preprocess()

Production crash (no sanitizer)

Scenario: standard malloc  →  tokens[1]._M_p = NULL
→ memcmp(NULL, "invariant", 57633) → SEGV

Scenario: pool allocator   →  tokens[1]._M_p = non-readable addr
→ memcmp(<bad ptr>, "invariant", N) → SEGV

Test

Added Test/pragma.stdgl.oob.vert covering the three short-token forms that previously triggered the crash.

@ashketchumwashere
ashketchumwashere force-pushed the fix/pragma-stdgl-oob-read branch from 5b81b71 to 1032879 Compare July 18, 2026 08:32
@ashketchumwashere

Copy link
Copy Markdown
Contributor Author

Hi @arcady-lunarg @jeremy-lunarg @saddamr3e — this PR adds a bounds check in handlePragma to guard against an OOB read when #pragma STDGL appears without a following token. No conflicts with main, CLA signed. Would appreciate a review when you get a chance. Thanks!

@dneto0 dneto0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@CLAassistant

CLAassistant commented Jul 22, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@arcady-lunarg arcady-lunarg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to need to add your test to the appropriate list of tests and add an expected result file so the test is actually run.

When a shader contains '#pragma STDGL' with fewer than 4 tokens, the
condition at ParseHelper.cpp:422 accessed tokens[1] and tokens[3]
without first checking tokens.size(), causing an out-of-bounds read
and a SEGV under AddressSanitizer.

Every other tokens[N] access in handlePragma() already has an explicit
size check (see lines 340, 366). Apply the same guard here:

  } else if (spvVersion.spv > 0 && tokens[0].compare("STDGL") == 0 &&
             tokens.size() >= 4 &&
             tokens[1].compare("invariant") == 0 &&
             tokens[3].compare("all") == 0) {

Minimal reproducer (15 bytes, requires Vulkan/SPIR-V target):
  #pragma STDGL

Found by libFuzzer + ASan on fuzz_glslang_direct harness.

Fixes: SEGV in glslang::TParseContext::handlePragma() (ParseHelper.cpp:422)
@ashketchumwashere
ashketchumwashere force-pushed the fix/pragma-stdgl-oob-read branch from 1032879 to 8bf014d Compare July 23, 2026 14:55
@ashketchumwashere

Copy link
Copy Markdown
Contributor Author

Hi @arcady-lunarg — addressed your feedback:

  • Renamed Test/pragma.stdgl.oob.vertTest/spv.pragma.stdgl.oob.vert (the bug requires Vulkan mode / spvVersion.spv > 0, so it belongs in the SPV compile test suite rather than the preprocessor suite)
  • Added "spv.pragma.stdgl.oob.vert" to the CompileVulkanToSpirvTest list in gtests/Spv.FromFile.cpp
  • Added Test/baseResults/spv.pragma.stdgl.oob.vert.out — generated from a build with the fix applied; confirms clean SPIR-V output with no crash

Without the fix, the test crashes (SIGSEGV / exit 139) under Vulkan semantics. With it, it compiles to a minimal SPIR-V module cleanly. Force-pushed to update the PR.

@arcady-lunarg arcady-lunarg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing of this pragma syntax is really not very robust but that's a pre-existing problem that should probably be a separate fix, so I'm inclined to merge this

@arcady-lunarg arcady-lunarg added the kokoro:run Trigger Google bot runs label Jul 23, 2026
@kokoro-team kokoro-team removed the kokoro:run Trigger Google bot runs label Jul 23, 2026
@arcady-lunarg
arcady-lunarg merged commit 85e2c34 into KhronosGroup:main Jul 23, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants