rust-fuzz/afl.rs

`cargo afl run` dumps core and exits status code 1, nothing reported?

ryanavella opened this issue · 6 comments

I am trying to run the following per the rust-fuzz tutorial:

cargo afl run fuzz-target < out/default/crashes/$CRASH_ID

I'm a new user to afl so I'm not sure if I'm using this correctly, but it produces a core dump and returns status code 1. No stdout or stderr is reported to the user.

Steps to Reproduce (MRE)

cargo install cargo-afl
cargo new fuzz-target
cd fuzz-target
cargo add afl

cat << EOF > src/main.rs
fn main() {
    afl::fuzz!(|b: &[u8]| {
        if !b.starts_with(b"foo"){ panic!(); }
    });
}
EOF

cargo afl build
mkdir in out
echo "foo" > in/foo
cargo afl fuzz -i in -o out target/debug/fuzz-target

Press CTRL+C after at least one crash, and then run the following:

cargo afl run fuzz-target < out/default/crashes/$CRASH_ID

When I run this, it returns exit code 1 and produces a core dump in my current directory.

Meta

rustc --version --verbose:

rustc 1.74.0 (79e9716c9 2023-11-13)
binary: rustc
commit-hash: 79e9716c980570bfd1f666e3b16ac583f0168962
commit-date: 2023-11-13
host: x86_64-unknown-freebsd
release: 1.74.0
LLVM version: 17.0.4

cargo --version --verbose

cargo 1.74.0 (ecb9851af 2023-10-18)
release: 1.74.0
commit-hash: ecb9851afd3095e988daaa35a48bc7f3cb748e04
commit-date: 2023-10-18
host: x86_64-unknown-freebsd
libgit2: 1.7.1 (sys:0.18.0 vendored)
libcurl: 8.4.0-DEV (sys:0.4.68+curl-8.4.0 vendored ssl:OpenSSL/1.1.1u)
ssl: OpenSSL 1.1.1u  30 May 2023
os: FreeBSD 14.0-RELEASE [64-bit]

uname -imrs

FreeBSD 14.0-RELEASE amd64 GENERIC

cargo afl --version

cargo-afl 0.14.5

For what it is worth, here is the backtrace I received from the core file.

Backtrace
* thread #1, name = 'fuzz-target', stop reason = signal SIGABRT
  * frame #0: 0x00002974f184f86a libc.so.7`__sys_thr_kill + 10
    frame #1: 0x00002974f17c7f54 libc.so.7`__raise + 52
    frame #2: 0x00002974f1878fc9 libc.so.7`abort + 73
    frame #3: 0x0000296ccdf04c47 fuzz-target`std::sys::unix::abort_internal::h992b47f24ac6a3af at mod.rs:376:14
    frame #4: 0x0000296ccdf00b37 fuzz-target`std::process::abort::h91b9c46d468f77e0 at process.rs:2279:5
    frame #5: 0x0000296ccdee1067 fuzz-target`afl::fuzz::_$u7b$$u7b$closure$u7d$$u7d$::hc398b556daaba4cd((null)=<unavailable>, (null)=<unavailable>) at lib.rs:63:13
    frame #6: 0x0000296ccdf02bb1 fuzz-target`std::panicking::rust_panic_with_hook::hd2a13c9b64ae7e46 [inlined] _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..Fn$LT$Args$GT$$GT$::call::hafb2dea5d2b54f9b at boxed.rs:2021:9
    frame #7: 0x0000296ccdf02bae fuzz-target`std::panicking::rust_panic_with_hook::hd2a13c9b64ae7e46 at panicking.rs:783:13
    frame #8: 0x0000296ccdf028fe fuzz-target`std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::h9ac9c946c66a8560 at panicking.rs:657:13
    frame #9: 0x0000296ccdf01616 fuzz-target`std::sys_common::backtrace::__rust_end_short_backtrace::hb608c4194996ec9f at backtrace.rs:171:18
    frame #10: 0x0000296ccdf02662 fuzz-target`rust_begin_unwind at panicking.rs:645:5
    frame #11: 0x0000296ccdf21075 fuzz-target`core::panicking::panic_fmt::h103b4a372bcb385a at panicking.rs:72:14
    frame #12: 0x0000296ccdf2122b fuzz-target`core::panicking::panic_explicit::h6b650f40c7ee937b [inlined] core::panicking::panic_display::h9097f4ed2464b41c at panicking.rs:193:5
    frame #13: 0x0000296ccdf211d7 fuzz-target`core::panicking::panic_explicit::h6b650f40c7ee937b at panicking.rs:176:5
    frame #14: 0x0000296ccdee183e fuzz-target`fuzz_target::main::_$u7b$$u7b$closure$u7d$$u7d$::panic_cold_explicit::h70a029568cd12d63 at panic.rs:87:13
    frame #15: 0x0000296ccdee1687 fuzz-target`fuzz_target::main::h10736bdfbb9f4451 [inlined] fuzz_target::main::_$u7b$$u7b$closure$u7d$$u7d$::h75f03b7b2242a18c((null)=<unavailable>, b=<unavailable>) at main.rs:3:36
    frame #16: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 [inlined] afl::fuzz::_$u7b$$u7b$closure$u7d$$u7d$::hfb553315eb64c87d at lib.rs:95:13
    frame #17: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 [inlined] core::ops::function::FnOnce::call_once::h63175571f8ea08dd((null)=<unavailable>, (null)=<unavailable>) at function.rs:250:5
    frame #18: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 [inlined] _$LT$core..panic..unwind_safe..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h8daaed7001155181(self=<unavailable>, (null)=<unavailable>) at unwind_safe.rs:272:9
    frame #19: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at panicking.rs:552:40
    frame #20: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at panicking.rs:516:19
    frame #21: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at panic.rs:142:14
    frame #22: 0x0000296ccdee165c fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at lib.rs:94:25
    frame #23: 0x0000296ccdee1639 fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at main.rs:2:5
    frame #24: 0x0000296ccdee1082 fuzz-target`std::sys_common::backtrace::__rust_begin_short_backtrace::hd1a1d2191216a87b [inlined] core::ops::function::FnOnce::call_once::hba499877655f7534((null)=(fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at main.rs:1), (null)=<unavailable>) at function.rs:250:5
    frame #25: 0x0000296ccdee1080 fuzz-target`std::sys_common::backtrace::__rust_begin_short_backtrace::hd1a1d2191216a87b(f=(fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at main.rs:1)) at backtrace.rs:155:18
    frame #26: 0x0000296ccdee10f8 fuzz-target`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h05e7304dd0fb1beb at rt.rs:167:18
    frame #27: 0x0000296ccdefcaff fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::h49a24f789d973f78 at function.rs:284:13
    frame #28: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::panicking::try::do_call::hd1fc7d1d9bcb32b3 at panicking.rs:552:40
    frame #29: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::panicking::try::h54306f74e4f900f1 at panicking.rs:516:19
    frame #30: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::panic::catch_unwind::hfe0cb6696625b8db at panic.rs:142:14
    frame #31: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h6a7cda9f09ce901f at rt.rs:148:48
    frame #32: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::panickingg::try::do_call::h90a377ba0d121926 at panicking.rs:552:40
    frame #33: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::panicking::try::h643c83836d10a76c at panicking.rs:516:19
    frame #34: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 [inlined] std::panic::catch_unwind::h8eabe8cc22ced1e2 at panic.rs:142:14
    frame #35: 0x0000296ccdefcafc fuzz-target`std::rt::lang_start_internal::hf83a4b5ae177f013 at rt.rs:148:20
    frame #36: 0x0000296ccdee10cc fuzz-target`std::rt::lang_start::h85c9333949cb386e(main=(fuzz-target`fuzz_target::main::h10736bdfbb9f4451 at main.rs:1), argc=1, argv=0x00002974eedfad68, sigpipe='\0') at rt.rs:166:17
    frame #37: 0x00002974f179cafa libc.so.7`__libc_start1 + 298
    frame #38: 0x0000296ccdee0fdd fuzz-target`_start at crt1_s.S:83

