Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running cargo afl fuzz does not interpret panics as crashes #499

Closed
BowTiedRadone opened this issue Jul 8, 2024 · 5 comments
Closed

Running cargo afl fuzz does not interpret panics as crashes #499

BowTiedRadone opened this issue Jul 8, 2024 · 5 comments

Comments

@BowTiedRadone
Copy link

BowTiedRadone commented Jul 8, 2024

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!

@BowTiedRadone
Copy link
Author

You can easily clone and try my current setup by visiting the following GitHub repository: rust-aflPlusPlus

@smoelius
Copy link
Member

smoelius commented Jul 9, 2024

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:

afl.rs/afl/src/lib.rs

Lines 49 to 52 in 462eff5

// this marker strings needs to be in the produced executable for
// afl-fuzz to detect `persistent mode` and `defered mode`
static PERSIST_MARKER: &str = "##SIG_AFL_PERSISTENT##\0";
static DEFERED_MARKER: &str = "##SIG_AFL_DEFER_FORKSRV##\0";

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!

@moodmosaic
Copy link

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).

@BowTiedRadone
Copy link
Author

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!

@smoelius
Copy link
Member

+1 Thank you, @moodmosaic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants