Coverage-guided fuzzing across every mainstream engine - libFuzzer (C/C++ in-process), AFL++ (out-of-process, QEMU mode for closed-source binaries), cargo-fuzz (Rust), Go native fuzzing (go test -fuzz), Atheris (Python), and Jazzer (JVM, @FuzzTest). Body covers choosing the right fuzzer for the language and build type (the routing tree) plus the engine-generic workflow: writing a small deterministic fuzz target, seed-corpus + dictionary construction, sanitizer selection (ASan + UBSan default, compatibility matrix), corpus minimisation, crash-artifact handling, and CI smoke-fuzz wiring with a cached corpus. Per-engine depth (flags, harness syntax, CI jobs) lives in references, as do the corpus-management and sanitizer-integration catalogs. Use when a project needs fuzz coverage and no fuzzer is chosen yet, or when authoring / running / maintaining a fuzz campaign with any of these engines. For triaging the resulting crashes see crash-triage-reference.
70
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per-engine reference for coverage-guided-fuzzing; the shared workflow
and fuzzer-choice routing live in ../SKILL.md.
Distinct from libFuzzer (in-process) - AFL++ (per github.com/AFLplusplus/AFLplusplus) fits binaries you can't compile-in (closed-source, multi-language) and tools that process inputs end-to-end (parsers reading files, decoders reading stdin).
For sanitiser pairing see sanitizer-integration.md; for corpus discipline see corpus-management.md.
Per AFL++ README:
# Docker
docker pull aflplusplus/aflplusplus
docker run -ti -v /location/of/your/target:/src aflplusplus/aflplusplus
# Or apt (Debian/Ubuntu)
apt-get install -y afl++For local build see docs/INSTALL.md in the AFL++ repo.
CC=afl-cc CXX=afl-c++ ./configure --disable-shared
make clean allOr LLVM-based (recommended):
CC=afl-clang-fast CXX=afl-clang-fast++ makeafl-clang-fast produces ~3x faster instrumented binaries than
the legacy afl-cc.
Add sanitisers in the standard way:
AFL_USE_ASAN=1 AFL_USE_UBSAN=1 CC=afl-clang-fast \
CFLAGS="-fno-sanitize-recover=all -fno-omit-frame-pointer -g" \
makeAFL_USE_ASAN + AFL_USE_UBSAN env vars pass the sanitiser flags
through AFL's compiler driver.
./afl-fuzz -i seeds/ -o output/ -- ./target @@Per AFL++ README:
-i seeds/ - input seed corpus-o output/ - output directory (created if absent)-- ./target @@ - target binary; @@ is replaced by AFL with
the input filename@@: -- ./target (AFL pipes
the input)| Flag | Effect |
|---|---|
-M name | Master process in parallel campaign |
-S name | Secondary process (slave) |
-x dict.txt | Use dictionary |
-t N | Per-input timeout in ms (default 1000) |
-m N | Memory limit per child (MB) |
-Q | QEMU mode for non-instrumented binaries |
-c path | CMPLog binary (companion mode for collisions) |
-d | "Quick" mode - deterministic mutations skipped |
# Terminal 1 - master
afl-fuzz -i seeds/ -o output/ -M main -- ./target @@
# Terminals 2..N - slaves
afl-fuzz -i seeds/ -o output/ -S slave1 -- ./target @@
afl-fuzz -i seeds/ -o output/ -S slave2 -- ./target @@The master runs deterministic mutations; slaves run random
mutations. Output corpus is shared via the output/ directory.
For closed-source binaries:
afl-fuzz -Q -i seeds/ -o output/ -- ./closed_source_target @@QEMU instruments via dynamic binary translation. ~5x slower than native instrumentation but works on any binary.
Output directory structure:
output/
fuzzer_stats # current run metrics
fuzzer_setup # configuration
plot_data # gnuplot-friendly time series
queue/
id:000000,orig:seed1.bin
id:000001,src:000000,op:... # mutated from id 0 with op
...
crashes/
id:000000,sig:11,src:000123,op:havoc,rep:8
README.txt
hangs/
id:000000,...Crash filename encoding (per AFL++ docs):
id:N - sequence numbersig:S - signal (11=SEGV, 6=SIGABRT, 9=SIGKILL, etc.)src:M - derived from queue entry Mop:X - mutation operator that produced itrep:R - repetition countReproduce a crash:
./target output/default/crashes/id:000000,sig:11,...
# Or for stdin-driven:
./target < output/default/crashes/id:000000,sig:11,...For automated bug filing, parse the sanitiser output (if compiled
with AFL_USE_ASAN=1) via the from-CI-failure workflow in bug-report-template
(qa-bug-repro plugin).
afl-tmin minimises a single crash input:
afl-tmin -i output/default/crashes/id:000000,sig:11,... \
-o minimised.bin \
-- ./target @@afl-cmin minimises the entire queue (corpus minimisation):
afl-cmin -i output/default/queue/ -o minimised_queue/ -- ./target @@- name: Install AFL++
run: docker pull aflplusplus/aflplusplus
- name: Build instrumented target
run: |
docker run -v $PWD:/src aflplusplus/aflplusplus bash -c \
"cd /src && CC=afl-clang-fast AFL_USE_ASAN=1 make"
- name: Smoke fuzz (5 min)
run: |
timeout 300 docker run -v $PWD:/src aflplusplus/aflplusplus \
afl-fuzz -i /src/seeds -o /src/output -- /src/target @@ || true
- name: Upload crashes
uses: actions/upload-artifact@v4
with:
name: afl-crashes
path: output/default/crashes/| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Target compiled without AFL instrumentation | No coverage signal; fuzzer is blind | Use afl-clang-fast or -Q (QEMU) |
-i - (continuation) without checking output state | Hard to resume; lost queue progress | Use canonical -i seeds/ on fresh runs |
| Single AFL slave on a multi-core machine | 80% of cores idle | Run N-1 slaves in parallel |
No AFL_USE_ASAN=1 | Only catches crashes, not memory bugs | Always set sanitiser env vars |
Treating hangs/ as bugs | Often false positives (slow targets, infinite loops in test data) | Investigate; raise -t for slow targets |
Long-running campaign without afl-cmin cycle | Queue bloats; cycle time degrades | Run afl-cmin weekly |
| Mixing AFL++ + libFuzzer corpora | Format incompatibility | Convert via afl-fuzz -i corpus -E ... round-trip |
LLVMFuzzerTestOneInput-like
persistent harnesses approach libFuzzer speed but require
source instrumentation.hangs/ reports many false positives. Slow but valid
inputs sit there until manually triaged.