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

Enable keep/reject inputs from the corpus #97

Merged
merged 4 commits into from Oct 18, 2022
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 89 additions & 16 deletions src/lib.rs
Expand Up @@ -14,10 +14,41 @@
pub use arbitrary;
use once_cell::sync::OnceCell;

/// Indicates whether the input should be kept in the corpus or rejected. This
/// should be returned by your fuzz target. If your fuzz target does not return
/// a value (i.e., returns `()`), then the input will be kept in the corpus.
#[derive(Debug)]
pub enum Corpus {
/// Keep the input in the corpus.
Keep,

/// Reject the input and do not keep it in the corpus.
Reject,
}

impl From<()> for Corpus {
fn from(_: ()) -> Self {
Self::Keep
}
}

impl Corpus {
#[doc(hidden)]
/// Convert this Corpus result into the [integer codes used by
/// `libFuzzer`](https://llvm.org/docs/LibFuzzer.html#rejecting-unwanted-inputs).
/// This is -1 for reject, 0 for keep.
pub fn to_libfuzzer_code(self) -> i32 {
match self {
Corpus::Keep => 0,
Corpus::Reject => -1,
}
}
}

extern "C" {
// We do not actually cross the FFI bound here.
#[allow(improper_ctypes)]
fn rust_fuzzer_test_input(input: &[u8]);
fn rust_fuzzer_test_input(input: &[u8]) -> i32;

fn LLVMFuzzerMutate(data: *mut u8, size: usize, max_size: usize) -> usize;
}
Expand All @@ -27,14 +58,17 @@ extern "C" {
pub fn test_input_wrap(data: *const u8, size: usize) -> i32 {
let test_input = ::std::panic::catch_unwind(|| unsafe {
let data_slice = ::std::slice::from_raw_parts(data, size);
rust_fuzzer_test_input(data_slice);
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();

match test_input {
Ok(i) => i,
Err(_) => {
// 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)]
Expand Down Expand Up @@ -86,6 +120,39 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize
/// # mod my_crate { pub fn parse(_: &[u8]) -> Result<(), ()> { unimplemented!() } }
/// ```
///
/// ## Rejecting Inputs
///
/// It may be desirable to reject some inputs, i.e. to not add them to the
/// corpus.
///
/// For example, when fuzzing an API consisting of parsing and other logic,
/// one may want to allow only those inputs into the corpus that parse
/// successfully. To indicate whether an input should be kept in or rejected
/// from the corpus, return either [Corpus::Keep] or [Corpus::Reject] from your
/// fuzz target. The default behavior (e.g. if `()` is returned) is to keep the
/// input in the corpus.
///
fitzgen marked this conversation as resolved.
Show resolved Hide resolved
/// For example:
///
/// ```no_run
/// #![no_main]
///
/// use libfuzzer_sys::{Corpus, fuzz_target};
///
/// fuzz_target!(|input: String| -> Corpus {
/// let parts: Vec<&str> = input.splitn(2, '=').collect();
/// if parts.len() != 2 {
/// return Corpus::Reject;
/// }
///
/// let key = parts[0];
/// let value = parts[1];
/// let _result: Result<_, _> = my_crate::parse(key, value);
/// Corpus::Keep
/// });
/// # mod my_crate { pub fn parse(_key: &str, _value: &str) -> Result<(), ()> { unimplemented!() } }
/// ```
///
/// ## Arbitrary Input Types
///
/// The input is a `&[u8]` slice by default, but you can take arbitrary input
Expand Down Expand Up @@ -139,7 +206,7 @@ macro_rules! fuzz_target {
const _: () = {
/// Auto-generated function
#[no_mangle]
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) -> i32 {
// When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug
// formatting of the input to that file. This is only intended for
// `cargo fuzz`'s use!
Expand All @@ -151,10 +218,11 @@ macro_rules! fuzz_target {
.expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file");
writeln!(&mut file, "{:?}", bytes)
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
return;
return 0;
}

run(bytes)
run(bytes);
0
}

// Split out the actual fuzzer into a separate function which is
Expand All @@ -181,10 +249,14 @@ macro_rules! fuzz_target {
};

(|$data:ident: $dty: ty| $body:block) => {
$crate::fuzz_target!(|$data: $dty| -> () $body);
};

(|$data:ident: $dty: ty| -> $rty: ty $body:block) => {
const _: () = {
/// Auto-generated function
#[no_mangle]
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) -> i32 {
use $crate::arbitrary::{Arbitrary, Unstructured};

// Early exit if we don't have enough bytes for the `Arbitrary`
Expand All @@ -194,7 +266,7 @@ macro_rules! fuzz_target {
// get to longer inputs that actually lead to interesting executions
// quicker.
if bytes.len() < <$dty as Arbitrary>::size_hint(0).0 {
return;
return -1;
}

let mut u = Unstructured::new(bytes);
Expand All @@ -214,20 +286,21 @@ macro_rules! fuzz_target {
Err(err) => writeln!(&mut file, "Arbitrary Error: {}", err),
})
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
return;
return -1;
}

let data = match data {
Ok(d) => d,
Err(_) => return,
Err(_) => return -1,
};

run(data)
let result = ::libfuzzer_sys::Corpus::from(run(data));
result.to_libfuzzer_code()
}

// See above for why this is split to a separate function.
#[inline(never)]
fn run($data: $dty) {
fn run($data: $dty) -> $rty {
$body
}
};
Expand Down