Running `cargo afl fuzz` does not interpret panics as crashes
Closed this issue · 5 comments
I am working on a Rust
+ afl++
PoC to start using the afl++
fuzzer against Rust binaries. My basic Rust program has a potential out-of-bounds
error. It reads text from a file, and if the string is longer than 100 chars, it crashes intentionally.
I test the cases manually, and it behaves as it should. However, when fuzzing with cargo afl fuzz
the panic is neither detected nor saved in the crashes folder.
Issue
The panic is not caught. Maybe I am missing something (the way of building the Rust project, the way of returning, the way of starting the fuzzing process - maybe I need some particular flags added to the cargo afl fuzz command).
Use Case
The place in the Rust code where the out-of-bounds
error is generated
// Deliberate out-of-bounds access (unsafe)
if buffer.len() >= 100 {
// Introduce a bug: accessing buffer beyond its length
let _crash_trigger = buffer[buffer.len() + 100]; // Cause an out-of-bounds access
}
Manually testing the binary
Passing test case
$ cat ./corpus/test.txt
test
$ ./target/debug/afl-rust ./corpus/test.txt
Buffer content: test
Crashing test case
$ cat ./corpus/crash.txt
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
$ ./target/debug/afl-rust ./corpus/crash.txt
thread 'main' panicked at src/main.rs:29:36:
index out of bounds: the len is 101 but the index is 201
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Building the Rust project
$ cargo clean
$ cargo afl build
Starting the fuzzing process
$ cargo afl fuzz -i ./corpus/ -o ./output ./target/debug/afl-rust @@
Thank you in advance!
You can easily clone and try my current setup by visiting the following GitHub repository: rust-aflPlusPlus
Hi, @BowTiedRadone. Thank you very much for providing the repo and for making the issue easy to reproduce! The easiest way to get your target working would be to use the afl::fuzz!
macro. A more elaborate answer appears below.
There appear to be at least two problems with the provided example.
First, when AFL++ fuzzes a target that doesn't use persistent mode, it writes the data that it generates to the target's standard input. Thus, rather than read a file named on the command line, the example should be made to look something like:
std::io::stdin().read_to_end(&mut buffer)?;
Second, a target binary must contain certain strings for AFL++ to handle it properly:
Lines 49 to 52 in 462eff5
The lack of those strings was causing AFL++ to mishandle the binary afl-rust
in the provided repo.
Most of what follows is ruminations on this second point.
It used to be one could simply add the following to get those strings into their binary:
#[allow(unused_imports)]
use afl::fuzz;
However, this no longer seems to work.
It also used to be that when AFL++ was given a binary without those strings, it would produce a "Looks like the target binary is not instrumented!" error message (see #470 (comment)). However, this also seems to no longer be the case.
Regardless, you are not the first to want to fuzz non-persistently. Hence, afl.rs should provide an easy way to get those strings into a binary.
One final note: since you included the crash file in the corpus, I would expect AFL++ to produce an error message like this:
[-] Hmm, looks like the target binary terminated before we could complete a
handshake with the injected code. You can try the following:
In other words, AFL++ expects corpus files to not cause the target to crash. So if you see that above, that's a sign that things are working!
Hi @smoelius, thanks for the thorough response. Perhaps, all that's needed is setting the RUSTFLAGS
environment variable to include -C panic=abort
:
export RUSTFLAGS="-C panic=abort"
cargo afl build
Coming from Haskell/GHC and .NET/CLR, this was initially surprising to me since the default behavior when a program crashes due to an unhandled exception is typically to abort the program and exit with a non-zero exit code.
It appears that the default behavior in Rust is to unwind the stack, which allows resources to be cleaned up. This does not typically result in a non-zero exit code.
From the book, the -C panic
option can be used to control the panic strategy:
-C panic=unwind (default)
: This strategy will unwind the stack and run destructors.-C panic=abort
: This strategy will abort immediately without unwinding (signaling a crash to the fuzzer, in our case).
Building with the panic flag was successful, and I can now detect crashes while fuzzing the resulting binary of the Rust app. Thank you, @smoelius and @moodmosaic, for your helpful responses!
+1 Thank you, @moodmosaic!