name: fuzzing-rust-expert description: Use this skill to fuzz open source Rust software projects.
Fuzzing Rust expert
This skill provides the agent with the knowledge and tools to write, build, and
validate fuzz targets for Rust projects integrated into OSS-Fuzz. Rust fuzzing
uses cargo-fuzz with the libfuzzer_sys crate, which drives libFuzzer.
Fundamental Concepts
OSS-Fuzz base image
Rust projects must use the Rust base builder image:
FROM gcr.io/oss-fuzz-base/base-builder-rust
Set language: rust in project.yaml.
Harness structure
Rust fuzz targets live in fuzz/fuzz_targets/<name>.rs within the crate being
fuzzed. The minimal harness using raw bytes:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
// Call into the target. The fuzzer will mutate `data` on every iteration.
let _ = my_crate::parse(data);
});
For structured fuzzing using the arbitrary crate (preferred when the target
expects typed input):
#![no_main]
use libfuzzer_sys::fuzz_target;
use arbitrary::Arbitrary;
#[derive(Debug, Arbitrary)]
struct MyInput {
header: u8,
payload: Vec<u8>,
flags: u32,
}
fuzz_target!(|input: MyInput| {
let _ = my_crate::process(input.header, &input.payload, input.flags);
});
The Cargo.toml for the fuzz directory must declare dependencies:
# fuzz/Cargo.toml
[package]
name = "my-crate-fuzz"
version = "0.0.0"
edition = "2021"
publish = false
[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1", features = ["derive"] } # only if using structured fuzzing
[dependencies.my-crate]
path = ".."
[[bin]]
name = "fuzz_target_name"
path = "fuzz_targets/fuzz_target_name.rs"
test = false
doc = false
Building in OSS-Fuzz
build.sh uses cargo fuzz build and then copies binaries to $OUT:
# build.sh
cd $SRC/<crate-dir>
cargo fuzz build -O # -O enables release optimisations; important for performance
FUZZ_TARGET_OUTPUT_DIR=$SRC/<crate-dir>/target/x86_64-unknown-linux-gnu/release
for f in fuzz/fuzz_targets/*.rs; do
name=$(basename "${f%.*}")
cp "$FUZZ_TARGET_OUTPUT_DIR/$name" "$OUT/"
done
If the project requires a nightly toolchain, set it in the Dockerfile:
ENV RUSTUP_TOOLCHAIN=nightly
Or pin to a specific nightly for reproducibility:
ENV RUSTUP_TOOLCHAIN=nightly-2025-07-03
Seed corpus and dictionaries
- Place seed files in
fuzz/corpus/<target_name>/within the repo; they are automatically picked up by cargo-fuzz and can be zipped for OSS-Fuzz. - To ship a corpus with OSS-Fuzz copy a zip to
$OUT/<target_name>_seed_corpus.zip. - Dictionaries go to
$OUT/<target_name>.dict.
Characteristics of good Rust fuzzing harnesses
- Targets attack surface: parsers, deserializers, protocol implementations, unsafe blocks, and any API that processes untrusted bytes.
- Uses structured fuzzing (
arbitrary) when the target expects typed data rather than raw bytes — this dramatically improves coverage. - Handles expected errors:
ResultandOptionshould be let-bound and ignored (let _ = ...). Only actual panics are findings. - Avoids panicking on every input: do not use
.unwrap()or.expect()on fallible operations driven by fuzz input — these create false positives. - Avoids non-determinism: no
rand, no system time, no thread spawning inside the fuzz callback. - Is fast: avoid I/O, heavy allocations, or expensive setup inside the
callback; do those outside the
fuzz_target!macro if needed withLazyLock/OnceLock. - Considers
unsafecode: if the crate hasunsafeblocks, write harnesses that exercise those paths — this is where memory bugs can still occur in Rust. - Builds in release mode (
-O): fuzzing in debug mode is much slower.
What Rust fuzzing finds
Rust's ownership model prevents most memory-corruption bugs, but fuzzing still finds:
- Panics:
unwrap/expectonNone/Err, index out of bounds, arithmetic overflow in debug mode, explicitpanic!calls. unsafebugs: use-after-free, out-of-bounds reads/writes, undefined behaviour inunsafeblocks — these are real memory bugs.- Logic errors: incorrect parsing, data corruption, wrong output.
- Hangs: infinite loops triggered by crafted input.
- Stack overflows: deep recursion on adversarial input.
Operational guidelines
- Always validate with:
python3 infra/helper.py build_fuzzers <project> python3 infra/helper.py check_build <project> python3 infra/helper.py run_fuzzer <project> <fuzzer_name> -- -max_total_time=30 - An instant crash usually means a
panic!on every input — check for.unwrap()calls on fuzz-driven paths and replace withif letor?. - Run
cargo buildandcargo testinside the crate before adding the fuzz harness to catch pre-existing compilation errors. - When iterating locally clone the upstream repo and switch the Dockerfile from
RUN git clonetoCOPYto avoid network round-trips. - If the crate uses
no_std, uselibfuzzer-sys'sno_stdfeature and ensure the harness does not rely onstd. - Document why each entry point was chosen and what class of bugs it may find.