Skip to content

Commit

Permalink
feat: improve clusterfuzz error message detection
Browse files Browse the repository at this point in the history
LLVMFuzzerTestOneInput is not supposed to ever return something other
than 0. Instead, the whole process should abort in case of a bug being
detected.

Hence, this replaces the previous returning of 1 (which triggered a
libfuzzer assertion downstream, that was detected by clusterfuzz as the
root cause of the error) by an abort (which lets clusterfuzz detect the
actual cause of the error in the panic message)
  • Loading branch information
Ekleog authored and Ekleog-NEAR committed Jul 13, 2023
1 parent ea11476 commit 31dbbaa
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 34 deletions.
58 changes: 25 additions & 33 deletions bolero-libfuzzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub mod fuzzer {
pub fn LLVMFuzzerStartTest(a: c_int, b: *const *const c_char) -> c_int;
}

static mut TESTFN: Option<&mut dyn FnMut(&[u8]) -> bool> = None;
static mut TESTFN: Option<&mut dyn FnMut(&[u8])> = None;

#[derive(Debug, Default)]
pub struct LibFuzzerEngine {
Expand Down Expand Up @@ -53,42 +53,37 @@ pub mod fuzzer {

let driver_mode = self.driver_mode;

start(&mut |slice: &[u8]| -> bool {
start(&mut |slice: &[u8]| {
let mut input = ByteSliceTestInput::new(slice, driver_mode);

match test.test(&mut input) {
Ok(_) => true,
Err(error) => {
eprintln!("test failed; shrinking input...");

let shrunken =
test.shrink(slice.to_vec(), None, driver_mode, self.shrink_time);

if let Some(shrunken) = shrunken {
eprintln!("{:#}", shrunken);
} else {
eprintln!(
"{:#}",
TestFailure {
seed: None,
error,
input
}
);
}

false
if let Err(error) = test.test(&mut input) {
eprintln!("test failed; shrinking input...");

let shrunken = test.shrink(slice.to_vec(), None, driver_mode, self.shrink_time);

if let Some(shrunken) = shrunken {
eprintln!("{:#}", shrunken);
} else {
eprintln!(
"{:#}",
TestFailure {
seed: None,
error,
input
}
);
}

std::process::abort();
}
})
}
}

fn start<F: FnMut(&[u8]) -> bool>(run_one_test: &mut F) -> Never {
fn start<F: FnMut(&[u8])>(run_one_test: &mut F) -> Never {
unsafe {
TESTFN = Some(std::mem::transmute(
run_one_test as &mut dyn FnMut(&[u8]) -> bool,
));
// The transmute extends the lifetime of run_one_test
TESTFN = Some(std::mem::transmute(run_one_test as &mut dyn FnMut(&[u8])));
}

// Libfuzzer can generate multiple jobs that can make the binary recurse.
Expand Down Expand Up @@ -138,11 +133,8 @@ pub mod fuzzer {
#[no_mangle]
pub unsafe extern "C" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32 {
let data_slice = std::slice::from_raw_parts(data, size);
if (TESTFN.as_mut().expect("uninitialized test function"))(data_slice) {
0
} else {
1
}
(TESTFN.as_mut().expect("uninitialized test function"))(data_slice);
0
}

#[doc(hidden)]
Expand Down
4 changes: 3 additions & 1 deletion cargo-bolero/src/build_clusterfuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ impl BuildClusterfuzz {

for test_name in test_names {
// : is not in VALID_TARGET_NAME_REGEX ; so we don’t use it and make sure to end in _fuzzer so we get picked up as a fuzzer
let path = dir.join(&format!("{}_fuzzer", test_name.replace(":", "-")));
let fuzzer_name = format!("{}_fuzzer", test_name.replace(":", "-"));

Check failure on line 68 in cargo-bolero/src/build_clusterfuzz.rs

View workflow job for this annotation

GitHub Actions / check

single-character string constant used as pattern
let path = dir.join(&fuzzer_name);
let contents = format!(
r#"#!/bin/sh
exec \
env BOLERO_TEST_NAME="{1}" \
BOLERO_LIBTEST_HARNESS=1 \
BOLERO_LIBFUZZER_ARGS="$*" \
RUST_BACKTRACE=1 \
"$(dirname "$0")/{0}" \
"{1}" \
--exact \
Expand Down

0 comments on commit 31dbbaa

Please sign in to comment.