Writing harnesses #
In the following, we will go over Rust specific tips to optimize the results from your harnesses. For general advice, refer to Writing harnesses.
Structure-Aware fuzzing with the arbitrary crate #
The
arbitrary crate simplifies writing fuzzing harnesses. By deriving a macro, Rust structs can be targeted for fuzzing. For example, the following code requires constructing a Name struct that owns a String. We derived the Arbitrary macro to facilitate the construction of such a Name.
use arbitrary::{Arbitrary};
#[derive(Debug, Arbitrary)]
pub struct Name {
data: String
}
impl Name {
pub fn check_buf(&self) {
let data = self.data.as_bytes();
if data.len() > 0 && data[0] == b'a' {
if data.len() > 1 && data[1] == b'b' {
if data.len() > 2 && data[2] == b'c' {
process::abort();
}
}
}
}
}
your_project that uses the arbitrary crateWith the arbitrary crate, we can easily write a fuzzing harness for this test, similar to the harness in Write a Fuzz test.
#![no_main]
use libfuzzer_sys::fuzz_target;
use arbitrary::{Arbitrary, Unstructured};
fn harness(data: &[u8]) {
// Wrap it in an `Unstructured`.
let mut unstructured = Unstructured::new(data);
// Generate an `Name` and run our checks.
if let Ok(name) = your_project::Name::arbitrary(&mut unstructured) {
name.check_buf();
}
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
The cargo-fuzz tool actually supports the arbitrary crate, so we can simplify this.
#![no_main]
use libfuzzer_sys::fuzz_target;
fn harness(data: &your_project::Name) {
data.check_buf();
}
fuzz_target!(|data: your_project::Name| {
harness(&data);
});
Both of the above examples require the arbitrary crate to be a dependency of your library crate. In the first example, you also need to add the dependency to the Cargo.toml in the fuzz/ directory. The second example does not require this because the arbitrary dependency of the libfuzzer_sys dependency is used. Here is the dependency declaration you need:
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
As usual, the fuzz test can be started using the following command:
cargo +nightly fuzz run fuzz_target_1
The arbitrary crate essentially offers a way to deserialize byte arrays to Rust structs. However, it is limited in that it does not offer the reverse function: serializing Rust structs to byte arrays. This becomes a problem when trying to prepare a corpus of seeds. It is not possible to purposefully construct byte-arrays that construct a specific Rust struct.
Therefore, the arbitrary crate is useful only when starting from an empty corpus. This is not an issue when using cargo-fuzz because it uses libFuzzer internally, and libFuzzer supports starting from an empty corpus. However, other fuzzers like AFL++ require a seed input.
Initialization code #
The fuzz_target! macro supports an init parameter that runs a block of code once before fuzzing begins. This corresponds to
LLVMFuzzerInitialize in libFuzzer.
Use init when your harness depends on one-time setup that should not repeat for every input, i.e., any expensive initialization of read-only global state that the harness requires. As suggested in
practical harness rules, keep in mind that reproducibility may be compromised if the SUT mutates the global state.
#![no_main]
use libfuzzer_sys::fuzz_target;
use std::sync::OnceLock;
static CHECK_BUF: OnceLock<project::CheckBufSlowInit> = OnceLock::new();
fuzz_target!(
init: {
CHECK_BUF.set(project::CheckBufSlowInit::new()).unwrap();
},
|data: &[u8]| {
let check_buf = CHECK_BUF.get().unwrap();
check_buf.check(data);
}
);
For full syntax details, see the
fuzz_target! macro documentation.