From e6a18755491e926b247afbb96c803aa89baa6ace Mon Sep 17 00:00:00 2001 From: Paul Grandperrin Date: Sat, 28 Apr 2018 16:27:08 +0200 Subject: [PATCH 1/2] Change API to be the same as Honggfuzz-rs and eventually AFL.rs closes rust-fuzz/cargo-fuzz#119 closes rust-fuzz/cargo-fuzz#101 --- .travis.yml | 1 + example/src/main.rs | 27 +++++--- example_arbitrary/src/main.rs | 21 +++++-- src/lib.rs | 113 ++++++++++++++++++++++------------ 4 files changed, 109 insertions(+), 53 deletions(-) diff --git a/.travis.yml b/.travis.yml index e8e86e9..c634ebc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ os: - linux env: - ARCH=x86_64 + - ASAN_OPTIONS=detect_odr_violation=0 notifications: email: false script: diff --git a/example/src/main.rs b/example/src/main.rs index 4b0ed1f..80ddb70 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -1,10 +1,23 @@ -#![no_main] - #[macro_use] extern crate libfuzzer_sys; -fuzz_target!(|data: &[u8]| { - if data == b"banana!" { - panic!("success!"); - } -}); +fn main() { + // Here you can parse `std::env::args and + // setup / initialize your project + + // The fuzz macro gives an arbitrary object (see `arbitrary crate`) + // to a closure-like block of code. + // For performance, it is recommended that you use the native type + // `&[u8]` when possible. + // Here, this slice will contain a "random" quantity of "random" data. + fuzz!(|data: &[u8]| { + if data.len() != 6 {return} + if data[0] != b'q' {return} + if data[1] != b'w' {return} + if data[2] != b'e' {return} + if data[3] != b'r' {return} + if data[4] != b't' {return} + if data[5] != b'y' {return} + panic!("BOOM") + }); +} diff --git a/example_arbitrary/src/main.rs b/example_arbitrary/src/main.rs index fea5ca9..0a45909 100644 --- a/example_arbitrary/src/main.rs +++ b/example_arbitrary/src/main.rs @@ -1,10 +1,19 @@ -#![no_main] - #[macro_use] extern crate libfuzzer_sys; +extern crate arbitrary; + +fn main() { + // Here you can parse `std::env::args and + // setup / initialize your project -fuzz_target!(|data: u16| { - if data == 0xba7 { // ba[nana] + // The fuzz macro gives an arbitrary object (see `arbitrary crate`) + // to a closure-like block of code. + // For performance, it is recommended that you use the native type + // `&[u8]` when possible. + // Here, this slice will contain a "random" quantity of "random" data. + fuzz!(|data: u16| { + if data == 0xba7 { // ba[nana] panic!("success!"); - } -}); + } + }); +} diff --git a/src/lib.rs b/src/lib.rs index 206f030..e660e9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,55 @@ -extern "C" { - #![allow(improper_ctypes)] // we do not actually cross the FFI bound here +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::ffi::CString; - fn rust_fuzzer_test_input(input: &[u8]); +extern "C" { + // This is the mangled name of the C++ function starting the fuzzer + fn _ZN6fuzzer12FuzzerDriverEPiPPPcPFiPKhmE(argc: *mut c_int, argv: *mut *mut *mut c_char, callback: extern fn(*const u8, usize) -> c_int ); } -#[export_name="LLVMFuzzerTestOneInput"] -pub fn test_input_wrap(data: *const u8, size: usize) -> i32 { - ::std::panic::catch_unwind(|| unsafe { +static mut STATIC_CLOSURE: Option> = None; + +// #[no_mangle] +// pub extern "C" fn LLVMFuzzerInitialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { +// 0 +// } + +#[no_mangle] +pub extern "C" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> c_int { + unsafe { let data_slice = ::std::slice::from_raw_parts(data, size); - rust_fuzzer_test_input(data_slice); - }) - .err().map(|_| - // hopefully the custom panic hook will be called before and abort the - // process before the stack frames are unwinded. - ::std::process::abort() - ); + if let Some(ref mut closure) = STATIC_CLOSURE { + // We still catch unwinding panics just in case the fuzzed code modifies + // the panic hook. + // If so, the fuzzer will be unable to tell different bugs appart and you will + // only be able to find one bug at a time before fixing it to then find a new one. + let did_panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + closure(data_slice); + })).is_err(); + + if did_panic { + // hopefully the custom panic hook will be called before and abort the + // process before the stack frames are unwinded. + std::process::abort(); + } + } + } 0 } -#[export_name="LLVMFuzzerInitialize"] -pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { +pub fn fuzz(closure: F) where F: Fn(&[u8]) + std::panic::RefUnwindSafe + 'static { + // Converts env::args() to C format + let args = std::env::args() + .map(|arg| CString::new(arg).unwrap()) // convert args to null terminated C strings + .collect::>(); + let c_args = args.iter() + .map(|arg| arg.as_ptr()) + .chain(std::iter::once(std::ptr::null())) // C standard expects the array of args to be null terminated + .collect::>(); + + let mut argc = c_args.len() as c_int - 1; + let mut argv = c_args.as_ptr() as *mut *mut c_char; + // Registers a panic hook that aborts the process before unwinding. // It is useful to abort before unwinding so that the fuzzer will then be // able to analyse the process stack frames to tell different bugs appart. @@ -28,38 +58,41 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize // impossible to build code using compiler plugins with this flag. // We will be able to remove this code when // https://github.com/rust-lang/cargo/issues/5423 is fixed. - ::std::panic::set_hook(Box::new(|_| { - ::std::process::abort(); + std::panic::set_hook(Box::new(|_| { + std::process::abort(); })); - 0 + + unsafe { + // save closure at static location + STATIC_CLOSURE = Some(Box::new(closure)); + + // call C++ mangled method `fuzzer::FuzzerDriver()` + _ZN6fuzzer12FuzzerDriverEPiPPPcPFiPKhmE(&mut argc, &mut argv, LLVMFuzzerTestOneInput); + } } #[macro_export] -macro_rules! fuzz_target { - (|$bytes:ident| $body:block) => { - #[no_mangle] - pub extern fn rust_fuzzer_test_input($bytes: &[u8]) { - $body - } +macro_rules! fuzz { + (|$buf:ident| $body:block) => { + libfuzzer_sys::fuzz(move |$buf| $body); }; - (|$data:ident: &[u8]| $body:block) => { - fuzz_target!(|$data| $body); + (|$buf:ident: &[u8]| $body:block) => { + libfuzzer_sys::fuzz(move |$buf| $body); }; - (|$data:ident: $dty: ty| $body:block) => { - extern crate arbitrary; - - #[no_mangle] - pub extern fn rust_fuzzer_test_input(bytes: &[u8]) { - use arbitrary::{Arbitrary, RingBuffer}; - - let $data: $dty = if let Ok(d) = RingBuffer::new(bytes, bytes.len()).and_then(|mut b|{ - Arbitrary::arbitrary(&mut b).map_err(|_| "") - }) { - d - } else { - return + (|$buf:ident: $dty: ty| $body:block) => { + libfuzzer_sys::fuzz(move |$buf| { + let $buf: $dty = { + use arbitrary::{Arbitrary, RingBuffer}; + if let Ok(d) = RingBuffer::new($buf, $buf.len()).and_then(|mut b|{ + Arbitrary::arbitrary(&mut b).map_err(|_| "") + }) { + d + } else { + return + } }; + $body - } + }); }; } From 9c6fcf3a6a3b53766eb3216d9c57bbbac05b41df Mon Sep 17 00:00:00 2001 From: Paul Grandperrin Date: Sat, 28 Apr 2018 20:32:24 +0200 Subject: [PATCH 2/2] Use more unsafe code to remove the 'static bound on the user's closure --- src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e660e9d..d80ed24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ extern "C" { fn _ZN6fuzzer12FuzzerDriverEPiPPPcPFiPKhmE(argc: *mut c_int, argv: *mut *mut *mut c_char, callback: extern fn(*const u8, usize) -> c_int ); } -static mut STATIC_CLOSURE: Option> = None; +static mut STATIC_CLOSURE: Option<&mut FnMut(&[u8])> = None; // #[no_mangle] // pub extern "C" fn LLVMFuzzerInitialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { @@ -37,7 +37,7 @@ pub extern "C" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> c_int 0 } -pub fn fuzz(closure: F) where F: Fn(&[u8]) + std::panic::RefUnwindSafe + 'static { +pub fn fuzz(closure: &mut F) where F: FnMut(&[u8]) + std::panic::RefUnwindSafe { // Converts env::args() to C format let args = std::env::args() .map(|arg| CString::new(arg).unwrap()) // convert args to null terminated C strings @@ -63,8 +63,8 @@ pub fn fuzz(closure: F) where F: Fn(&[u8]) + std::panic::RefUnwindSafe + 'sta })); unsafe { - // save closure at static location - STATIC_CLOSURE = Some(Box::new(closure)); + // save closure at static location and forget its lifetime + STATIC_CLOSURE = Some(std::mem::transmute(closure as &mut FnMut(&[u8]))); // call C++ mangled method `fuzzer::FuzzerDriver()` _ZN6fuzzer12FuzzerDriverEPiPPPcPFiPKhmE(&mut argc, &mut argv, LLVMFuzzerTestOneInput); @@ -74,13 +74,13 @@ pub fn fuzz(closure: F) where F: Fn(&[u8]) + std::panic::RefUnwindSafe + 'sta #[macro_export] macro_rules! fuzz { (|$buf:ident| $body:block) => { - libfuzzer_sys::fuzz(move |$buf| $body); + libfuzzer_sys::fuzz(&mut |$buf| $body); }; (|$buf:ident: &[u8]| $body:block) => { - libfuzzer_sys::fuzz(move |$buf| $body); + libfuzzer_sys::fuzz(&mut |$buf| $body); }; (|$buf:ident: $dty: ty| $body:block) => { - libfuzzer_sys::fuzz(move |$buf| { + libfuzzer_sys::fuzz(&mut |$buf| { let $buf: $dty = { use arbitrary::{Arbitrary, RingBuffer}; if let Ok(d) = RingBuffer::new($buf, $buf.len()).and_then(|mut b|{