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
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 crash-triage-reference.
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 |
crash-triage-reference sibling's scope, not this skill's.crash-triage-reference - reading + classifying the crashes this workflow produces