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 fuzzing
  • afl: 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 = { git = "https://github.com/servo/rust-url.git", rev = "bfa167b4e0253642b6766a7aa74a99df60a94048" }

We chose a particular revision of url that is known to have a bug. Your goal is to find it!

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

Like cargo fuzz, cargo afl build will provide the arguments --cfg fuzzing to build each crate in the dependency graph, which will enable any code paths annotated with #[cfg(fuzzing)].

Provide starting inputs

AFL strictly requires starting inputs, and will not execute at all without being provided an input directory containing at least one example input. A high-quality input corpus contains many different examples of valid inputs which exercise different features of the parsing process being fuzzed. Further instruction on crafting an effective input corpus is available in the AFL README, including discussion of the dictionary approach for highly verbose data formats such as HTML.

For this tutorial, we won't be generating a high-quality corpus, but just enough to demonstrate the basic mechanism. To do this, we'll create a directory called in with a few files 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 containing input files AFL will use as seeds.

Inputs can be filtered by their file extension with the -e ext flag.

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.