From 36a24ab41842056b60dcfc82faa537b189c79066 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Tue, 5 Nov 2024 16:49:07 +0100 Subject: [PATCH] Custom Executor Example (#2570) * [WIP] Custom Executor Example * readme * src/main.rs * Finish * fix warnings * reame * CI --- .github/workflows/build_and_test.yml | 1 + .../baby_fuzzer_custom_executor/.gitignore | 2 + .../baby_fuzzer_custom_executor/Cargo.toml | 27 +++ .../baby_fuzzer_custom_executor/Makefile.toml | 50 ++++++ .../baby_fuzzer_custom_executor/README.md | 12 ++ .../baby_fuzzer_custom_executor/src/main.rs | 163 ++++++++++++++++++ libafl/src/feedbacks/mod.rs | 2 +- 7 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 fuzzers/baby/baby_fuzzer_custom_executor/.gitignore create mode 100644 fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml create mode 100644 fuzzers/baby/baby_fuzzer_custom_executor/Makefile.toml create mode 100644 fuzzers/baby/baby_fuzzer_custom_executor/README.md create mode 100644 fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 254dcd3d94..27433d1f36 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -246,6 +246,7 @@ jobs: - ./fuzzers/baby/backtrace_baby_fuzzers/rust_code_with_inprocess_executor - ./fuzzers/baby/backtrace_baby_fuzzers/command_executor - ./fuzzers/baby/backtrace_baby_fuzzers/forkserver_executor + - ./fuzzers/baby/baby_fuzzer_custom_executor # Binary-only - ./fuzzers/binary_only/fuzzbench_fork_qemu diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/.gitignore b/fuzzers/baby/baby_fuzzer_custom_executor/.gitignore new file mode 100644 index 0000000000..e0921f291e --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_custom_executor/.gitignore @@ -0,0 +1,2 @@ +libpng-* +corpus \ No newline at end of file diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml b/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml new file mode 100644 index 0000000000..09b8eb782c --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "fuzzer_custom_executor" +version = "0.13.2" +authors = [ + "Andrea Fioraldi ", + "Dominik Maier ", +] +edition = "2021" + +[features] +default = ["std"] +tui = [] +std = [] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[dependencies] +libafl = { path = "../../../libafl/" } +libafl_bolts = { path = "../../../libafl_bolts/" } diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/Makefile.toml b/fuzzers/baby/baby_fuzzer_custom_executor/Makefile.toml new file mode 100644 index 0000000000..5b4eed6324 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_custom_executor/Makefile.toml @@ -0,0 +1,50 @@ +# Variables +[env] +FUZZER_NAME = 'fuzzer_custom_executor' +PROJECT_DIR = { script = ["pwd"] } +CARGO_TARGET_DIR = { value = "target", condition = { env_not_set = [ + "CARGO_TARGET_DIR", +] } } +PROFILE = { value = "release" } +PROFILE_DIR = { value = "release" } +FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}' + +[tasks.build] +alias = "fuzzer" + +[tasks.fuzzer] +description = "Build the fuzzer" +script = "cargo build --profile=${PROFILE}" + +[tasks.run] +description = "Run the fuzzer" +command = "${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}" +dependencies = ["fuzzer"] + +[tasks.test] +description = "Run a short test" +linux_alias = "test_unix" +mac_alias = "test_unix" +windows_alias = "unsupported" + +[tasks.test_unix] +script_runner = "@shell" +script = ''' +timeout 30s ${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME} | tee fuzz_stdout.log || true +if grep -qa "objectives: 1" fuzz_stdout.log; then + echo "Fuzzer is working" +else + echo "Fuzzer does not generate any testcases or any crashes" + exit 1 +fi +''' +dependencies = ["fuzzer"] + +# Clean up +[tasks.clean] +# Disable default `clean` definition +clear = true +script_runner = "@shell" +script = ''' +cargo clean +''' diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/README.md b/fuzzers/baby/baby_fuzzer_custom_executor/README.md new file mode 100644 index 0000000000..0a3d16475b --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_custom_executor/README.md @@ -0,0 +1,12 @@ +# Baby fuzzer with Custom Executor + +This is a minimalistic example about how to create a LibAFL-based fuzzer. + +In contrast to the normal baby fuzzer, this uses a (very simple) custom executor. + +The custom executor won't catch any timeouts or actual errors (i.e., memory corruptions, etc.) in the target. + +The tested program is a simple Rust function without any instrumentation. +For real fuzzing, you will want to add some sort to add coverage or other feedback. + +You can run this example using `cargo run`, and you can enable the TUI feature by running `cargo run --features tui`. diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs b/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs new file mode 100644 index 0000000000..aa46efbf03 --- /dev/null +++ b/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs @@ -0,0 +1,163 @@ +#[cfg(windows)] +use std::ptr::write_volatile; +use std::{ + marker::PhantomData, + path::PathBuf, + ptr::{addr_of, addr_of_mut, write}, +}; + +#[cfg(feature = "tui")] +use libafl::monitors::tui::TuiMonitor; +#[cfg(not(feature = "tui"))] +use libafl::monitors::SimpleMonitor; +use libafl::{ + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::SimpleEventManager, + executors::{Executor, ExitKind, WithObservers}, + feedback_and_fast, + feedbacks::{CrashFeedback, MaxMapFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + generators::RandPrintablesGenerator, + inputs::HasTargetBytes, + mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator}, + observers::StdMapObserver, + schedulers::QueueScheduler, + stages::mutational::StdMutationalStage, + state::{HasExecutions, State, StdState, UsesState}, +}; +use libafl_bolts::{current_nanos, nonzero, rands::StdRand, tuples::tuple_list, AsSlice}; + +/// Coverage map with explicit assignments due to the lack of instrumentation +static mut SIGNALS: [u8; 16] = [0; 16]; +static mut SIGNALS_PTR: *mut u8 = addr_of_mut!(SIGNALS) as _; +static SIGNALS_LEN: usize = unsafe { (*addr_of!(SIGNALS)).len() }; + +/// Assign a signal to the signals map +fn signals_set(idx: usize) { + unsafe { write(SIGNALS_PTR.add(idx), 1) }; +} + +struct CustomExecutor { + phantom: PhantomData, +} + +impl CustomExecutor { + pub fn new(_state: &S) -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl UsesState for CustomExecutor { + type State = S; +} + +impl Executor for CustomExecutor +where + EM: UsesState, + S: State + HasExecutions, + Z: UsesState, + Self::Input: HasTargetBytes, +{ + fn run_target( + &mut self, + _fuzzer: &mut Z, + state: &mut Self::State, + _mgr: &mut EM, + input: &Self::Input, + ) -> Result { + // We need to keep track of the exec count. + *state.executions_mut() += 1; + + let target = input.target_bytes(); + let buf = target.as_slice(); + signals_set(0); + if !buf.is_empty() && buf[0] == b'a' { + signals_set(1); + if buf.len() > 1 && buf[1] == b'b' { + signals_set(2); + if buf.len() > 2 && buf[2] == b'c' { + return Ok(ExitKind::Crash); + } + } + } + Ok(ExitKind::Ok) + } +} + +#[allow(clippy::similar_names, clippy::manual_assert)] +pub fn main() { + // Create an observation channel using the signals map + let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS_LEN) }; + + // Feedback to rate the interestingness of an input + let mut feedback = MaxMapFeedback::new(&observer); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_and_fast!( + // Look for crashes. + CrashFeedback::new(), + // We `and` the MaxMapFeedback to only end up with crashes that trigger new coverage. + // We use the _fast variant to make sure it's not evaluated every time, even if the crash didn't trigger.. + // We have to give this one a name since it differs from the first map. + MaxMapFeedback::with_name("on_crash", &observer) + ); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap(); + + // The Monitor trait define how the fuzzer stats are displayed to the user + #[cfg(not(feature = "tui"))] + let mon = SimpleMonitor::new(|s| println!("{s}")); + #[cfg(feature = "tui")] + let mon = TuiMonitor::builder() + .title("Baby Fuzzer") + .enhanced_graphics(false) + .build(); + + // The event manager handle the various events generated during the fuzzing loop + // such as the notification of the addition of a new item to the corpus + let mut mgr = SimpleEventManager::new(mon); + + // A queue policy to get testcasess from the corpus + let scheduler = QueueScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // Create the executor for an in-process function with just one observer + let executor = CustomExecutor::new(&state); + + let mut executor = WithObservers::new(executor, tuple_list!(observer)); + + // Generator of printable bytearrays of max size 32 + let mut generator = RandPrintablesGenerator::new(nonzero!(32)); + + // Generate 8 initial inputs + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) + .expect("Failed to generate the initial corpus"); + + // Setup a mutational stage with a basic bytes mutator + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in the fuzzing loop"); +} diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 707acbf24b..5a2e463583 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -828,7 +828,7 @@ impl ExitKindLogic for GenericDiffLogic { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ExitKindFeedback { #[cfg(feature = "track_hit_feedbacks")] - // The previous run's result of `Self::is_interesting` + /// The previous run's result of [`Self::is_interesting`] last_result: Option, name: Cow<'static, str>, phantom: PhantomData L>,