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..1fcf02b 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 { + // 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(|$buf| $body); }; - (|$data:ident: &[u8]| $body:block) => { - fuzz_target!(|$data| $body); + (|$buf:ident: &[u8]| $body:block) => { + libfuzzer_sys::fuzz(|$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(|$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 - } + }); }; }