diff --git a/CHANGELOG.md b/CHANGELOG.md index ab111ff..e089dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,20 @@ Released YYYY-MM-DD. -------------------------------------------------------------------------------- +## 0.4.9 + +Released YYYY-MM-DD. + +### Added + +* The `example_init` demonstrates how to pass an initialization code block to the `fuzz_target!` macro. + +### Changed + +* The `fuzz_target!` macro now supports the generation of `LLVMFuzzerInitialize` to execute initialization code once before running the fuzzer. This change is not breaking and is completely backward compatible. + +-------------------------------------------------------------------------------- + ## 0.4.8 Released 2024-11-07. diff --git a/Cargo.toml b/Cargo.toml index beaf4c5..27cae5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "./example/fuzz", "./example_arbitrary/fuzz", "./example_crossover/fuzz", + "./example_init/fuzz", "./example_mutator/fuzz", ] diff --git a/ci/script.sh b/ci/script.sh index 3eb2d17..782fd04 100755 --- a/ci/script.sh +++ b/ci/script.sh @@ -36,4 +36,10 @@ cargo fuzz build --dev (! cargo fuzz run --release boom -- -runs=10000000) popd +pushd ./example_init +cargo fuzz build +cargo fuzz build --dev +(! cargo fuzz run --release bigbang -- -runs=10000000) +popd + echo "All good!" diff --git a/example_init/.gitignore b/example_init/.gitignore new file mode 100644 index 0000000..d8d1df6 --- /dev/null +++ b/example_init/.gitignore @@ -0,0 +1 @@ +crash-* diff --git a/example_init/Cargo.toml b/example_init/Cargo.toml new file mode 100644 index 0000000..74bd477 --- /dev/null +++ b/example_init/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "example_init" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/example_init/fuzz/Cargo.toml b/example_init/fuzz/Cargo.toml new file mode 100644 index 0000000..2ea16b0 --- /dev/null +++ b/example_init/fuzz/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example_init-fuzz" +version = "0.1.0" +authors = ["Andrea Cappa"] +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { path = "../.." } +example_init = { path = ".." } + +[[bin]] +name = "bigbang" +path = "fuzz_targets/bigbang.rs" diff --git a/example_init/fuzz/fuzz_targets/bigbang.rs b/example_init/fuzz/fuzz_targets/bigbang.rs new file mode 100755 index 0000000..68d9588 --- /dev/null +++ b/example_init/fuzz/fuzz_targets/bigbang.rs @@ -0,0 +1,13 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!( + init: { + // Custom initialization code here + println!("Initializing fuzzer..."); + example_init::initialize(); + }, + |data: &[u8]| { + example_init::bigbang(data); +}); diff --git a/example_init/src/lib.rs b/example_init/src/lib.rs new file mode 100644 index 0000000..a5f1129 --- /dev/null +++ b/example_init/src/lib.rs @@ -0,0 +1,22 @@ +use std::sync::OnceLock; + +static EXTRA_DATA: OnceLock<&'static str> = OnceLock::new(); + +pub fn bigbang(data: &[u8]) { + // The fuzzer needs to mutate input to be "bigbang!" + // Init needs to be called before bigbang() is called + // This actually proves that the fuzzer is calling init before bigbang + if data == &b"bigbang!"[..] && is_initialized() { + panic!("bigbang!"); + } +} + +pub fn initialize() { + EXTRA_DATA + .set("initialized") + .expect("should only initialize once"); +} + +pub fn is_initialized() -> bool { + EXTRA_DATA.get().is_some() +} diff --git a/src/lib.rs b/src/lib.rs index 8df0e5c..424a763 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,6 @@ pub fn rust_libfuzzer_debug_path() -> &'static Option { } #[doc(hidden)] -#[export_name = "LLVMFuzzerInitialize"] pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { // Registers a panic hook that aborts the process before unwinding. // It is useful to abort before unwinding so that the fuzzer will then be @@ -89,10 +88,10 @@ 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. - let default_hook = ::std::panic::take_hook(); - ::std::panic::set_hook(Box::new(move |panic_info| { + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { default_hook(panic_info); - ::std::process::abort(); + std::process::abort(); })); 0 } @@ -196,11 +195,48 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize /// /// You can also enable the `arbitrary` crate's custom derive via this crate's /// `"arbitrary-derive"` cargo feature. +/// +/// ## Init Code +/// +/// Init code to the fuzz target by using the `init` keyword. This is called once before the fuzzer starts. +/// Supports short |input| or |input: | syntax. +/// +/// ```no_run +/// #![no_main] +/// +/// use libfuzzer_sys::fuzz_target; +/// use std::collections::HashSet; +/// use std::sync::OnceLock; +/// +/// static DICTIONARY: OnceLock> = OnceLock::new(); +/// +/// fuzz_target!( +/// init: { +/// let read_dictionary = |_| unimplemented!(); +/// let dictionary = read_dictionary("/usr/share/dict/words"); +/// DICTIONARY.set(dictionary).unwrap(); +/// }, +/// |input| { +/// // Use the initialized `DICTIONARY` here... +/// } +/// ); +/// ``` +/// #[macro_export] macro_rules! fuzz_target { - (|$bytes:ident| $body:expr) => { + (init: $init:expr, |$bytes:ident| $body:expr) => { const _: () = { - /// Auto-generated function + /// Auto-generated functions + /// LLVMFuzzerInitialize is called once before the fuzzer starts. + #[no_mangle] + pub extern "C" fn LLVMFuzzerInitialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { + $crate::initialize(_argc, _argv); + + // Supplied init code + $init; + 0 + } + #[no_mangle] pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) -> i32 { // When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug @@ -240,17 +276,47 @@ macro_rules! fuzz_target { }; }; + (|$bytes:ident| $body:expr) => { + $crate::fuzz_target!(|$bytes: &[u8]| $body); + }; + (|$data:ident: &[u8]| $body:expr) => { - $crate::fuzz_target!(|$data| $body); + $crate::fuzz_target!(init: (), |$data| $body); }; (|$data:ident: $dty:ty| $body:expr) => { - $crate::fuzz_target!(|$data: $dty| -> () { $body }); + $crate::fuzz_target!(init: (), |$data: $dty| -> () { $body }); }; (|$data:ident: $dty:ty| -> $rty:ty $body:block) => { + $crate::fuzz_target!(init: (), |$data: $dty| -> $rty { $body }); + }; + + (init: $init:expr, |$data:ident: &[u8]| $body:expr) => { + $crate::fuzz_target!(init: $init, |$data| $body); + }; + + (init: $init:expr, |$bytes:ident| $body:expr) => { + $crate::fuzz_target!(init: $init, |$bytes: &[u8]| $body); + }; + + (init: $init:expr, |$data:ident: $dty:ty| $body:expr) => { + $crate::fuzz_target!(init: $init, |$data: $dty| -> () { $body }); + }; + + (init: $init:expr, |$data:ident: $dty:ty| -> $rty:ty $body:block) => { const _: () = { - /// Auto-generated function + /// Auto-generated functions + /// LLVMFuzzerInitialize is called once before the fuzzer starts. + #[no_mangle] + pub extern "C" fn LLVMFuzzerInitialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { + $crate::initialize(_argc, _argv); + + // Supplied init code + $init; + 0 + } + #[no_mangle] pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) -> i32 { use $crate::arbitrary::{Arbitrary, Unstructured}; @@ -293,7 +359,6 @@ macro_rules! fuzz_target { let result = ::libfuzzer_sys::Corpus::from(__libfuzzer_sys_run(data)); result.to_libfuzzer_code() } - // See above for why this is split to a separate function. #[inline(never)] fn __libfuzzer_sys_run($data: $dty) -> $rty {