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.
cargo-fuzz (per github.com/rust-fuzz/cargo-fuzz) requires Rust nightly because libFuzzer integration depends on unstable compiler features.
For sanitiser pairing: cargo-fuzz auto-enables ASan by default (per the cargo-fuzz README). See sanitizer-integration.md for ASan + UBSan composition. For corpus discipline see corpus-management.md.
Arbitrary trait.cargo test.For raw libFuzzer in C/C++ with Rust FFI see libfuzzer.md.
Per the cargo-fuzz README:
# Rust nightly is required
rustup install nightly
# Install cargo-fuzz
cargo install cargo-fuzzIn your crate root:
cargo fuzz initThis creates a fuzz/ subdirectory:
fuzz/
Cargo.toml
fuzz_targets/
fuzz_target_1.rs # generated default targetcargo fuzz add parse_queryCreates fuzz/fuzz_targets/parse_query.rs:
#![no_main]
use libfuzzer_sys::fuzz_target;
use my_crate::parser;
fuzz_target!(|data: &[u8]| {
let _ = parser::parse_query(data);
});Per the cargo-fuzz docs, fuzz_target! is the macro that wires up
the libFuzzer entry point (LLVMFuzzerTestOneInput under the
hood). The closure body is what runs per input.
ArbitraryRaw byte slices work for binary formats; for structured inputs use
the arbitrary crate:
#![no_main]
use libfuzzer_sys::fuzz_target;
use arbitrary::Arbitrary;
#[derive(Debug, Arbitrary)]
struct Request {
host: String,
port: u16,
body: Vec<u8>,
}
fuzz_target!(|req: Request| {
let _ = handle_request(&req.host, req.port, &req.body);
});Add arbitrary = { version = "1", features = ["derive"] } to
fuzz/Cargo.toml.
# Nightly toolchain required
cargo +nightly fuzz run parse_queryThis builds the target with libFuzzer instrumentation + ASan and runs indefinitely.
| Option | Effect |
|---|---|
--release | Release-mode build (faster, less debug info) |
--debug-assertions | Keep debug assertions in release mode |
--sanitizer=<name> | address (default), leak, memory, thread, none |
--jobs=N | Parallel workers |
--no-default-features | Disable default cargo-fuzz features |
-- <libFuzzer-flag> | Pass through to libFuzzer (e.g., -max_total_time=300) |
cargo +nightly fuzz run parse_query -- -max_total_time=300UBSan (via --sanitizer=none + custom RUSTFLAGS) and MSan variants:
see "Sanitiser variants" below.
cargo +nightly fuzz run parse_query \
fuzz/artifacts/parse_query/crash-<sha1>Or:
cargo +nightly fuzz fmt parse_query \
fuzz/artifacts/parse_query/crash-<sha1>
# Prints the crash input in a Rust-readable formatPer cargo-fuzz convention:
fuzz/
corpus/
parse_query/ # evolved corpus
artifacts/
parse_query/
crash-<sha1> # crash artefacts
leak-<sha1>
timeout-<sha1>Sanitiser report format is identical to libFuzzer / ASan - see sanitizer-integration.md "Reading a sanitiser report", and the "Reading a sanitiser report" section below for the ASan report anatomy (bug class, access, stack, allocation site).
cargo fuzz fmt decodes binary inputs into a Rust-readable form
(useful when using Arbitrary - recovers the struct).
Smoke-fuzz every target for 5 min on each PR (nightly toolchain, cached corpus, uploaded artifacts): see "Full CI job" below.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Using stable toolchain | cargo-fuzz needs nightly | rustup install nightly; use cargo +nightly fuzz |
Raw &[u8] for structured input | Mutation hits format errors more than logic | Use Arbitrary + a custom struct |
| Empty seed corpus | Fuzzer wanders; slow path discovery | Drop a few representative inputs in fuzz/corpus/<target>/ |
Ignoring --release | Debug builds slow iteration | Use --release for long campaigns |
No cargo fuzz fmt on crash | Hard-to-read crash inputs | Always cargo fuzz fmt before filing a bug |
Committing fuzz/artifacts/ to repo | Repo bloat | .gitignore artifacts; persist via CI cache |
| Mixing fuzz targets in one file | Cargo treats each fuzz_targets/*.rs as one binary | One file per target |
Arbitrary derive is shallow. Custom types need manual
impl Arbitrary for non-trivial mutation strategies.fuzz/corpus/<target>/ -
no sharing across targets.arbitrary crate -
docs.rs/arbitrary.libfuzzer-sys crate -
docs.rs/libfuzzer-sys.# UBSan via none sanitiser + custom RUSTFLAGS
RUSTFLAGS="-Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=4 -Zsanitizer=undefined" \
cargo +nightly fuzz run --sanitizer=none parse_query
# MSan
cargo +nightly fuzz run --sanitizer=memory parse_queryReport format is identical to libFuzzer / ASan (per clang.llvm.org/docs/AddressSanitizer.html):
==1234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f...
READ of size 4 at 0x7f... thread T0
#0 0x4015a3 in process_input src/parser.rs:42:5
#1 0x4012f0 in rust_fuzzer_test_input parse_query.rs:8:5
0x7f... is located 0 bytes to the right of 16-byte region [0x7f..., 0x7f...)
allocated by thread T0 here:
#0 0x40e7c0 in __interceptor_mallocheap-buffer-overflow, stack-use-after-return,
use-after-free, double-free, memory-leak.jobs:
fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@nightly
- run: cargo install cargo-fuzz
- uses: actions/cache@v4
with:
path: |
fuzz/corpus
~/.cargo/registry
target
key: fuzz-${{ github.sha }}
restore-keys: fuzz-
- name: Smoke fuzz (5 min per target)
run: |
for target in $(cargo fuzz list); do
timeout 300 cargo +nightly fuzz run $target -- -max_total_time=300 || true
done
- uses: actions/upload-artifact@v4
if: always()
with:
name: fuzz-artifacts
path: fuzz/artifacts/