Tutorial
For this tutorial, we are going to fuzz the URL parser rust-url. Our goal here is to find some input generated by the fuzzer such that, when passed to Url::parse
, it causes some sort of panic or crash to happen.
Create a fuzz target
The first thing we’ll do is create a fuzz target in the form of a Rust binary crate. AFL will call the resulting binary, supplying generated bytes to standard input that we’ll pass to Url::parse
.
cargo new --bin url-fuzz-target
cd url-fuzz-target
We’ll need two dependencies in this crate:
url
: the crate we’re fuzzingafl
: not required, but includes a couple utility functions to assist in creating fuzz targets
So add these to the Cargo.toml
file:
[dependencies]
afl = "*"
url = "*"
Now we’ll need to write the source for the fuzz target in src/main.rs
:
#[macro_use]
extern crate afl;
extern crate url;
fn main() {
fuzz!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = url::Url::parse(&s);
}
});
}
fuzz!
is a utility macro provided by the afl
crate that reads bytes from standard input and
passes the bytes to the provided closure.
In the body of the closure, we call Url::parse
with the bytes that AFL generated. If all goes well, url::Url::parse
will return an Ok
containing a valid Url
, or an Err
indicating a Url
could not be constructed from the String
. If Url::parse
panics while parsing the String
, AFL will treat it as a crash and the AFL UI will indicate as such.
One important detail about the fuzz!
macro: if a panic occurs within the body of the closure, the panic will be caught and process::abort
will be subsequently called. Without the call to process::abort
, AFL would not consider the unwinding panic to be a crash.
Build the fuzz target
Normally, one uses cargo build
to compile a Cargo-based Rust project. To get AFL to work with Rust, a few extra compiler flags need to be passed to rustc during the build process. To make this easier, there is an AFL cargo subcommand (provided by the afl
crate) that automatically passes these rustc flags for us. To use it, you’ll do something like:
cargo afl <cargo command>
Since we want to build this crate, we’ll run:
cargo afl build
Provide starting inputs
AFL doesn't strictly require starting inputs, but providing some can make AFL’s job easier since it won’t need to ‘learn’ what a valid URL looks like. To do this, we'll create a directory called in
with a few files (filenames don’t matter) containing valid URLs:
mkdir in
echo "tcp://example.com/" > in/url
echo "ssh://192.168.1.1" > in/url2
echo "http://www.example.com:80/foo?hi=bar" > in/url3
Start fuzzing
To begin fuzzing, we’ll run:
cargo afl fuzz -i in -o out target/debug/url-fuzz-target
The fuzz
subcommand of cargo-afl
is the primary interface for fuzzing Rust code with AFL. For those already familiar with AFL, the fuzz
subcommand of cargo-afl
is identical to running afl-fuzz
.
The -i
flag specifies a directory full of input files AFL will use as seeds.
The -o
flag specifies a directory AFL will write all its state and results to.
The last argument target/debug/url-fuzz-target
specifies the fuzz target binary AFL will call, supplying random bytes to standard input.
As soon as you run this command, you should see AFL’s interface start up:
For more information about this UI and what each of the sections mean, see this resource hosted on the AFL website.
AFL will run indefinitely, so if you want to quit, press CTRL-C
.
Reproducing
Once you have a few crashes collected from running your fuzzer, you can reproduce them by passing them in manually to your test case. This is typically done via stdin
. E.g. for url-fuzz-target
the command would be:
cargo afl run url-fuzz-target < out/default/crashes/crash_file
where out
is the -o
parameter from your fuzz command and crash_file
is an arbitrary file in the crashes
directory.