Fuzzing Rust code

And now for something completely different.

ELF is the file format used by most executable binaries on Unix systems, including Linux. For $reasons you want to implement a command-line tool to check whether a file is a valid ELF binary or not. When invoked on an existing file it should return 0 (and output a nice explanation message) if the file is a valid ELF binary, 1 otherwise (and output another nice explanation message).

Oh, look! There is a handy elf_rs crate on crates.io, let's use that as plumbing for implementing your tool.

Exercise 4.a: To begin with:

  1. create a package check_elf containing a library crate,
  2. add to it as a dependency version 0.1.3 of elf_rs (e.g., by running: cargo add elf_rs@0.1.3) and verify in Cargo.toml that the dependency has been added correctly,
  3. in src/lib.ml implement the following functions by filling in their todo!()-s:
#![allow(unused)]
fn main() {
pub fn is_valid_elf(content: &[u8]) -> bool {
    todo!()
}

pub fn is_valid_elf_file<P: AsRef<Path>>(filename: P) -> bool {
    todo!()
}
}

Tips:

  • To check if a file is valid ELF with elf_rs just built an Elf structure using Elf::from_bytes on the bytes (Vec<u8>) read from a file. Iff you obtain an Ok(_) result the file is valid ELF.
  • Use the former function to implement the latter.

In addition to integration tests, Rust packages can contain examples located under the top-level examples/ directory. Each *.rs file in there is a standalone binary crate, with a main() function as its entry point. Individual examples can be built and run using, respectively, cargo build --example=NAME and cargo run --example=NAME. Examples are useful both as sample usage of a library crate and as standalone executables that do something useful.

Exercise 4.b: Create an example file examples/check_elf.rs by completing the following skeleton:

fn main() {
    let filename = std::env::args().nth(1).unwrap();  // get the first argument on the CLI or panic
	todo!();
}

where you can already find an example of how to read the first CLI argument. Instead of todo!() you should use your library crate functions to check if the specified executable is a valid ELF or not. If it is output a message saying so and exit with code 0 (check out the std::process::exit function from the stdlib for that); if it isn't print the opposite message and exit with code 1.

When done test your example program to make sure it behaves like this:

$ cargo run --example=check_elf /bin/ls
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/examples/check_elf /bin/ls`
/bin/ls is a valid ELF file
$ echo $?
0

$ cargo run --example=check_elf ./Cargo.toml 
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/examples/check_elf ./Cargo.toml`
./Cargo.toml is NOT a valid ELF file
$ echo $?
1

Looks like your tool is working! Or is it? Let's use fuzzing to check our robust it is against malformed inputs.

First, you need to install the cargo fuzz sub-command and initialize fuzzing in your check_elf package:

$ rustup toolchain add nightly
$ cargo +nightly fuzz init
$ ls fuzz/fuzz_targets/
fuzz_target_1.rs

Note that you need to use the "nightly" Rust toolchain (and hence install it beforehand if you haven't yet), because cargo fuzz is not fully stable yet. The +nightly flag passed to cargo ensures that the subsequent command (fuzz init in this case) are run using that toolchain.

Exercise 4.c: Edit fuzz_target_1.rs to fuzz the input of your is_valid_elf function. (It should be straightforward to do so, as the type of fuzzed payload and what that function expects are very close.) Now run cargo +nightly fuzz run fuzz_target_1 to fuzz is_valid_elf. It will (most likely) identify a pretty serious issue. What is it? How do you explain an issue like that arising in a Rust program? (Spoiler: you will learn a lot more about this in the next lecture.)

Exercise 4.d: The problem you have encountered has been reported as a bug to the elf_rs maintainers a while ago. Since then, the issue has been fixed and the most recent version of elf_rs at the time of writing (0.3.0) is no longer affected by this nasty problem. Update the version of elf_rs you are using in Cargo.toml and try fuzzing again. Is the problem still there? (Phew!)