fuzzing-rust-expert

star 12.4k

Use this skill to fuzz open source Rust software projects.

google By google schedule Updated 6/12/2026

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.
  • For targets that parse a structured format, generating seeds with a script beats hand-picking a few files — random mutation rarely passes the parser's early checks (note: cargo-fuzz's arbitrary is the better route when the target takes typed data rather than a byte format). See the structured seed generation reference.

Characteristics of good Rust fuzzing harnesses

  1. Targets attack surface: parsers, deserializers, protocol implementations, unsafe blocks, and any API that processes untrusted bytes.
  2. Uses structured fuzzing (arbitrary) when the target expects typed data rather than raw bytes — this dramatically improves coverage.
  3. Handles expected errors: Result and Option should be let-bound and ignored (let _ = ...). Only actual panics are findings.
  4. Avoids panicking on every input: do not use .unwrap() or .expect() on fallible operations driven by fuzz input — these create false positives.
  5. Avoids non-determinism: no rand, no system time, no thread spawning inside the fuzz callback.
  6. Is fast: avoid I/O, heavy allocations, or expensive setup inside the callback; do those outside the fuzz_target! macro if needed with LazyLock / OnceLock.
  7. Considers unsafe code: if the crate has unsafe blocks, write harnesses that exercise those paths — this is where memory bugs can still occur in Rust.
  8. 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/expect on None/Err, index out of bounds, arithmetic overflow in debug mode, explicit panic! calls.
  • unsafe bugs: use-after-free, out-of-bounds reads/writes, undefined behaviour in unsafe blocks — 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 with if let or ?.
  • Run cargo build and cargo test inside 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 clone to COPY to avoid network round-trips.
  • If the crate uses no_std, use libfuzzer-sys's no_std feature and ensure the harness does not rely on std.
  • Document why each entry point was chosen and what class of bugs it may find.
Install via CLI
npx skills add https://github.com/google/oss-fuzz --skill fuzzing-rust-expert
Repository Details
star Stars 12,352
call_split Forks 2,790
navigation Branch main
article Path SKILL.md
More from Creator