I can see it it panics at the appropriate location and then later aborts, so I hope I'm not misunderstanding how afl is intended to be used? I noticed the rust-fuzz tutorial mentions the following:

... if a panic occurs within the body of the closure, the panic will be caught and process::abort will be subsequently called. Without the call to process::abort, AFL would not consider the unwinding panic to be a crash.

So it's not clear to me if cargo afl run is supposed to pretty-print the panic message, or if the intended use-case is for developers who want a quick backtrace through e.g. lldb?

First of all, thank you for the meticulous reproduction instructions!

I agree the behavior you've described/observed is not great.

For comparison, it looks like libfuzzer calls the previous hook before aborting: https://github.com/rust-fuzz/libfuzzer/blob/910a31af2be04fa135da9361a5089b8c39c0b631/src/lib.rs#L89-L93

I opened a PR to emulate this behavior in afl.rs. Could I impose on you to try it?

Unfortunately, because afl.rs's full submodule tree is large, it's best to clone it and refer to it by path. That is:

git clone https://github.com/rust-fuzz/afl.rs
cd afl.rs
git checkout call-existing-panic-hook

And then change the fuzz-target project's afl dependency to:

afl = { path = "path-to-afl-subdirectory-of-cloned-repo" }

Please let me know if this more closely matches what you'd expect, or if you think there is still room for improvement.

Please let me know if this more closely matches what you'd expect, or if you think there is still room for improvement.

This matches what I'd expect, thank you. Out of curiosity, what does the typical use-case of the afl crate look like? I was trying my best to follow the rust-fuzz tutorial, but am wondering if I am using it as you expect normal users would?

I'm mainly using it to catch unexpected panics in the (preferably) non-panicking APIs of my libraries. I ruled out cargo-fuzz and honggfuzz because they don't yet support FreeBSD (my daily driver), while at least afl seemed easier on the surface to get up-and-running on FreeBSD.

Out of curiosity, what does the typical use-case of the afl crate look like?

I'm not sure, TBH.

I think a common use case is to verify the absence of crashes. For example, I think that is what proc-macro2 uses it for: https://github.com/dtolnay/proc-macro2/blob/cd31a69e9c6ece9e6a1bf366aae37ae5b00cef82/fuzz/fuzz_targets/parse_token_stream.rs#L51

Personally, I most often use afl.rs through this wrapper: https://github.com/trailofbits/test-fuzz

The problem you pointed out was in the afl::fuzz! macro. One can dispense with the macro and instead fuzz a binary that reads from standard input. Maybe that contributes to this problem not having come up before?

Sorry I don't have a good answer to your question.

No reason to apologize, this actually helps a lot! I wasn't aware of test-fuzz and I think I'll try it out myself.

I think your PR addresses my issue, so feel free to close.

Thanks for your inquiry, which has improved the codebase!