preprocessor: fix unbounded MacroExpand/PrescanMacroArg mutual recursion (stack overflow)#4338
Conversation
arcady-lunarg
left a comment
There was a problem hiding this comment.
Before anything else, you're going to need to a) get rid of extraneous whitespace diffs, and b) add a test that exercises your new check.
| // | ||
| /****************************************************************************\ | ||
| Copyright (c) 2002, NVIDIA Corporation. | ||
|
|
There was a problem hiding this comment.
Get rid of these extraneous whitespace changes
There was a problem hiding this comment.
Fixed. I've removed all extraneous whitespace changes and added Test/preprocessor.macro.recursion.vert — a 210-level nested F(F(...F(1)...)) call that exercises the macroExpandDepth guard. With the fix it produces 10 "macro expansion depth limit exceeded" errors cleanly; without it the same input causes a stack overflow (SIGSEGV with 4 MB stack / timeout).
ca5d815 to
0d49458
Compare
|
Hi @arcady-lunarg — just wanted to flag that I've addressed your feedback: all extraneous whitespace changes have been removed and I've added |
dneto0
left a comment
There was a problem hiding this comment.
This looks good to me. Neither GLSL nor C++98 specify a minimum number of macro expansion nesting levels. 200 seems fine.
MacroExpand can call PrescanMacroArg (to pre-expand function-like macro arguments), which in turn calls MacroExpand for any macros found in the argument token stream. With deeply nested macro invocations in the source, this creates a call chain of depth proportional to the nesting level. At 200+ levels the native C++ stack overflows. Add macroExpandDepth to TPpContext (initialized to 0) and a RAII DepthGuard in MacroExpand that increments it on entry and decrements on exit. If the depth reaches maxMacroExpandDepth (200) MacroExpand emits a preprocessor error and returns MacroExpandNotStarted, unwinding the recursion cleanly instead of crashing. Add test preprocessor.macro.recursion.vert: F(x)=x nested 210 times triggers the guard 10 times with 'macro expansion depth limit exceeded' errors; without the guard the same input causes a stack overflow.
0d49458 to
8c6c961
Compare
Summary
Fixes a stack-overflow vulnerability in the GLSL preprocessor where
MacroExpandandPrescanMacroArgcould recurse into each other withoutbound, exhausting the process stack.
Root Cause
macro->busywas set to 1 after thePrescanMacroArgloop(Pp.cpp, previously after
pushInput). ButPrescanMacroArgitself callsMacroExpandto expand tokens it finds in macro arguments. Becausebusy == 0during that pre-scan, a macro whose argument contains aninvocation of itself (directly or through a chain) causes:
until the process stack is exhausted. Confirmed via OSS-Fuzz:
Fix
Fix 1 (root cause) — PpContext.h + Pp.cpp:
Move
macro->busy = 1to before thePrescanMacroArgloop. Thismatches standard C preprocessor semantics (C17 §6.10.3.4): a macro
currently being expanded must not be re-expanded, including during argument
pre-scanning. GCC and Clang both enforce this.
Fix 2 (defense-in-depth) — PpContext.h + Pp.cpp:
Add a
macroExpandDepthcounter (bounded at 200) toTPpContext. A RAIIDepthGuardincrements/decrements it on eachMacroExpandentry. Thisindependently prevents any future unbounded recursion path not caught by
the busy flag, and makes the limit explicit and auditable.
Reproduction