Fuzzing is a great tool for coverage testing, and discovering bugs. Rust has a utility (cargo-fuzz
) for tying into the powerful libFuzzer
and sanitizer libraries packaged with LLVM.
Cargo-fuzz is a tool available through the cargo
toolchain, and requires the nightly compiler. Ensure that the nightly compiler is available on the system where cargo-fuzz is going to be installed.
For example, the following commands utilize rustup and cargo install to setup a fuzzing environment:
rustup install nightly
rustup default nightly
cargo install cargo-fuzz
Cargo-fuzz needs a special file structure to work properly. Luckily, cargo-fuzz comes packed with a tool that makes this setup very simple.
Navigate to the root directory of the package you want to test, then run:
cd /path/to/package/root
cargo-fuzz init [-t first_test_name]
After running the above commands, you should have a fuzz
directory in the current working directory. This directory contains everything cargo-fuzz will need to start fuzzing the package!
Under the fuzz/fuzz_targets
directory, there should be a single Rust source file. If no initial target is provided (the -t
flag used during cargo-fuzz init
), the first test should be called fuzz_target_1.rs
. If a initialization target was provided, the first file will be the supplied target name.
Running fuzz tests is similarly easy to setting up the fuzz directory. From the same directory where the fuzz tests were initialized, run the following command:
cargo-fuzz run <test_name> # do NOT include the .rs from the file name
Since the test harness doesn't include any fuzzing code yet, the test will run indefinitely (no crashes will be found). However, some interesting output will be generated by cargo-fuzz, and the fuzzer will actually run the empty test harness.
For details on cargo-fuzz output, check the great write-up here.
So, the tools are installed, the project directory is instrumented, and there is an empty fuzzing harness. Now what?
Let's fill in the blanks using a simple test case, fuzzing the keccak hash library used by Parity.
First, repeat the steps to initialize a new fuzzing project:
cd /path/to/parity/util/hash
rustup run nightly bash # run a bash shell setup for nightly Rust
cargo-fuzz init -t keccak
Now, there should be a fuzz
directory with an empty fuzz harness called keccak.rs
. Let's open the file:
(fuzz/fuzz_targets/keccak.rs)
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate keccak_hash;
fuzz_target!(|data: &[u8]| {
// fuzz code goes here
});
Most of this is boilerplate we don't have to worry about for now. If we look inside util/hash/lib.rs
, we find the function we're interested in pub fn keccak<T: AsRef<[u8]>>(s: T) -> H256
. Well this is just too easy, keccak()
takes a ref pointer to a u8 slice for input, which is just what cargo-fuzz provides via the fuzz_target!()
macro.
Now we just need to make keccak()
visible to our fuzzer, and supply it with the input supplied in data
:
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate keccak_hash;
use keccak_hash::keccak;
fuzz_target!(|data: &[u8]| {
keccak(data);
});
Then, just run the test from the util/hash
directory:
cargo-fuzz run keccak -- -runs=100
The extra flags on the end are to pipe options to libFuzzer. In this case, we are telling cargo-fuzz to stop after 100 runs. Otherwise, it would run until it finds a crash (unlikely for such a simple fuzzing test).