Use when a project needs fuzz testing, a fuzz campaign must be written or maintained, or fuzzer crashes need triage. Picks libFuzzer, AFL++, cargo-fuzz, Go fuzzing, Atheris or Jazzer by language, then covers fuzz targets, seed corpora, dictionaries, sanitizers, crash triage and CI smoke fuzzing.
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Coverage-guided fuzzers mutate inputs, watch which code paths each input reaches, and keep mutating the inputs that find new coverage. Every mainstream engine implements the same loop; they differ in language, process model, and toolchain integration. This umbrella covers choosing the engine, the engine-generic workflow (target → corpus → sanitizers → CI), and links the per-engine references that carry exact flags and harness syntax.
| Engine | Language / niche | Reference |
|---|---|---|
| libFuzzer | C/C++ callable APIs, in-process (also Swift) | references/libfuzzer.md |
| AFL++ | File/stdin-driven binaries, closed-source via QEMU | references/afl-plus-plus.md |
| cargo-fuzz | Rust crates (libFuzzer + cargo, nightly) | references/cargo-fuzz.md |
| Go native | Go packages (go test -fuzz, Go 1.18+) | references/go-native-fuzzing.md |
| Atheris | Python libraries + CPython extensions | references/atheris.md |
| Jazzer | Java / Kotlin / Scala / Groovy (@FuzzTest) | references/jazzer.md |
Shared catalogs: references/corpus-management.md (seed / evolved corpus, dictionaries, minimisation) and references/sanitizer-integration.md (ASan / UBSan / MSan / TSan / LSan flags + compatibility).
Step 1: Identify target language(s).
↓
+-------+----------+--------+--------+-------+--------+--------+
| C/C++ | Rust | Go | Python | JVM | Other | Binary |
| | | | | | | (no |
| | | | | | | source)|
+-------+----------+--------+--------+-------+--------+--------+
↓ ↓ ↓ ↓ ↓ ↓ ↓
Step 2: AFL++
Library cargo- go test Atheris Jazzer Choose -Q mode
function? fuzz -fuzz per LLVM
YES -fsanitize
↓ support
libFuzzer
(in-process)
OR
AFL++ (file-driven)| Target characteristic | Route to |
|---|---|
| C / C++ library with callable function API | libFuzzer |
| C / C++ binary processing files | AFL++ |
| C / C++ source unavailable | AFL++ (-Q QEMU) |
| Rust crate | cargo-fuzz |
| Rust binary processing files | AFL++ |
| Go package | Go native fuzzing |
| Pure Python or CPython native extension | Atheris |
| Java / Kotlin / Scala / Groovy library | Jazzer |
| Swift library | libFuzzer (Swift wraps libFuzzer natively) |
Routing rationale:
Arbitrary
for structured input; requires nightly. Rust binaries (not callable
APIs) → AFL++.testing (Go 1.18+); failing
inputs auto-save as regression fixtures. CGo dependencies → AFL++ -Q.hypothesis-testing, qa-property-based
plugin) is complementary, not competing.@FuzzTest and ships
JVM-level sanitizers (deserialization, SSRF, ReDoS, command injection).Every engine calls your target repeatedly with mutated bytes (or typed values). The rules are engine-independent:
FuzzedDataProvider (libFuzzer / Atheris / Jazzer), Arbitrary
(cargo-fuzz), typed f.Fuzz parameters (Go).Exact harness syntax per engine is in the reference files.
Bootstrap with 5-50 hand-curated diverse inputs (from spec keywords, test fixtures, or PII-scrubbed production samples) and, for structured formats (JSON / XML / SQL / protobuf), a dictionary of grammar tokens - without one the fuzzer slowly rediscovers the grammar. Keep seeds versioned and read-only; let the evolved corpus live in the output directory (CI cache, not the repo). Construction strategies, per-engine directory layouts, and minimisation cadence: references/corpus-management.md.
A fuzzer without sanitizers catches only hard crashes - 80%+ of memory bugs are silent without them. Defaults per language:
-fsanitize=fuzzer,address,undefined -fno-sanitize-recover=all);
MSan needs a separate whole-program-instrumented binary.go test -race) is the TSan-equivalent.Compatibility matrix, build flags, ASAN_OPTIONS / UBSAN_OPTIONS, and
report anatomy:
references/sanitizer-integration.md.
Run locally until coverage plateaus; minimise the corpus periodically
(-merge=1 / afl-cmin) so cycle time stays flat; minimise every crash
input before filing it. Crash artifacts (crash-<sha1> etc.) and their
handling are cataloged in
references/corpus-management.md;
classification and exploitability rules live in references/crash-triage.md.
CI runs a short smoke fuzz (3-5 min per target) on every PR; long campaigns run outside CI. The engine-generic shape:
- uses: actions/cache@v4 # evolved corpus accumulates across runs
with:
path: fuzz/corpus
key: fuzz-corpus-${{ github.sha }}
restore-keys: fuzz-corpus-
- name: Smoke fuzz (5 min)
run: ./fuzz_target -max_total_time=300 fuzz/corpus fuzz/seeds
- uses: actions/upload-artifact@v4
if: always()
with: { name: fuzz-crashes, path: "crash-* leak-* timeout-* oom-*" }Complete per-engine CI jobs (AFL++ Docker, cargo-fuzz nightly matrix, Go
target loop, Jazzer JAZZER_FUZZ, Atheris) are in each engine's
reference file.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Picking AFL++ for a callable C/C++ library API | Out-of-process overhead 10-100x | libFuzzer for in-process |
| Picking libFuzzer for a file-processing binary | Adapter glue is complex; AFL++ handles @@ cleanly | AFL++ |
| Picking cargo-fuzz on stable Rust | Won't compile | Nightly toolchain |
| Fuzzing without sanitizers | Catches only crashes; most bugs silent | Step 3 defaults |
| No seed corpus / no dictionary for structured formats | Fuzzer wanders; slow path discovery | Step 2 |
| Never minimising the corpus | Cycle time degrades; coverage redundant | Weekly -merge=1 / afl-cmin |
| Mixing fuzzer corpora without conversion | libFuzzer / AFL++ formats aren't compatible | One fuzzer's corpus; convert if needed |
| Routing on language alone, ignoring source availability | Closed-source needs QEMU regardless | Factor in availability |