From 2f54e9dc01b5846ab3542d24721758a1f02239ff Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Thu, 20 May 2021 16:49:12 +0200 Subject: [PATCH] UserStats (#114) * MultiStats * custom event in MapFeedback * fix introspection * fix windows * clippy * fix nostd * bump to 0.3.2 --- fuzzers/baby_fuzzer/Cargo.toml | 2 +- fuzzers/frida_libpng/Cargo.toml | 2 +- fuzzers/frida_libpng/src/fuzzer.rs | 6 +- fuzzers/libfuzzer_libmozjpeg/Cargo.toml | 2 +- fuzzers/libfuzzer_libpng/Cargo.toml | 2 +- fuzzers/libfuzzer_libpng/src/lib.rs | 4 +- fuzzers/libfuzzer_libpng_launcher/Cargo.toml | 2 +- fuzzers/libfuzzer_libpng_launcher/src/lib.rs | 6 +- fuzzers/libfuzzer_reachability/Cargo.toml | 2 +- fuzzers/libfuzzer_stb_image/Cargo.toml | 2 +- fuzzers/libfuzzer_stb_image/src/main.rs | 4 +- libafl/Cargo.toml | 4 +- libafl/src/events/llmp.rs | 23 ++-- libafl/src/events/mod.rs | 18 ++- libafl/src/events/simple.rs | 19 ++- libafl/src/executors/inprocess.rs | 6 +- libafl/src/feedbacks/map.rs | 28 ++++- libafl/src/feedbacks/mod.rs | 62 +++++++--- libafl/src/fuzzer.rs | 22 ++-- libafl/src/stats/mod.rs | 50 +++++++- libafl/src/stats/multi.rs | 115 +++++++++++++++++++ libafl_cc/Cargo.toml | 2 +- libafl_derive/Cargo.toml | 2 +- libafl_frida/Cargo.toml | 4 +- libafl_frida/src/asan_rt.rs | 10 +- libafl_targets/Cargo.toml | 4 +- 26 files changed, 329 insertions(+), 74 deletions(-) create mode 100644 libafl/src/stats/multi.rs diff --git a/fuzzers/baby_fuzzer/Cargo.toml b/fuzzers/baby_fuzzer/Cargo.toml index 97fe602b1b..1d8aedf13d 100644 --- a/fuzzers/baby_fuzzer/Cargo.toml +++ b/fuzzers/baby_fuzzer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "baby_fuzzer" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index abd3a88b44..262189c463 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frida_libpng" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" build = "build.rs" diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index f26c88052e..2b80c2d3b8 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -34,7 +34,7 @@ use libafl::{ observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, - stats::SimpleStats, + stats::MultiStats, Error, }; @@ -320,13 +320,13 @@ unsafe fn fuzz( ) -> Result<(), Error> { let stats_closure = |s| println!("{}", s); // 'While the stats are state, they are usually used in the broker - which is likely never restarted - let stats = SimpleStats::new(stats_closure); + let stats = MultiStats::new(stats_closure); #[cfg(target_os = "android")] AshmemService::start().expect("Failed to start Ashmem service"); let shmem_provider = StdShMemProvider::new()?; - let mut client_init_stats = || Ok(SimpleStats::new(stats_closure)); + let mut client_init_stats = || Ok(MultiStats::new(stats_closure)); let mut run_client = |state: Option>, mut mgr| { // The restarting state will spawn the same process again as child, then restarted it each time it crashes. diff --git a/fuzzers/libfuzzer_libmozjpeg/Cargo.toml b/fuzzers/libfuzzer_libmozjpeg/Cargo.toml index f166ef35fa..6137d50894 100644 --- a/fuzzers/libfuzzer_libmozjpeg/Cargo.toml +++ b/fuzzers/libfuzzer_libmozjpeg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libmozjpeg" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_libpng/Cargo.toml b/fuzzers/libfuzzer_libpng/Cargo.toml index 248f76bb5f..126ba381d9 100644 --- a/fuzzers/libfuzzer_libpng/Cargo.toml +++ b/fuzzers/libfuzzer_libpng/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libpng" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_libpng/src/lib.rs b/fuzzers/libfuzzer_libpng/src/lib.rs index 73a64cb029..33c5a2eb0d 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -21,7 +21,7 @@ use libafl::{ observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, - stats::SimpleStats, + stats::MultiStats, Error, }; @@ -49,7 +49,7 @@ pub fn main() { /// The actual fuzzer fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { // 'While the stats are state, they are usually used in the broker - which is likely never restarted - let stats = SimpleStats::new(|s| println!("{}", s)); + let stats = MultiStats::new(|s| println!("{}", s)); // The restarting state will spawn the same process again as child, then restarted it each time it crashes. let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) { diff --git a/fuzzers/libfuzzer_libpng_launcher/Cargo.toml b/fuzzers/libfuzzer_libpng_launcher/Cargo.toml index 1eb8695145..92fc00261f 100644 --- a/fuzzers/libfuzzer_libpng_launcher/Cargo.toml +++ b/fuzzers/libfuzzer_libpng_launcher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_libpng_launcher" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs index b179dce6cd..c1d9b0980a 100644 --- a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs +++ b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs @@ -29,7 +29,7 @@ use libafl::{ observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, StdState}, - stats::SimpleStats, + stats::MultiStats, }; use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM}; @@ -58,8 +58,8 @@ pub fn main() { let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let stats_closure = |s| println!("{}", s); - let stats = SimpleStats::new(stats_closure); - let mut client_init_stats = || Ok(SimpleStats::new(stats_closure)); + let stats = MultiStats::new(stats_closure); + let mut client_init_stats = || Ok(MultiStats::new(stats_closure)); let mut run_client = |state: Option>, mut restarting_mgr| { let corpus_dirs = &[PathBuf::from("./corpus")]; diff --git a/fuzzers/libfuzzer_reachability/Cargo.toml b/fuzzers/libfuzzer_reachability/Cargo.toml index 8bace9b6a5..510873ab01 100644 --- a/fuzzers/libfuzzer_reachability/Cargo.toml +++ b/fuzzers/libfuzzer_reachability/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_reachability" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" diff --git a/fuzzers/libfuzzer_stb_image/Cargo.toml b/fuzzers/libfuzzer_stb_image/Cargo.toml index 8bdfb95cf8..ea49909a65 100644 --- a/fuzzers/libfuzzer_stb_image/Cargo.toml +++ b/fuzzers/libfuzzer_stb_image/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfuzzer_stb_image" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] edition = "2018" build = "build.rs" diff --git a/fuzzers/libfuzzer_stb_image/src/main.rs b/fuzzers/libfuzzer_stb_image/src/main.rs index ee5efc7431..d0b677e082 100644 --- a/fuzzers/libfuzzer_stb_image/src/main.rs +++ b/fuzzers/libfuzzer_stb_image/src/main.rs @@ -19,7 +19,7 @@ use libafl::{ observers::{StdMapObserver, TimeObserver}, stages::{StdMutationalStage, TracingStage}, state::{HasCorpus, StdState}, - stats::SimpleStats, + stats::MultiStats, Error, }; @@ -48,7 +48,7 @@ pub fn main() { /// The actual fuzzer fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { // 'While the stats are state, they are usually used in the broker - which is likely never restarted - let stats = SimpleStats::new(|s| println!("{}", s)); + let stats = MultiStats::new(|s| println!("{}", s)); // The restarting state will spawn the same process again as child, then restarted it each time it crashes. let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) { diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 29b05cee79..f43956f647 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi ", "Dominik Maier "] description = "Slot your own fuzzers together and extend their features using Rust" documentation = "https://docs.rs/libafl" @@ -52,7 +52,7 @@ path = "./examples/llmp_test/main.rs" required-features = ["std"] [dependencies] -libafl_derive = { optional = true, path = "../libafl_derive", version = "0.3.1" } +libafl_derive = { optional = true, path = "../libafl_derive", version = "0.3.2" } tuple_list = "0.1.2" hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible num = "0.4.0" diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 1dca46f3f5..97b5c59ab2 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -249,7 +249,7 @@ where let client = stats.client_stats_mut_for(sender_id); client.update_corpus_size(*corpus_size as u64); client.update_executions(*executions as u64, *time); - stats.display(event.name().to_string() + " #" + &sender_id.to_string()); + stats.display(event.name().to_string(), sender_id); Ok(BrokerEventResult::Forward) } Event::UpdateStats { @@ -260,7 +260,17 @@ where // TODO: The stats buffer should be added on client add. let client = stats.client_stats_mut_for(sender_id); client.update_executions(*executions as u64, *time); - stats.display(event.name().to_string() + " #" + &sender_id.to_string()); + stats.display(event.name().to_string(), sender_id); + Ok(BrokerEventResult::Handled) + } + Event::UpdateUserStats { + name, + value, + phantom: _, + } => { + let client = stats.client_stats_mut_for(sender_id); + client.update_user_stats(name.clone(), value.clone()); + stats.display(event.name().to_string(), sender_id); Ok(BrokerEventResult::Handled) } #[cfg(feature = "introspection")] @@ -282,9 +292,7 @@ where client.update_introspection_stats(**introspection_stats); // Display the stats via `.display` only on core #1 - if sender_id == 1 { - stats.display(event.name().to_string() + " #" + &sender_id.to_string()); - } + stats.display(event.name().to_string(), sender_id); // Correctly handled the event Ok(BrokerEventResult::Handled) @@ -292,7 +300,7 @@ where Event::Objective { objective_size } => { let client = stats.client_stats_mut_for(sender_id); client.update_objective_size(*objective_size as u64); - stats.display(event.name().to_string() + " #" + &sender_id.to_string()); + stats.display(event.name().to_string(), sender_id); Ok(BrokerEventResult::Handled) } Event::Log { @@ -301,6 +309,7 @@ where phantom: _, } => { let (_, _) = (severity_level, message); + // TODO rely on Stats #[cfg(feature = "std")] println!("[LOG {}]: {}", severity_level, message); Ok(BrokerEventResult::Handled) @@ -338,7 +347,7 @@ where let observers: OT = postcard::from_bytes(&observers_buf)?; // TODO include ExitKind in NewTestcase let is_interesting = - fuzzer.is_interesting(state, &input, &observers, &ExitKind::Ok)?; + fuzzer.is_interesting(state, self, &input, &observers, &ExitKind::Ok)?; if fuzzer .add_if_interesting(state, &input, is_interesting)? .is_some() diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 5d54739940..d1e08e6715 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -9,7 +9,7 @@ use alloc::{string::String, vec::Vec}; use core::{fmt, marker::PhantomData, time::Duration}; use serde::{Deserialize, Serialize}; -use crate::{inputs::Input, observers::ObserversTuple, Error}; +use crate::{inputs::Input, observers::ObserversTuple, stats::UserStats, Error}; #[cfg(feature = "introspection")] use crate::stats::ClientPerfStats; @@ -95,6 +95,15 @@ where /// [`PhantomData`] phantom: PhantomData, }, + /// New stats. + UpdateUserStats { + /// Custom user stats name + name: String, + /// Custom user stats value + value: UserStats, + /// [`PhantomData`] + phantom: PhantomData, + }, /// New stats with performance stats. #[cfg(feature = "introspection")] UpdatePerfStats { @@ -143,11 +152,16 @@ where observers_buf: _, time: _, executions: _, - } => "New Testcase", + } => "Testcase", Event::UpdateStats { time: _, executions: _, phantom: _, + } + | Event::UpdateUserStats { + name: _, + value: _, + phantom: _, } => "Stats", #[cfg(feature = "introspection")] Event::UpdatePerfStats { diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index 101117d995..c349e07b3b 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -100,7 +100,7 @@ where stats .client_stats_mut_for(0) .update_executions(*executions as u64, *time); - stats.display(event.name().to_string()); + stats.display(event.name().to_string(), 0); Ok(BrokerEventResult::Handled) } Event::UpdateStats { @@ -112,7 +112,18 @@ where stats .client_stats_mut_for(0) .update_executions(*executions as u64, *time); - stats.display(event.name().to_string()); + stats.display(event.name().to_string(), 0); + Ok(BrokerEventResult::Handled) + } + Event::UpdateUserStats { + name, + value, + phantom: _, + } => { + stats + .client_stats_mut_for(0) + .update_user_stats(name.clone(), value.clone()); + stats.display(event.name().to_string(), 0); Ok(BrokerEventResult::Handled) } #[cfg(feature = "introspection")] @@ -125,14 +136,14 @@ where // TODO: The stats buffer should be added on client add. stats.client_stats_mut()[0].update_executions(*executions as u64, *time); stats.client_stats_mut()[0].update_introspection_stats(**introspection_stats); - stats.display(event.name().to_string()); + stats.display(event.name().to_string(), 0); Ok(BrokerEventResult::Handled) } Event::Objective { objective_size } => { stats .client_stats_mut_for(0) .update_objective_size(*objective_size as u64); - stats.display(event.name().to_string()); + stats.display(event.name().to_string(), 0); Ok(BrokerEventResult::Handled) } Event::Log { diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index e138ca8cd8..994f26e1ec 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -365,7 +365,7 @@ mod unix_signal_handler { let interesting = fuzzer .objective_mut() - .is_interesting(state, &input, observers, &ExitKind::Timeout) + .is_interesting(state, event_mgr, &input, observers, &ExitKind::Timeout) .expect("In timeout handler objective failure."); if interesting { @@ -514,7 +514,7 @@ mod unix_signal_handler { let interesting = fuzzer .objective_mut() - .is_interesting(state, &input, observers, &ExitKind::Crash) + .is_interesting(state, event_mgr, &input, observers, &ExitKind::Crash) .expect("In crash handler objective failure."); if interesting { @@ -658,7 +658,7 @@ mod windows_exception_handler { let interesting = fuzzer .objective_mut() - .is_interesting(state, &input, observers, &ExitKind::Crash) + .is_interesting(state, event_mgr, &input, observers, &ExitKind::Crash) .expect("In crash handler objective failure."); if interesting { diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index bd3a0b2511..99b9f52acd 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -11,11 +11,13 @@ use serde::{Deserialize, Serialize}; use crate::{ bolts::{tuples::Named, AsSlice}, corpus::Testcase, + events::{Event, EventFirer}, executors::ExitKind, feedbacks::{Feedback, FeedbackState, FeedbackStatesTuple}, inputs::Input, observers::{MapObserver, ObserversTuple}, state::{HasFeedbackStates, HasMetadata}, + stats::UserStats, Error, }; @@ -212,14 +214,16 @@ where S: HasFeedbackStates, FT: FeedbackStatesTuple, { - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, _input: &I, observers: &OT, _exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { let mut interesting = false; @@ -248,6 +252,7 @@ where for i in 0..size { let history = map_state.history_map[i]; let item = observer.map()[i]; + // TODO maybe walk again the histroy map only when it is interesting is more efficient if item != initial { self.indexes.as_mut().unwrap().push(i); } @@ -287,6 +292,23 @@ where } } + if interesting { + let mut filled = 0; + for i in 0..size { + if map_state.history_map[i] != initial { + filled += 1; + } + } + manager.fire( + state, + Event::UpdateUserStats { + name: self.name.to_string(), + value: UserStats::Ratio(filled, size as u64), + phantom: PhantomData, + }, + )?; + } + Ok(interesting) } @@ -433,14 +455,16 @@ where I: Input, O: MapObserver, { - fn is_interesting( + fn is_interesting( &mut self, _state: &mut S, + _manager: &mut EM, _input: &I, observers: &OT, _exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { // TODO Replace with match_name_type when stable diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index bb69b4c27c..5484e685b8 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::{ bolts::tuples::{MatchName, Named}, corpus::Testcase, + events::EventFirer, executors::ExitKind, inputs::Input, observers::{ObserversTuple, TimeObserver}, @@ -29,20 +30,24 @@ where I: Input, { /// `is_interesting ` return if an input is worth the addition to the corpus - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple; #[cfg(feature = "introspection")] - fn is_interesting_with_perf( + #[allow(clippy::too_many_arguments)] + fn is_interesting_with_perf( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, @@ -50,13 +55,14 @@ where feedback_index: usize, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { // Start a timer for this feedback let start_time = crate::cpu::read_time_counter(); // Execute this feedback - let ret = self.is_interesting(state, input, observers, &exit_kind); + let ret = self.is_interesting(state, manager, input, observers, &exit_kind); // Get the elapsed time for checking this feedback let elapsed = crate::cpu::read_time_counter() - start_time; @@ -124,29 +130,32 @@ where B: Feedback, I: Input, { - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { let a = self .first - .is_interesting(state, input, observers, exit_kind)?; + .is_interesting(state, manager, input, observers, exit_kind)?; let b = self .second - .is_interesting(state, input, observers, exit_kind)?; + .is_interesting(state, manager, input, observers, exit_kind)?; Ok(a && b) } #[cfg(feature = "introspection")] - fn is_interesting_with_perf( + fn is_interesting_with_perf( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, @@ -154,11 +163,13 @@ where feedback_index: usize, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { // Execute this feedback let a = self.first.is_interesting_with_perf( state, + manager, input, observers, &exit_kind, @@ -167,6 +178,7 @@ where )?; let b = self.second.is_interesting_with_perf( state, + manager, input, observers, &exit_kind, @@ -241,29 +253,32 @@ where B: Feedback, I: Input, { - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { let a = self .first - .is_interesting(state, input, observers, exit_kind)?; + .is_interesting(state, manager, input, observers, exit_kind)?; let b = self .second - .is_interesting(state, input, observers, exit_kind)?; + .is_interesting(state, manager, input, observers, exit_kind)?; Ok(a || b) } #[cfg(feature = "introspection")] - fn is_interesting_with_perf( + fn is_interesting_with_perf( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, @@ -271,11 +286,13 @@ where feedback_index: usize, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { // Execute this feedback let a = self.first.is_interesting_with_perf( state, + manager, input, observers, &exit_kind, @@ -284,6 +301,7 @@ where )?; let b = self.second.is_interesting_with_perf( state, + manager, input, observers, &exit_kind, @@ -354,19 +372,21 @@ where A: Feedback, I: Input, { - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { Ok(!self .first - .is_interesting(state, input, observers, exit_kind)?) + .is_interesting(state, manager, input, observers, exit_kind)?) } #[inline] @@ -442,14 +462,16 @@ impl Feedback for () where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _state: &mut S, + _manager: &mut EM, _input: &I, _observers: &OT, _exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { Ok(false) @@ -471,14 +493,16 @@ impl Feedback for CrashFeedback where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _state: &mut S, + _manager: &mut EM, _input: &I, _observers: &OT, exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { if let ExitKind::Crash = exit_kind { @@ -518,14 +542,16 @@ impl Feedback for TimeoutFeedback where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _state: &mut S, + _manager: &mut EM, _input: &I, _observers: &OT, exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { if let ExitKind::Timeout = exit_kind { @@ -570,14 +596,16 @@ impl Feedback for TimeFeedback where I: Input, { - fn is_interesting( + fn is_interesting( &mut self, _state: &mut S, + _manager: &mut EM, _input: &I, observers: &OT, _exit_kind: &ExitKind, ) -> Result where + EM: EventFirer, OT: ObserversTuple, { // TODO Replace with match_name_type when stable diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index 7385e403b1..fd90f82c08 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -3,7 +3,7 @@ use crate::{ bolts::current_time, corpus::{Corpus, CorpusScheduler, Testcase}, - events::{Event, EventManager}, + events::{Event, EventFirer, EventManager}, executors::{ Executor, ExitKind, HasExecHooks, HasExecHooksTuple, HasObservers, HasObserversHooks, }, @@ -69,15 +69,19 @@ where pub trait IsInteresting where OT: ObserversTuple, + I: Input, { /// Evaluate if a set of observation channels has an interesting state - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, - ) -> Result; + ) -> Result + where + EM: EventFirer; } /// Add to the state if interesting @@ -259,18 +263,19 @@ where S: HasCorpus, { /// Evaluate if a set of observation channels has an interesting state - fn is_interesting( + fn is_interesting( &mut self, state: &mut S, + manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, ) -> Result where - OT: ObserversTuple, + EM: EventFirer, { self.feedback_mut() - .is_interesting(state, input, observers, exit_kind) + .is_interesting(state, manager, input, observers, exit_kind) } } @@ -521,7 +526,7 @@ where #[cfg(not(feature = "introspection"))] let is_interesting = self .feedback_mut() - .is_interesting(state, &input, observers, &exit_kind)?; + .is_interesting(state, event_mgr, &input, observers, &exit_kind)?; #[cfg(feature = "introspection")] let is_interesting = { @@ -533,6 +538,7 @@ where let feedback_index = 0; let is_interesting = self.feedback_mut().is_interesting_with_perf( state, + event_mgr, &input, observers, &exit_kind, @@ -552,7 +558,7 @@ where start_timer!(state); let is_solution = self .objective_mut() - .is_interesting(state, &input, observers, &exit_kind)?; + .is_interesting(state, event_mgr, &input, observers, &exit_kind)?; mark_feature_time!(state, PerfFeature::GetObjectivesInterestingAll); diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 6641a858df..80e2a6284e 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -1,9 +1,14 @@ //! Keep stats, and dispaly them to the user. Usually used in a broker, or main node, of some sort. +pub mod multi; +pub use multi::MultiStats; + use serde::{Deserialize, Serialize}; use alloc::{string::String, vec::Vec}; -use core::{time, time::Duration}; +use core::{fmt, time, time::Duration}; + +use hashbrown::HashMap; #[cfg(feature = "introspection")] use alloc::string::ToString; @@ -14,6 +19,30 @@ use crate::bolts::current_time; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds +/// User-defined stats types +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum UserStats { + Number(u64), + String(String), + Ratio(u64, u64), +} + +impl fmt::Display for UserStats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UserStats::Number(n) => write!(f, "{}", n), + UserStats::String(s) => write!(f, "{}", s), + UserStats::Ratio(a, b) => { + if *b == 0 { + write!(f, "{}/{}", a, b) + } else { + write!(f, "{}/{} ({}%)", a, b, a * 100 / b) + } + } + } + } +} + /// A simple struct to keep track of client stats #[derive(Debug, Clone, Default)] pub struct ClientStats { @@ -30,6 +59,8 @@ pub struct ClientStats { pub last_window_time: time::Duration, /// The last executions per sec pub last_execs_per_sec: f32, + /// User-defined stats + pub user_stats: HashMap, /// Client performance statistics #[cfg(feature = "introspection")] @@ -90,6 +121,16 @@ impl ClientStats { self.last_execs_per_sec as u64 } + /// Update the user-defined stat with name and value + pub fn update_user_stats(&mut self, name: String, value: UserStats) { + self.user_stats.insert(name, value); + } + + /// Get a user-defined stat using the name + pub fn get_user_stats(&mut self, name: &str) -> Option<&UserStats> { + self.user_stats.get(name) + } + /// Update the current [`ClientPerfStats`] with the given [`ClientPerfStats`] #[cfg(feature = "introspection")] pub fn update_introspection_stats(&mut self, introspection_stats: ClientPerfStats) { @@ -109,7 +150,7 @@ pub trait Stats { fn start_time(&mut self) -> time::Duration; /// show the stats to the user - fn display(&mut self, event_msg: String); + fn display(&mut self, event_msg: String, sender_id: u32); /// Amount of elements in the corpus (combined for all children) fn corpus_size(&self) -> u64 { @@ -186,10 +227,11 @@ where self.start_time } - fn display(&mut self, event_msg: String) { + fn display(&mut self, event_msg: String, sender_id: u32) { let fmt = format!( - "[{}] clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", + "[{} #{}] clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", event_msg, + sender_id, self.client_stats().len(), self.corpus_size(), self.objective_size(), diff --git a/libafl/src/stats/multi.rs b/libafl/src/stats/multi.rs new file mode 100644 index 0000000000..3af0b24537 --- /dev/null +++ b/libafl/src/stats/multi.rs @@ -0,0 +1,115 @@ +//! Stats to disply both cumulative and per-client stats + +use alloc::{string::String, vec::Vec}; +use core::{time, time::Duration}; + +#[cfg(feature = "introspection")] +use alloc::string::ToString; + +use crate::{ + bolts::current_time, + stats::{ClientStats, Stats}, +}; + +/// Tracking stats during fuzzing and display both per-client and cumulative info. +#[derive(Clone, Debug)] +pub struct MultiStats +where + F: FnMut(String), +{ + print_fn: F, + start_time: Duration, + corpus_size: usize, + client_stats: Vec, +} + +impl Stats for MultiStats +where + F: FnMut(String), +{ + /// the client stats, mutable + fn client_stats_mut(&mut self) -> &mut Vec { + &mut self.client_stats + } + + /// the client stats + fn client_stats(&self) -> &[ClientStats] { + &self.client_stats + } + + /// Time this fuzzing run stated + fn start_time(&mut self) -> time::Duration { + self.start_time + } + + fn display(&mut self, event_msg: String, sender_id: u32) { + let pad = if event_msg.len() < 9 { + " ".repeat(9 - event_msg.len()) + } else { + String::new() + }; + let head = format!("{}{} #{}", event_msg, pad, sender_id); + let global_fmt = format!( + "[{}] (GLOBAL) clients: {}, corpus: {}, objectives: {}, executions: {}, exec/sec: {}", + head, + self.client_stats().len(), + self.corpus_size(), + self.objective_size(), + self.total_execs(), + self.execs_per_sec() + ); + (self.print_fn)(global_fmt); + + let client = self.client_stats_mut_for(sender_id); + let cur_time = current_time(); + let exec_sec = client.execs_per_sec(cur_time); + + let pad = " ".repeat(head.len()); + let mut fmt = format!( + " {} (CLIENT) corpus: {}, objectives: {}, executions: {}, exec/sec: {}", + pad, client.corpus_size, client.objective_size, client.executions, exec_sec + ); + for (key, val) in &client.user_stats { + fmt += &format!(", {}: {}", key, val); + } + (self.print_fn)(fmt); + + // Only print perf stats if the feature is enabled + #[cfg(feature = "introspection")] + { + // Print the client performance stats. Skip the Client 0 which is the broker + for (i, client) in self.client_stats.iter().skip(1).enumerate() { + let fmt = format!("Client {:03}: {}", i + 1, client.introspection_stats); + (self.print_fn)(fmt); + } + + // Separate the spacing just a bit + (self.print_fn)("\n".to_string()); + } + } +} + +impl MultiStats +where + F: FnMut(String), +{ + /// Creates the stats, using the `current_time` as `start_time`. + pub fn new(print_fn: F) -> Self { + Self { + print_fn, + start_time: current_time(), + corpus_size: 0, + client_stats: vec![], + } + } + + /// Creates the stats with a given `start_time`. + pub fn with_time(print_fn: F, start_time: time::Duration) -> Self { + Self { + print_fn, + start_time, + corpus_size: 0, + client_stats: vec![], + } + } +} diff --git a/libafl_cc/Cargo.toml b/libafl_cc/Cargo.toml index 9500348ac7..302402bb0f 100644 --- a/libafl_cc/Cargo.toml +++ b/libafl_cc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_cc" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi "] description = "Commodity library to wrap compilers and link LibAFL" documentation = "https://docs.rs/libafl_cc" diff --git a/libafl_derive/Cargo.toml b/libafl_derive/Cargo.toml index 878fd34d21..36ef86f99f 100644 --- a/libafl_derive/Cargo.toml +++ b/libafl_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_derive" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi "] description = "Derive proc-macro crate for LibAFL" documentation = "https://docs.rs/libafl_derive" diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 343d34b885..ccafb22010 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_frida" -version = "0.3.1" +version = "0.3.2" authors = ["s1341 "] description = "Frida backend library for LibAFL" documentation = "https://docs.rs/libafl_frida" @@ -15,7 +15,7 @@ cc = { version = "1.0", features = ["parallel"] } [dependencies] libafl = { path = "../libafl", version = "0.3.1", features = ["std", "libafl_derive"] } -libafl_targets = { path = "../libafl_targets", version = "0.3.1" } +libafl_targets = { path = "../libafl_targets", version = "0.3.2" } nix = "0.20.0" libc = "0.2.92" hashbrown = "0.11" diff --git a/libafl_frida/src/asan_rt.rs b/libafl_frida/src/asan_rt.rs index c362d7fff1..dd58e4cdf3 100644 --- a/libafl_frida/src/asan_rt.rs +++ b/libafl_frida/src/asan_rt.rs @@ -14,6 +14,7 @@ use libafl::{ tuples::Named, }, corpus::Testcase, + events::EventFirer, executors::{CustomExitKind, ExitKind, HasExecHooks}, feedbacks::Feedback, inputs::{HasTargetBytes, Input}, @@ -1777,13 +1778,18 @@ impl Feedback for AsanErrorsFeedback where I: Input + HasTargetBytes, { - fn is_interesting( + fn is_interesting( &mut self, _state: &mut S, + _manager: &mut EM, _input: &I, observers: &OT, _exit_kind: &ExitKind, - ) -> Result { + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { let observer = observers .match_name::("AsanErrors") .expect("An AsanErrorsFeedback needs an AsanErrorsObserver"); diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index 0c88416e07..c1f4eb9ded 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_targets" -version = "0.3.1" +version = "0.3.2" authors = ["Andrea Fioraldi "] description = "Common code for target instrumentation that can be used combined with LibAFL" documentation = "https://docs.rs/libafl_targets" @@ -25,6 +25,6 @@ cc = { version = "1.0", features = ["parallel"] } [dependencies] rangemap = "0.1.10" -libafl = { path = "../libafl", version = "0.3.1", features = [] } +libafl = { path = "../libafl", version = "0.3.2", features = [] } serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib serde-big-array = "0.3.2"