This repository is a small, self-contained fuzzing demo designed to be readable and presentable quickly.
It includes:
- a mutation-based fuzzer for CLI programs (
tools/fuzz.py) - a crash minimizer using delta debugging (ddmin) + byte-level simplifications (
tools/reduce.py) - three vulnerable/buggy C targets (
targets/*.c) with seed corpus (seeds/*)
Required:
cmake(3.16+ recommended)- C compiler:
cc/clang/gcc - a CMake backend:
makeorninja python3(standard library only)- POSIX
sh(fortools/repro.sh)
Optional:
clang-format(only for thecformatbuild target)blackpython package (only for thepyformatbuild target)
-
Tools:
tools/fuzz.py- mutation-based fuzzer:- loads initial corpus from
--seeds - repeatedly mutates inputs (bit flips, overwrite chunk, insert/delete, append)
- runs target with timeout
- detects crash by signal termination (
returncode < 0) - saves crashes and “interesting” inputs
- loads initial corpus from
tools/reduce.py- crash minimizer:- validates input reproduces a crash (
returncode < 0) - ddmin: chunk deletions that preserve the crash
- byte-level simplification: replace bytes with simple values + single-byte deletions
- outputs minimized input and a reduction log
- validates input reproduces a crash (
tools/gen_bin_seeds.py- generates safe binary seeds forbin_parserintoseeds/bin_parser/during the build.
-
Targets:
targets/cmd_parser.c- Text command parser target with a stack overflow bug.targets/bin_parser.c- Binary format parser with a destination buffer overflow bug.
cmake -S . -B build
cmake --build buildBinaries are produced in build/bin/
Building with sanitizers (optional):
cmake -S . -B build -DSAN=ON
cmake --build build- Clean build artifacts only:
cmake --build build --target clean - Remove out/ (fuzzer output):
cmake --build build --target clean-out - Remove generated
seeds/bin_parser/:cmake --build build --target clean-seeds - Remove build dir +
out/+ generated bin seeds:cmake --build build --target distclean
All C targets accept the same options:
- stdin mode:
./build/bin/<target> --stdin < input.bin - file mode:
./build/bin/<target> --file input.bin
The fuzzer and reducer use the same two modes.
Behavior:
- supports commands like
PING,ADD ..., andSET ... SET <arg>copies<arg>into a fixed 64-byte stack buffer
Bug:
- uses unsafe
strcpy(fr.buf, arg)with no bounds check - a stack-adjacent guard is checked; if overwritten, the program forces a deterministic SIGSEGV
Seeds:
seeds/cmd_parser/seed1.txt:SET ...input close to buffer boundaryseeds/cmd_parser/seed2.txt:PING
Expected crash symptom:
- terminated by a signal (often
SIGSEGV) - in Python:
returncode < 0(often-11on Linux)
Format:
- bytes 0..3: ASCII
"BINF" - byte 4:
payload_len(u8) - bytes 5..: payload
Bug:
- checks the source length (
n >= 5 + payload_len) - then copies
payload_lenbytes into a fixed 16-byte stack buffer viamemcpy - missing check
payload_len <= 16→ destination overflow - guard corruption forces deterministic SIGSEGV
Seeds:
- generated by
tools/gen_bin_seeds.pyintoseeds/bin_parser/ - the payload is long, but
payload_lenstarts at 15/16 (safe) - mutating only the length byte upward commonly triggers the overflow quickly
Expected crash symptom:
- terminated by a signal (often
SIGSEGV) - in Python:
returncode < 0
A run is considered a crash if:
- it did not time out, and
- the process was terminated by a signal →
returncode < 0
Timeouts are treated as hangs (counted by the fuzzer, not stored as crashes).
If you fuzz with --out out/<name>, the fuzzer creates:
-
out/<name>/crashes/id_000001.bin,id_000002.bin, ...id_000001.bin.info(containsreturncode,signal,duration_s)
-
out/<name>/corpus/id_000001.bin, ...- “interesting” inputs saved by a cheap behavioral fingerprint heuristic
cmake --build build --target run-fuzz-cmd
cmake --build build --target run-fuzz-bincmd_parser (stdin):
python3 tools/fuzz.py --target build/bin/cmd_parser --mode stdin \
--seeds seeds/cmd_parser --out out/cmd_parserbin_parser (file):
python3 tools/fuzz.py --target build/bin/bin_parser --mode file \
--seeds seeds/bin_parser --out out/bin_parserWhat to expect:
- periodic stats:
[stat] iters=... exec/s=... corpus=... crashes=... hangs=...
- when a crash is found:
[CRASH] saved: out/.../crashes/id_XXXXXX.bin (signal=SIG... rc=...)
Example for cmd_parser (stdin mode):
python3 tools/reduce.py --target build/bin/cmd_parser --mode stdin \
--input out/cmd_parser/crashes/id_000001.bin \
--out out/cmd_parser/minimized.bin \
--log out/cmd_parser/reduce.logExample for bin_parser (file mode):
python3 tools/reduce.py --target build/bin/bin_parser --mode file \
--input out/bin_parser/crashes/id_000001.bin \
--out out/bin_parser/minimized.bin \
--log out/bin_parser/reduce.logOutputs:
- minimized input:
.../minimized.bin - reduction log:
.../reduce.log(shows successful ddmin deletions and byte-level steps)
Use tools/repro.sh:
stdin mode:
sh tools/repro.sh --target build/bin/cmd_parser --mode stdin \
--input out/cmd_parser/minimized.binfile mode:
sh tools/repro.sh --target build/bin/bin_parser --mode file \
--input out/bin_parser/minimized.binExpected behavior:
- the process terminates by a signal (shell may print “Segmentation fault” / “Floating point exception”)