From 12b9584ad8df6a88bf8d8c3b4d7c0717ac639ae8 Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Wed, 7 Apr 2021 21:22:26 +0000 Subject: [PATCH 01/18] Rework to put `ClientPerfStats` in `State` and pass that along. Still need to work on getting granular information from `Feedback` and `Observer` --- fuzzers/libfuzzer_libpng/Cargo.toml | 3 +- fuzzers/libfuzzer_libpng/Makefile | 52 ++++ fuzzers/libfuzzer_libpng/src/lib.rs | 8 +- libafl/src/cpu.rs | 8 + libafl/src/events/llmp.rs | 32 ++- libafl/src/events/mod.rs | 24 ++ libafl/src/events/simple.rs | 13 + libafl/src/fuzzer.rs | 58 ++++- libafl/src/lib.rs | 1 + libafl/src/stages/mod.rs | 8 +- libafl/src/stages/mutational.rs | 21 +- libafl/src/state/mod.rs | 101 ++++++++ libafl/src/stats/mod.rs | 379 ++++++++++++++++++++++++++++ 13 files changed, 693 insertions(+), 15 deletions(-) create mode 100644 fuzzers/libfuzzer_libpng/Makefile create mode 100644 libafl/src/cpu.rs diff --git a/fuzzers/libfuzzer_libpng/Cargo.toml b/fuzzers/libfuzzer_libpng/Cargo.toml index 1f125edadc..3d62c63f47 100644 --- a/fuzzers/libfuzzer_libpng/Cargo.toml +++ b/fuzzers/libfuzzer_libpng/Cargo.toml @@ -15,7 +15,8 @@ opt-level = 3 debug = true [dependencies] -libafl = { path = "../../libafl/" } +libafl = { path = "../../libafl/", features = ["default", "perf_stats"] } +# libafl = { path = "../../libafl/", features = ["default"] } libafl_targets = { path = "../../libafl_targets/", features = ["pcguard_hitcounts", "libfuzzer"] } # TODO Include it only when building cc libafl_cc = { path = "../../libafl_cc/" } diff --git a/fuzzers/libfuzzer_libpng/Makefile b/fuzzers/libfuzzer_libpng/Makefile new file mode 100644 index 0000000000..540fcbdab5 --- /dev/null +++ b/fuzzers/libfuzzer_libpng/Makefile @@ -0,0 +1,52 @@ +PWD=`pwd` + +all: + # Build the libpng libfuzzer library + cargo build --release + + # Build the libpng harness + $(PWD)/target/release/cxx \ + $(PWD)/harness.cc \ + $(PWD)/libpng-1.6.37/.libs/libpng16.a \ + -I$(PWD)/libpng-1.6.37/ \ + -o fuzzer \ + -lm -lz + +run: all + ./fuzzer & + ./fuzzer >/dev/null 2>/dev/null & + +test: all + timeout 60s ./fuzzer & + timeout 59s taskset 0x00000001 ./fuzzer >/dev/null 2>/dev/null & + timeout 59s taskset 0x00000002 ./fuzzer >/dev/null 2>/dev/null & + timeout 59s taskset 0x00000004 ./fuzzer >/dev/null 2>/dev/null & + timeout 59s taskset 0x00000008 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000010 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000020 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000040 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000080 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000100 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000200 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000400 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000800 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00001000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00002000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00004000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00008000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00010000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00020000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00040000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00080000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00100000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00200000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00400000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00800000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x01000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x02000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x04000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x08000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x10000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x20000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x40000000 ./fuzzer >/dev/null 2>/dev/null & + # timeout 59s taskset 0x80000000 ./fuzzer >/dev/null 2>/dev/null & diff --git a/fuzzers/libfuzzer_libpng/src/lib.rs b/fuzzers/libfuzzer_libpng/src/lib.rs index 92cd079c24..e64987ed4f 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -76,6 +76,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> StdRand::with_seed(current_nanos()), // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), + // Feedbacks to rate the interestingness of an input tuple_list!( MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), @@ -84,11 +85,13 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Corpus in which we store solutions (crashes in this example), // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(objective_dir).unwrap(), - // Feedbacks to recognize an input as solution + + // Objectives to recognize an input as solution tuple_list!(CrashFeedback::new(), TimeoutFeedback::new()), ) }); + println!("We're a client, let's fuzz :)"); // Create a PNG dictionary if not existing @@ -151,6 +154,9 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?; + print!("DONE\n"); + loop {} + // Never reached Ok(()) } diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs new file mode 100644 index 0000000000..e1b30c7fd9 --- /dev/null +++ b/libafl/src/cpu.rs @@ -0,0 +1,8 @@ +//! Architecture agnostic utility functions + +/// Read the time counter. This is primarily used for benchmarking various components in +/// the fuzzer. +#[cfg(target_arch="x86_64")] +pub fn read_time_counter() -> u64 { + unsafe { core::arch::x86_64::_rdtsc() } +} diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index cbd113ef30..ba409f6525 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -223,7 +223,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.to_string()); Ok(BrokerEventResult::Forward) } Event::UpdateStats { @@ -234,7 +234,35 @@ 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()); + if sender_id == 1 { + stats.display(event.name().to_string() + " #" + &sender_id.to_string()); + } + Ok(BrokerEventResult::Handled) + } + #[cfg(feature = "perf_stats")] + Event::UpdatePerfStats { + time, + executions, + perf_stats, + phantom: _, + } => { + // TODO: The stats buffer should be added on client add. + + // Get the client for the sender ID + let client = stats.client_stats_mut_for(sender_id); + + // Update the normal stats for this client + client.update_executions(*executions as u64, *time); + + // Update the performance stats for this client + client.update_perf_stats(*perf_stats); + + // Display the stats via `.display` only on core #1 + if sender_id == 1 { + stats.display(event.name().to_string() + " #" + &sender_id.to_string()); + } + + // Correctly handled the event Ok(BrokerEventResult::Handled) } Event::Objective { objective_size } => { diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index b2c089a699..fdcc3b4a24 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -17,6 +17,9 @@ use crate::{ Error, }; +#[cfg(feature = "perf_stats")] +use crate::stats::ClientPerfStats; + /// The log event severity #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum LogSeverity { @@ -97,6 +100,20 @@ where executions: usize, phantom: PhantomData, }, + /// New stats with performance stats. + #[cfg(feature = "perf_stats")] + UpdatePerfStats { + /// The time of generation of the event + time: Duration, + + /// The executions of this client + executions: usize, + + /// Current performance statistics + perf_stats: ClientPerfStats, + + phantom: PhantomData, + }, /// A new objective was found Objective { /// Objective corpus size @@ -136,6 +153,13 @@ where executions: _, phantom: _, } => "Stats", + #[cfg(feature = "perf_stats")] + Event::UpdatePerfStats { + time: _, + executions: _, + perf_stats: _, + phantom: _, + } => "PerfStats", Event::Objective { objective_size: _ } => "Objective", Event::Log { severity_level: _, diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index ab0d88cab3..f21af63309 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -98,6 +98,19 @@ where stats.display(event.name().to_string()); Ok(BrokerEventResult::Handled) } + #[cfg(feature = "perf_stats")] + Event::UpdatePerfStats { + time, + executions, + perf_stats, + phantom: _, + } => { + // 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_perf_stats(*perf_stats); + stats.display(event.name().to_string()); + Ok(BrokerEventResult::Handled) + } Event::Objective { objective_size } => { stats.client_stats_mut()[0].update_objective_size(*objective_size as u64); stats.display(event.name().to_string()); diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index 062ae077cc..179c310d2c 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -5,16 +5,17 @@ use crate::{ inputs::Input, observers::ObserversTuple, stages::StagesTuple, - state::HasExecutions, + state::{HasExecutions, HasClientPerfStats}, utils::current_time, + cpu, Error, }; use alloc::string::ToString; use core::{marker::PhantomData, time::Duration}; -/// Send a stats update all 6 (or more) seconds -const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(6 * 1000); +/// Send a stats update all 1 (or more) seconds +const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(1000); /// Holds a set of stages pub trait HasStages @@ -68,7 +69,7 @@ pub trait Fuzzer { last = Self::maybe_report_stats(state, manager, last, stats_timeout)?; } } - + /// Fuzz for n iterations /// Returns the index of the last fuzzed corpus item fn fuzz_loop_for( @@ -96,6 +97,7 @@ pub trait Fuzzer { Ok(ret) } + /// Given the last time, if stats_timeout seconds passed, send off an info/stats/heartbeat message to the broker. /// Returns the new `last` time (so the old one, unless `stats_timeout` time has passed and stats have been sent) /// Will return an Error, if the stats could not be sent. @@ -160,7 +162,7 @@ where impl Fuzzer for StdFuzzer where CS: CorpusScheduler, - S: HasExecutions, + S: HasExecutions + HasClientPerfStats, ST: StagesTuple, EM: EventManager, E: Executor + HasObservers, @@ -176,7 +178,8 @@ where ) -> Result { let cur = current_time(); if cur - last > stats_timeout { - //println!("Fire {:?} {:?} {:?}", cur, last, stats_timeout); + // Default no perf_stats implmentation + #[cfg(not(feature = "perf_stats"))] manager.fire( state, Event::UpdateStats { @@ -185,6 +188,25 @@ where phantom: PhantomData, }, )?; + + // If performance stats are requested, fire the `UpdatePerfStats` event + #[cfg(feature = "perf_stats")] + { + state.perf_stats_mut().set_current_time(cpu::read_time_counter()); + + // Send the current stats over to the manager. This `.clone` shouldn't be + // costly as `ClientPerfStats` impls `Copy` since it only contains `u64`s + manager.fire( + state, + Event::UpdatePerfStats { + executions: *state.executions(), + time: cur, + perf_stats: state.perf_stats().clone(), + phantom: PhantomData, + }, + )?; + } + Ok(cur) } else { if cur.as_millis() % 1000 == 0 {} @@ -199,12 +221,36 @@ where manager: &mut EM, scheduler: &CS, ) -> Result { + // Init timer for scheduler + #[cfg(feature="perf_stats")] + state.perf_stats_mut().start_timer(); + + // Get the next index from the scheduler let idx = scheduler.next(state)?; + // Mark the elapsed time for the scheduler + #[cfg(feature="perf_stats")] + state.perf_stats_mut().mark_scheduler_time(); + + // Mark the elapsed time for the scheduler + #[cfg(feature="perf_stats")] + state.perf_stats_mut().reset_stage_index(); + + // Execute all stages self.stages_mut() .perform_all(state, executor, manager, scheduler, idx)?; + // Init timer for manager + #[cfg(feature="perf_stats")] + state.perf_stats_mut().start_timer(); + + // Execute the manager manager.process(state, executor, scheduler)?; + + // Mark the elapsed time for the manager + #[cfg(feature="perf_stats")] + state.perf_stats_mut().mark_manager_time(); + Ok(idx) } } diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 0cc2676e27..c71ca902e4 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -34,6 +34,7 @@ pub mod stages; pub mod state; pub mod stats; pub mod utils; +pub mod cpu; pub mod fuzzer; pub use fuzzer::*; diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index b324779990..ef0f432870 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -5,7 +5,9 @@ pub use mutational::{MutationalStage, StdMutationalStage}; //pub use power::PowerMutationalStage; use crate::{ - bolts::tuples::TupleList, events::EventManager, executors::Executor, inputs::Input, Error, + bolts::tuples::TupleList, events::EventManager, executors::Executor, inputs::Input, + state::HasClientPerfStats, + Error }; /// A stage is one step in the fuzzing process. @@ -68,6 +70,7 @@ where EM: EventManager, E: Executor, I: Input, + S: HasClientPerfStats { fn perform_all( &mut self, @@ -77,8 +80,11 @@ where scheduler: &CS, corpus_idx: usize, ) -> Result<(), Error> { + // Perform the current stage self.0 .perform(state, executor, manager, scheduler, corpus_idx)?; + + // Execute the remaining stages self.1 .perform_all(state, executor, manager, scheduler, corpus_idx) } diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 9c9b4d3731..95aafdb87c 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -8,9 +8,11 @@ use crate::{ mutators::Mutator, observers::ObserversTuple, stages::Stage, - state::{Evaluator, HasCorpus, HasRand}, + state::{Evaluator, HasCorpus, HasRand, HasClientPerfStats}, + stats::PerfFeature, utils::Rand, Error, + start_timer, mark_feature_time }; // TODO multi mutators stage @@ -22,7 +24,7 @@ pub trait MutationalStage: Stage where M: Mutator, I: Input, - S: HasCorpus + Evaluator, + S: HasCorpus + Evaluator + HasClientPerfStats, C: Corpus, EM: EventManager, E: Executor + HasObservers, @@ -47,18 +49,29 @@ where scheduler: &CS, corpus_idx: usize, ) -> Result<(), Error> { + print!("Perform mutational\n"); + let num = self.iterations(state); + for i in 0..num { + start_timer!(state); let mut input_mut = state .corpus() .get(corpus_idx)? .borrow_mut() .load_input()? .clone(); + mark_feature_time!(state, PerfFeature::GetInputFromCorpus); + + start_timer!(state); self.mutator_mut().mutate(state, &mut input_mut, i as i32)?; + mark_feature_time!(state, PerfFeature::Mutate); + let (_, corpus_idx) = state.evaluate_input(input_mut, executor, manager, scheduler)?; + start_timer!(state); self.mutator_mut().post_exec(state, i as i32, corpus_idx)?; + mark_feature_time!(state, PerfFeature::MutatePostExec); } Ok(()) } @@ -89,7 +102,7 @@ impl MutationalStage where M: Mutator, I: Input, - S: HasCorpus + Evaluator + HasRand, + S: HasCorpus + Evaluator + HasRand + HasClientPerfStats, C: Corpus, EM: EventManager, E: Executor + HasObservers, @@ -120,7 +133,7 @@ impl Stage where M: Mutator, I: Input, - S: HasCorpus + Evaluator + HasRand, + S: HasCorpus + Evaluator + HasRand + HasClientPerfStats, C: Corpus, EM: EventManager, E: Executor + HasObservers, diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index b55cec8fc1..a37256ba79 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -18,9 +18,12 @@ use crate::{ inputs::Input, observers::ObserversTuple, utils::Rand, + stats::{ClientPerfStats, PerfFeature}, Error, + start_timer, mark_feature_time }; + #[cfg(feature = "std")] use crate::inputs::bytes::BytesInput; @@ -70,6 +73,16 @@ where fn rand_mut(&mut self) -> &mut R; } +/// Trait for offering a [`ClientPerfStats`] +pub trait HasClientPerfStats +{ + /// [`ClientPerfStats`] itself + fn perf_stats(&self) -> &ClientPerfStats; + + /// Mutatable ref to [`ClientPerfStats`] + fn perf_stats_mut(&mut self) -> &mut ClientPerfStats; +} + /// Trait for elements offering metadata pub trait HasMetadata { /// A map, storing all metadata @@ -237,6 +250,10 @@ where /// MaxSize testcase size for mutators that appreciate it max_size: usize, + /// Performance statistics for this fuzzer + #[cfg(feature="perf_stats")] + perf_stats: ClientPerfStats, + phantom: PhantomData, } @@ -394,6 +411,28 @@ where } } +/// Trait that that has [`HasExecution`] and [`HasClientPerfStats`] when "perf_stats" +/// feature is enabled, and only [`HasExecution`] when "perf_stats" is disabled +#[cfg(feature="perf_stats")] +pub trait HasFuzzerStats: HasExecutions + HasClientPerfStats {} + +/// Trait that that has [`HasExecution`] and [`HasClientPerfStats`] when "perf_stats" +/// feature is enabled, and only [`HasExecution`] when "perf_stats" is disabled +#[cfg(not(feature="perf_stats"))] +pub trait HasFuzzerStats: HasExecutions {} + +impl HasFuzzerStats for State +where + C: Corpus, + I: Input, + R: Rand, + FT: FeedbacksTuple, + SC: Corpus, + OFT: FeedbacksTuple, +{ + +} + impl HasMaxSize for State where C: Corpus, @@ -508,13 +547,16 @@ where CS: CorpusScheduler, { let (fitness, is_solution) = self.execute_input(&input, executor, manager)?; + let observers = executor.observers(); if is_solution { // If the input is a solution, add it to the respective corpus self.solutions_mut().add(Testcase::new(input.clone()))?; } + let corpus_idx = self.add_if_interesting(&input, fitness, scheduler)?; + if corpus_idx.is_some() { let observers_buf = manager.serialize_observers(observers)?; manager.fire( @@ -640,24 +682,42 @@ where C: Corpus, EM: EventManager, { + start_timer!(self); executor.pre_exec_observers()?; + mark_feature_time!(self, PerfFeature::PreExecObservers); + start_timer!(self); executor.pre_exec(self, event_mgr, input)?; + mark_feature_time!(self, PerfFeature::PreExec); + + start_timer!(self); let exit_kind = executor.run_target(input)?; + mark_feature_time!(self, PerfFeature::TargetExecution); + + start_timer!(self); executor.post_exec(self, event_mgr, input)?; + mark_feature_time!(self, PerfFeature::PostExec); *self.executions_mut() += 1; + + start_timer!(self); executor.post_exec_observers()?; + mark_feature_time!(self, PerfFeature::PostExecObservers); + start_timer!(self); let observers = executor.observers(); let fitness = self.feedbacks_mut() .is_interesting_all(&input, observers, exit_kind.clone())?; + mark_feature_time!(self, PerfFeature::GetFeedbackInterestingAll); + start_timer!(self); let is_solution = self .objectives_mut() .is_interesting_all(&input, observers, exit_kind)? > 0; + mark_feature_time!(self, PerfFeature::GetObjectivesInterestingAll); + Ok((fitness, is_solution)) } @@ -708,7 +768,48 @@ where solutions, objectives, max_size: DEFAULT_MAX_SIZE, + #[cfg(feature="perf_stats")] + perf_stats: ClientPerfStats::new(), phantom: PhantomData, } } + +} + +#[cfg(feature = "perf_stats")] +impl HasClientPerfStats for State +where + C: Corpus, + I: Input, + R: Rand, + FT: FeedbacksTuple, + SC: Corpus, + OFT: FeedbacksTuple, +{ + fn perf_stats(&self) -> &ClientPerfStats { + &self.perf_stats + } + + fn perf_stats_mut(&mut self) -> &mut ClientPerfStats { + &mut self.perf_stats + } +} + +#[cfg(not(feature = "perf_stats"))] +impl HasClientPerfStats for State +where + C: Corpus, + I: Input, + R: Rand, + FT: FeedbacksTuple, + SC: Corpus, + OFT: FeedbacksTuple, +{ + fn perf_stats(&self) -> &ClientPerfStats { + unimplemented!() + } + + fn perf_stats_mut(&mut self) -> &mut ClientPerfStats { + unimplemented!() + } } diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 6a0883c707..511aa118bd 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -1,9 +1,18 @@ //! Keep stats, and dispaly them to the user. Usually used in a broker, or main node, of some sort. +use serde::{Deserialize, Serialize}; + use alloc::{string::String, vec::Vec}; use core::{time, time::Duration}; +#[cfg(feature="std")] +use std::convert::TryInto; + +#[cfg(not(feature="std"))] +use core::convert::TryInto; + use crate::utils::current_time; +use crate::cpu; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds @@ -23,6 +32,10 @@ pub struct ClientStats { pub last_window_time: time::Duration, /// The last executions per sec pub last_execs_per_sec: f32, + + /// Client performance statistics + #[cfg(feature = "perf_stats")] + pub perf_stats: ClientPerfStats } impl ClientStats { @@ -77,6 +90,12 @@ impl ClientStats { self.last_execs_per_sec * (1.0 - 1.0 / 16.0) + cur_avg * (1.0 / 16.0); self.last_execs_per_sec as u64 } + + /// Update the current [`ClientPerfStats`] with the given [`ClientPerfStats`] + #[cfg(feature = "perf_stats")] + pub fn update_perf_stats(&mut self, perf_stats: ClientPerfStats) { + self.perf_stats = perf_stats; + } } /// The stats trait keeps track of all the client's stats, and offers methods to dispaly them. @@ -93,6 +112,7 @@ pub trait Stats { /// show the stats to the user fn display(&mut self, event_msg: String); + /// Amount of elements in the corpus (combined for all children) fn corpus_size(&self) -> u64 { self.client_stats() @@ -178,6 +198,20 @@ where self.execs_per_sec() ); (self.print_fn)(fmt); + + + // Only print perf stats if the feature is enabled + #[cfg(feature = "perf_stats")] + { + // 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.perf_stats); + (self.print_fn)(fmt); + } + + // Separate the spacing just a bit + print!("\n"); + } } } @@ -203,3 +237,348 @@ where } } } + +#[macro_export] +macro_rules! start_timer { + ($state:expr) => {{ + // Start the timer + #[cfg(feature="perf_stats")] + $state.perf_stats_mut().start_timer(); + }} +} + +#[macro_export] +macro_rules! mark_feature_time { + ($state:expr, $feature:expr) => {{ + // Mark the elapsed time for the given feature + #[cfg(feature="perf_stats")] + $state.perf_stats_mut().mark_feature_time($feature); + }} +} + +/// Number of stages in the fuzzer +const NUM_STAGES: usize = 4; + +/// Client performance statistics +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +pub struct ClientPerfStats { + /// Starting counter (in clock cycles from [`crate::cpu::read_time_counter`]) + start_time: u64, + + /// Current counter in the fuzzer (in clock cycles from + /// [`crate::cpu::read_time_counter`] + current_time: u64, + + /// Clock cycles spent in the scheduler + scheduler: u64, + + /// Clock cycles spent in the manager + manager: u64, + + /// Current stage index to write the next stage benchmark time + curr_stage: u8, + + /// Flag to dictate this stage is in use. Used during printing to not print the empty + /// stages if they are not in use + stages_used: [bool; NUM_STAGES], + + /// Clock cycles spent in the the various features of each stage. Can currently keep + /// track of [`NUM_STAGE_FEATURES`] + stages: [[u64; PerfFeature::Count as usize]; NUM_STAGES], + + /// Current time set by `start_timer` + timer_start: Option +} + +/// Various features that are measured for performance +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[repr(u8)] +pub enum PerfFeature { + /// Getting an input from the corpus + GetInputFromCorpus = 0, + + /// Mutating the input + Mutate = 1, + + /// Post-Exec Mutator callback + MutatePostExec = 2, + + /// Actual time spent executing the target + TargetExecution = 3, + + /// Time spent in the [`pre_exec`](crate::executors::Executor::pre_exec) callback + PreExec = 4, + + /// Time spent in the [`post_exec`](crate::executors::Executor::post_exec) callback + PostExec = 5, + + /// Time spent in the [`pre_exec_observers`](crate::executors::Executor::pre_exec_observers) callback + PreExecObservers = 6, + + /// Time spent in the [`post_exec_observers`](crate::executors::Executor::post_exec_observers) callback + PostExecObservers = 7, + + /// Time spent getting the feedback from [`is_interesting_all`] from all feedbacks + GetFeedbackInterestingAll = 8, + + /// Time spent getting the feedback from [`is_interesting_all`] from all observers + GetObjectivesInterestingAll = 9, + + /// Used as a counter to know how many elements are in [`PerfFeature`]. Must be the + /// last value in the enum. + Count + // !! No more values here since Count is last! !! + // !! No more values here since Count is last! !! +} + +// TryFromPrimitive requires `std` so these are implemented manually +impl From for usize { + fn from(val: PerfFeature) -> usize { + match val { + PerfFeature::GetInputFromCorpus => PerfFeature::GetInputFromCorpus as usize, + PerfFeature::Mutate => PerfFeature::Mutate as usize, + PerfFeature::MutatePostExec => PerfFeature::MutatePostExec as usize, + PerfFeature::TargetExecution => PerfFeature::TargetExecution as usize, + PerfFeature::PreExec => PerfFeature::PreExec as usize, + PerfFeature::PostExec => PerfFeature::PostExec as usize, + PerfFeature::PreExecObservers => PerfFeature::PreExecObservers as usize, + PerfFeature::PostExecObservers => PerfFeature::PostExecObservers as usize, + PerfFeature::GetFeedbackInterestingAll + => PerfFeature::GetFeedbackInterestingAll as usize, + PerfFeature::GetObjectivesInterestingAll + => PerfFeature::GetObjectivesInterestingAll as usize, + PerfFeature::Count => PerfFeature::Count as usize + } + } +} + +// TryFromPrimitive requires `std` so these are implemented manually +impl From for PerfFeature { + fn from(val: usize) -> PerfFeature { + match val { + 0 => PerfFeature::GetInputFromCorpus, + 1 => PerfFeature::Mutate, + 2 => PerfFeature::MutatePostExec, + 3 => PerfFeature::TargetExecution, + 4 => PerfFeature::PreExec, + 5 => PerfFeature::PostExec, + 6 => PerfFeature::PreExecObservers, + 7 => PerfFeature::PostExecObservers, + 8 => PerfFeature::GetFeedbackInterestingAll, + 9 => PerfFeature::GetObjectivesInterestingAll, + _ => panic!("Unknown PerfFeature: {}", val) + } + } +} + +/// Number of features we can measure for performance +const NUM_PERF_FEATURES: usize = PerfFeature::Count as usize; + +#[cfg(feature = "perf_stats")] +impl ClientPerfStats { + /// Create a blank [`ClientPerfStats`] with the `start_time` and `current_time` with + /// the current clock counter + pub fn new() -> Self { + let start_time = cpu::read_time_counter().try_into().unwrap(); + + Self { + start_time: start_time, + current_time: start_time, + scheduler: 0, + manager: 0, + curr_stage: 0, + stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], + stages_used: [false; NUM_STAGES], + timer_start: None + } + } + + /// Set the current time with the given time + pub fn set_current_time(&mut self, time: u64) { + self.current_time = time; + } + + /// Start a timer with the current time counter + pub fn start_timer(&mut self) { + self.timer_start = Some(cpu::read_time_counter()); + } + + /// Update the current [`ClientPerfStats`] with the given [`ClientPerfStats`] + pub fn update(&mut self, stats: ClientPerfStats) { + self.set_current_time(stats.current_time); + self.update_scheduler(stats.scheduler); + self.update_manager(stats.manager); + self.update_stages(stats.stages); + } + + /// Gets the elapsed time since the internal timer started. Resets the timer when + /// finished execution. + fn mark_time(&mut self) -> u64 { + match self.timer_start { + None => { + // Warning message if marking time without starting the timer first + eprint!("Attempted to `mark_time` without starting timer first."); + + // Return 0 for no time marked + 0 + } + Some(timer_start) => { + // Calculate the elapsed time + let elapsed = cpu::read_time_counter() - timer_start; + + // Reset the timer + self.timer_start = None; + + // Return the elapsed time + elapsed + } + } + } + + /// Update the time spent in the scheduler with the elapsed time that we have seen + pub fn mark_scheduler_time(&mut self) { + // Get the current elapsed time + let elapsed = self.mark_time(); + + // Add the time to the scheduler stat + self.update_scheduler(elapsed) + } + + /// Update the time spent in the scheduler with the elapsed time that we have seen + pub fn mark_manager_time(&mut self) { + // Get the current elapsed time + let elapsed = self.mark_time(); + + // Add the time the manager stat + self.update_manager(elapsed); + } + + /// Update the time spent in the given [`PerfFeature`] with the elapsed time that we have seen + pub fn mark_feature_time(&mut self, feature: PerfFeature) { + // Get the current elapsed time + let elapsed = self.mark_time(); + + // Add the time the the given feature + self.update_feature(feature, elapsed); + } + + /// Add the given `time` to the `scheduler` stats + pub fn update_scheduler(&mut self, time: u64) { + self.scheduler = self.scheduler.checked_add(time) + .expect("update_scheduler overflow"); + } + + /// Add the given `time` to the `manager` stats + pub fn update_manager(&mut self, time: u64) { + self.manager = self.manager.checked_add(time) + .expect("update_manager overflow"); + } + + /// Update the total stage counter and increment the stage counter for the next stage + pub fn finish_stage(&mut self) { + // Increment the stage to the next index. The check is only done if this were to + // be used past the length of the `self.stages` buffer + self.curr_stage += 1; + } + + /// Reset the stage index counter to zero + pub fn reset_stage_index(&mut self) { + self.curr_stage = 0; + } + + /// Update the time spent in the stages + pub fn update_stages(&mut self, stages: [[u64; NUM_PERF_FEATURES]; NUM_STAGES]) + { + for (stage_index, features) in stages.iter().enumerate() { + for (feature_index, feature) in features.iter().enumerate() { + self.stages[stage_index][feature_index] = + self.stages[stage_index][feature_index].checked_add(*feature) + .expect("Stage overflow"); + } + } + } + + /// Update the given [`PerfFeature`] with the given `time` + pub fn update_feature(&mut self, feature: PerfFeature, time: u64) { + // Sanity check that these stats have enough room for these benchmarks + assert!((self.curr_stage as usize) < NUM_STAGES, "Current fuzzer has more stages + than the `ClientPerfStats` supports ({}). Please update the NUM_STAGES + const value in src/stats/mod.rs and rebuild", NUM_STAGES); + + // Get the current stage index as `usize` + let stage_index: usize = self.curr_stage.try_into().unwrap(); + + // Get the index of the given feature + let feature_index: usize = feature.try_into().unwrap(); + + // Update the given feature + self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] + .checked_add(time) + .expect("Stage overflow"); + + // Set that the current stage is being used + self.stages_used[stage_index] = true; + } +} + +#[cfg(feature = "perf_stats")] +impl core::fmt::Display for ClientPerfStats { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + // Calculate the elapsed time from the stats + let elapsed: f64 = (self.current_time - self.start_time) as f64; + + // Calculate the percentages for each benchmark + let scheduler_percent = self.scheduler as f64 / elapsed; + let manager_percent = self.manager as f64 / elapsed; + + // Calculate the remaining percentage that has not been benchmarked + let mut other_percent = 1.0; + other_percent -= scheduler_percent; + other_percent -= manager_percent; + + // Create the formatted string + write!(f, "Scheduler: {:4.2} | Manager: {:4.2} | Stages:\n", scheduler_percent, + manager_percent)?; + + // Calculate each stage + for (stage_index, features) in self.stages.iter().enumerate() { + // Make sure this stage is actually used before dumping its information + if !self.stages_used[stage_index as usize] { + continue; + } + + // Write the stage header + write!(f, " Stage {}:\n", stage_index)?; + + for (feature_index, feature) in features.iter().enumerate() { + // Calculate this current stage's percentage + let feature_percent = *feature as f64 / elapsed; + + // Ignore this feature if it isn't used + if feature_percent == 0.0 { + continue; + } + + // Update the other percent by removing this current percent + other_percent -= feature_percent; + + // Get the actual feature from the feature index for printing its name + let feature: PerfFeature = feature_index.into(); + + // Write the percentage for this feature + write!(f, " {:6.4}: {:?}\n", feature_percent, feature)?; + } + } + + write!(f, " Not Measured: {:4.2}", other_percent)?; + + Ok(()) + } +} + +#[cfg(feature = "perf_stats")] +impl Default for ClientPerfStats { + fn default() -> Self { + Self::new() + } +} From c017d728f7db0c19b0b01e59abf14547d11a347a Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Wed, 7 Apr 2021 16:28:22 -0500 Subject: [PATCH 02/18] Add perf_stats feature to libafl/Cargo.toml --- libafl/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 72aafb481c..8262e7464a 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -35,6 +35,7 @@ std = [] # print, sharedmap, ... support anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint. derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_small_maps = [] # reduces initial map size for llmp +perf_stats = [] # Include performance statistics of the fuzzing pipeline [[example]] name = "llmp_test" From 23061efcbf316d4a658d9beba115eb7584a16350 Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 12 Apr 2021 12:06:05 +0000 Subject: [PATCH 03/18] Update feedbacks to have with_perf --- libafl/Cargo.toml | 1 + libafl/src/feedbacks/mod.rs | 49 ++++++++++++++++ libafl/src/stages/mutational.rs | 4 +- libafl/src/state/mod.rs | 25 ++++++-- libafl/src/stats/mod.rs | 100 +++++++++++++++++++++++++++----- 5 files changed, 161 insertions(+), 18 deletions(-) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 72aafb481c..8262e7464a 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -35,6 +35,7 @@ std = [] # print, sharedmap, ... support anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint. derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_small_maps = [] # reduces initial map size for llmp +perf_stats = [] # Include performance statistics of the fuzzing pipeline [[example]] name = "llmp_test" diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 53e2cf4b8e..8286475c94 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -13,6 +13,10 @@ use crate::{ inputs::Input, observers::{ObserversTuple, TimeObserver}, Error, + state::HasClientPerfStats, + stats::{PerfFeature, NUM_FEEDBACKS}, + start_timer, + mark_feedback_time }; use core::time::Duration; @@ -57,6 +61,20 @@ where exit_kind: ExitKind, ) -> Result; + /// Get the total interestingness value from all feedbacks with performance + /// statistics included + #[cfg(feature="perf_stats")] + fn is_interesting_all_with_perf( + &mut self, + input: &I, + observers: &OT, + exit_kind: ExitKind, + feedback_stats: &mut [u64; NUM_FEEDBACKS], + feedback_index: usize + ) -> Result { + return Ok(0); + } + /// Write metadata for this testcase fn append_metadata_all(&mut self, testcase: &mut Testcase) -> Result<(), Error>; @@ -105,6 +123,37 @@ where + self.1.is_interesting_all(input, observers, exit_kind)?) } + #[cfg(feature="perf_stats")] + fn is_interesting_all_with_perf( + &mut self, + input: &I, + observers: &OT, + exit_kind: ExitKind, + feedback_stats: &mut [u64; NUM_FEEDBACKS], + feedback_index: usize + ) -> Result { + let mut res = 0; + + // Start a timer for this feedback + let start_time = crate::cpu::read_time_counter(); + + // Execute this feedback + res += self.0.is_interesting(input, observers, exit_kind.clone())?; + + // Get the elapsed time for checking this feedback + let elapsed = crate::cpu::read_time_counter() - start_time; + + // Add this stat to the feedback metrics + feedback_stats[feedback_index] = elapsed; + + // Increment the index + let next_index = feedback_index + 1; + + // Continue executing the chain + Ok(res + self.1.is_interesting_all_with_perf(input, observers, exit_kind, + feedback_stats, next_index)?) + } + fn append_metadata_all(&mut self, testcase: &mut Testcase) -> Result<(), Error> { self.0.append_metadata(testcase)?; self.1.append_metadata_all(testcase) diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 95aafdb87c..3b746aaefd 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -67,7 +67,9 @@ where self.mutator_mut().mutate(state, &mut input_mut, i as i32)?; mark_feature_time!(state, PerfFeature::Mutate); - let (_, corpus_idx) = state.evaluate_input(input_mut, executor, manager, scheduler)?; + // Time is measured directly the `evaluate_input` function + let (_, corpus_idx) = state.evaluate_input(input_mut, executor, manager, + scheduler)?; start_timer!(state); self.mutator_mut().post_exec(state, i as i32, corpus_idx)?; diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index a37256ba79..b3a913344e 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -704,12 +704,29 @@ where executor.post_exec_observers()?; mark_feature_time!(self, PerfFeature::PostExecObservers); - start_timer!(self); let observers = executor.observers(); - let fitness = - self.feedbacks_mut() + #[cfg(not(feature="perf_stats"))] + let fitness = self.feedbacks_mut() .is_interesting_all(&input, observers, exit_kind.clone())?; - mark_feature_time!(self, PerfFeature::GetFeedbackInterestingAll); + + #[cfg(feature="perf_stats")] + let fitness = { + // Init temporary feedback stats here. We can't use the typical pattern above + // since we need a `mut self` for `feedbacks_mut`, so we can't also hand a + // new `mut self` to `is_interesting_all_with_perf`. We use this stack + // variable to get the stats and then update the feedbacks directly + let mut feedback_stats = [0_u64; crate::stats::NUM_FEEDBACKS]; + let mut feedback_index = 0; + let fitness = self.feedbacks_mut() + .is_interesting_all_with_perf(&input, observers, exit_kind.clone(), + &mut feedback_stats, feedback_index)?; + + // Update the feedback stats + self.perf_stats_mut().update_feedbacks(feedback_stats); + + // Return the total fitness + fitness + }; start_timer!(self); let is_solution = self diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 511aa118bd..4b679158a4 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -256,9 +256,21 @@ macro_rules! mark_feature_time { }} } +#[macro_export] +macro_rules! mark_feedback_time { + ($state:expr) => {{ + // Mark the elapsed time for the given feature + #[cfg(feature="perf_stats")] + $state.perf_stats_mut().mark_feedback_time(); + }} +} + /// Number of stages in the fuzzer const NUM_STAGES: usize = 4; +/// Number of feedback mechanisms to measure for performance +pub const NUM_FEEDBACKS: usize = 4; + /// Client performance statistics #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct ClientPerfStats { @@ -278,14 +290,19 @@ pub struct ClientPerfStats { /// Current stage index to write the next stage benchmark time curr_stage: u8, + /// Current feedback index to write the next feedback benchmark time + curr_feedback: u8, + /// Flag to dictate this stage is in use. Used during printing to not print the empty - /// stages if they are not in use + /// stages if they are not in use. stages_used: [bool; NUM_STAGES], - /// Clock cycles spent in the the various features of each stage. Can currently keep - /// track of [`NUM_STAGE_FEATURES`] + /// Clock cycles spent in the the various features of each stage stages: [[u64; PerfFeature::Count as usize]; NUM_STAGES], + /// Clock cycles spent in each feedback mechanism of the fuzzer. + feedbacks: [u64; NUM_FEEDBACKS], + /// Current time set by `start_timer` timer_start: Option } @@ -382,14 +399,16 @@ impl ClientPerfStats { let start_time = cpu::read_time_counter().try_into().unwrap(); Self { - start_time: start_time, - current_time: start_time, - scheduler: 0, - manager: 0, - curr_stage: 0, - stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], - stages_used: [false; NUM_STAGES], - timer_start: None + start_time: start_time, + current_time: start_time, + scheduler: 0, + manager: 0, + curr_stage: 0, + curr_feedback: 0, + stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], + stages_used: [false; NUM_STAGES], + feedbacks: [0; NUM_FEEDBACKS], + timer_start: None } } @@ -409,6 +428,7 @@ impl ClientPerfStats { self.update_scheduler(stats.scheduler); self.update_manager(stats.manager); self.update_stages(stats.stages); + self.update_feedbacks(stats.feedbacks); } /// Gets the elapsed time since the internal timer started. Resets the timer when @@ -462,6 +482,30 @@ impl ClientPerfStats { self.update_feature(feature, elapsed); } + /// Update the time spent in the current [`Feedback`] with the elapsed time that we + /// have seen + pub fn mark_feedback_time(&mut self) { + // Sanity check that these stats have enough room for these benchmarks + assert!((self.curr_feedback as usize) < NUM_FEEDBACKS, "Current fuzzer has more + stages than the `ClientPerfStats` supports ({}). Please update the + NUM_FEEDBACKS const value in src/stats/mod.rs and rebuild", + NUM_FEEDBACKS); + + // Get the current elapsed time + let elapsed = self.mark_time(); + + // Get a `usize` for the index + let index: usize = self.curr_feedback.try_into().unwrap(); + + // Update the current feedback's time with the given time + self.feedbacks[index] = self.feedbacks[index] + .checked_add(elapsed) + .expect("mark_feedback_time overflow"); + + // Increment the feedback index to the next feedback + self.curr_feedback += 1; + } + /// Add the given `time` to the `scheduler` stats pub fn update_scheduler(&mut self, time: u64) { self.scheduler = self.scheduler.checked_add(time) @@ -486,9 +530,22 @@ impl ClientPerfStats { self.curr_stage = 0; } + /// Reset the feedback index counter to zero + pub fn reset_feedback_index(&mut self) { + self.curr_feedback = 0; + } + + /// Update the time spent in the feedbacks + pub fn update_feedbacks(&mut self, feedbacks: [u64; NUM_FEEDBACKS]) { + for (feedback_index, feedback) in feedbacks.iter().enumerate() { + self.feedbacks[feedback_index] = + self.feedbacks[feedback_index].checked_add(*feedback) + .expect("update_feedback overflow"); + } + } + /// Update the time spent in the stages - pub fn update_stages(&mut self, stages: [[u64; NUM_PERF_FEATURES]; NUM_STAGES]) - { + pub fn update_stages(&mut self, stages: [[u64; NUM_PERF_FEATURES]; NUM_STAGES]) { for (stage_index, features) in stages.iter().enumerate() { for (feature_index, feature) in features.iter().enumerate() { self.stages[stage_index][feature_index] = @@ -568,6 +625,23 @@ impl core::fmt::Display for ClientPerfStats { // Write the percentage for this feature write!(f, " {:6.4}: {:?}\n", feature_percent, feature)?; } + + for (feedback_index, feedback) in self.feedbacks.iter().enumerate() { + // Calculate this current stage's percentage + let feedback_percent = *feedback as f64 / elapsed; + + // Ignore this feedback if it isn't used + if feedback_percent == 0.0 { + continue; + } + + // Update the other percent by removing this current percent + other_percent -= feedback_percent; + + // Write the percentage for this feedback + write!(f, " {:6.4}: Feedback index {}\n", feedback_percent, + feedback_index)?; + } } write!(f, " Not Measured: {:4.2}", other_percent)?; From fedb5f8a67d31c0a1022f61a45fff778328a1b6b Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Sun, 18 Apr 2021 14:47:37 -0500 Subject: [PATCH 04/18] Remove unneeeded print statement --- libafl/src/stages/mutational.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 0a88df11b1..9c64152421 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -50,8 +50,6 @@ where scheduler: &CS, corpus_idx: usize, ) -> Result<(), Error> { - print!("Perform mutational\n"); - let num = self.iterations(state); for i in 0..num { From 7423cefccc8b70880b4517c1be781cc546757f06 Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Sun, 18 Apr 2021 15:13:34 -0500 Subject: [PATCH 05/18] cargo fmt all the things --- libafl/src/cpu.rs | 2 +- libafl/src/events/mod.rs | 2 +- libafl/src/feedbacks/mod.rs | 24 +++-- libafl/src/lib.rs | 2 +- libafl/src/stages/mod.rs | 7 +- libafl/src/stages/mutational.rs | 8 +- libafl/src/state/mod.rs | 36 ++++---- libafl/src/stats/mod.rs | 150 ++++++++++++++++++-------------- 8 files changed, 127 insertions(+), 104 deletions(-) diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs index e1b30c7fd9..9725746ba0 100644 --- a/libafl/src/cpu.rs +++ b/libafl/src/cpu.rs @@ -2,7 +2,7 @@ /// Read the time counter. This is primarily used for benchmarking various components in /// the fuzzer. -#[cfg(target_arch="x86_64")] +#[cfg(target_arch = "x86_64")] pub fn read_time_counter() -> u64 { unsafe { core::arch::x86_64::_rdtsc() } } diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index fdcc3b4a24..67c75eefa8 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -17,7 +17,7 @@ use crate::{ Error, }; -#[cfg(feature = "perf_stats")] +#[cfg(feature = "perf_stats")] use crate::stats::ClientPerfStats; /// The log event severity diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 11439a2d3c..b2fdb13c72 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -11,12 +11,12 @@ use crate::{ corpus::Testcase, executors::ExitKind, inputs::Input, + mark_feedback_time, observers::{ObserversTuple, TimeObserver}, - Error, + start_timer, state::HasClientPerfStats, stats::{PerfFeature, NUM_FEEDBACKS}, - start_timer, - mark_feedback_time + Error, }; use core::time::Duration; @@ -63,14 +63,14 @@ where /// Get the total interestingness value from all feedbacks with performance /// statistics included - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] fn is_interesting_all_with_perf( &mut self, input: &I, observers: &OT, exit_kind: ExitKind, feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize + feedback_index: usize, ) -> Result { return Ok(0); } @@ -123,14 +123,14 @@ where + self.1.is_interesting_all(input, observers, exit_kind)?) } - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] fn is_interesting_all_with_perf( &mut self, input: &I, observers: &OT, exit_kind: ExitKind, feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize + feedback_index: usize, ) -> Result { let mut res = 0; @@ -150,8 +150,14 @@ where let next_index = feedback_index + 1; // Continue executing the chain - Ok(res + self.1.is_interesting_all_with_perf(input, observers, exit_kind, - feedback_stats, next_index)?) + Ok(res + + self.1.is_interesting_all_with_perf( + input, + observers, + exit_kind, + feedback_stats, + next_index, + )?) } fn append_metadata_all(&mut self, testcase: &mut Testcase) -> Result<(), Error> { diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index c71ca902e4..00e8abe76f 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -23,6 +23,7 @@ pub use libafl_derive::*; pub mod bolts; pub mod corpus; +pub mod cpu; pub mod events; pub mod executors; pub mod feedbacks; @@ -34,7 +35,6 @@ pub mod stages; pub mod state; pub mod stats; pub mod utils; -pub mod cpu; pub mod fuzzer; pub use fuzzer::*; diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index ef0f432870..16637ac219 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -5,9 +5,8 @@ pub use mutational::{MutationalStage, StdMutationalStage}; //pub use power::PowerMutationalStage; use crate::{ - bolts::tuples::TupleList, events::EventManager, executors::Executor, inputs::Input, - state::HasClientPerfStats, - Error + bolts::tuples::TupleList, events::EventManager, executors::Executor, inputs::Input, + state::HasClientPerfStats, Error, }; /// A stage is one step in the fuzzing process. @@ -70,7 +69,7 @@ where EM: EventManager, E: Executor, I: Input, - S: HasClientPerfStats + S: HasClientPerfStats, { fn perform_all( &mut self, diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 9c64152421..87195e11df 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -5,14 +5,15 @@ use crate::{ events::EventManager, executors::{Executor, HasObservers}, inputs::Input, + mark_feature_time, mutators::Mutator, observers::ObserversTuple, stages::Stage, - state::{Evaluator, HasCorpus, HasRand, HasClientPerfStats}, + start_timer, + state::{Evaluator, HasClientPerfStats, HasCorpus, HasRand}, stats::PerfFeature, utils::Rand, Error, - start_timer, mark_feature_time }; // TODO multi mutators stage @@ -67,8 +68,7 @@ where mark_feature_time!(state, PerfFeature::Mutate); // Time is measured directly the `evaluate_input` function - let (_, corpus_idx) = state.evaluate_input(input_mut, executor, manager, - scheduler)?; + let (_, corpus_idx) = state.evaluate_input(input_mut, executor, manager, scheduler)?; start_timer!(state); self.mutator_mut().post_exec(state, i as i32, corpus_idx)?; diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index a71dc9132c..d33106f96c 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -16,14 +16,14 @@ use crate::{ feedbacks::FeedbacksTuple, generators::Generator, inputs::Input, + mark_feature_time, observers::ObserversTuple, - utils::Rand, + start_timer, stats::{ClientPerfStats, PerfFeature}, + utils::Rand, Error, - start_timer, mark_feature_time }; - #[cfg(feature = "std")] use crate::inputs::bytes::BytesInput; @@ -74,8 +74,7 @@ where } /// Trait for offering a [`ClientPerfStats`] -pub trait HasClientPerfStats -{ +pub trait HasClientPerfStats { /// [`ClientPerfStats`] itself fn perf_stats(&self) -> &ClientPerfStats; @@ -251,7 +250,7 @@ where max_size: usize, /// Performance statistics for this fuzzer - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] perf_stats: ClientPerfStats, phantom: PhantomData, @@ -413,12 +412,12 @@ where /// Trait that that has [`HasExecution`] and [`HasClientPerfStats`] when "perf_stats" /// feature is enabled, and only [`HasExecution`] when "perf_stats" is disabled -#[cfg(feature="perf_stats")] +#[cfg(feature = "perf_stats")] pub trait HasFuzzerStats: HasExecutions + HasClientPerfStats {} /// Trait that that has [`HasExecution`] and [`HasClientPerfStats`] when "perf_stats" /// feature is enabled, and only [`HasExecution`] when "perf_stats" is disabled -#[cfg(not(feature="perf_stats"))] +#[cfg(not(feature = "perf_stats"))] pub trait HasFuzzerStats: HasExecutions {} impl HasFuzzerStats for State @@ -430,7 +429,6 @@ where SC: Corpus, OFT: FeedbacksTuple, { - } impl HasMaxSize for State @@ -704,11 +702,12 @@ where mark_feature_time!(self, PerfFeature::PostExecObservers); let observers = executor.observers(); - #[cfg(not(feature="perf_stats"))] - let fitness = self.feedbacks_mut() + #[cfg(not(feature = "perf_stats"))] + let fitness = + self.feedbacks_mut() .is_interesting_all(&input, observers, exit_kind.clone())?; - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] let fitness = { // Init temporary feedback stats here. We can't use the typical pattern above // since we need a `mut self` for `feedbacks_mut`, so we can't also hand a @@ -716,9 +715,13 @@ where // variable to get the stats and then update the feedbacks directly let mut feedback_stats = [0_u64; crate::stats::NUM_FEEDBACKS]; let mut feedback_index = 0; - let fitness = self.feedbacks_mut() - .is_interesting_all_with_perf(&input, observers, exit_kind.clone(), - &mut feedback_stats, feedback_index)?; + let fitness = self.feedbacks_mut().is_interesting_all_with_perf( + &input, + observers, + exit_kind.clone(), + &mut feedback_stats, + feedback_index, + )?; // Update the feedback stats self.perf_stats_mut().update_feedbacks(feedback_stats); @@ -784,12 +787,11 @@ where solutions, objectives, max_size: DEFAULT_MAX_SIZE, - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] perf_stats: ClientPerfStats::new(), phantom: PhantomData, } } - } #[cfg(feature = "perf_stats")] diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 5418000d81..fc34d050fb 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -5,14 +5,14 @@ use serde::{Deserialize, Serialize}; use alloc::{string::String, vec::Vec}; use core::{time, time::Duration}; -#[cfg(feature="std")] +#[cfg(feature = "std")] use std::convert::TryInto; -#[cfg(not(feature="std"))] +#[cfg(not(feature = "std"))] use core::convert::TryInto; -use crate::utils::current_time; use crate::cpu; +use crate::utils::current_time; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds @@ -35,7 +35,7 @@ pub struct ClientStats { /// Client performance statistics #[cfg(feature = "perf_stats")] - pub perf_stats: ClientPerfStats + pub perf_stats: ClientPerfStats, } impl ClientStats { @@ -113,7 +113,6 @@ pub trait Stats { /// show the stats to the user fn display(&mut self, event_msg: String); - /// Amount of elements in the corpus (combined for all children) fn corpus_size(&self) -> u64 { self.client_stats() @@ -200,7 +199,6 @@ where ); (self.print_fn)(fmt); - // Only print perf stats if the feature is enabled #[cfg(feature = "perf_stats")] { @@ -243,27 +241,27 @@ where macro_rules! start_timer { ($state:expr) => {{ // Start the timer - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] $state.perf_stats_mut().start_timer(); - }} + }}; } #[macro_export] macro_rules! mark_feature_time { ($state:expr, $feature:expr) => {{ // Mark the elapsed time for the given feature - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] $state.perf_stats_mut().mark_feature_time($feature); - }} + }}; } #[macro_export] macro_rules! mark_feedback_time { ($state:expr) => {{ // Mark the elapsed time for the given feature - #[cfg(feature="perf_stats")] + #[cfg(feature = "perf_stats")] $state.perf_stats_mut().mark_feedback_time(); - }} + }}; } /// Number of stages in the fuzzer @@ -278,7 +276,7 @@ pub struct ClientPerfStats { /// Starting counter (in clock cycles from [`crate::cpu::read_time_counter`]) start_time: u64, - /// Current counter in the fuzzer (in clock cycles from + /// Current counter in the fuzzer (in clock cycles from /// [`crate::cpu::read_time_counter`] current_time: u64, @@ -305,7 +303,7 @@ pub struct ClientPerfStats { feedbacks: [u64; NUM_FEEDBACKS], /// Current time set by `start_timer` - timer_start: Option + timer_start: Option, } /// Various features that are measured for performance @@ -344,9 +342,8 @@ pub enum PerfFeature { /// Used as a counter to know how many elements are in [`PerfFeature`]. Must be the /// last value in the enum. - Count - // !! No more values here since Count is last! !! - // !! No more values here since Count is last! !! + Count, // !! No more values here since Count is last! !! + // !! No more values here since Count is last! !! } // TryFromPrimitive requires `std` so these are implemented manually @@ -354,18 +351,20 @@ impl From for usize { fn from(val: PerfFeature) -> usize { match val { PerfFeature::GetInputFromCorpus => PerfFeature::GetInputFromCorpus as usize, - PerfFeature::Mutate => PerfFeature::Mutate as usize, - PerfFeature::MutatePostExec => PerfFeature::MutatePostExec as usize, - PerfFeature::TargetExecution => PerfFeature::TargetExecution as usize, - PerfFeature::PreExec => PerfFeature::PreExec as usize, - PerfFeature::PostExec => PerfFeature::PostExec as usize, - PerfFeature::PreExecObservers => PerfFeature::PreExecObservers as usize, - PerfFeature::PostExecObservers => PerfFeature::PostExecObservers as usize, - PerfFeature::GetFeedbackInterestingAll - => PerfFeature::GetFeedbackInterestingAll as usize, - PerfFeature::GetObjectivesInterestingAll - => PerfFeature::GetObjectivesInterestingAll as usize, - PerfFeature::Count => PerfFeature::Count as usize + PerfFeature::Mutate => PerfFeature::Mutate as usize, + PerfFeature::MutatePostExec => PerfFeature::MutatePostExec as usize, + PerfFeature::TargetExecution => PerfFeature::TargetExecution as usize, + PerfFeature::PreExec => PerfFeature::PreExec as usize, + PerfFeature::PostExec => PerfFeature::PostExec as usize, + PerfFeature::PreExecObservers => PerfFeature::PreExecObservers as usize, + PerfFeature::PostExecObservers => PerfFeature::PostExecObservers as usize, + PerfFeature::GetFeedbackInterestingAll => { + PerfFeature::GetFeedbackInterestingAll as usize + } + PerfFeature::GetObjectivesInterestingAll => { + PerfFeature::GetObjectivesInterestingAll as usize + } + PerfFeature::Count => PerfFeature::Count as usize, } } } @@ -384,7 +383,7 @@ impl From for PerfFeature { 7 => PerfFeature::PostExecObservers, 8 => PerfFeature::GetFeedbackInterestingAll, 9 => PerfFeature::GetObjectivesInterestingAll, - _ => panic!("Unknown PerfFeature: {}", val) + _ => panic!("Unknown PerfFeature: {}", val), } } } @@ -398,22 +397,22 @@ impl ClientPerfStats { /// the current clock counter pub fn new() -> Self { let start_time = cpu::read_time_counter().try_into().unwrap(); - + Self { - start_time: start_time, - current_time: start_time, - scheduler: 0, - manager: 0, - curr_stage: 0, + start_time: start_time, + current_time: start_time, + scheduler: 0, + manager: 0, + curr_stage: 0, curr_feedback: 0, - stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], - stages_used: [false; NUM_STAGES], - feedbacks: [0; NUM_FEEDBACKS], - timer_start: None + stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], + stages_used: [false; NUM_STAGES], + feedbacks: [0; NUM_FEEDBACKS], + timer_start: None, } } - /// Set the current time with the given time + /// Set the current time with the given time pub fn set_current_time(&mut self, time: u64) { self.current_time = time; } @@ -422,7 +421,7 @@ impl ClientPerfStats { pub fn start_timer(&mut self) { self.timer_start = Some(cpu::read_time_counter()); } - + /// Update the current [`ClientPerfStats`] with the given [`ClientPerfStats`] pub fn update(&mut self, stats: ClientPerfStats) { self.set_current_time(stats.current_time); @@ -483,14 +482,17 @@ impl ClientPerfStats { self.update_feature(feature, elapsed); } - /// Update the time spent in the current [`Feedback`] with the elapsed time that we + /// Update the time spent in the current [`Feedback`] with the elapsed time that we /// have seen pub fn mark_feedback_time(&mut self) { // Sanity check that these stats have enough room for these benchmarks - assert!((self.curr_feedback as usize) < NUM_FEEDBACKS, "Current fuzzer has more + assert!( + (self.curr_feedback as usize) < NUM_FEEDBACKS, + "Current fuzzer has more stages than the `ClientPerfStats` supports ({}). Please update the - NUM_FEEDBACKS const value in src/stats/mod.rs and rebuild", - NUM_FEEDBACKS); + NUM_FEEDBACKS const value in src/stats/mod.rs and rebuild", + NUM_FEEDBACKS + ); // Get the current elapsed time let elapsed = self.mark_time(); @@ -500,8 +502,8 @@ impl ClientPerfStats { // Update the current feedback's time with the given time self.feedbacks[index] = self.feedbacks[index] - .checked_add(elapsed) - .expect("mark_feedback_time overflow"); + .checked_add(elapsed) + .expect("mark_feedback_time overflow"); // Increment the feedback index to the next feedback self.curr_feedback += 1; @@ -509,14 +511,18 @@ impl ClientPerfStats { /// Add the given `time` to the `scheduler` stats pub fn update_scheduler(&mut self, time: u64) { - self.scheduler = self.scheduler.checked_add(time) - .expect("update_scheduler overflow"); + self.scheduler = self + .scheduler + .checked_add(time) + .expect("update_scheduler overflow"); } /// Add the given `time` to the `manager` stats pub fn update_manager(&mut self, time: u64) { - self.manager = self.manager.checked_add(time) - .expect("update_manager overflow"); + self.manager = self + .manager + .checked_add(time) + .expect("update_manager overflow"); } /// Update the total stage counter and increment the stage counter for the next stage @@ -539,9 +545,9 @@ impl ClientPerfStats { /// Update the time spent in the feedbacks pub fn update_feedbacks(&mut self, feedbacks: [u64; NUM_FEEDBACKS]) { for (feedback_index, feedback) in feedbacks.iter().enumerate() { - self.feedbacks[feedback_index] = - self.feedbacks[feedback_index].checked_add(*feedback) - .expect("update_feedback overflow"); + self.feedbacks[feedback_index] = self.feedbacks[feedback_index] + .checked_add(*feedback) + .expect("update_feedback overflow"); } } @@ -549,9 +555,9 @@ impl ClientPerfStats { pub fn update_stages(&mut self, stages: [[u64; NUM_PERF_FEATURES]; NUM_STAGES]) { for (stage_index, features) in stages.iter().enumerate() { for (feature_index, feature) in features.iter().enumerate() { - self.stages[stage_index][feature_index] = - self.stages[stage_index][feature_index].checked_add(*feature) - .expect("Stage overflow"); + self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] + .checked_add(*feature) + .expect("Stage overflow"); } } } @@ -559,9 +565,13 @@ impl ClientPerfStats { /// Update the given [`PerfFeature`] with the given `time` pub fn update_feature(&mut self, feature: PerfFeature, time: u64) { // Sanity check that these stats have enough room for these benchmarks - assert!((self.curr_stage as usize) < NUM_STAGES, "Current fuzzer has more stages + assert!( + (self.curr_stage as usize) < NUM_STAGES, + "Current fuzzer has more stages than the `ClientPerfStats` supports ({}). Please update the NUM_STAGES - const value in src/stats/mod.rs and rebuild", NUM_STAGES); + const value in src/stats/mod.rs and rebuild", + NUM_STAGES + ); // Get the current stage index as `usize` let stage_index: usize = self.curr_stage.try_into().unwrap(); @@ -571,8 +581,8 @@ impl ClientPerfStats { // Update the given feature self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] - .checked_add(time) - .expect("Stage overflow"); + .checked_add(time) + .expect("Stage overflow"); // Set that the current stage is being used self.stages_used[stage_index] = true; @@ -587,7 +597,7 @@ impl core::fmt::Display for ClientPerfStats { // Calculate the percentages for each benchmark let scheduler_percent = self.scheduler as f64 / elapsed; - let manager_percent = self.manager as f64 / elapsed; + let manager_percent = self.manager as f64 / elapsed; // Calculate the remaining percentage that has not been benchmarked let mut other_percent = 1.0; @@ -595,8 +605,11 @@ impl core::fmt::Display for ClientPerfStats { other_percent -= manager_percent; // Create the formatted string - write!(f, "Scheduler: {:4.2} | Manager: {:4.2} | Stages:\n", scheduler_percent, - manager_percent)?; + write!( + f, + "Scheduler: {:4.2} | Manager: {:4.2} | Stages:\n", + scheduler_percent, manager_percent + )?; // Calculate each stage for (stage_index, features) in self.stages.iter().enumerate() { @@ -640,8 +653,11 @@ impl core::fmt::Display for ClientPerfStats { other_percent -= feedback_percent; // Write the percentage for this feedback - write!(f, " {:6.4}: Feedback index {}\n", feedback_percent, - feedback_index)?; + write!( + f, + " {:6.4}: Feedback index {}\n", + feedback_percent, feedback_index + )?; } } From 4ad9c105b614e8de247dad573dd0c82ed9677a57 Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 19 Apr 2021 15:46:40 -0500 Subject: [PATCH 06/18] use local llvmint vs cpu specific asm for reading cycle counter --- libafl/.libs/llvmint/Cargo.toml | 8 ++++++++ libafl/.libs/llvmint/src/lib.rs | 6 ++++++ libafl/Cargo.toml | 3 ++- libafl/src/cpu.rs | 12 +++++++----- libafl/src/fuzzer.rs | 3 +-- libafl/src/stats/mod.rs | 12 +++++------- 6 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 libafl/.libs/llvmint/Cargo.toml create mode 100644 libafl/.libs/llvmint/src/lib.rs diff --git a/libafl/.libs/llvmint/Cargo.toml b/libafl/.libs/llvmint/Cargo.toml new file mode 100644 index 0000000000..30b1143ed4 --- /dev/null +++ b/libafl/.libs/llvmint/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "llvmint" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/libafl/.libs/llvmint/src/lib.rs b/libafl/.libs/llvmint/src/lib.rs new file mode 100644 index 0000000000..6b2e1b36fa --- /dev/null +++ b/libafl/.libs/llvmint/src/lib.rs @@ -0,0 +1,6 @@ +#![feature(link_llvm_intrinsics)] + +extern { + #[link_name = "llvm.readcyclecounter"] + pub fn readcyclecounter() -> u64; +} diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 2e3f996ed7..a93efb5f89 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -36,7 +36,7 @@ anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_small_maps = [] # reduces initial map size for llmp llmp_debug = [] # Enables debug output for LLMP -perf_stats = [] # Include performance statistics of the fuzzing pipeline +perf_stats = ["llvmint"] # Include performance statistics of the fuzzing pipeline [[example]] name = "llmp_test" @@ -59,6 +59,7 @@ serde_json = { version = "1.0", optional = true, default-features = false, featu num_enum = "0.5.1" backtrace = "0.3" # for llmp_debug +llvmint = { path = "./.libs/llvmint", optional = true } # Contains llvm.readcyclecounter for perf_stats [target.'cfg(unix)'.dependencies] libc = "0.2" # For (*nix) libc diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs index 9725746ba0..48a73023b7 100644 --- a/libafl/src/cpu.rs +++ b/libafl/src/cpu.rs @@ -1,8 +1,10 @@ -//! Architecture agnostic utility functions +//! Architecture agnostic processor features -/// Read the time counter. This is primarily used for benchmarking various components in -/// the fuzzer. -#[cfg(target_arch = "x86_64")] +/// Read time counter using [`llvmint::readcyclecounter`] +/// +/// This function is a wrapper around [`llvmint`] to make it easier to test various +/// implementations of reading a cycle counter. In this way, an experiment only has to +/// change this implementation rather than every instead of [`cpu::read_time_counter`] pub fn read_time_counter() -> u64 { - unsafe { core::arch::x86_64::_rdtsc() } + unsafe { llvmint::readcyclecounter() } } diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index cd2f7b485b..970df91c08 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -2,7 +2,6 @@ use crate::{ corpus::CorpusScheduler, - cpu, events::{Event, EventManager}, executors::{Executor, HasObservers}, inputs::Input, @@ -195,7 +194,7 @@ where { state .perf_stats_mut() - .set_current_time(cpu::read_time_counter()); + .set_current_time(crate::cpu::read_time_counter()); // Send the current stats over to the manager. This `.clone` shouldn't be // costly as `ClientPerfStats` impls `Copy` since it only contains `u64`s diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index fc34d050fb..a655e1756a 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -11,7 +11,6 @@ use std::convert::TryInto; #[cfg(not(feature = "std"))] use core::convert::TryInto; -use crate::cpu; use crate::utils::current_time; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds @@ -273,11 +272,10 @@ pub const NUM_FEEDBACKS: usize = 4; /// Client performance statistics #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct ClientPerfStats { - /// Starting counter (in clock cycles from [`crate::cpu::read_time_counter`]) + /// Starting counter (in clock cycles from [`cpu::read_time_counter`]) start_time: u64, - /// Current counter in the fuzzer (in clock cycles from - /// [`crate::cpu::read_time_counter`] + /// Current counter in the fuzzer (in clock cycles from [`cpu::read_time_counter`] current_time: u64, /// Clock cycles spent in the scheduler @@ -396,7 +394,7 @@ impl ClientPerfStats { /// Create a blank [`ClientPerfStats`] with the `start_time` and `current_time` with /// the current clock counter pub fn new() -> Self { - let start_time = cpu::read_time_counter().try_into().unwrap(); + let start_time = crate::cpu::read_time_counter().try_into().unwrap(); Self { start_time: start_time, @@ -419,7 +417,7 @@ impl ClientPerfStats { /// Start a timer with the current time counter pub fn start_timer(&mut self) { - self.timer_start = Some(cpu::read_time_counter()); + self.timer_start = Some(crate::cpu::read_time_counter()); } /// Update the current [`ClientPerfStats`] with the given [`ClientPerfStats`] @@ -444,7 +442,7 @@ impl ClientPerfStats { } Some(timer_start) => { // Calculate the elapsed time - let elapsed = cpu::read_time_counter() - timer_start; + let elapsed = crate::cpu::read_time_counter() - timer_start; // Reset the timer self.timer_start = None; From b9a0c7c19f22693d1044981b882407203d183dfe Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 19 Apr 2021 15:56:07 -0500 Subject: [PATCH 07/18] Remove debug testing code --- fuzzers/libfuzzer_libpng/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/fuzzers/libfuzzer_libpng/src/lib.rs b/fuzzers/libfuzzer_libpng/src/lib.rs index 8e7f1f817a..5c2fd9f6c7 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -154,9 +154,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?; - print!("DONE\n"); - loop {} - // Never reached Ok(()) } From 2a96fe31ce3857e4e28bc64f6c82c10459f04f8d Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 19 Apr 2021 15:59:17 -0500 Subject: [PATCH 08/18] Stats timeout to 3 seconds --- libafl/src/fuzzer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index 970df91c08..b573fe0f80 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -15,8 +15,8 @@ use crate::{ use alloc::string::ToString; use core::{marker::PhantomData, time::Duration}; -/// Send a stats update all 1 (or more) seconds -const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(1000); +/// Send a stats update all 3 (or more) seconds +const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(3 * 1000); /// Holds a set of stages pub trait HasStages From 55703cba429a68ff90ef4f005ffd1824d354f7a0 Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 19 Apr 2021 16:01:11 -0500 Subject: [PATCH 09/18] Inline smallish functions for ClientPerfStats --- libafl/src/stats/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index a655e1756a..62aa92b31a 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -411,11 +411,13 @@ impl ClientPerfStats { } /// Set the current time with the given time + #[inline] pub fn set_current_time(&mut self, time: u64) { self.current_time = time; } /// Start a timer with the current time counter + #[inline] pub fn start_timer(&mut self) { self.timer_start = Some(crate::cpu::read_time_counter()); } @@ -431,6 +433,7 @@ impl ClientPerfStats { /// Gets the elapsed time since the internal timer started. Resets the timer when /// finished execution. + #[inline] fn mark_time(&mut self) -> u64 { match self.timer_start { None => { @@ -454,6 +457,7 @@ impl ClientPerfStats { } /// Update the time spent in the scheduler with the elapsed time that we have seen + #[inline] pub fn mark_scheduler_time(&mut self) { // Get the current elapsed time let elapsed = self.mark_time(); @@ -463,6 +467,7 @@ impl ClientPerfStats { } /// Update the time spent in the scheduler with the elapsed time that we have seen + #[inline] pub fn mark_manager_time(&mut self) { // Get the current elapsed time let elapsed = self.mark_time(); @@ -472,6 +477,7 @@ impl ClientPerfStats { } /// Update the time spent in the given [`PerfFeature`] with the elapsed time that we have seen + #[inline] pub fn mark_feature_time(&mut self, feature: PerfFeature) { // Get the current elapsed time let elapsed = self.mark_time(); @@ -508,6 +514,7 @@ impl ClientPerfStats { } /// Add the given `time` to the `scheduler` stats + #[inline] pub fn update_scheduler(&mut self, time: u64) { self.scheduler = self .scheduler @@ -516,6 +523,7 @@ impl ClientPerfStats { } /// Add the given `time` to the `manager` stats + #[inline] pub fn update_manager(&mut self, time: u64) { self.manager = self .manager @@ -524,6 +532,7 @@ impl ClientPerfStats { } /// Update the total stage counter and increment the stage counter for the next stage + #[inline] pub fn finish_stage(&mut self) { // Increment the stage to the next index. The check is only done if this were to // be used past the length of the `self.stages` buffer @@ -531,11 +540,13 @@ impl ClientPerfStats { } /// Reset the stage index counter to zero + #[inline] pub fn reset_stage_index(&mut self) { self.curr_stage = 0; } /// Reset the feedback index counter to zero + #[inline] pub fn reset_feedback_index(&mut self) { self.curr_feedback = 0; } From 042bfcec9381bf47e8cedbd1edbd52cd52590a6e Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 19 Apr 2021 17:35:00 -0500 Subject: [PATCH 10/18] Remove .libs/llvmint and have the correct conditional compilation of link_llvm_intrinsics on the perf_stats feature --- libafl/.libs/llvmint/Cargo.toml | 8 -------- libafl/.libs/llvmint/src/lib.rs | 6 ------ libafl/Cargo.toml | 4 +--- libafl/src/cpu.rs | 7 ++++++- libafl/src/lib.rs | 1 + 5 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 libafl/.libs/llvmint/Cargo.toml delete mode 100644 libafl/.libs/llvmint/src/lib.rs diff --git a/libafl/.libs/llvmint/Cargo.toml b/libafl/.libs/llvmint/Cargo.toml deleted file mode 100644 index 30b1143ed4..0000000000 --- a/libafl/.libs/llvmint/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "llvmint" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/libafl/.libs/llvmint/src/lib.rs b/libafl/.libs/llvmint/src/lib.rs deleted file mode 100644 index 6b2e1b36fa..0000000000 --- a/libafl/.libs/llvmint/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![feature(link_llvm_intrinsics)] - -extern { - #[link_name = "llvm.readcyclecounter"] - pub fn readcyclecounter() -> u64; -} diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index a93efb5f89..22856a12fd 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -36,7 +36,7 @@ anymapdbg = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_small_maps = [] # reduces initial map size for llmp llmp_debug = [] # Enables debug output for LLMP -perf_stats = ["llvmint"] # Include performance statistics of the fuzzing pipeline +perf_stats = [] # Include performance statistics of the fuzzing pipeline [[example]] name = "llmp_test" @@ -57,9 +57,7 @@ libafl_derive = { version = "*", optional = true, path = "../libafl_derive" } serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap #TODO: for llmp brotli = { version = "3.3.0", default-features = false } # brotli compression num_enum = "0.5.1" - backtrace = "0.3" # for llmp_debug -llvmint = { path = "./.libs/llvmint", optional = true } # Contains llvm.readcyclecounter for perf_stats [target.'cfg(unix)'.dependencies] libc = "0.2" # For (*nix) libc diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs index 48a73023b7..41390b74e6 100644 --- a/libafl/src/cpu.rs +++ b/libafl/src/cpu.rs @@ -1,10 +1,15 @@ //! Architecture agnostic processor features +extern { + #[link_name = "llvm.readcyclecounter"] + pub fn readcyclecounter() -> u64; +} + /// Read time counter using [`llvmint::readcyclecounter`] /// /// This function is a wrapper around [`llvmint`] to make it easier to test various /// implementations of reading a cycle counter. In this way, an experiment only has to /// change this implementation rather than every instead of [`cpu::read_time_counter`] pub fn read_time_counter() -> u64 { - unsafe { llvmint::readcyclecounter() } + unsafe { readcyclecounter() } } diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 00e8abe76f..627cec6781 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -3,6 +3,7 @@ Welcome to libAFL */ #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "perf_stats", feature(link_llvm_intrinsics))] #[macro_use] extern crate alloc; From 2b6b369c56a4c70d5bd881c2f05a7c1ecf9d1afe Mon Sep 17 00:00:00 2001 From: ctfhacker Date: Mon, 19 Apr 2021 17:35:24 -0500 Subject: [PATCH 11/18] pub(crate) the NUM_FEEDBACK and NUM_STAGES consts --- libafl/src/stats/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 62aa92b31a..b6d865639b 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -264,10 +264,10 @@ macro_rules! mark_feedback_time { } /// Number of stages in the fuzzer -const NUM_STAGES: usize = 4; +pub(crate) const NUM_STAGES: usize = 4; /// Number of feedback mechanisms to measure for performance -pub const NUM_FEEDBACKS: usize = 4; +pub(crate) const NUM_FEEDBACKS: usize = 4; /// Client performance statistics #[derive(Serialize, Deserialize, Debug, Copy, Clone)] From b9e75c0c98fdfb1e70778e6f3612a94b71dcd21a Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Sat, 1 May 2021 13:24:02 +0200 Subject: [PATCH 12/18] Tcp Broker to Broker Communication (#66) * initial b2b implementation * no_std and clippy fixes * b2b testcase added * more correct testcases * fixed b2b * typo * fixed unused warning --- libafl/Cargo.toml | 4 +- libafl/examples/llmp_test/main.rs | 23 +- libafl/src/bolts/compress.rs | 1 + libafl/src/bolts/llmp.rs | 602 +++++++++++++++++++++++++----- libafl/src/bolts/shmem.rs | 15 +- libafl/src/events/llmp.rs | 6 +- 6 files changed, 553 insertions(+), 98 deletions(-) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 12c79f9405..8b2d3cd75f 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -40,7 +40,8 @@ anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disab derive = ["libafl_derive"] # provide derive(SerdeAny) macro. llmp_small_maps = [] # reduces initial map size for llmp llmp_debug = ["backtrace"] # Enables debug output for LLMP -llmp_compression = [] #llmp compression using GZip +llmp_compression = [] # llmp compression using GZip +llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. [[example]] name = "llmp_test" @@ -61,6 +62,7 @@ libafl_derive = { version = "0.1.0", optional = true, path = "../libafl_derive" serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap compression = { version = "0.1.5" } num_enum = "0.5.1" +hostname = "^0.3" # Is there really no gethostname in the stdlib? [target.'cfg(target_os = "android")'.dependencies] backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug diff --git a/libafl/examples/llmp_test/main.rs b/libafl/examples/llmp_test/main.rs index 9b6cde1ecb..0324dace4f 100644 --- a/libafl/examples/llmp_test/main.rs +++ b/libafl/examples/llmp_test/main.rs @@ -83,7 +83,7 @@ fn large_msg_loop(port: u16) -> ! { fn broker_message_hook( client_id: u32, tag: llmp::Tag, - _flags: llmp::Flag, + _flags: llmp::Flags, message: &[u8], ) -> Result { match tag { @@ -120,22 +120,31 @@ fn main() { let mode = std::env::args() .nth(1) - .expect("no mode specified, chose 'broker', 'ctr', 'adder', or 'large'"); + .expect("no mode specified, chose 'broker', 'b2b', 'ctr', 'adder', or 'large'"); let port: u16 = std::env::args() .nth(2) .unwrap_or("1337".into()) .parse::() .unwrap(); + // in the b2b use-case, this is our "own" port, we connect to the "normal" broker node on startup. + let b2b_port: u16 = std::env::args() + .nth(3) + .unwrap_or("4242".into()) + .parse::() + .unwrap(); println!("Launching in mode {} on port {}", mode, port); match mode.as_str() { "broker" => { let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap(); - broker - .launch_listener(llmp::Listener::Tcp( - std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(), - )) - .unwrap(); + broker.launch_tcp_listener_on(port).unwrap(); + broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) + } + "b2b" => { + let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap(); + broker.launch_tcp_listener_on(b2b_port).unwrap(); + // connect back to the main broker. + broker.connect_b2b(("127.0.0.1", port)).unwrap(); broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) } "ctr" => { diff --git a/libafl/src/bolts/compress.rs b/libafl/src/bolts/compress.rs index 1da1f84cb8..9b34cc682d 100644 --- a/libafl/src/bolts/compress.rs +++ b/libafl/src/bolts/compress.rs @@ -42,6 +42,7 @@ impl GzipCompressor { /// Decompression. /// Flag is used to indicate if it's compressed or not + #[allow(clippy::unused_self)] pub fn decompress(&self, buf: &[u8]) -> Result, Error> { Ok(buf .iter() diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index 565f6c769c..439e212399 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -2,9 +2,9 @@ A library for low level message passing To send new messages, the clients place a new message at the end of their -client_out_map. If the ringbuf is filled up, they start place a +client_out_map. If the current map is filled up, they place a LLMP_AGE_END_OF_PAGE_V1 msg and alloc a new shmap. -Once the broker mapped a page, it flags it save for unmapping. +Once the broker mapped this same page, it flags it as safe for unmapping. ```text [client0] [client1] ... [clientN] @@ -41,7 +41,7 @@ current map. [client0] [client1] ... [clientN] ``` -In the future, if we need zero copy, the current_broadcast_map could instead +In the future, if we would need zero copy, the current_broadcast_map could instead list the client_out_map ID an offset for each message. In that case, the clients also need to create new shmaps once their bufs are filled up. @@ -50,11 +50,14 @@ To use, you will have to create a broker using llmp_broker_new(). Then register some clientloops using llmp_broker_register_threaded_clientloop (or launch them as seperate processes) and call llmp_broker_run(); +For broker2broker communication, all messages are forwarded via network sockets. + */ use alloc::{string::String, vec::Vec}; use core::{ cmp::max, + convert::TryFrom, fmt::Debug, mem::size_of, ptr, slice, @@ -64,9 +67,11 @@ use core::{ use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::{ + convert::TryInto, env, io::{Read, Write}, - net::{SocketAddr, TcpListener, TcpStream}, + net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, + sync::mpsc::channel, thread, }; @@ -104,8 +109,23 @@ const LLMP_TAG_NEW_SHM_CLIENT: Tag = 0xC11E471; /// The sender on this map is exiting (if broker exits, clients should exit gracefully); const LLMP_TAG_EXITING: Tag = 0x13C5171; -pub const LLMP_FLAG_INITIALIZED: Flag = 0x0; -pub const LLMP_FLAG_COMPRESSED: Flag = 0x1; +/// Unused... +pub const LLMP_FLAG_INITIALIZED: Flags = 0x0; +/// This message was compressed in transit +pub const LLMP_FLAG_COMPRESSED: Flags = 0x1; +/// From another broker. +pub const LLMP_FLAG_FROM_B2B: Flags = 0x2; + +/// Timt the broker 2 broker connection waits for incoming data, +/// before checking for own data to forward again. +const _LLMP_B2B_BLOCK_TIME: Duration = Duration::from_millis(3_000); + +/// If broker2broker is enabled, bind to public IP +#[cfg(feature = "llmp_bind_public")] +const _LLMP_BIND_ADDR: &str = "0.0.0.0"; +/// If broker2broker is disabled, bind to localhost +#[cfg(not(feature = "llmp_bind_public"))] +const _LLMP_BIND_ADDR: &str = "127.0.0.1"; /// An env var of this value indicates that the set value was a NULL PTR const _NULL_ENV_STR: &str = "_NULL"; @@ -127,29 +147,89 @@ static mut GLOBAL_SIGHANDLER_STATE: LlmpBrokerSignalHandler = LlmpBrokerSignalHa /// TAGs used thorughout llmp pub type Tag = u32; -pub type Flag = u64; +/// The client ID == the sender id. +pub type ClientId = u32; +/// The broker ID, for broker 2 broker communication. +pub type BrokerId = u32; +/// The flags, indicating, for example, enabled compression. +pub type Flags = u32; +/// The message ID, an ever-increasing number, unique only to a sharedmap/page. +pub type MessageId = u64; /// This is for the server the broker will spawn. /// If an llmp connection is local - use sharedmaps /// or remote (broker2broker) - forwarded via tcp +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum TcpRequest { - LocalClientHello { shmem: ShMemDescription }, - RemoteBrokerHello, - RemoteNewMessage { tag: Tag, payload: Vec }, + /// We would like to be a local client. + LocalClientHello { shmem_description: ShMemDescription }, + /// We would like to establish a b2b connection. + RemoteBrokerHello { hostname: String }, +} + +impl TryFrom<&Vec> for TcpRequest { + type Error = crate::Error; + + fn try_from(bytes: &Vec) -> Result { + Ok(postcard::from_bytes(bytes)?) + } +} + +/// Messages for broker 2 broker connection. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TcpRemoteNewMessage { + // The client ID of the original broker + client_id: ClientId, + // The message tag + tag: Tag, + // The flags + flags: Flags, + // The actual content of the message + payload: Vec, +} + +impl TryFrom<&Vec> for TcpRemoteNewMessage { + type Error = crate::Error; + + fn try_from(bytes: &Vec) -> Result { + Ok(postcard::from_bytes(bytes)?) + } } /// Responses for requests to the server. +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum TcpResponse { + /// After receiving a new connection, the broker immediately sends a Hello. + BrokerConnectHello { + /// The broker page a new local client can listen on + broker_map_description: ShMemDescription, + /// This broker's hostname + hostname: String, + }, LocalClientAccepted { - client_id: u32, - shmem: ShMemDescription, + /// The ClientId this client should send messages as + /// Mainly used for client-side deduplication of incoming messages + client_id: ClientId, }, RemoteBrokerAccepted { - broker_id: u32, - hostname: String, + /// The broker id of this element + broker_id: BrokerId, + }, + /// Something went wrong when processing the request. + Error { + /// Error description + description: String, }, } +impl TryFrom<&Vec> for TcpResponse { + type Error = crate::Error; + + fn try_from(bytes: &Vec) -> Result { + Ok(postcard::from_bytes(bytes)?) + } +} + /// Abstraction for listeners #[cfg(feature = "std")] pub enum Listener { @@ -226,6 +306,59 @@ fn msg_offset_from_env(env_name: &str) -> Result, Error> { }) } +/// Send one message as `u32` len and `[u8;len]` bytes +#[cfg(feature = "std")] +fn send_tcp_msg(stream: &mut TcpStream, msg: &T) -> Result<(), Error> +where + T: Serialize, +{ + let msg = postcard::to_allocvec(msg)?; + if msg.len() > u32::MAX as usize { + return Err(Error::IllegalState(format!( + "Trying to send message a tcp message > u32! (size: {})", + msg.len() + ))); + } + + #[cfg(feature = "llmp_debug")] + println!("LLMP TCP: Sending {} bytes", msg.len()); + + let size_bytes = (msg.len() as u32).to_be_bytes(); + stream.write_all(&size_bytes)?; + stream.write_all(&msg)?; + + #[cfg(feature = "llmp_debug")] + println!("LLMP TCP: Sending {} bytes finished.", msg.len()); + + Ok(()) +} + +/// Receive one message of `u32` len and `[u8; len]` bytes +#[cfg(feature = "std")] +fn recv_tcp_msg(stream: &mut TcpStream) -> Result, Error> { + // Always receive one be u32 of size, then the command. + + #[cfg(feature = "llmp_debug")] + println!( + "LLMP TCP: Waiting for packet... (Timeout: {:?})", + stream.read_timeout().unwrap_or(None) + ); + + let mut size_bytes = [0u8; 4]; + stream.read_exact(&mut size_bytes)?; + let size = u32::from_be_bytes(size_bytes); + let mut bytes = vec![]; + bytes.resize(size as usize, 0u8); + + #[cfg(feature = "llmp_debug")] + println!("LLMP TCP: Receiving payload of size {}", size); + + stream + .read_exact(&mut bytes) + .expect("Failed to read message body"); + Ok(bytes) +} + /// In case we don't have enough space, make sure the next page will be large /// enough. For now, we want to have at least enough space to store 2 of the /// largest messages we encountered (plus message one new_page message). @@ -324,18 +457,22 @@ pub enum LlmpMsgHookResult { #[repr(C, packed)] pub struct LlmpMsg { /// A tag - pub tag: Tag, + pub tag: Tag, //u32 /// Sender of this messge - pub sender: u32, + pub sender: ClientId, //u32 + /// ID of another Broker, for b2b messages + pub broker: BrokerId, //u32 /// flags, currently only used for indicating compression - pub flags: Flag, + pub flags: Flags, //u32 /// The message ID, unique per page - pub message_id: u64, + pub message_id: MessageId, //u64 /// Buffer length as specified by the user pub buf_len: u64, /// (Actual) buffer length after padding + // Padding makes sure the next msg is aligned. pub buf_len_padded: u64, - /// The buf + /// The actual payload buf + // We try to keep the start of buf 64-bit aligned! pub buf: [u8; 0], } @@ -399,7 +536,7 @@ where #[cfg(feature = "std")] /// Creates either a broker, if the tcp port is not bound, or a client, connected to this port. pub fn on_port(shmem_provider: SP, port: u16) -> Result { - match TcpListener::bind(format!("127.0.0.1:{}", port)) { + match TcpListener::bind(format!("{}:{}", _LLMP_BIND_ADDR, port)) { Ok(listener) => { // We got the port. We are the broker! :) dbg!("We're the broker"); @@ -449,7 +586,7 @@ where } } - pub fn send_buf_with_flags(&mut self, tag: Tag, buf: &[u8], flags: Flag) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, buf: &[u8], flags: Flags) -> Result<(), Error> { match self { LlmpConnection::IsBroker { broker } => broker.send_buf_with_flags(tag, flags, buf), LlmpConnection::IsClient { client } => client.send_buf_with_flags(tag, flags, buf), @@ -700,7 +837,7 @@ where #[cfg(all(feature = "llmp_debug", feature = "std"))] dbg!( page, - (*page), + *page, (*page).size_used, complete_msg_size, EOP_MSG_SIZE, @@ -918,7 +1055,7 @@ where } } - pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flag, buf: &[u8]) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flags, buf: &[u8]) -> Result<(), Error> { // Make sure we don't reuse already allocated tags if tag == LLMP_TAG_NEW_SHM_CLIENT || tag == LLMP_TAG_END_OF_PAGE @@ -1159,8 +1296,9 @@ where } } + /// Receive the buffer, also reading the LLMP internal message flags #[inline] - pub fn recv_buf_with_flags(&mut self) -> Result, Error> { + pub fn recv_buf_with_flags(&mut self) -> Result, Error> { unsafe { Ok(match self.recv()? { Some(msg) => Some(( @@ -1176,7 +1314,7 @@ where /// Returns the next sender, tag, buf, looping until it becomes available #[inline] - pub fn recv_buf_blocking(&mut self) -> Result<(u32, Tag, &[u8]), Error> { + pub fn recv_buf_blocking(&mut self) -> Result<(ClientId, Tag, &[u8]), Error> { unsafe { let msg = self.recv_blocking()?; Ok(( @@ -1233,7 +1371,7 @@ where SHM: ShMem, { /// Creates a new page, initializing the passed shared mem struct - pub fn new(sender: u32, mut new_map: SHM) -> Self { + pub fn new(sender: ClientId, mut new_map: SHM) -> Self { #[cfg(all(feature = "llmp_debug", feature = "std"))] println!( "LLMP_DEBUG: Initializing map on {} with size {}", @@ -1403,7 +1541,7 @@ impl Handler for LlmpBrokerSignalHandler { /// It may intercept messages passing through. impl LlmpBroker where - SP: ShMemProvider, + SP: ShMemProvider + 'static, { /// Create and initialize a new llmp_broker pub fn new(mut shmem_provider: SP) -> Result { @@ -1447,6 +1585,69 @@ where }); } + /// Connects to a broker running on another machine. + /// This will spawn a new background thread, registered as client, that proxies all messages to a remote machine. + /// Returns the description of the new page that still needs to be announced/added to the broker afterwards. + #[cfg(feature = "std")] + pub fn connect_b2b(&mut self, addr: A) -> Result<(), Error> + where + A: ToSocketAddrs, + { + let mut stream = TcpStream::connect(addr)?; + println!("B2B: Connected to {:?}", stream); + + match (&recv_tcp_msg(&mut stream)?).try_into()? { + TcpResponse::BrokerConnectHello { + broker_map_description: _, + hostname, + } => println!("B2B: Connected to {}", hostname), + _ => { + return Err(Error::IllegalState( + "Unexpected response from B2B server received.".to_string(), + )) + } + }; + + let hostname = hostname::get() + .unwrap_or_else(|_| "".into()) + .to_string_lossy() + .into(); + + send_tcp_msg(&mut stream, &TcpRequest::RemoteBrokerHello { hostname })?; + + let broker_id = match (&recv_tcp_msg(&mut stream)?).try_into()? { + TcpResponse::RemoteBrokerAccepted { broker_id } => { + println!("B2B: Got Connection Ack, broker_id {}", broker_id); + broker_id + } + _ => { + return Err(Error::IllegalState( + "Unexpected response from B2B server received.".to_string(), + )); + } + }; + + // TODO: use broker ids! + println!("B2B: We are broker {}", broker_id); + + // TODO: handle broker_ids properly/at all. + let map_description = Self::b2b_thread_on( + stream, + &self.shmem_provider, + self.llmp_clients.len() as ClientId, + &self.llmp_out.out_maps.first().unwrap().shmem.description(), + )?; + + let new_map = + LlmpSharedMap::existing(self.shmem_provider.from_description(map_description)?); + + { + self.register_client(new_map); + } + + Ok(()) + } + /// For internal use: Forward the current message to the out map. unsafe fn forward_msg(&mut self, msg: *mut LlmpMsg) -> Result<(), Error> { let mut out: *mut LlmpMsg = self.alloc_next((*msg).buf_len_padded as usize)?; @@ -1471,7 +1672,7 @@ where #[inline] pub fn once(&mut self, on_new_msg: &mut F) -> Result<(), Error> where - F: FnMut(u32, Tag, Flag, &[u8]) -> Result, + F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result, { compiler_fence(Ordering::SeqCst); for i in 0..self.llmp_clients.len() { @@ -1502,7 +1703,7 @@ where /// 5 millis of sleep can't hurt to keep busywait not at 100% pub fn loop_forever(&mut self, on_new_msg: &mut F, sleep_time: Option) where - F: FnMut(u32, Tag, Flag, &[u8]) -> Result, + F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result, { #[cfg(unix)] if let Err(_e) = unsafe { setup_signal_handler(&mut GLOBAL_SIGHANDLER_STATE) } { @@ -1539,20 +1740,221 @@ where self.llmp_out.send_buf(tag, buf) } - pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flag, buf: &[u8]) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flags, buf: &[u8]) -> Result<(), Error> { self.llmp_out.send_buf_with_flags(tag, flags, buf) } - #[cfg(feature = "std")] /// Launches a thread using a tcp listener socket, on which new clients may connect to this broker /// Does so on the given port. + #[cfg(feature = "std")] pub fn launch_tcp_listener_on(&mut self, port: u16) -> Result, Error> { - let listener = TcpListener::bind(format!("127.0.0.1:{}", port))?; + let listener = TcpListener::bind(format!("{}:{}", _LLMP_BIND_ADDR, port))?; // accept connections and process them, spawning a new thread for each one println!("Server listening on port {}", port); self.launch_listener(Listener::Tcp(listener)) } + /// Announces a new client on the given shared map. + /// Called from a background thread, typically. + /// Upon receiving this message, the broker should map the announced page and start trckang it for new messages. + #[allow(dead_code)] + fn announce_new_client( + sender: &mut LlmpSender, + shmem_description: &ShMemDescription, + ) -> Result<(), Error> { + unsafe { + let msg = sender + .alloc_next(size_of::()) + .expect("Could not allocate a new message in shared map."); + (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; + let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; + (*pageinfo).shm_str = *shmem_description.id.as_slice(); + (*pageinfo).map_size = shmem_description.size; + sender.send(msg) + } + } + + /// For broker to broker connections: + /// Launches a proxy thread. + /// It will read outgoing messages from the given broker map (and handle EOP by mapping a new page). + /// This function returns the ShMemDescription the client uses to place incoming messages. + /// The thread exits, when the remote broker disconnects. + #[cfg(feature = "std")] + fn b2b_thread_on( + mut stream: TcpStream, + shmem_provider: &SP, + b2b_client_id: ClientId, + broker_map_description: &ShMemDescription, + ) -> Result { + let broker_map_description = *broker_map_description; + let mut shmem_provider_clone = shmem_provider.clone(); + + // A channel to get the new "client's" sharedmap id from + let (send, recv) = channel(); + + // (For now) the thread remote broker 2 broker just acts like a "normal" llmp client, except it proxies all messages to the attached socket, in both directions. + thread::spawn(move || { + // as always, call post_fork to potentially reconnect the provider (for threaded/forked use) + shmem_provider_clone.post_fork(); + + #[cfg(fature = "llmp_debug")] + println!("B2b: Spawned proxy thread"); + + // The background thread blocks on the incoming connection for 15 seconds (if no data is available), then checks if it should forward own messages, then blocks some more. + stream + .set_read_timeout(Some(_LLMP_B2B_BLOCK_TIME)) + .expect("Failed to set tcp stream timeout"); + + let mut new_sender = + match LlmpSender::new(shmem_provider_clone.clone(), b2b_client_id, false) { + Ok(new_sender) => new_sender, + Err(e) => { + panic!("B2B: Could not map shared map: {}", e); + } + }; + + send.send(new_sender.out_maps.first().unwrap().shmem.description()) + .expect("B2B: Error sending map description to channel!"); + + // the receiver receives from the local broker, and forwards it to the tcp stream. + let mut local_receiver = LlmpReceiver::on_existing_from_description( + shmem_provider_clone, + &LlmpDescription { + last_message_offset: None, + shmem: broker_map_description, + }, + ) + .expect("Failed to map local page in broker 2 broker thread!"); + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!("B2B: Starting proxy loop :)"); + + loop { + // first, forward all data we have. + while let Some((client_id, tag, flags, payload)) = local_receiver + .recv_buf_with_flags() + .expect("Error reading from local page!") + { + if client_id == b2b_client_id { + dbg!("Ignored message we probably sent earlier (same id)", tag); + continue; + } + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!( + "Fowarding message via broker2broker connection", + payload.len() + ); + // We got a new message! Forward... + send_tcp_msg( + &mut stream, + &TcpRemoteNewMessage { + client_id, + tag, + flags, + payload: payload.to_vec(), + }, + ) + .expect("Error sending message via broker 2 broker"); + } + + // Then, see if we can receive something. + // We set a timeout on the receive earlier. + // This makes sure we will still forward our own stuff. + // Forwarding happens between each recv, too, as simplification. + // We ignore errors completely as they may be timeout, or stream closings. + // Instead, we catch stream close when/if we next try to send. + if let Ok(val) = recv_tcp_msg(&mut stream) { + let msg: TcpRemoteNewMessage = (&val).try_into().expect( + "Illegal message received from broker 2 broker connection - shutting down.", + ); + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!( + "Fowarding incoming message from broker2broker connection", + msg.payload.len() + ); + + // TODO: Could probably optimize this somehow to forward all queued messages between locks... oh well. + // Todo: somehow mangle in the other broker id? ClientId? + new_sender + .send_buf_with_flags(msg.tag, msg.flags | LLMP_FLAG_FROM_B2B, &msg.payload) + .expect("B2B: Error forwarding message. Exiting."); + } else { + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!("Received no input, timeout or closed. Looping back up :)"); + } + } + }); + + let ret = recv.recv().map_err(|_| { + Error::Unknown("Error launching background thread for b2b communcation".to_string()) + }); + + #[cfg(all(feature = "llmp_debug", feature = "std"))] + dbg!("B2B: returning from loop. Success: {}", ret.is_ok()); + + ret + } + + /// handles a single tcp request in the current context. + #[cfg(feature = "std")] + fn handle_tcp_request( + mut stream: TcpStream, + request: &TcpRequest, + current_client_id: &mut u32, + sender: &mut LlmpSender, + shmem_provider: &SP, + broker_map_description: &ShMemDescription, + ) { + match request { + TcpRequest::LocalClientHello { shmem_description } => { + match Self::announce_new_client(sender, shmem_description) { + Ok(()) => (), + Err(e) => println!("Error forwarding client on map: {:?}", e), + }; + + if let Err(e) = send_tcp_msg( + &mut stream, + &TcpResponse::LocalClientAccepted { + client_id: *current_client_id, + }, + ) { + println!("An error occurred sending via tcp {}", e); + }; + *current_client_id += 1; + } + TcpRequest::RemoteBrokerHello { hostname } => { + println!("B2B new client: {}", hostname); + + // TODO: Clean up broker ids. + if send_tcp_msg( + &mut stream, + &TcpResponse::RemoteBrokerAccepted { + broker_id: *current_client_id, + }, + ) + .is_err() + { + println!("Error accepting broker, ignoring."); + return; + } + + if let Ok(shmem_description) = Self::b2b_thread_on( + stream, + shmem_provider, + *current_client_id, + &broker_map_description, + ) { + if Self::announce_new_client(sender, &shmem_description).is_err() { + println!("B2B: Error announcing client {:?}", shmem_description); + }; + *current_client_id += 1; + } + } + }; + } + #[cfg(feature = "std")] /// Launches a thread using a listener socket, on which new clients may connect to this broker pub fn launch_listener(&mut self, listener: Listener) -> Result, Error> { @@ -1562,37 +1964,43 @@ where // to read from the initial map id. let client_out_map_mem = &self.llmp_out.out_maps.first().unwrap().shmem; - let broadcast_map_description = postcard::to_allocvec(&client_out_map_mem.description())?; - - let mut incoming_map_description_serialized = vec![0u8; broadcast_map_description.len()]; + let broker_map_description = client_out_map_mem.description(); + let hostname = hostname::get() + .unwrap_or_else(|_| "".into()) + .to_string_lossy() + .into(); + let broker_hello = TcpResponse::BrokerConnectHello { + broker_map_description, + hostname, + }; - let llmp_tcp_id = self.llmp_clients.len() as u32; + let llmp_tcp_id = self.llmp_clients.len() as ClientId; // Tcp out map sends messages from background thread tcp server to foreground client let tcp_out_map = LlmpSharedMap::new( llmp_tcp_id, self.shmem_provider.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?, ); - let shmem_id = tcp_out_map.shmem.id(); - let tcp_out_map_str = *shmem_id.as_slice(); - let tcp_out_map_size = tcp_out_map.shmem.len(); + let tcp_out_map_description = tcp_out_map.shmem.description(); self.register_client(tcp_out_map); let mut shmem_provider_clone = self.shmem_provider.clone(); Ok(thread::spawn(move || { + // Call `post_fork` (even though this is not forked) so we get a new connection to the cloned `ShMemServer` if we are using a `ServedShMemProvider` shmem_provider_clone.post_fork(); - // Clone so we get a new connection to the AshmemServer if we are using - // ServedShMemProvider - let mut new_client_sender = LlmpSender { - id: 0, + + let mut current_client_id = llmp_tcp_id + 1; + + let mut tcp_incoming_sender = LlmpSender { + id: llmp_tcp_id, last_msg_sent: ptr::null_mut(), out_maps: vec![LlmpSharedMap::existing( shmem_provider_clone - .from_id_and_size(ShMemId::from_slice(&tcp_out_map_str), tcp_out_map_size) + .from_description(tcp_out_map_description) .unwrap(), )], - // drop pages to the broker if it already read them + // drop pages to the broker, if it already read them. keep_pages_forever: false, shmem_provider: shmem_provider_clone.clone(), }; @@ -1601,38 +2009,40 @@ where match listener.accept() { ListenerStream::Tcp(mut stream, addr) => { dbg!("New connection", addr, stream.peer_addr().unwrap()); - match stream.write(&broadcast_map_description) { - Ok(_) => {} // fire & forget + + // Send initial information, without anyone asking. + // This makes it a tiny bit easier to map the broker map for new Clients. + match send_tcp_msg(&mut stream, &broker_hello) { + Ok(()) => {} Err(e) => { - dbg!("Could not send to shmap to client", e); + dbg!("Error sending initial hello: {:?}", e); continue; } - }; - match stream.read_exact(&mut incoming_map_description_serialized) { - Ok(()) => (), + } + + let buf = match recv_tcp_msg(&mut stream) { + Ok(buf) => buf, Err(e) => { - dbg!("Ignoring failed read from client", e); + dbg!("Error receving from tcp", e); continue; } }; - if let Ok(incoming_map_description) = postcard::from_bytes::( - &incoming_map_description_serialized, - ) { - unsafe { - let msg = new_client_sender - .alloc_next(size_of::()) - .expect("Could not allocate a new message in shared map."); - (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; - let pageinfo = - (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; - (*pageinfo).shm_str = *incoming_map_description.id.as_slice(); - (*pageinfo).map_size = incoming_map_description.size; - match new_client_sender.send(msg) { - Ok(()) => (), - Err(e) => println!("Error forwarding client on map: {:?}", e), - }; + let req = match (&buf).try_into() { + Ok(req) => req, + Err(e) => { + dbg!("Could not deserialize tcp message", e); + continue; } - } + }; + + Self::handle_tcp_request( + stream, + &req, + &mut current_client_id, + &mut tcp_incoming_sender, + &shmem_provider_clone, + &broker_map_description, + ); } ListenerStream::Empty() => { continue; @@ -1646,7 +2056,7 @@ where #[inline] unsafe fn handle_new_msgs(&mut self, client_id: u32, on_new_msg: &mut F) -> Result<(), Error> where - F: FnMut(u32, Tag, Flag, &[u8]) -> Result, + F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result, { let mut next_id = self.llmp_clients.len() as u32; @@ -1880,7 +2290,7 @@ where self.sender.send_buf(tag, buf) } - pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flag, buf: &[u8]) -> Result<(), Error> { + pub fn send_buf_with_flags(&mut self, tag: Tag, flags: Flags, buf: &[u8]) -> Result<(), Error> { self.sender.send_buf_with_flags(tag, flags, buf) } @@ -1943,7 +2353,7 @@ where self.receiver.recv_buf_blocking() } - pub fn recv_buf_with_flags(&mut self) -> Result, Error> { + pub fn recv_buf_with_flags(&mut self) -> Result, Error> { self.receiver.recv_buf_with_flags() } @@ -1957,26 +2367,48 @@ where #[cfg(feature = "std")] /// Create a LlmpClient, getting the ID from a given port pub fn create_attach_to_tcp(mut shmem_provider: SP, port: u16) -> Result { - let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port))?; + let mut stream = TcpStream::connect(format!("{}:{}", _LLMP_BIND_ADDR, port))?; println!("Connected to port {}", port); - // First, get the serialized description size by serializing a dummy. - let dummy_description = ShMemDescription { - size: 0, - id: ShMemId::default(), + let broker_map_description = if let TcpResponse::BrokerConnectHello { + broker_map_description, + hostname: _, + } = (&recv_tcp_msg(&mut stream)?).try_into()? + { + broker_map_description + } else { + return Err(Error::IllegalState( + "Received unexpected Broker Hello".to_string(), + )); }; - let mut new_broker_map_str = postcard::to_allocvec(&dummy_description)?; - stream.read_exact(&mut new_broker_map_str)?; + let map = LlmpSharedMap::existing(shmem_provider.from_description(broker_map_description)?); + let mut ret = Self::new(shmem_provider, map)?; - let broker_map_description: ShMemDescription = postcard::from_bytes(&new_broker_map_str)?; + let client_hello_req = TcpRequest::LocalClientHello { + shmem_description: ret.sender.out_maps.first().unwrap().shmem.description(), + }; - let map = LlmpSharedMap::existing(shmem_provider.from_description(broker_map_description)?); - let ret = Self::new(shmem_provider, map)?; + send_tcp_msg(&mut stream, &client_hello_req)?; + + let client_id = if let TcpResponse::LocalClientAccepted { client_id } = + (&recv_tcp_msg(&mut stream)?).try_into()? + { + client_id + } else { + return Err(Error::IllegalState( + "Unexpected Response from Broker".to_string(), + )); + }; + + // Set our ID to the one the broker sent us.. + // This is mainly so we can filter out our own msgs later. + ret.sender.id = client_id; + // Also set the sender on our initial llmp map correctly. + unsafe { + (*ret.sender.out_maps.first_mut().unwrap().page_mut()).sender = client_id; + } - let own_map_description_bytes = - postcard::to_allocvec(&ret.sender.out_maps.first().unwrap().shmem.description())?; - stream.write_all(&own_map_description_bytes)?; Ok(ret) } } diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index 2b64dfc04f..9640e60212 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -171,8 +171,8 @@ pub trait ShMemProvider: Send + Clone + Default + Debug { )) } - /// This method should be called after a fork or thread creation event, allowing the ShMem to - /// reset thread specific info. + /// This method should be called after a fork or after cloning/a thread creation event, allowing the ShMem to + /// reset thread specific info, and potentially reconnect. fn post_fork(&mut self) { // do nothing } @@ -183,6 +183,9 @@ pub trait ShMemProvider: Send + Clone + Default + Debug { } } +/// A Refernce Counted shared map, +/// that can use internal mutability. +/// Useful if the `ShMemProvider` needs to keep local state. #[derive(Debug, Clone)] pub struct RcShMem { internal: ManuallyDrop, @@ -216,6 +219,9 @@ impl Drop for RcShMem { } } +/// A Refernce Counted `ShMemProvider`, +/// that can use internal mutability. +/// Useful if the `ShMemProvider` needs to keep local state. #[derive(Debug, Clone)] pub struct RcShMemProvider { internal: Rc>, @@ -274,6 +280,11 @@ where } } +/// A Unix sharedmem implementation. +/// +/// On Android, this is partially reused to wrap `Ashmem`, +/// Although for an `AshmemShMemProvider using a unix domain socket +/// Is needed on top. #[cfg(all(unix, feature = "std"))] pub mod unix_shmem { diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 90fd00f864..865f02ebda 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -15,7 +15,7 @@ use crate::bolts::{ use crate::{ bolts::{ - llmp::{self, Flag, LlmpClientDescription, LlmpSender, Tag}, + llmp::{self, Flags, LlmpClientDescription, LlmpSender, Tag}, shmem::ShMemProvider, }, corpus::CorpusScheduler, @@ -173,7 +173,7 @@ where #[cfg(feature = "llmp_compression")] let compressor = &self.compressor; broker.loop_forever( - &mut |sender_id: u32, tag: Tag, _flags: Flag, msg: &[u8]| { + &mut |sender_id: u32, tag: Tag, _flags: Flags, msg: &[u8]| { if tag == LLMP_TAG_EVENT_TO_BOTH { #[cfg(not(feature = "llmp_compression"))] let event_bytes = msg; @@ -376,7 +376,7 @@ where #[cfg(feature = "llmp_compression")] fn fire(&mut self, _state: &mut S, event: Event) -> Result<(), Error> { let serialized = postcard::to_allocvec(&event)?; - let flags: Flag = LLMP_FLAG_INITIALIZED; + let flags: Flags = LLMP_FLAG_INITIALIZED; match self.compressor.compress(&serialized)? { Some(comp_buf) => { From 6cc1e9669849285a80ed2cf4f9aa66c92ddd8b05 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Fri, 7 May 2021 01:36:31 +0200 Subject: [PATCH 13/18] clippy fixes --- fuzzers/libfuzzer_libpng/Makefile | 77 +++++++++++++++-------------- fuzzers/libfuzzer_libpng/README.md | 4 +- fuzzers/libfuzzer_libpng/src/lib.rs | 4 +- libafl/src/cpu.rs | 1 + libafl/src/events/llmp.rs | 2 +- libafl/src/events/mod.rs | 2 +- libafl/src/events/simple.rs | 2 +- libafl/src/fuzzer.rs | 6 +-- libafl/src/stats/mod.rs | 16 +++--- 9 files changed, 60 insertions(+), 54 deletions(-) diff --git a/fuzzers/libfuzzer_libpng/Makefile b/fuzzers/libfuzzer_libpng/Makefile index 540fcbdab5..4c256c89d7 100644 --- a/fuzzers/libfuzzer_libpng/Makefile +++ b/fuzzers/libfuzzer_libpng/Makefile @@ -1,52 +1,55 @@ PWD=`pwd` +FUZZER_NAME="fuzzer_libpng" all: # Build the libpng libfuzzer library cargo build --release # Build the libpng harness - $(PWD)/target/release/cxx \ + $(PWD)/target/release/libafl_cxx \ $(PWD)/harness.cc \ $(PWD)/libpng-1.6.37/.libs/libpng16.a \ -I$(PWD)/libpng-1.6.37/ \ - -o fuzzer \ + -o $(FUZZER_NAME) \ -lm -lz run: all - ./fuzzer & - ./fuzzer >/dev/null 2>/dev/null & + ./$(FUZZER_NAME) & + sleep 0.2 + ./$(FUZZER_NAME) >/dev/null 2>/dev/null & test: all - timeout 60s ./fuzzer & - timeout 59s taskset 0x00000001 ./fuzzer >/dev/null 2>/dev/null & - timeout 59s taskset 0x00000002 ./fuzzer >/dev/null 2>/dev/null & - timeout 59s taskset 0x00000004 ./fuzzer >/dev/null 2>/dev/null & - timeout 59s taskset 0x00000008 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000010 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000020 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000040 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000080 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000100 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000200 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000400 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00000800 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00001000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00002000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00004000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00008000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00010000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00020000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00040000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00080000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00100000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00200000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00400000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x00800000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x01000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x02000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x04000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x08000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x10000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x20000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x40000000 ./fuzzer >/dev/null 2>/dev/null & - # timeout 59s taskset 0x80000000 ./fuzzer >/dev/null 2>/dev/null & + timeout 60s ./$(FUZZER_NAME) & + sleep 0.2 + timeout 59s taskset 0x00000001 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + timeout 59s taskset 0x00000002 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + timeout 59s taskset 0x00000004 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + timeout 59s taskset 0x00000008 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000010 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000020 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000040 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000080 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000100 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000200 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000400 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00000800 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00001000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00002000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00004000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00008000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00010000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00020000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00040000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00080000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00100000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00200000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00400000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x00800000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x01000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x02000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x04000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x08000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x10000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x20000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x40000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + # timeout 59s taskset 0x80000000 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & diff --git a/fuzzers/libfuzzer_libpng/README.md b/fuzzers/libfuzzer_libpng/README.md index 922667e8fe..6484bf62e6 100644 --- a/fuzzers/libfuzzer_libpng/README.md +++ b/fuzzers/libfuzzer_libpng/README.md @@ -6,6 +6,8 @@ In contrast to other fuzzer examples, this setup uses `fuzz_loop_for`, to occasi While this costs performance, it can be useful for targets with memory leaks or other instabilities. If your target is really instable, however, consider exchanging the `InProcessExecutor` for a `ForkserverExecutor` instead. +It also uses the `introspection` feature, printing fuzzer stats during execution. + To show off crash detection, we added a `ud2` instruction to the harness, edit harness.cc if you want a non-crashing example. It has been tested on Linux. @@ -51,7 +53,7 @@ This allows you to run multiple different builds of the same fuzzer alongside, f ## 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. +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. ``` ./fuzzer_libpng diff --git a/fuzzers/libfuzzer_libpng/src/lib.rs b/fuzzers/libfuzzer_libpng/src/lib.rs index e0bcc13d89..35a1bc0bc7 100644 --- a/fuzzers/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/libfuzzer_libpng/src/lib.rs @@ -63,7 +63,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> } }, }; - + // Create an observation channel using the coverage map let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges)); @@ -78,7 +78,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> StdRand::with_seed(current_nanos()), // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), - // Feedbacks to rate the interestingness of an input feedback_or!( MaxMapFeedback::new_tracking_with_observer(&edges_observer, true, false), @@ -92,7 +91,6 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> ) }); - println!("We're a client, let's fuzz :)"); // Create a PNG dictionary if not existing diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs index 3fcb1adeb8..88650feb91 100644 --- a/libafl/src/cpu.rs +++ b/libafl/src/cpu.rs @@ -6,6 +6,7 @@ /// implementations of reading a cycle counter. In this way, an experiment only has to /// change this implementation rather than every instead of [`cpu::read_time_counter`] #[cfg(target_arch = "x86_64")] +#[must_use] pub fn read_time_counter() -> u64 { unsafe { core::arch::x86_64::_rdtsc() } } diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 450a30209d..8147140b76 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -262,7 +262,7 @@ where client.update_executions(*executions as u64, *time); // Update the performance stats for this client - client.update_introspection_stats(*introspection_stats); + client.update_introspection_stats(**introspection_stats); // Display the stats via `.display` only on core #1 if sender_id == 1 { diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 20cc3ba7ed..dfabe1162d 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -111,7 +111,7 @@ where executions: usize, /// Current performance statistics - introspection_stats: ClientPerfStats, + introspection_stats: Box, phantom: PhantomData, }, diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index e3858c919c..6e702dde73 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -115,7 +115,7 @@ 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.client_stats_mut()[0].update_introspection_stats(**introspection_stats); stats.display(event.name().to_string()); Ok(BrokerEventResult::Handled) } diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index 414234760f..22224df57c 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -120,9 +120,9 @@ pub trait Fuzzer { Ok(ret) } - /// Given the last time, if stats_timeout seconds passed, send off an info/stats/heartbeat message to the broker. + /// Given the last time, if `stats_timeout` seconds passed, send off an info/stats/heartbeat message to the broker. /// Returns the new `last` time (so the old one, unless `stats_timeout` time has passed and stats have been sent) - /// Will return an Error, if the stats could not be sent. + /// Will return an [`crate::Error`], if the stats could not be sent. fn maybe_report_stats( state: &mut S, manager: &mut EM, @@ -225,7 +225,7 @@ where Event::UpdatePerfStats { executions: *state.executions(), time: cur, - introspection_stats: state.introspection_stats().clone(), + introspection_stats: Box::new(*state.introspection_stats()), phantom: PhantomData, }, )?; diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 41791eac99..d3feeed3d8 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -396,11 +396,12 @@ const NUM_PERF_FEATURES: usize = PerfFeature::Count as usize; impl ClientPerfStats { /// Create a blank [`ClientPerfStats`] with the `start_time` and `current_time` with /// the current clock counter + #[must_use] pub fn new() -> Self { - let start_time = crate::cpu::read_time_counter().try_into().unwrap(); + let start_time = crate::cpu::read_time_counter(); Self { - start_time: start_time, + start_time, current_time: start_time, scheduler: 0, manager: 0, @@ -604,6 +605,7 @@ impl ClientPerfStats { #[cfg(feature = "introspection")] impl core::fmt::Display for ClientPerfStats { + #[allow(clippy::cast_precision_loss)] fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { // Calculate the elapsed time from the stats let elapsed: f64 = (self.current_time - self.start_time) as f64; @@ -620,7 +622,7 @@ impl core::fmt::Display for ClientPerfStats { // Create the formatted string writeln!( f, - "Scheduler: {:4.2} | Manager: {:4.2} | Stages:\n", + "Scheduler: {:4.2} | Manager: {:4.2} | Stages:", scheduler_percent, manager_percent )?; @@ -632,7 +634,7 @@ impl core::fmt::Display for ClientPerfStats { } // Write the stage header - write!(f, " Stage {}:\n", stage_index)?; + writeln!(f, " Stage {}:", stage_index)?; for (feature_index, feature) in features.iter().enumerate() { // Calculate this current stage's percentage @@ -650,7 +652,7 @@ impl core::fmt::Display for ClientPerfStats { let feature: PerfFeature = feature_index.into(); // Write the percentage for this feature - write!(f, " {:6.4}: {:?}\n", feature_percent, feature)?; + writeln!(f, " {:6.4}: {:?}", feature_percent, feature)?; } for (feedback_index, feedback) in self.feedbacks.iter().enumerate() { @@ -666,9 +668,9 @@ impl core::fmt::Display for ClientPerfStats { other_percent -= feedback_percent; // Write the percentage for this feedback - write!( + writeln!( f, - " {:6.4}: Feedback index {}\n", + " {:6.4}: Feedback index {}", feedback_percent, feedback_index )?; } From 1fa893eccf8e003ef2ca66c685de5974034ba021 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Fri, 7 May 2021 01:57:29 +0200 Subject: [PATCH 14/18] fallback to systemtime on non-x86 --- fuzzers/libfuzzer_libpng/Makefile | 8 ++++++++ libafl/src/cpu.rs | 27 +++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/fuzzers/libfuzzer_libpng/Makefile b/fuzzers/libfuzzer_libpng/Makefile index 4c256c89d7..7d05488587 100644 --- a/fuzzers/libfuzzer_libpng/Makefile +++ b/fuzzers/libfuzzer_libpng/Makefile @@ -18,6 +18,14 @@ run: all sleep 0.2 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & +short_test: all + timeout 11s ./$(FUZZER_NAME) & + sleep 0.2 + timeout 10s taskset -c 0 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + timeout 10s taskset -c 1 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + timeout 10s taskset -c 2 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + timeout 10s taskset -c 3 ./$(FUZZER_NAME) >/dev/null 2>/dev/null & + test: all timeout 60s ./$(FUZZER_NAME) & sleep 0.2 diff --git a/libafl/src/cpu.rs b/libafl/src/cpu.rs index 88650feb91..7575c2585a 100644 --- a/libafl/src/cpu.rs +++ b/libafl/src/cpu.rs @@ -1,12 +1,31 @@ //! Architecture agnostic processor features -/// Read time counter using [`llvmint::readcyclecounter`] +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +use crate::utils::current_nanos; + +// TODO: Add more architectures, using C code, see +// https://github.com/google/benchmark/blob/master/src/cycleclock.h +// Or using llvm intrinsics (if they ever should become available in stable rust?) + +/// Read a timestamp for measurements. /// -/// This function is a wrapper around [`llvmint`] to make it easier to test various -/// implementations of reading a cycle counter. In this way, an experiment only has to +/// This function is a wrapper around different ways to get a timestamp, fast +/// In this way, an experiment only has to /// change this implementation rather than every instead of [`cpu::read_time_counter`] -#[cfg(target_arch = "x86_64")] +/// It is using [`rdtsc`] on `x86_64` and `x86`. +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] #[must_use] pub fn read_time_counter() -> u64 { unsafe { core::arch::x86_64::_rdtsc() } } + +/// Read a timestamp for measurements. +/// +/// This function is a wrapper around different ways to get a timestamp, fast +/// In this way, an experiment only has to +/// change this implementation rather than every instead of [`cpu::read_time_counter`] +/// On unsupported architectures, it's falling back to normal system time, in millis. +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +pub fn read_time_counter() -> u64 { + current_nanos() +} From 5d1eb27bda4f1ae6abe43de0d4f777dcf1d58071 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Fri, 7 May 2021 02:02:01 +0200 Subject: [PATCH 15/18] make clippy more strict --- clippy.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clippy.sh b/clippy.sh index 01b7093e3c..67a8c4709d 100755 --- a/clippy.sh +++ b/clippy.sh @@ -3,16 +3,12 @@ cargo clean -p libafl RUST_BACKTRACE=full cargo clippy --all --all-features --tests -- \ -D clippy::pedantic \ - -W clippy::cast_sign_loss \ -W clippy::similar-names \ - -W clippy::cast_ptr_alignment \ - -W clippy::cast_possible_wrap \ -W clippy::unused_self \ -W clippy::too_many_lines \ -W clippy::option_if_let_else \ -W clippy::must-use-candidate \ -W clippy::if-not-else \ - -W clippy::doc-markdown \ -A clippy::type_repetition_in_bounds \ -A clippy::missing-errors-doc \ -A clippy::cast-possible-truncation \ From f56f3406d52406d8e882fd749de99fef7eba6217 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Fri, 7 May 2021 09:50:51 +0200 Subject: [PATCH 16/18] small fixes --- libafl/src/state/mod.rs | 2 +- libafl/src/stats/mod.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 801e18260d..6357f184fe 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -697,7 +697,7 @@ where let is_interesting = { // Init temporary feedback stats here. We can't use the typical pattern above // since we need a `mut self` for `feedbacks_mut`, so we can't also hand a - // new `mut self` to `is_interesting_all_with_perf`. We use this stack + // new `mut self` to `is_interesting_with_perf`. We use this stack // variable to get the stats and then update the feedbacks directly let mut feedback_stats = [0_u64; crate::stats::NUM_FEEDBACKS]; let feedback_index = 0; diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index d3feeed3d8..5c84c043ae 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -266,10 +266,10 @@ macro_rules! mark_feedback_time { } /// Number of stages in the fuzzer -pub(crate) const NUM_STAGES: usize = 4; +pub(crate) const NUM_STAGES: usize = 8; /// Number of feedback mechanisms to measure for performance -pub(crate) const NUM_FEEDBACKS: usize = 4; +pub(crate) const NUM_FEEDBACKS: usize = 16; /// Client performance statistics #[derive(Serialize, Deserialize, Debug, Copy, Clone)] @@ -296,6 +296,7 @@ pub struct ClientPerfStats { /// stages if they are not in use. stages_used: [bool; NUM_STAGES], + // TODO(andrea) use an hashmap and indetify feaures using a &'static str /// Clock cycles spent in the the various features of each stage stages: [[u64; PerfFeature::Count as usize]; NUM_STAGES], @@ -334,10 +335,10 @@ pub enum PerfFeature { /// Time spent in the [`post_exec_observers`](crate::executors::Executor::post_exec_observers) callback PostExecObservers = 7, - /// Time spent getting the feedback from [`is_interesting_all`] from all feedbacks + /// Time spent getting the feedback from [`is_interesting`] from all feedbacks GetFeedbackInterestingAll = 8, - /// Time spent getting the feedback from [`is_interesting_all`] from all observers + /// Time spent getting the feedback from [`is_interesting`] from all objectives GetObjectivesInterestingAll = 9, /// Used as a counter to know how many elements are in [`PerfFeature`]. Must be the From 1e61748c6f1438dba1d723576465720c4b7c5f9b Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Fri, 7 May 2021 09:58:25 +0200 Subject: [PATCH 17/18] bump 0.2.1 --- libafl/Cargo.toml | 4 ++-- libafl_cc/Cargo.toml | 2 +- libafl_derive/Cargo.toml | 2 +- libafl_frida/Cargo.toml | 6 +++--- libafl_targets/Cargo.toml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 14bdff55b2..6c6a6bf901 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl" -version = "0.2.0" +version = "0.2.1" authors = ["Andrea Fioraldi ", "Dominik Maier "] description = "Slot your own fuzzers together and extend their features using Rust" documentation = "https://docs.rs/libafl" @@ -61,7 +61,7 @@ erased-serde = "0.3.12" postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat static_assertions = "1.1.0" ctor = "0.1.20" -libafl_derive = { version = "0.1.0", optional = true, path = "../libafl_derive" } +libafl_derive = { optional = true, path = "../libafl_derive", version = "0.2.1" } serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap compression = { version = "0.1.5" } num_enum = "0.5.1" diff --git a/libafl_cc/Cargo.toml b/libafl_cc/Cargo.toml index 88cd09a948..0e82b0fe1a 100644 --- a/libafl_cc/Cargo.toml +++ b/libafl_cc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_cc" -version = "0.1.0" +version = "0.2.1" 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 d95d012ef6..d38de29c32 100644 --- a/libafl_derive/Cargo.toml +++ b/libafl_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_derive" -version = "0.1.0" +version = "0.2.1" 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 8aa72e7578..c0bfc0b349 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_frida" -version = "0.2.0" +version = "0.2.1" authors = ["s1341 "] description = "Frida backend library for LibAFL" documentation = "https://docs.rs/libafl_frida" @@ -14,8 +14,8 @@ edition = "2018" cc = { version = "1.0", features = ["parallel"] } [dependencies] -libafl = { path = "../libafl", version = "0.2.0", features = ["std", "libafl_derive"] } -libafl_targets = { path = "../libafl_targets", version = "0.1.0" } +libafl = { path = "../libafl", version = "0.2.1", features = ["std", "libafl_derive"] } +libafl_targets = { path = "../libafl_targets", version = "0.2.1" } nix = "0.20.0" libc = "0.2.92" hashbrown = "0.11" diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index 810df875a9..aa4b6bdb6d 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libafl_targets" -version = "0.1.0" +version = "0.2.1" authors = ["Andrea Fioraldi "] description = "Common code for target instrumentation that can be used combined with LibAFL" documentation = "https://docs.rs/libafl_targets" From 92116b963c2d41d81e22c7f2eb70185eb1e5b280 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Fri, 7 May 2021 10:00:07 +0200 Subject: [PATCH 18/18] readme --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index c9b452463a..a61ba68c6e 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,6 @@ Rust directly, instructions can be found [here](https://www.rust-lang.org/tools/ git clone https://github.com/AFLplusplus/LibAFL ``` -If you want to get the latest and greatest features, -``` -git checkout dev -``` - Build the library using ``` @@ -65,7 +60,6 @@ cargo doc cd docs && mdbook serve ``` - We collect all example fuzzers in [`./fuzzers`](./fuzzers/). Be sure to read their documentation (and source), this is *the natural way to get started!*