diff --git a/fuzzers/libfuzzer_libpng/src/lib.rs b/fuzzers/libfuzzer_libpng/src/lib.rs index 093bbf3fb2..b1367d442f 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -182,8 +182,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re let iters = 1_000_000; fuzzer.fuzz_loop_for( &mut stages, - &mut state, &mut executor, + &mut state, &mut restarting_mgr, iters, )?; diff --git a/fuzzers/libfuzzer_reachability/README.md b/fuzzers/libfuzzer_reachability/README.md index b92817791d..e4d5f2bffd 100644 --- a/fuzzers/libfuzzer_reachability/README.md +++ b/fuzzers/libfuzzer_reachability/README.md @@ -15,12 +15,13 @@ To build this example, run ```bash cargo build --release +clang -c weak.c -o weak.o ``` This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target. -The compiler wrappers, `libafl_cc` and libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag). +The compiler wrappers, `libafl_cc` and `libafl_cxx`, will end up in `./target/release/` (or `./target/debug`, in case you did not build with the `--release` flag). Then download libpng, and unpack the archive: ```bash @@ -33,7 +34,7 @@ Now compile libpng, using the libafl_cc compiler wrapper: ```bash cd libpng-1.6.37 ./configure -make CC="$(pwd)/../target/release/libafl_cc" CXX="$(pwd)/../target/release/libafl_cxx" -j `nproc` +LIBAFL_WEAK=../weak.o make CC="$(pwd)/../target/release/libafl_cc" CXX="$(pwd)/../target/release/libafl_cxx" -j `nproc` ``` You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`. @@ -42,13 +43,17 @@ Now, we have to build the libfuzzer harness and link all together to create our ``` cd .. -./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm +LIBAFL_WEAK=./weak.o ./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm ``` Afterward, the fuzzer will be ready to run. Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following. This allows you to run multiple different builds of the same fuzzer alongside, for example, with and without ASAN (`-fsanitize=address`) or with different mutators. +This example also shows you how to use a user-defined variable from LibAFL. +`diff.patch` adds an array `__libafl_target_list` to `png.c`. In order to read from this variable from LibAFL, you need to weakly define __libafl_target_list as in `weak.c`. +For building, you have to set `LIBAFL_WEAK` to point to the compiled `weak.o`, so that the compiler can find this `weak.o` file and link successfully. + ## Run The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. Currently you must run the clients from the libfuzzer_libpng directory for them to be able to access the PNG corpus. diff --git a/fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs b/fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs index a860154614..c2d060aa0f 100644 --- a/fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs +++ b/fuzzers/libfuzzer_reachability/src/bin/libafl_cc.rs @@ -5,6 +5,7 @@ pub fn main() { let args: Vec = env::args().collect(); if args.len() > 1 { let mut dir = env::current_exe().unwrap(); + let weak = env::var("LIBAFL_WEAK").unwrap(); let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() { @@ -22,6 +23,7 @@ pub fn main() { .silence(true) .from_args(&args) .expect("Failed to parse the command line") + .add_link_arg(weak) .link_staticlib(&dir, "libfuzzer_libpng") .add_arg("-fsanitize-coverage=trace-pc-guard") .run() diff --git a/fuzzers/libfuzzer_reachability/src/lib.rs b/fuzzers/libfuzzer_reachability/src/lib.rs index f52f6bffb6..eed219ebd9 100644 --- a/fuzzers/libfuzzer_reachability/src/lib.rs +++ b/fuzzers/libfuzzer_reachability/src/lib.rs @@ -147,8 +147,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re let iters = 1_000_000; fuzzer.fuzz_loop_for( &mut stages, - &mut state, &mut executor, + &mut state, &mut restarting_mgr, iters, )?; diff --git a/fuzzers/libfuzzer_reachability/weak.c b/fuzzers/libfuzzer_reachability/weak.c new file mode 100644 index 0000000000..8d898f8592 --- /dev/null +++ b/fuzzers/libfuzzer_reachability/weak.c @@ -0,0 +1,3 @@ +#include +__attribute__((weak, visibility("default"))) size_t *__libafl_target_list; + diff --git a/fuzzers/tutorial/src/lib.rs b/fuzzers/tutorial/src/lib.rs index c6c61e6835..f87c8dc7ed 100644 --- a/fuzzers/tutorial/src/lib.rs +++ b/fuzzers/tutorial/src/lib.rs @@ -173,8 +173,8 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re let iters = 1_000_000; fuzzer.fuzz_loop_for( &mut stages, - &mut state, &mut executor, + &mut state, &mut restarting_mgr, iters, )?; diff --git a/libafl/src/bolts/mod.rs b/libafl/src/bolts/mod.rs index 0c1f0c9221..7ad085a054 100644 --- a/libafl/src/bolts/mod.rs +++ b/libafl/src/bolts/mod.rs @@ -20,6 +20,7 @@ pub mod shmem; pub mod staterestore; pub mod tuples; +use alloc::string::String; use core::time; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; @@ -85,3 +86,10 @@ pub fn current_nanos() -> u64 { pub fn current_milliseconds() -> u64 { current_time().as_millis() as u64 } + +/// Format a `Duration` into a HMS string +#[must_use] +pub fn format_duration_hms(duration: &time::Duration) -> String { + let secs = duration.as_secs(); + format!("{}h-{}m-{}s", (secs / 60) / 60, (secs / 60) % 60, secs % 60) +} diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index b8a1d70922..287513287a 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -866,7 +866,7 @@ where } // Storing state in the last round did not work - panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! (Child exited with: {})", child_status); + panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! This can happen if the child calls `exit()`, in that case make sure it uses `abort()`, if it got killed unrecoverable (OOM), or if there is a bug in the fuzzer itself. (Child exited with: {})", child_status); } ctr = ctr.wrapping_add(1); diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index 55bbd76a15..9a651a97dd 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -376,7 +376,7 @@ where } // Storing state in the last round did not work - panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! (Child exited with: {})", child_status); + panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! This can happen if the child calls `exit()`, in that case make sure it uses `abort()`, if it got killed unrecoverable (OOM), or if there is a bug in the fuzzer itself. (Child exited with: {})", child_status); } ctr = ctr.wrapping_add(1); diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index be8c44b6aa..a6fe74d544 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -188,8 +188,8 @@ pub trait Fuzzer { fn fuzz_loop_for( &mut self, stages: &mut ST, - state: &mut S, executor: &mut E, + state: &mut S, manager: &mut EM, iters: u64, ) -> Result { diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 6df1af87ff..339582d886 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -42,20 +42,20 @@ pub use fuzzer::*; /// The `stats` module got renamed to [`monitors`]. /// It monitors and displays the statistics of the fuzzing process. -#[deprecated(since = "0.6.0", note = "The `stats` module got renamed to `monitors`")] +#[deprecated(since = "0.7.0", note = "The `stats` module got renamed to `monitors`")] pub mod stats { #[deprecated( - since = "0.6.0", + since = "0.7.0", note = "Use monitors::MultiMonitor instead of stats::MultiStats!" )] pub use crate::monitors::MultiMonitor as MultiStats; #[deprecated( - since = "0.6.0", + since = "0.7.0", note = "Use monitors::SimpleMonitor instead of stats::SimpleStats!" )] pub use crate::monitors::SimpleMonitor as SimpleStats; #[deprecated( - since = "0.6.0", + since = "0.7.0", note = "Use monitors::UserMonitor instead of stats::SimpleStats!" )] pub use crate::monitors::UserStats; diff --git a/libafl/src/monitors/mod.rs b/libafl/src/monitors/mod.rs index 476a4cb22a..7a7c47aa32 100644 --- a/libafl/src/monitors/mod.rs +++ b/libafl/src/monitors/mod.rs @@ -13,7 +13,7 @@ use hashbrown::HashMap; #[cfg(feature = "introspection")] use alloc::string::ToString; -use crate::bolts::current_time; +use crate::bolts::{current_time, format_duration_hms}; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds @@ -269,10 +269,10 @@ where fn display(&mut self, event_msg: String, sender_id: u32) { let fmt = format!( - "{}: [{} #{}] clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", - (current_time() - self.start_time).as_millis(), + "[{} #{}] run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", event_msg, sender_id, + format_duration_hms(&(current_time() - self.start_time)), self.client_stats().len(), self.corpus_size(), self.objective_size(), diff --git a/libafl/src/monitors/multi.rs b/libafl/src/monitors/multi.rs index c767800b4f..5f4dbab134 100644 --- a/libafl/src/monitors/multi.rs +++ b/libafl/src/monitors/multi.rs @@ -7,7 +7,7 @@ use core::{time, time::Duration}; use alloc::string::ToString; use crate::{ - bolts::current_time, + bolts::{current_time, format_duration_hms}, monitors::{ClientStats, Monitor}, }; @@ -50,8 +50,9 @@ where }; let head = format!("{}{} {}", event_msg, pad, sender); let global_fmt = format!( - "[{}] (GLOBAL) clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", + "[{}] (GLOBAL) run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", head, + format_duration_hms(&(current_time() - self.start_time)), self.client_stats().len(), self.corpus_size(), self.objective_size(), diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 33362df5c8..aa263899be 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -26,6 +26,11 @@ pub use concolic::ConcolicTracingStage; #[cfg(feature = "std")] pub use concolic::SimpleConcolicMutationalStage; +#[cfg(feature = "std")] +pub mod sync; +#[cfg(feature = "std")] +pub use sync::*; + use crate::Error; use core::{convert::From, marker::PhantomData}; diff --git a/libafl/src/stages/push/mutational.rs b/libafl/src/stages/push/mutational.rs index ffb50c7a84..ce4753419c 100644 --- a/libafl/src/stages/push/mutational.rs +++ b/libafl/src/stages/push/mutational.rs @@ -58,7 +58,6 @@ where { initialized: bool, state: Rc>, - current_iter: Option, current_corpus_idx: Option, testcases_to_do: usize, testcases_done: usize, @@ -201,7 +200,7 @@ where // We already ran once self.post_exec() } else { - self.init() // TODO: Corpus idx + self.init() }; if let Err(err) = step_success { //let errored = true; @@ -264,7 +263,6 @@ where phantom: PhantomData, initialized: false, state, - current_iter: None, current_corpus_idx: None, // todo testcases_to_do: 0, testcases_done: 0, diff --git a/libafl/src/stages/sync.rs b/libafl/src/stages/sync.rs new file mode 100644 index 0000000000..b3d29feef8 --- /dev/null +++ b/libafl/src/stages/sync.rs @@ -0,0 +1,183 @@ +//| The [`MutationalStage`] is the default stage used during fuzzing. +//! For the current input, it will perform a range of random mutations, and then run them in the executor. + +use core::marker::PhantomData; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use crate::{ + bolts::rands::Rand, + corpus::Corpus, + fuzzer::Evaluator, + inputs::Input, + stages::Stage, + state::{HasClientPerfMonitor, HasCorpus, HasMetadata, HasRand}, + Error, +}; + +#[derive(Serialize, Deserialize)] +pub struct SyncFromDiskMetadata { + pub last_time: SystemTime, +} + +crate::impl_serdeany!(SyncFromDiskMetadata); + +impl SyncFromDiskMetadata { + #[must_use] + pub fn new(last_time: SystemTime) -> Self { + Self { last_time } + } +} + +/// A stage that loads testcases from disk to sync with other fuzzers such as AFL++ +pub struct SyncFromDiskStage +where + C: Corpus, + CB: FnMut(&mut Z, &mut S, &Path) -> Result, + I: Input, + R: Rand, + S: HasClientPerfMonitor + HasCorpus + HasRand + HasMetadata, + Z: Evaluator, +{ + sync_dir: PathBuf, + load_callback: CB, + #[allow(clippy::type_complexity)] + phantom: PhantomData<(C, E, EM, I, R, S, Z)>, +} + +impl Stage for SyncFromDiskStage +where + C: Corpus, + CB: FnMut(&mut Z, &mut S, &Path) -> Result, + I: Input, + R: Rand, + S: HasClientPerfMonitor + HasCorpus + HasRand + HasMetadata, + Z: Evaluator, +{ + #[inline] + fn perform( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + _corpus_idx: usize, + ) -> Result<(), Error> { + let last = state + .metadata() + .get::() + .map(|m| m.last_time); + let path = self.sync_dir.clone(); + if let Some(max_time) = + self.load_from_directory(&path, &last, fuzzer, executor, state, manager)? + { + if last.is_none() { + state + .metadata_mut() + .insert(SyncFromDiskMetadata::new(max_time)); + } else { + state + .metadata_mut() + .get_mut::() + .unwrap() + .last_time = max_time; + } + } + + #[cfg(feature = "introspection")] + state.introspection_monitor_mut().finish_stage(); + + Ok(()) + } +} + +impl SyncFromDiskStage +where + C: Corpus, + CB: FnMut(&mut Z, &mut S, &Path) -> Result, + I: Input, + R: Rand, + S: HasClientPerfMonitor + HasCorpus + HasRand + HasMetadata, + Z: Evaluator, +{ + /// Creates a new [`SyncFromDiskStage`] + #[must_use] + pub fn new(sync_dir: PathBuf, load_callback: CB) -> Self { + Self { + sync_dir, + load_callback, + phantom: PhantomData, + } + } + + fn load_from_directory( + &mut self, + in_dir: &Path, + last: &Option, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + ) -> Result, Error> { + let mut max_time = None; + for entry in fs::read_dir(in_dir)? { + let entry = entry?; + let path = entry.path(); + let attributes = fs::metadata(&path); + + if attributes.is_err() { + continue; + } + + let attr = attributes?; + + if attr.is_file() && attr.len() > 0 { + if let Ok(time) = attr.modified() { + if let Some(l) = last { + if time.duration_since(*l).is_err() { + continue; + } + } + max_time = Some(max_time.map_or(time, |t: SystemTime| t.max(time))); + let input = (self.load_callback)(fuzzer, state, &path)?; + drop(fuzzer.evaluate_input(state, executor, manager, input)?); + } + } else if attr.is_dir() { + let dir_max_time = + self.load_from_directory(&path, last, fuzzer, executor, state, manager)?; + if let Some(time) = dir_max_time { + max_time = Some(max_time.map_or(time, |t: SystemTime| t.max(time))); + } + } + } + + Ok(max_time) + } +} + +impl + SyncFromDiskStage Result, E, EM, I, R, S, Z> +where + C: Corpus, + I: Input, + R: Rand, + S: HasClientPerfMonitor + HasCorpus + HasRand + HasMetadata, + Z: Evaluator, +{ + /// Creates a new [`SyncFromDiskStage`] invoking `Input::from_file` to load inputs + #[must_use] + pub fn with_from_file(sync_dir: PathBuf) -> Self { + fn load_callback(_: &mut Z, _: &mut S, p: &Path) -> Result { + I::from_file(p) + } + Self { + sync_dir, + load_callback: load_callback::<_, _, I>, + phantom: PhantomData, + } + } +}