You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fuzz the GDL reader with libFuzzer; fix 5 reader UB/DoS bugs
Add an opt-in libFuzzer harness (test/fuzz/, -DXCONQ_FUZZ=ON, Clang only)
for the GDL lisp reader -- the parser that untrusted downloaded modules,
save files, and network transfer-protocol messages all feed through.
fuzz_gdl.cc drives read_form_from_string() (the lowest reader entry) over
the whole input buffer; it links a fuzz-instrumented copy of the low-level
reader objects plus fuzz_stubs.cc (fake high-level callbacks, mirroring
imf2imf.cc's standalone-reader link closure). Interpreting parsed forms
(interp_form in read.cc) is not fuzzed: it needs full world state and does
file I/O, so a harness over it would surface init-order artifacts.
Corpus seeds regression cases + small real GDL modules; gdl.dict is ~1000
GDL tokens extracted from kernel/*.def by gen_dict.sh. A non-optional CI
`fuzz` leg builds the harness, replays the corpus (the fuzz-labelled CTest),
and runs ~5 min of live fuzzing under ASan+UBSan. Longer runs / OSS-Fuzz
documented in test/fuzz/README.md.
Two campaigns (a shallow-bug burst, then a post-fix run to coverage
saturation) surfaced five memory-safety / UB bugs, all in kernel/lisp.cc,
each reachable from a truncated or hostile module/save/network message:
- Infinite allocation loop (DoS) on an unclosed list ending in a symbol
(e.g. "(foo"): strmgetc returns EOF at a string's terminating NUL without
consuming, but strmungetc decremented the position unconditionally, so
pushing that EOF back re-exposed the previous char and read_list re-read
it forever, consing without bound. Fixed: strmungetc(EOF) is now a no-op,
as in stdio's ungetc.
- Signed overflow accumulating a long digit run (num*10 + digit); saturates
at INT_MAX, with (ch - '0') parenthesized so precedence can't overflow the
intermediate near INT_MAX.
- Signed overflow in the decimal factor*num step; saturates.
- Left shift of a negative value in dice bit-packing ((dice-2)<<7 etc.) for
out-of-range dice like "1d"/"1d1" (already warned, then computed anyway);
fields packed as unsigned now, bit-identical for valid dice.
- Signed overflow accumulating a long octal escape (8*octch + digit); only
the low byte is kept, so it accumulates as unsigned now.
All behavior-preserving for valid input: the quick ctest lane (555 tests,
incl. check-save's save/restore fidelity comparison) stays green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0 commit comments