Super Hero Rust Fuzzing
Summary:
A guide to finding flaws, 0days and bugs in your rust code using fuzzing with code samples.
This article shows you how to fuzz rust code with afl, hongfuzz and libfuzzer.
Rust is a beautiful language but that doesnt mean you cant break things by poking
at them.
Rust kryptonite
Rust is a memory safe language, but safety can also be disabled
and unsafe code can be created by using the “unsafe” function.
Using unsafe rust you can bypass a lot of safety checks the rust compiler does.
Disable the use of unsafe rust:
You can simply tell the compiler to not allow any unsafe rust
in your code by adding
#![forbid(unsafe_code)]
At the beginning of your code.
The awesome folks at the rust core team have
published a book covering the dark arts of rust, linked below:
https://doc.rust-lang.org/stable/nomicon/
Finding Flaws, 0days, and bugs in rust code
How to fuzz rust code
Fuzzing - the art of finding bugs by feeding a program automated generated data
What we want to do is:
* Pic a crate from crates.io
* Pic a function to fuzz, using docs.rs we can easily see a high-level overview of functions we can cherry-pick from
* Feed the function inputs from our fuzzer
When fuzzing with rust you have some options:
AFL
The legendary American Fuzzy loop which has taken home several prices and
found bugs in a countless amount of software.
Fuzzing rust with AFL is pretty straight forward you create a crate: First of we want to install the AFL crate:
# cargo search afl
afl = "0.8.0" # Fuzzing Rust code with american-fuzzy-lop
afl-stat = "0.1.0" # Parsing AFL status file fuzzer_stats
afl-sys = "0.0.0" # Wrapper around AFL source
bolero-afl = "0.5.0" # afl plugin for bolero
afl-plugin = "0.0.0" # LLVM instrumentation compiler plugin for afl.rs
roughenough-fuzz = "0.1.0" # Fuzzing for Roughenough: a Rust implementation
ol
... and 20 crates more (use --limit N to see more)
# cargo install afl
Updating crates.io index
Downloaded afl v0.8.0
Downloaded 1 crate (2.7 MB) in 8.13s
Installing afl v0.8.0
Downloaded xdg v2.2.0
Compiling libc v0.2.71
Compiling semver-parser v0.7.0
Compiling bitflags v1.2.1
Compiling unicode-width v0.1.7
Compiling xdg v2.2.0
Compiling strsim v0.8.0
Compiling ansi_term v0.11.0
Compiling vec_map v0.8.2
Compiling cc v1.0.54
Compiling semver v0.9.0
Compiling textwrap v0.11.0
Compiling rustc_version v0.2.3
Compiling afl v0.8.0
Compiling atty v0.2.14
Compiling clap v2.33.1
Finished release [optimized] target(s) in 1m 54s
Installing /root/.cargo/bin/cargo-afl
Installed package `afl v0.8.0` (executable `cargo-afl`)
cargo new –bin example cd example/
Sample code:
#[macro_use]
extern crate afl;
extern crate mycrate; //the crate we want to fuzz
fn main() {
fuzz!(|data: &[u8]| {
mycrate::myfunction(&data); //call the function you want to fuzz with the input from afl
});
}
Then we need to build it with AFL:
$ cargo afl build --release
AFL wants us to give it a couple of sample inputs so we should give it some legitimate inputs
$ mkdir in && cd in/
$ echo "test0" > 0.txt
$ echo "t[es@t" > 1.txt
$ echo "l0tes" > 2.txt
$ cd ../
Alternatively we could just grab random data from urandom and feed it:
for ((i=1;i<=12;i++)); do
head /dev/urandom | tr -dc A-Za-z0-9 | head -c $i > $i.inp.txt
done
Once all that is done we can now start fuzzing the rust code with AFL:
$ cargo afl fuzz -i in -o out target/release/example
“in” being our directory filled with the input AFL will take
as a template and generate similar input that it will feed the program with.
“out” is our output
Sidenote:
AFL does a lot of writes to the disk and requires a lot of disk usage.
In order to give your hard drives a longer lifetime,
we recommend that you run AFL with a RAM disk system such as tmpfs.
libfuzzer
LibFuzzer is the easiest approach to fuzzing rust code
Example usage:
Install cargo-fuzz and clone the git repository:
$ cargo install cargo-fuzz
$ git clone mygitrepo/
$ cd mygitrepo/
Use cargo-fuzz to initialize and create a fuzzing target
$ cargo fuzz init
$ cargo fuzz list
fuzz_target_1
This will create our fuzzing directory “fuzz” with the file fuzz/fuzz_targets/fuzz_target_1.rs
Sample code:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
use snap::read; //import the crate we want to fuzz
let mut buf = vec![];
read::FrameDecoder::new(data).read_to_end(&mut buf).unwrap();
// fuzzed code goes here
});
start libfuzzer
cargo fuzz run fuzz_target_1 --release -s none --debug-assertions
Sidenote:
You can increase the number of threads being used by passing along the –jobs flag.
Replicate the crash
Once you have found a crash you want to be able to run the program with
the same input the fuzzer created in order to report it to the author
luckily libfuzzer will save all crashes in the artifacts directory
so you can simply view it and analyze it
$ cargo fuzz run fuzz_target_1 fuzz/artifacts/fuzz_target_1/crash-803d11
We tried fuzzing snapd which resulted in breaking it after a few seconds:
hongfuzz
$ cargo install honggfuzz
Create the example directory
$ cargo new --bin example
$ cd example/
add hongfuzz and the crate your fuzzing to Cargo.toml [dependencies] honggfuzz = “0.5”
#[macro_use]
extern crate honggfuzz;
extern crate mycrate; //import the crate you want to fuzz
fn main() {
loop {
fuzz!(|data: &[u8]| {
mycrate::thefunction(&data); // pass the data input in to the function you want to fuzz
});
}
}
Start fuzzing:
$ cargo hfuzz run example
If you want it to stop fuzzing after it has
found a crash you can simply run it as:
HFUZZ_RUN_ARGS="--exit_upon_crash" cargo hfuzz run example
Replicate the crash
Hong fuzz stores the details of the crash in the hfuzz_workspace directory
$ ls hfuzz_workspace/example/*.fuzz
hfuzz_workspace/gh/SIGILL.PC.55555555b0d5.STACK.ded8a
0111.CODE.2.ADDR.55555555b0d5.INSTR.popcnt_%rsi,%rdx.fuzz
$ cargo hfuzz run-debug example hfuzz_workspace/example/SIG
ILL.PC.55555555b0d5.STACK.ded8a
0111.CODE.2.ADDR.55555555b0d5.INSTR.popcnt_%rsi,%rdx.fuzz
Adress Sanitizer
Address sanitizer is a tool written by Google and implemented into several compilers.
That helps detect memory bugs such as buffer overflows and similar memory bugs.
The Address sanitizer is enabled by default in cargo fuzz.
You can disable the address sanitizer
checks with cargo fuzz by passing along the “-s none” flag.
That said we would like to end it quoting one of the most active people in the rust
fuzzing space Sergey “Shnatsel” Davidoff:
if you want to claim some zero-day vulnerability discoveries to
your name, just pick a crate that has unsafe blocks in it, ideally
with something like mem::uninitialized() or vec.set_len(), and give it
a spin in a “run twice, compare results” fuzzing harness with libdiffuzz.
here should be plenty of low-hanging fruit because nobody’s
tried picking any of it yet.
What have people found so far?
A large chunk of bugs have been found in various crates such as:
- 13 bugs in the image crate
- 6 bugs in the claxon crate, one being a security vulnerability(memory discloser)
- A stack overflow vulnerability in the prost crate
- 7 bugs in rust itself (rustc) was found with AFL
- A heap overflow vulnerability in the v_escape crate
Sidenote:
We actually live sync the rust fuzzing trophies into our backend so
that people can use it at rust.firosolutions.com and with other parts
of their system
If you want to go more in-dept on fuzzing we recommend that you read hacker
Charlie millers book:
Fuzzing for Software Security Testing and Quality Assurance
External links:
https://docs.rs/afl/
https://rust.firosolutions.com/
https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html
https://github.com/rust-fuzz/trophy-case
https://medium.com/@shnatsel/auditing-popular-rust-crates-how-a-one-line-unsafe-has-nearly-ruined-everything-fab2d837ebb1
https://medium.com/@shnatsel/how-ive-found-vulnerability-in-a-popular-rust-crate-and-you-can-too-3db081a67fb
https://github.com/seanmonstar/httparse/issues/9
https://github.com/rust-lang-deprecated/rustc-serialize/issues/109
https://github.com/alexcrichton/tar-rs/issues/23
https://github.com/netvl/xml-rs/issues/93