cargo-fuzz - Fuzzing Rust with libFuzzer Cheatsheet
cargo-fuzz is the standard way to fuzz Rust code. It wires up libFuzzer with a Cargo-native workflow: write a fuzz target as an ordinary Rust function taking arbitrary bytes, run cargo fuzz run, and coverage-guided fuzzing hunts for panics, arithmetic overflows, and — in unsafe code — genuine memory errors. Safe Rust prevents memory corruption, but it does not prevent panics, infinite loops, or logic bugs, and any unsafe block or C dependency reintroduces the full class of memory issues.
Installation
| Step | Command |
|---|
| Install | cargo install cargo-fuzz |
| Nightly required | rustup install nightly (libFuzzer needs nightly) |
| Initialize | cargo fuzz init (in your crate) |
| Verify | cargo fuzz --version |
Creating a Fuzz Target
cargo fuzz init # creates fuzz/ directory
cargo fuzz add parse_input # creates a new target
// fuzz/fuzz_targets/parse_input.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = my_crate::parse(s); // panics here become findings
}
});
Running
| Command | Description |
|---|
cargo fuzz run parse_input | Fuzz the target |
cargo fuzz run parse_input -- -max_total_time=300 | Run for 5 minutes |
cargo fuzz run parse_input -j 8 | 8 parallel jobs |
cargo fuzz list | List targets |
cargo fuzz build | Build without running |
Raw bytes waste time on inputs your parser rejects immediately. The arbitrary crate builds typed values instead:
use arbitrary::Arbitrary;
#[derive(Arbitrary, Debug)]
struct Config { name: String, retries: u8, enabled: bool }
fuzz_target!(|cfg: Config| {
let _ = my_crate::apply_config(cfg);
});
| Benefit | Why |
|---|
| Valid-shaped inputs | Reaches deeper logic |
| Typed targets | Fuzz APIs, not just parsers |
Debug output | Readable reproducers |
Sanitizers
| Sanitizer | Flag |
|---|
| AddressSanitizer (default) | -s address |
| LeakSanitizer | included with ASan |
| MemorySanitizer | -s memory |
| ThreadSanitizer | -s thread |
| None (faster) | -s none |
# Fuzz unsafe FFI code with ASan
cargo fuzz run ffi_target -s address
Corpus and Coverage
| Command | Purpose |
|---|
fuzz/corpus/<target>/ | Where interesting inputs accumulate |
cargo fuzz cmin <target> | Minimize the corpus |
cargo fuzz tmin <target> <input> | Minimize a crashing input |
cargo fuzz coverage <target> | Generate coverage data |
| Seed corpus | Add valid examples to bootstrap |
Seeding the corpus with real, valid inputs is the single most effective way to make fuzzing productive quickly.
Reproducing Crashes
# Crashes land in fuzz/artifacts/<target>/
cargo fuzz run parse_input fuzz/artifacts/parse_input/crash-abc123
# Shrink it to a minimal case
cargo fuzz tmin parse_input fuzz/artifacts/parse_input/crash-abc123
Convert the minimized input into a regular #[test] so the bug stays fixed.
What to Fuzz in Rust
| Good target | Why |
|---|
| Parsers / deserializers | Untrusted input, complex state |
unsafe blocks | Memory safety actually at risk |
| FFI boundaries | C code behind the interface |
| Protocol/state machines | Ordering bugs |
| Compression / encoding | Classic bug territory |
| Less useful | Why |
|---|
| Pure safe arithmetic | Panics are usually obvious |
| Thin wrappers | Little logic to explore |
cargo-fuzz vs Other Fuzzers
| Aspect | cargo-fuzz | AFL++ (afl.rs) | LibAFL | honggfuzz-rs |
|---|
| Engine | libFuzzer | AFL++ | Custom | honggfuzz |
| Setup | Easiest for Rust | Moderate | Build your own | Easy |
| Structured input | arbitrary | arbitrary | Custom types | arbitrary |
| Best for | Standard Rust fuzzing | Persistent/binary modes | Unusual targets | Hardware feedback |
For non-Rust targets see AFL++ or honggfuzz; for custom fuzzers see LibAFL.
Resources