cargo-fuzz-rust
Author and run cargo-fuzz - Rust fuzzing via libFuzzer with cargo integration. Covers `cargo install cargo-fuzz`, `cargo fuzz init` + `cargo fuzz add {target}` for harness scaffolding, the `fuzz_target!` macro for entry-point declaration, the `Arbitrary` trait for structured input mutation, and `cargo fuzz run` invocation. Requires Rust nightly. Use for fuzz testing Rust libraries - cargo-fuzz wraps libFuzzer with native Rust ergonomics.
Install with skills.sh (any agent)
npx skills add testland/qa --skill cargo-fuzz-rustcargo-fuzz-rust
Overview
cargo-fuzz (per github.com/rust-fuzz/cargo-fuzz (opens in new window)) 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 sanitiser-integration-reference for ASan + UBSan composition. For corpus discipline see corpus-management-reference.
When to use
For raw libFuzzer in C/C++ with Rust FFI see libfuzzer-cpp.
Authoring
Install
Per the cargo-fuzz README:
# Rust nightly is required
rustup install nightly
# Install cargo-fuzz
cargo install cargo-fuzzInitialise
In your crate root:
cargo fuzz initThis creates a fuzz/ subdirectory:
fuzz/
Cargo.toml
fuzz_targets/
fuzz_target_1.rs # generated default targetAdd a fuzz target
cargo 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.
Structured input via Arbitrary
Raw 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.
Running
Basic run
# Nightly toolchain required
cargo +nightly fuzz run parse_queryThis builds the target with libFuzzer instrumentation + ASan and runs indefinitely.
Common options
| 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=300Sanitiser variants
UBSan (via --sanitizer=none + custom RUSTFLAGS) and MSan variants: see references/crash-reports-and-ci.md.
Reproducing a crash
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 formatCrash artefacts location
Per cargo-fuzz convention:
fuzz/
corpus/
parse_query/ # evolved corpus
artifacts/
parse_query/
crash-<sha1> # crash artefacts
leak-<sha1>
timeout-<sha1>Parsing results
Sanitiser report format is identical to libFuzzer / ASan - see sanitiser-integration-reference "Reading a sanitiser report", and references/crash-reports-and-ci.md 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).
CI integration
Smoke-fuzz every target for 5 min on each PR (nightly toolchain, cached corpus, uploaded artifacts): see references/crash-reports-and-ci.md.
Anti-patterns
| 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 |
Limitations
References
cargo-fuzz: sanitiser variants, crash reports, and CI
View source (opens in new window)cargo-fuzz: sanitiser variants, crash reports, and CI
Deep reference for cargo-fuzz-rust. The core install / init / add / run / reproduce workflow lives in the skill spine; this file holds the advanced sanitiser variants, the ASan report anatomy, and the CI job.
Sanitiser variants
# 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_queryReading a sanitiser report
Report format is identical to libFuzzer / ASan (per clang.llvm.org/docs/AddressSanitizer.html (opens in new window)):
==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_mallocCI integration
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/Related skills
afl-plus-plus
Author and run AFL++ - out-of-process coverage-guided fuzzer (a community fork of Google's original AFL with improved mutations and instrumentation). Covers afl-cc / afl-clang-fast instrumented build, afl-fuzz invocation, parallel master/slave (-M / -S), dictionary support (-x), QEMU mode (-Q) for binaries without source, output structure (queue / crashes / hangs), crash minimisation (afl-tmin), corpus minimisation (afl-cmin), crash filename triage, and CI integration. Use for fuzzing standalone binaries (file processors, command-line tools) where libFuzzer's in-process model doesn't fit; for cross-fuzzer corpus strategy see corpus-management-reference.
atheris-python-fuzzing
Author and run Atheris - Google's Python coverage-guided fuzzer built on libFuzzer. Covers pip installation, atheris.Setup + atheris.Fuzz invocation, TestOneInput(data: bytes) target signature, FuzzedDataProvider for structured input, instrument_imports() / instrument_func decorators for coverage instrumentation, and libFuzzer-passthrough flags (-atheris_runs, -max_total_time, -dict). Use for fuzzing Python libraries - also supports CPython native-extension fuzzing.
corpus-management-reference
Pure-reference catalog of fuzz-corpus management practices. Defines what a corpus is (seed corpus + evolved corpus saved by the fuzzer), corpus directory layout per libFuzzer / AFL++ / Go native / cargo-fuzz / OSS-Fuzz, the canonical crash-artefact naming (crash-{sha1} / leak-{sha1} / timeout-{sha1}), seed corpus construction strategies (sample-from-prod, sample-from-test-fixtures, from-spec-keywords), corpus minimisation, dictionary files, and the OSS-Fuzz integration corpus sync. Use as the corpus-discipline reference when building a fuzz target or maintaining a long-running fuzz campaign.
crash-triage-reference
Pure-reference catalog for manually triaging individual fuzzer crash artifacts - reading ASan, UBSan, and MSan output; classifying findings as LIKELY-EXPLOITABLE, MEDIUM, or BENIGN; deduplicating by stack-hash; and minimizing reproducers with -minimize_crash. Use when you need to understand what a specific crash means, build exploitability intuition, or manually work a small set of findings. For automated bulk triage across a full artifact directory, run automated findings triage instead.
fuzz-tool-selector
Routes a fuzz-target authoring task to the right fuzzer for the detected language and build type. Decision tree: C/C++ → libfuzzer-cpp + afl-plus-plus; Rust → cargo-fuzz-rust (or libfuzzer-cpp via FFI); Go → go-native-fuzzing; Python → atheris-python-fuzzing; JVM → jazzer-jvm-fuzzing; closed-source binary → afl-plus-plus in QEMU mode; mature open-source project → ossfuzz-integration. Use when a project needs coverage-guided fuzzing and no fuzzer has been chosen for its language or toolchain yet.
go-native-fuzzing
Author and run Go's native fuzzing (Go 1.18+) - coverage-guided fuzzing built into the standard testing package via FuzzXxx functions. Covers f.Add seed-corpus declaration, f.Fuzz callback signature with typed parameters, testdata/fuzz/{FuzzXxx}/ directory layout for seeds + regression cases, the -fuzz flag for `go test`, and CI integration via short smoke runs. Use for fuzz testing Go libraries - Go's native approach integrates seamlessly with standard `go test` rather than requiring a separate toolchain like AFL++.
jazzer-jvm-fuzzing
Author and run Jazzer - Code Intelligence's JVM coverage-guided fuzzer built on libFuzzer. Covers Maven / Gradle / standalone JAR installation, the @FuzzTest annotation (JUnit 5 integration), typed parameter mutation (String, primitives, byte[]), built-in JVM sanitisers (SSRF / path traversal / OS command injection / deserialization gadget / ReDoS), and the JAZZER_FUZZ=1 env var to switch between regression and fuzzing modes. Use for fuzz testing Java / Kotlin libraries - particularly effective against parsing, deserialization, and HTTP-handling code.
libfuzzer-cpp
Author and run LLVM libFuzzer for C/C++ - in-process coverage-guided fuzzing. Covers harness authoring (LLVMFuzzerTestOneInput entry point), build with -fsanitize=fuzzer,address,undefined, runtime flags (-max_total_time, -runs, -dict, -fork, -workers), corpus + crash-artefact handling, and CI integration. Use for libraries / parsers / decoders in C/C++ where in-process fuzzing of a function is the right scope. Compose with ASan + UBSan from sanitiser-integration-reference and corpus discipline from corpus-management-reference.
ossfuzz-integration
Author and submit a project to Google OSS-Fuzz - the open-source continuous fuzzing service that runs libFuzzer / AFL++ / Honggfuzz campaigns on Google infrastructure 24x7. Covers the project.yaml + Dockerfile + build.sh contract, the $OUT/$WORK conventions, supported languages + sanitisers, seed-corpus + dictionary submission, the OSS-Fuzz Build Status dashboard, and the disclosure SLA (issues filed in Monorail with 90-day deadline). Use to offload long-running fuzz campaigns to dedicated infrastructure rather than self-hosting.
sanitiser-integration-reference
Pure-reference catalog of compiler sanitisers used with fuzz testing - AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), MemorySanitizer (MSan), ThreadSanitizer (TSan), and LeakSanitizer (LSan). Explains what each detects, compatibility (can ASan + UBSan combine? - yes; ASan + MSan? - no), build flags, runtime options (ASAN_OPTIONS / UBSAN_OPTIONS env vars), and the typical ~2x slowdown per ASan. Use to pick the right sanitiser per fuzz target, configure the build, and interpret crash reports.