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:
- create a package
check_elf
containing a library crate, - 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 inCargo.toml
that the dependency has been added correctly, - in
src/lib.ml
implement the following functions by filling in theirtodo!()
-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 anElf
structure usingElf::from_bytes
on the bytes (Vec<u8>
) read from a file. Iff you obtain anOk(_)
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!)