For this tutorial, we're going to be fuzzing the URL parsing crate 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.

To start, clone the rust-url repository and change directories into it:

git clone https://github.com/servo/rust-url.git
cd rust-url

Although we could fuzz the latest commit on master, we're going to checkout a specific revision that is known to have a parsing bug:

git checkout bfa167b4e0253642b6766a7aa74a99df60a94048

Initialize cargo-fuzz:

cargo fuzz init

This will create a directory called fuzz_targets which will contain a collection of fuzzing targets. It is generally a good idea to check in the files generated by init. Each fuzz target is a Rust program that is given random data and tests a crate (in this case, rust-url). cargo fuzz init automatically generates an initial fuzz target for us. Use cargo fuzz list to view the list of all existing fuzz targets:

cargo fuzz list

The source code for this fuzz target by default lives in fuzz/fuzz_targets/<fuzz target name>.rs. Open that file and edit it to look like this:

#[macro_use] extern crate libfuzzer_sys;
extern crate url;

fuzz_target!(|data: &[u8]| {
    if let Ok(s) = std::str::from_utf8(data) {
        let _ = url::Url::parse(s);

libFuzzer is going to repeatedly call the body of fuzz_target!() with a slice of pseudo-random bytes, until your program hits an error condition (segfault, panic, etc). Write your fuzz_target!() body to hit the entry point you need.

Since the generated data is a byte slice, we'll need to convert it to a UTF-8 &str since rust-url expects that when parsing.

To begin fuzzing, run:

cargo fuzz run <fuzz target name>

Congratulations, you're fuzzing! The output you're seeing is generated by the fuzzer libFuzzer. To learn more about what the output means see the 'output' section in the libFuzzer documentation.

If you leave it going for long enough you'll eventually discover a crash. The output would look something like this:

#56232	NEW    cov: 2066 corp: 110/4713b exec/s: 11246 rss: 170Mb L: 42 MS: 1 EraseBytes-
#58397	NEW    cov: 2069 corp: 111/4755b exec/s: 11679 rss: 176Mb L: 42 MS: 1 EraseBytes-
#59235	NEW    cov: 2072 corp: 112/4843b exec/s: 11847 rss: 178Mb L: 88 MS: 4 InsertByte-ChangeBit-CopyPart-CopyPart-
#60882	NEW    cov: 2075 corp: 113/4953b exec/s: 12176 rss: 183Mb L: 110 MS: 1 InsertRepeatedBytes-
thread '<unnamed>' panicked at 'index out of bounds: the len is 1 but the index is 1', src/host.rs:105
note: Run with `RUST_BACKTRACE=1` for a backtrace.
==70997== ERROR: libFuzzer: deadly signal
    #0 0x1097c5500 in __sanitizer_print_stack_trace (libclang_rt.asan_osx_dynamic.dylib:x86_64+0x62500)
    #1 0x108383d1b in fuzzer::Fuzzer::CrashCallback() (fuzzer_script_1:x86_64+0x10002fd1b)
    #2 0x108383ccd in fuzzer::Fuzzer::StaticCrashSignalCallback() (fuzzer_script_1:x86_64+0x10002fccd)
    #3 0x1083d19c7 in fuzzer::CrashHandler(int, __siginfo*, void*) (fuzzer_script_1:x86_64+0x10007d9c7)
    #33 0x10838b393 in fuzzer::Fuzzer::Loop() (fuzzer_script_1:x86_64+0x100037393)
    #34 0x1083650ec in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (fuzzer_script_1:x86_64+0x1000110ec)
    #35 0x108396c3f in main (fuzzer_script_1:x86_64+0x100042c3f)
    #36 0x7fff91552234 in start (libdyld.dylib:x86_64+0x5234)

NOTE: libFuzzer has rudimentary signal handlers.
      Combine libFuzzer with AddressSanitizer or similar for better crash reports.
SUMMARY: libFuzzer: deadly signal
MS: 2 InsertByte-EraseBytes-; base unit: 3c4fc9770beb5a732d1b78f38cc8b62b20cb997c
artifact_prefix='/private/tmp/rust-url/fuzz/artifacts/fuzzer_script_1/'; Test unit written to /home/user/rust-url/fuzz/artifacts/fuzzer_script_1/crash-e9b1b5183e46a288c25a2a073262cdf35408f697
Base64: aHR0cDovL1s6XTp4xaQBOn8BWcWkDXh4OnhpOgBpOlwNfnhAACUKACkg

The line in the output that starts with http is the input that causes a panic in rust-url.