Skip to content

Commit

Permalink
Change API to be the same as Honggfuzz-rs and eventually AFL.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulGrandperrin authored and Manishearth committed Dec 31, 2019
1 parent 0c45075 commit 785aa9c
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 80 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ os:
- linux
env:
- ARCH=x86_64
- ASAN_OPTIONS=detect_odr_violation=0
notifications:
email: false
script:
Expand Down
16 changes: 8 additions & 8 deletions example/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#![no_main]
use libfuzzer_sys::fuzz;

use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
if data == b"banana!" {
panic!("success!");
}
});
fn main() {
fuzz!(|data: &[u8]| {
if data == b"banana!" {
panic!("success!");
}
});
}
27 changes: 20 additions & 7 deletions example_arbitrary/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
#![no_main]
use libfuzzer_sys::fuzz;

use libfuzzer_sys::fuzz_target;
fn main() {
// Here you can parse `std::env::args and
// setup / initialize your project

fuzz_target!(|data: u16| {
if data == 0xba7 { // ba[nana]
panic!("success!");
}
});
// 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")
});
}
143 changes: 78 additions & 65 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,79 @@

pub use arbitrary;


use std::os::raw::c_char;
use std::os::raw::c_int;
use std::ffi::CString;
use std::{panic, ptr};

extern "C" {
// We do not actually cross the FFI bound here.
#[allow(improper_ctypes)]
fn rust_fuzzer_test_input(input: &[u8]);
// 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 );
}

static mut STATIC_CLOSURE: *const () = ptr::null();

#[doc(hidden)]
#[export_name = "LLVMFuzzerTestOneInput"]
pub fn test_input_wrap(data: *const u8, size: usize) -> i32 {
let test_input = ::std::panic::catch_unwind(|| unsafe {
pub extern "C" fn test_one_input<F>(data: *const u8, size: usize) -> c_int where F: Fn(&[u8]) + panic::RefUnwindSafe {
unsafe {
let data_slice = ::std::slice::from_raw_parts(data, size);
rust_fuzzer_test_input(data_slice);
});
if test_input.err().is_some() {
// hopefully the custom panic hook will be called before and abort the
// process before the stack frames are unwinded.
::std::process::abort();
let closure = STATIC_CLOSURE as *const F;
// 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
}

#[doc(hidden)]
#[export_name = "LLVMFuzzerInitialize"]
pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize {
/// Run libfuzzer with a given closure
///
/// This is the undelying API used by the [`fuzz!()`] macro, use that instead where possible.
pub fn fuzz<F>(closure: F) where F: Fn(&[u8]) + std::panic::RefUnwindSafe + Sync + Send {
// 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::<Vec<_>>();
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::<Vec<*const c_char>>();

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.
//
//
// HACK / FIXME: it would be better to use `-C panic=abort` but it's currently
// 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.
let default_hook = ::std::panic::take_hook();
::std::panic::set_hook(Box::new(move |panic_info| {
let default_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
default_hook(panic_info);
::std::process::abort();
std::process::abort();
}));
0

unsafe {
assert!(STATIC_CLOSURE.is_null());
// save closure capture at static location
STATIC_CLOSURE = Box::into_raw(Box::new(closure)) as *const ();

// call C++ mangled method `fuzzer::FuzzerDriver()`
_ZN6fuzzer12FuzzerDriverEPiPPPcPFiPKhmE(&mut argc, &mut argv, test_one_input::<F>);
}
}

/// Define a fuzz target.
Expand All @@ -61,9 +96,7 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize
/// might fail and return an `Err`, but it shouldn't ever panic or segfault.
///
/// ```no_run
/// #![no_main]
///
/// use libfuzzer_sys::fuzz_target;
/// use libfuzzer_sys::fuzz;
///
/// // Note: `|input|` is short for `|input: &[u8]|`.
/// fuzz_target!(|input| {
Expand All @@ -83,65 +116,45 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize
/// following:
///
/// ```no_run
/// #![no_main]
///
/// use libfuzzer_sys::{arbitrary::{Arbitrary, Unstructured}, fuzz_target};
/// use libfuzzer_sys::{arbitrary, fuzz};
///
/// #[derive(Debug)]
/// #[derive(Debug, arbitrary::Arbitrary)]
/// pub struct Rgb {
/// r: u8,
/// g: u8,
/// b: u8,
/// }
///
/// impl Arbitrary for Rgb {
/// fn arbitrary<U>(raw: &mut U) -> Result<Self, U::Error>
/// where
/// U: Unstructured + ?Sized
/// {
/// let mut buf = [0; 3];
/// raw.fill_buffer(&mut buf)?;
/// let r = buf[0];
/// let g = buf[1];
/// let b = buf[2];
/// Ok(Rgb { r, g, b })
/// }
/// }
///
/// // Write a fuzz target that works with RGB colors instead of raw bytes.
/// fuzz_target!(|color: Rgb| {
/// fuzz!(|color: Rgb| {
/// my_crate::convert_color(color);
/// });
/// # mod my_crate { fn convert_color(_: super::Rgb) {} }
#[macro_export]
macro_rules! fuzz_target {
(|$bytes:ident| $body:block) => {
#[no_mangle]
pub extern "C" fn rust_fuzzer_test_input($bytes: &[u8]) {
$body
}
macro_rules! fuzz {
(|$buf:ident| $body:block) => {
$crate::fuzz(|$buf| $body);
};

(|$data:ident: &[u8]| $body:block) => {
fuzz_target!(|$data| $body);
(|$buf:ident: &[u8]| $body:block) => {
$crate::fuzz(|$buf| $body);
};
(|$buf:ident: $dty: ty| $body:block) => {
$crate::fuzz(|$buf| {
let $buf: $dty = {
use $crate::arbitrary::{Arbitrary, RingBuffer};
let mut buf = match RingBuffer::new($buf, $buf.len()) {
Ok(b) => b,
Err(_) => return,
};

(|$data:ident: $dty: ty| $body:block) => {
#[no_mangle]
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
use libfuzzer_sys::arbitrary::{Arbitrary, RingBuffer};

let mut buf = match RingBuffer::new(bytes, bytes.len()) {
Ok(b) => b,
Err(_) => return,
};

let $data: $dty = match Arbitrary::arbitrary(&mut buf) {
Ok(d) => d,
Err(_) => return,
let d: $dty = match Arbitrary::arbitrary(&mut buf) {
Ok(d) => d,
Err(_) => return,
};
d
};

$body
}
});
};
}

0 comments on commit 785aa9c

Please sign in to comment.