From f7e61665be2a9e9977a48b68cdf790ca141794e5 Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Mon, 27 Jan 2025 13:56:43 +0100 Subject: [PATCH] refactoring --- fuzzers/FRET/benchmark/plot_all_benchmarks.sh | 2 +- fuzzers/FRET/benchmark/plot_all_traces.sh | 4 +- fuzzers/FRET/benchmark/plot_multi.r | 4 +- fuzzers/FRET/src/config.rs | 4 +- fuzzers/FRET/src/fuzzer.rs | 15 +- fuzzers/FRET/src/systemstate/ARCH.md | 8 +- fuzzers/FRET/src/systemstate/feedbacks.rs | 338 ++--------------- fuzzers/FRET/src/systemstate/helpers.rs | 187 ++++++--- fuzzers/FRET/src/systemstate/mod.rs | 4 +- fuzzers/FRET/src/systemstate/mutational.rs | 53 +-- fuzzers/FRET/src/systemstate/observers.rs | 168 --------- fuzzers/FRET/src/systemstate/stg.rs | 49 ++- .../systemstate/target_os/freertos/config.rs | 47 +-- .../src/systemstate/target_os/freertos/mod.rs | 8 +- .../target_os/freertos/qemu_module.rs | 7 +- fuzzers/FRET/src/systemstate/target_os/mod.rs | 53 ++- fuzzers/FRET/src/time/clock.rs | 228 +++++------ fuzzers/FRET/src/time/worst.rs | 354 ++++-------------- fuzzers/FRET/tests/run_test.sh | 16 +- 19 files changed, 487 insertions(+), 1062 deletions(-) delete mode 100644 fuzzers/FRET/src/systemstate/observers.rs diff --git a/fuzzers/FRET/benchmark/plot_all_benchmarks.sh b/fuzzers/FRET/benchmark/plot_all_benchmarks.sh index 8714b6a29c..48607b3c64 100644 --- a/fuzzers/FRET/benchmark/plot_all_benchmarks.sh +++ b/fuzzers/FRET/benchmark/plot_all_benchmarks.sh @@ -1,6 +1,6 @@ BDIR=remote plot () { - [ ! -f ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/$BDIR/${1}${2}_all.png ] && Rscript plot_multi.r $BDIR/timedump ${1}${2} ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/$BDIR + [ ! -f ../benchmark/$BDIR/${1}${2}_all.png ] && Rscript plot_multi.r $BDIR/timedump ${1}${2} ../benchmark/$BDIR } # Only bytes diff --git a/fuzzers/FRET/benchmark/plot_all_traces.sh b/fuzzers/FRET/benchmark/plot_all_traces.sh index 27e2575f09..e035c02266 100644 --- a/fuzzers/FRET/benchmark/plot_all_traces.sh +++ b/fuzzers/FRET/benchmark/plot_all_traces.sh @@ -24,5 +24,5 @@ do # fi done < <(find ./remote/timedump -maxdepth 2 -type 'f' -iregex '.*\.case') -# echo "${PLOTS[@]}" -snakemake -c 6 --keep-incomplete "${PLOTS[@]}" +echo "${PLOTS[@]}" +snakemake -c 6 --rerun-incomplete --keep-incomplete "${PLOTS[@]}" diff --git a/fuzzers/FRET/benchmark/plot_multi.r b/fuzzers/FRET/benchmark/plot_multi.r index 66c424a01c..9bfc98a1b8 100644 --- a/fuzzers/FRET/benchmark/plot_multi.r +++ b/fuzzers/FRET/benchmark/plot_multi.r @@ -16,7 +16,7 @@ if (length(args)==0) { target="waters" #target="waters_int" #target="watersv2_int" - outputpath="~/code/FRET/LibAFL/fuzzers/FRET/benchmark/" + outputpath="../benchmark" #MY_SELECTION <- c('state', 'afl', 'graph', 'random') SAVE_FILE=TRUE } else { @@ -39,7 +39,7 @@ if (is.null(worst_case)) { #MY_COLORS=c("green","blue","red", "orange", "pink", "black") MY_COLORS <- c("green", "blue", "red", "magenta", "orange", "cyan", "pink", "gray", "orange", "black", "yellow","brown") -BENCHDIR=sprintf("~/code/FRET/LibAFL/fuzzers/FRET/benchmark/%s",runtype) +BENCHDIR=sprintf("../benchmark/%s",runtype) BASENAMES=Filter(function(x) x!="" && substr(x,1,1)!='.',list.dirs(BENCHDIR,full.names=FALSE)) PATTERNS="%s#[0-9]*.time$" #RIBBON='sd' diff --git a/fuzzers/FRET/src/config.rs b/fuzzers/FRET/src/config.rs index 34ee6935a0..3967ac3baa 100644 --- a/fuzzers/FRET/src/config.rs +++ b/fuzzers/FRET/src/config.rs @@ -77,7 +77,7 @@ pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> { } pub fn get_target_ranges( - elf: &EasyElf, + _elf: &EasyElf, symbols: &HashMap<&'static str, GuestAddr>, ) -> HashMap<&'static str, std::ops::Range> { let mut ranges = HashMap::new(); @@ -91,7 +91,5 @@ pub fn get_target_ranges( symbols["__API_CODE_START__"]..symbols["__API_CODE_END__"], ); - crate::systemstate::target_os::freertos::config::add_target_ranges(elf, symbols, &mut ranges); - ranges } diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index f25ae9b4ae..b0dc07f92e 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -15,7 +15,7 @@ elf::EasyElf, emu::Emulator, modules::{edges::{self}, EdgeCoverageModule, Filter use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; use rand::{SeedableRng, StdRng, Rng}; use crate::{ - config::{get_target_ranges, get_target_symbols}, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{get_function_range, load_symbol, try_load_symbol}, mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, STGSnippetStage}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}, target_os::freertos::{config::get_range_groups, qemu_module::FreeRTOSSystemStateHelper, FreeRTOSSystem}}, time::{ + config::{get_target_ranges, get_target_symbols}, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{get_function_range, input_bytes_to_interrupt_times, load_symbol, try_load_symbol}, mutational::{InterruptShiftStage, STGSnippetStage}, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}, target_os::freertos::{config::get_range_groups, qemu_module::FreeRTOSSystemStateHelper, FreeRTOSSystem}}, time::{ clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP, QEMU_ICOUNT_SHIFT, QEMU_ISNS_PER_USEC}, qemustate::QemuStateRestoreHelper, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, RateLimitedMonitor, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler} } }; @@ -322,7 +322,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { }; // Create an observation channel to keep track of the execution time - let clock_time_observer = QemuClockObserver::new("clocktime", if cli.dump_times {cli.dump_name.clone().map(|x| x.with_extension("time"))} else {None} ); + let clock_time_observer = QemuClockObserver::new("clocktime"); // if cli.dump_times {cli.dump_name.clone().map(|x| x.with_extension("time"))} else {None} // Create an observation channel using the coverage map #[cfg(feature = "observe_edges")] @@ -343,14 +343,11 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { addr_of_mut!(MAX_STG_NUM) )}.track_indices(); - #[cfg(feature = "observe_systemstate")] - let systemstate_observer = QemuSystemStateObserver::<_,FreeRTOSSystem>::new(&cli.select_task); - // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR let mut feedback = feedback_or!( // Time feedback, this one does not need a feedback state - ClockTimeFeedback::::new_with_observer(&clock_time_observer, &cli.select_task) + ClockTimeFeedback::::new_with_observer(&clock_time_observer, &cli.select_task, if cli.dump_times {cli.dump_name.clone().map(|x| x.with_extension("time"))} else {None}) ); #[cfg(feature = "feed_genetic")] let mut feedback = feedback_or!( @@ -373,12 +370,12 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { #[cfg(all(feature = "observe_systemstate"))] let mut feedback = feedback_or!( feedback, - DumpSystraceFeedback::::with_dump(if cli.dump_traces {cli.dump_name.clone().map(|x| x.with_extension("trace.ron"))} else {None}, &cli.select_task) + DumpSystraceFeedback::::with_dump(if cli.dump_traces {cli.dump_name.clone().map(|x| x.with_extension("trace.ron"))} else {None}) ); #[cfg(feature = "trace_stg")] let mut feedback = feedback_or!( feedback, - StgFeedback::::new(if cli.dump_graph {cli.dump_name.clone()} else {None}) + StgFeedback::::new(cli.select_task.clone(), if cli.dump_graph {cli.dump_name.clone()} else {None}) ); #[cfg(feature = "feed_stg_edge")] let mut feedback = feedback_or!( @@ -441,7 +438,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { let observer_list = tuple_list!(); #[cfg(feature = "observe_systemstate")] - let observer_list = (systemstate_observer, (stg_coverage_observer, observer_list)); // must come after clock + let observer_list = (stg_coverage_observer, observer_list); // must come after clock #[cfg(feature = "observe_edges")] let observer_list = (edges_observer, observer_list); let observer_list = (clock_time_observer, observer_list); diff --git a/fuzzers/FRET/src/systemstate/ARCH.md b/fuzzers/FRET/src/systemstate/ARCH.md index 62ffe8981d..53ad90773b 100644 --- a/fuzzers/FRET/src/systemstate/ARCH.md +++ b/fuzzers/FRET/src/systemstate/ARCH.md @@ -3,4 +3,10 @@ - ``fuzzer.rs`` resolves symbols and creates ``api_ranges`` and ``isr_ranges`` - ``helpers::QemuSystemStateHelper`` captures a series of ``RawFreeRTOSSystemState`` - ``observers::QemuSystemStateObserver`` divides this into ``ReducedFreeRTOSSystemState`` and ``ExecInterval``, the first contains the raw states and the second contains information about the flow between states -- ``stg::StgFeedback`` builds an stg from the intervals \ No newline at end of file +- ``stg::StgFeedback`` builds an stg from the intervals +## Target-specific (systemstate/target_os) +- config ``add_target_symbols`` and ``get_range_groups`` resolve important symbols +- provides a helper (e.g. ``FreeRTOSSystemStateHelper`` ) to capture the state + - collects locally into e.g. ``CURRENT_SYSTEMSTATE_VEC`` + - post-processing + - replaces ``SystemTraceData`` in state metadata \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/feedbacks.rs b/fuzzers/FRET/src/systemstate/feedbacks.rs index 19b7870a61..0ed67c68d6 100644 --- a/fuzzers/FRET/src/systemstate/feedbacks.rs +++ b/fuzzers/FRET/src/systemstate/feedbacks.rs @@ -1,179 +1,23 @@ -use crate::time::clock::QemuClockObserver; -use hashbrown::HashMap; -use libafl::common::HasNamedMetadata; -use libafl::corpus::Testcase; +use libafl::{ + common::HasMetadata, + executors::ExitKind, + feedbacks::Feedback, + observers::ObserversTuple, + prelude::{State, UsesInput}, + state::MaybeHasClientPerfMonitor, + Error, +}; use libafl::events::EventFirer; -use libafl::feedbacks::Feedback; -use libafl::prelude::State; -use libafl::prelude::UsesInput; -use libafl::state::MaybeHasClientPerfMonitor; -use libafl::Error; -use libafl::{common::HasMetadata, executors::ExitKind, observers::ObserversTuple}; use libafl_bolts::Named; -use serde::{Deserialize, Serialize}; -use std::collections::hash_map::DefaultHasher; -use std::hash::Hash; -use std::hash::Hasher; use std::path::PathBuf; -use super::observers::QemuSystemStateObserver; use super::target_os::TargetSystem; -use super::ExecInterval; -use libafl_bolts::prelude::SerdeAny; use std::borrow::Cow; use std::marker::PhantomData; use crate::systemstate::target_os::*; use libafl::prelude::StateInitializer; -//============================= Feedback - -/// Shared Metadata for a systemstateFeedback -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct SystemStateFeedbackState -where - SYS: TargetSystem, -{ - name: Cow<'static, str>, - known_traces: HashMap, // encounters,ticks,length - longest: Vec, -} -libafl_bolts::impl_serdeany!(SystemStateFeedbackState); - -impl Named for SystemStateFeedbackState -where - SYS: TargetSystem, -{ - #[inline] - fn name(&self) -> &Cow<'static, str> { - &self.name - } -} - -impl Default for SystemStateFeedbackState -where - SYS: TargetSystem, -{ - fn default() -> Self { - Self { - name: Cow::from("systemstate".to_string()), - known_traces: HashMap::new(), - longest: Vec::new(), - } - } -} - -// /// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`] -// #[derive(Serialize, Deserialize, Clone, Debug)] -// pub struct NovelSystemStateFeedback -// { -// name: Cow<'static, str>, -// last_trace: Option>, -// // known_traces: HashMap, -// } - -// impl StateInitializer for NovelSystemStateFeedback {} - -// impl Feedback for NovelSystemStateFeedback -// where -// S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata, -// S::Input: Default, -// EM: EventFirer, -// OT: ObserversTuple, -// { -// fn is_interesting( -// &mut self, -// state: &mut S, -// _manager: &mut EM, -// _input: &I, -// observers: &OT, -// _exit_kind: &ExitKind, -// ) -> Result -// where -// { -// let observer : &QemuSystemStateObserver = observers.match_name::>("systemstate") -// .expect("QemuSystemStateObserver not found"); -// let clock_observer = observers.match_name::("clocktime") //TODO not fixed -// .expect("QemuClockObserver not found"); -// let feedbackstate = match state -// .named_metadata_map_mut() -// .get_mut::("systemstate") { -// Some(s) => s, -// Option::None => { -// let n=SystemStateFeedbackState::default(); -// state.named_metadata_map_mut().insert("systemstate",n); -// state.named_metadata_map_mut().get_mut::("systemstate").unwrap() -// } -// }; -// #[cfg(feature = "trace_job_response_times")] -// let last_runtime = observer.last_runtime(); -// #[cfg(not(feature = "trace_job_response_times"))] -// let last_runtime = clock_observer.last_runtime(); -// // let feedbackstate = state -// // .feedback_states_mut() -// // .match_name_mut::("systemstate") -// // .unwrap(); -// // Do Stuff -// let mut hasher = DefaultHasher::new(); -// observer.last_run.hash(&mut hasher); -// let somehash = hasher.finish(); -// let mut is_novel = false; -// let mut takes_longer = false; -// match feedbackstate.known_traces.get_mut(&somehash) { -// Option::None => { -// is_novel = true; -// feedbackstate.known_traces.insert(somehash,(1,last_runtime,observer.last_run.len())); -// } -// Some(s) => { -// s.0+=1; -// if s.1 < last_runtime { -// s.1 = last_runtime; -// takes_longer = true; -// } -// } -// } -// if observer.last_run.len() > feedbackstate.longest.len() { -// feedbackstate.longest=observer.last_run.clone(); -// } -// self.last_trace = Some(observer.last_run.clone()); -// // if (!is_novel) { println!("not novel") }; -// Ok(is_novel | takes_longer) -// } - -// /// Append to the testcase the generated metadata in case of a new corpus item -// #[inline] -// fn append_metadata(&mut self, _state: &mut S, _manager: &mut EM, _observers: &OT, testcase: &mut Testcase) -> Result<(), Error> { -// let a = self.last_trace.take(); -// match a { -// Some(s) => testcase.metadata_map_mut().insert(FreeRTOSSystemStateMetadata::new(s)), -// Option::None => (), -// } -// Ok(()) -// } - -// /// Discard the stored metadata in case that the testcase is not added to the corpus -// #[inline] -// fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { -// self.last_trace = None; -// Ok(()) -// } -// } - -// impl Named for NovelSystemStateFeedback -// { -// #[inline] -// fn name(&self) -> &Cow<'static, str> { -// &self.name -// } -// } - -// impl Default for NovelSystemStateFeedback -// { -// fn default() -> Self { -// Self {name: Cow::from("NovelSystemStateFeedback".to_string()), last_trace: None } -// } -// } - //=========================== Debugging Feedback /// A [`Feedback`] meant to dump the system-traces for debugging. Depends on [`QemuSystemStateObserver`] #[derive(Debug)] @@ -183,8 +27,6 @@ where { name: Cow<'static, str>, dumpfile: Option, - dump_metadata: bool, - select_task: Option, // TODO: use some global config for this phantom: PhantomData, } @@ -206,122 +48,25 @@ where _exit_kind: &ExitKind, ) -> Result where { - if self.dumpfile.is_none() { - return Ok(false); - }; - let trace = state - .metadata::() - .expect("TraceData not found"); - - let names: Vec = trace - .states() - .iter() - .map(|x| x.current_task().task_name().clone()) - .collect(); match &self.dumpfile { Some(s) => { - let per_task_metadata = if let Some(worst_instance) = trace.worst_jobs_per_task_by_time().get(self.select_task.as_ref().unwrap_or(&"".to_string())) - { - // extract computation time spent in each task and abb - let t: Vec<_> = trace.intervals() - .iter() - .filter(|x| { - x.start_tick < worst_instance.response - && x.end_tick > worst_instance.release - }) - .cloned() - .collect(); - // task_name -> addr -> (count, time) - let mut ret: HashMap> = - HashMap::new(); - let mut t2 = t.clone(); - t2.sort_by_key(|x| x.get_task_name_unchecked()); - t2.chunk_by_mut(|x, y| { - x.get_task_name_unchecked() == y.get_task_name_unchecked() - }) - .for_each(|x| { - x.sort_by_key(|y| y.abb.as_ref().unwrap().start); - x.chunk_by(|y, z| { - y.abb.as_ref().unwrap().start == z.abb.as_ref().unwrap().start - }) - .for_each(|y| { - match ret.get_mut(&y[0].get_task_name_unchecked()) { - Option::None => { - ret.insert( - y[0].get_task_name_unchecked(), - HashMap::from([( - y[0].abb.as_ref().unwrap().start, - ( - y.len(), - y.iter().filter(|x| x.is_abb_end()).count(), - y.iter().map(|z| z.get_exec_time()).sum::<_>(), - ), - )]), - ); - } - Some(x) => { - x.insert( - y[0].abb.as_ref().unwrap().start, - ( - y.len(), - y.iter().filter(|x| x.is_abb_end()).count(), - y.iter().map(|z| z.get_exec_time()).sum(), - ), - ); - } - } - }); - }); - // dbg!(&ret); - ret - } else { - HashMap::new() - }; + let trace = state + .metadata::() + .expect("TraceData not found"); std::fs::write( s, - ron::to_string(&( - &trace, - per_task_metadata, - )) - .expect("Error serializing hashmap"), + ron::to_string(trace) + .expect("Error serializing hashmap"), ) .expect("Can not dump to file"); self.dumpfile = None } Option::None => { - if self.dump_metadata { - println!("{:?}\n{:?}", trace, names); - } + () } }; - // if self.dump_metadata {self.last_trace=Some(observer.last_trace.clone());} Ok(false) } - /// Append to the testcase the generated metadata in case of a new corpus item - #[inline] - fn append_metadata( - &mut self, - _state: &mut S, - _manager: &mut EM, - _observers: &OT, - _testcase: &mut Testcase, - ) -> Result<(), Error> { - if !self.dump_metadata { - return Ok(()); - } - // let a = self.last_trace.take(); - // match a { - // Some(s) => testcase.metadata_map_mut().insert(FreeRTOSSystemStateMetadata::new(s)), - // None => (), - // } - Ok(()) - } - - /// Discard the stored metadata in case that the testcase is not added to the corpus - #[inline] - fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { - Ok(()) - } } impl Named for DumpSystraceFeedback @@ -344,29 +89,15 @@ where Self { name: Cow::from("Dumpsystemstate".to_string()), dumpfile: None, - dump_metadata: false, phantom: PhantomData, - select_task: None, } } #[allow(unused)] - pub fn with_dump(dumpfile: Option, select_task: &Option) -> Self { + pub fn with_dump(dumpfile: Option) -> Self { Self { name: Cow::from("Dumpsystemstate".to_string()), dumpfile: dumpfile, - dump_metadata: false, phantom: PhantomData, - select_task: select_task.clone(), - } - } - #[allow(unused)] - pub fn metadata_only() -> Self { - Self { - name: Cow::from("Dumpsystemstate".to_string()), - dumpfile: None, - dump_metadata: true, - phantom: PhantomData, - select_task: None, } } } @@ -386,58 +117,43 @@ impl StateInitializer for SystraceErrorFeedback where SYS: Targe impl Feedback for SystraceErrorFeedback where - S: State + UsesInput + MaybeHasClientPerfMonitor, + S: State + UsesInput + MaybeHasClientPerfMonitor + HasMetadata, EM: EventFirer, OT: ObserversTuple, SYS: TargetSystem, { fn is_interesting( &mut self, - _state: &mut S, + state: &mut S, _manager: &mut EM, _input: &I, - observers: &OT, + _observers: &OT, _exit_kind: &ExitKind, ) -> Result where { #[cfg(feature = "trace_stg")] { - let observer = observers - .match_name::>("systemstate") - .expect("QemuSystemStateObserver not found"); - let is_err = (!observer.success || observer.do_report); if let Some(m) = self.max_reports { if m <= 0 { return Ok(false); } - if is_err { + let need_to_debug = state + .metadata::() + .expect("TraceData not found") + .need_to_debug(); + if need_to_debug { self.max_reports = Some(m - 1); } + return Ok(self.dump_case && need_to_debug); + } else { + return Ok(false); } - return Ok(self.dump_case && is_err); } #[cfg(not(feature = "trace_stg"))] { return Ok(false); } } - /// Append to the testcase the generated metadata in case of a new corpus item - #[inline] - fn append_metadata( - &mut self, - _state: &mut S, - _manager: &mut EM, - _observers: &OT, - _testcase: &mut Testcase, - ) -> Result<(), Error> { - Ok(()) - } - - /// Discard the stored metadata in case that the testcase is not added to the corpus - #[inline] - fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { - Ok(()) - } } impl Named for SystraceErrorFeedback diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs index e3228ad2a9..690081b5d9 100644 --- a/fuzzers/FRET/src/systemstate/helpers.rs +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -1,29 +1,18 @@ -use std::ops::Range; use hashbrown::HashMap; -use hashbrown::HashSet; -use libafl::prelude::ExitKind; -use libafl::prelude::UsesInput; -use libafl_qemu::elf::EasyElf; -use libafl_qemu::modules::NopAddressFilter; -use libafl_qemu::modules::NopPageFilter; -use libafl_qemu::read_user_reg_unchecked; -use libafl_qemu::GuestAddr; -use libafl_qemu::GuestPhysAddr; -use libafl_qemu::QemuHooks; -use libafl_qemu::Hook; -use libafl_qemu::modules::{EmulatorModule, EmulatorModuleTuple}; -use libafl_qemu::sys::TCGTemp; -use libafl_qemu::qemu::MemAccessInfo; - -use super::CaptureEvent; -use libafl_qemu::EmulatorModules; -use libafl::prelude::ObserversTuple; +use libafl_bolts::prelude::{SerdeAny, SerdeAnyMap}; +use libafl_qemu::{elf::EasyElf, read_user_reg_unchecked, GuestAddr, GuestPhysAddr}; +use std::ops::Range; +use std::cmp::min; +use crate::{fuzzer::{DO_NUM_INTERRUPT, FIRST_INT}, time::clock::QEMU_ISNS_PER_USEC}; +use super::{ + target_os::{SystemTraceData, TargetSystem}, + ExecInterval, +}; //============================= API symbols - /// Read ELF program headers to resolve physical load addresses. fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { let ret; @@ -38,24 +27,28 @@ fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { } /// Lookup a symbol in the ELF file, optionally resolve segment offsets -pub fn load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> GuestAddr { +pub fn load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAddr { try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol)) } -pub fn try_load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> Option { - let ret = elf - .resolve_symbol(symbol, 0); +/// Lookup a symbol in the ELF file, optionally resolve segment offsets +pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option { + let ret = elf.resolve_symbol(symbol, 0); if do_translation { - Option::map_or(ret, None, |x| Some(virt2phys(x as GuestPhysAddr,&elf) as GuestAddr)) - } else {ret} + Option::map_or(ret, None, |x| { + Some(virt2phys(x as GuestPhysAddr, &elf) as GuestAddr) + }) + } else { + ret + } } /// Try looking up the address range of a function in the ELF file pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option> { let gob = elf.goblin(); - let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect(); - funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value)); + let mut funcs: Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect(); + funcs.sort_unstable_by(|x, y| x.st_value.cmp(&y.st_value)); for sym in &gob.syms { if let Some(sym_name) = gob.strtab.get_at(sym.st_name) { @@ -82,10 +75,22 @@ pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option( + ranges: &'a Vec<(String, Range)>, + addr: GuestAddr, +) -> Option<&'a std::ops::Range> { + for (_, r) in ranges { + if r.contains(&addr) { + return Some(r); + } + } + return None; +} -//============================= Utility functions +//============================= QEMU related utility functions -pub fn get_icount(emulator : &libafl_qemu::Qemu) -> u64 { +pub fn get_icount(emulator: &libafl_qemu::Qemu) -> u64 { unsafe { // TODO: investigate why can_do_io is not set sometimes, as this is just a workaround let c = emulator.cpu_from_index(0); @@ -97,29 +102,127 @@ pub fn get_icount(emulator : &libafl_qemu::Qemu) -> u64 { } } -pub fn read_rec_return_stackframe(emu : &libafl_qemu::Qemu, lr : GuestAddr) -> GuestAddr { - let lr_ = lr & u32::MAX-1; +pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize,u32)) -> Vec { + let len = buf.len(); + let mut start_tick; + let mut ret = Vec::with_capacity(min(DO_NUM_INTERRUPT, len/4)); + for i in 0..DO_NUM_INTERRUPT { + let mut buf4b : [u8; 4] = [0,0,0,0]; + if len >= (i+1)*4 { + for j in 0usize..4usize { + buf4b[j]=buf[i*4+j]; + } + start_tick = u32::from_le_bytes(buf4b); + if start_tick < FIRST_INT {start_tick=0;} + ret.push(start_tick); + } else {break;} + } + ret.sort_unstable(); + // obey the minimum inter arrival time while maintaining the sort + for i in 0..ret.len() { + if ret[i]==0 {continue;} + for j in i+1..ret.len()-1 { + if ret[j]-ret[i] < config.1 * QEMU_ISNS_PER_USEC { + // ret[j] = u32::saturating_add(ret[i],config.1 * QEMU_ISNS_PER_USEC); + ret[j] = 0; // remove the interrupt + ret.sort_unstable(); + } else {break;} + } + } + ret +} + +pub fn interrupt_times_to_input_bytes(interrupt_times: &[u32]) -> Vec { + let mut ret = Vec::with_capacity(interrupt_times.len()*4); + for i in interrupt_times { + ret.extend(u32::to_le_bytes(*i)); + } + ret +} + +pub fn read_rec_return_stackframe(emu: &libafl_qemu::Qemu, lr: GuestAddr) -> GuestAddr { + let lr_ = lr & u32::MAX - 1; if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF8 || lr_ == 0xFFFFFFF0 { - unsafe { // if 0xFFFFFFF0/1 0xFFFFFFF8/9 -> "main stack" MSP let mut buf = [0u8; 4]; - let sp : GuestAddr = if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF0 { // PSP + let sp: GuestAddr = if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF0 { + // PSP read_user_reg_unchecked(emu) as u32 } else { emu.read_reg(13).unwrap() }; - let ret_pc = sp+0x18; // https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-processor/exception-model/exception-entry-and-return - emu.read_mem(ret_pc, buf.as_mut_slice()); + let ret_pc = sp + 0x18; // https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-processor/exception-model/exception-entry-and-return + emu.read_mem(ret_pc, buf.as_mut_slice()) + .expect("Failed to read return address"); return u32::from_le_bytes(buf); // elseif 0xfffffffc/d - }} else { + } else { return lr; }; } -pub fn in_any_range<'a>(ranges: &'a Vec<(String, Range)>, addr : GuestAddr) -> Option<&'a std::ops::Range> { - for (_,r) in ranges { - if r.contains(&addr) {return Some(r);} +//============================= Tracing related utility functions + +pub fn metadata_insert_or_update_get( + metadata: &mut SerdeAnyMap, + default: impl FnOnce() -> T, + update: impl FnOnce(&mut T), +) -> &mut T +where + T: SerdeAny, +{ + if metadata.contains::() { + let v = metadata.get_mut::().unwrap(); + update(v); + return v; + } else { + return metadata.get_or_insert_with(default); } - return None; -} \ No newline at end of file +} + + +/// Build an ABB-profile from a stretch of intervals +/// returns mapping: task_name -> (abb_addr -> (interval_count, exec_count, exec_time)) +pub fn abb_profile( + mut intervals: Vec, +) -> HashMap> { + let mut ret: HashMap> = HashMap::new(); + intervals.sort_by_key(|x| x.get_task_name_unchecked()); + intervals + .chunk_by_mut(|x, y| x.get_task_name_unchecked() == y.get_task_name_unchecked()) + .for_each(|x| { + x.sort_by_key(|y| y.abb.as_ref().unwrap().start); + x.chunk_by(|y, z| y.abb.as_ref().unwrap().start == z.abb.as_ref().unwrap().start) + .for_each(|y| match ret.get_mut(&y[0].get_task_name_unchecked()) { + Option::None => { + ret.insert( + y[0].get_task_name_unchecked(), + HashMap::from([( + y[0].abb.as_ref().unwrap().start, + ( + y.len(), + y.iter().filter(|x| x.is_abb_end()).count(), + y.iter().map(|z| z.get_exec_time()).sum::<_>(), + ), + )]), + ); + } + Some(x) => { + x.insert( + y[0].abb.as_ref().unwrap().start, + ( + y.len(), + y.iter().filter(|x| x.is_abb_end()).count(), + y.iter().map(|z| z.get_exec_time()).sum(), + ), + ); + } + }); + }); + ret +} + + +pub fn unmut(x: &mut T) -> &T { + &(*x) +} diff --git a/fuzzers/FRET/src/systemstate/mod.rs b/fuzzers/FRET/src/systemstate/mod.rs index 51f108c174..29cfc08012 100644 --- a/fuzzers/FRET/src/systemstate/mod.rs +++ b/fuzzers/FRET/src/systemstate/mod.rs @@ -11,7 +11,6 @@ use serde::{Deserialize, Serialize}; use itertools::Itertools; pub mod helpers; -pub mod observers; pub mod feedbacks; pub mod schedulers; pub mod stg; @@ -254,6 +253,9 @@ impl RTOSJob { self.hash_cache } } + pub fn response_time(&self) -> u64 { + self.response-self.release + } } // ============================= Generalized job instances diff --git a/fuzzers/FRET/src/systemstate/mutational.rs b/fuzzers/FRET/src/systemstate/mutational.rs index 3b35728aa5..b5765c5dd8 100644 --- a/fuzzers/FRET/src/systemstate/mutational.rs +++ b/fuzzers/FRET/src/systemstate/mutational.rs @@ -19,7 +19,7 @@ use std::borrow::Cow; use simple_moving_average::SMA; -use super::{stg::{STGEdge, STGNode}, target_os::TargetSystem, RTOSJob}; +use super::{helpers::{input_bytes_to_interrupt_times, interrupt_times_to_input_bytes}, stg::{STGEdge, STGNode}, target_os::TargetSystem, RTOSJob}; // pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*us*/ * QEMU_ISNS_PER_USEC; // one isn per 2**4 ns @@ -27,43 +27,6 @@ use super::{stg::{STGEdge, STGNode}, target_os::TargetSystem, RTOSJob}; // 1ms = 62500 insn // 1us = 62.5 insn -pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize,u32)) -> Vec { - let len = buf.len(); - let mut start_tick; - let mut ret = Vec::with_capacity(min(DO_NUM_INTERRUPT, len/4)); - for i in 0..DO_NUM_INTERRUPT { - let mut buf4b : [u8; 4] = [0,0,0,0]; - if len >= (i+1)*4 { - for j in 0usize..4usize { - buf4b[j]=buf[i*4+j]; - } - start_tick = u32::from_le_bytes(buf4b); - if start_tick < FIRST_INT {start_tick=0;} - ret.push(start_tick); - } else {break;} - } - ret.sort_unstable(); - // obey the minimum inter arrival time while maintaining the sort - for i in 0..ret.len() { - if ret[i]==0 {continue;} - for j in i+1..ret.len()-1 { - if ret[j]-ret[i] < config.1 as u32 * QEMU_ISNS_PER_USEC { - // ret[j] = u32::saturating_add(ret[i],config.1 * QEMU_ISNS_PER_USEC); - ret[j] = 0; // remove the interrupt - ret.sort_unstable(); - } else {break;} - } - } - ret -} - -pub fn interrupt_times_to_input_bytes(interrupt_times: &[u32]) -> Vec { - let mut ret = Vec::with_capacity(interrupt_times.len()*4); - for i in interrupt_times { - ret.extend(u32::to_le_bytes(*i)); - } - ret -} //======================= Custom mutator @@ -249,10 +212,9 @@ where } else if choice <= 75 { // 0.5 * 0.25 = 12.5% of cases let feedbackstate = match state - .named_metadata_map() - .get::>("stgfeedbackstate") { - Some(s) => s, - Option::None => { + .metadata::>() { + Ok(s) => s, + Error => { panic!("STGfeedbackstate not visible") } }; @@ -565,10 +527,9 @@ where // eprintln!("Run mutator {}", current_case.metadata_map().get::().is_some()); if let Some(meta) = current_case.metadata_map().get::() { let feedbackstate = match state - .named_metadata_map() - .get::>("stgfeedbackstate") { - Some(s) => s, - Option::None => { + .metadata::>() { + Ok(s) => s, + Error => { panic!("STGfeedbackstate not visible") } }; diff --git a/fuzzers/FRET/src/systemstate/observers.rs b/fuzzers/FRET/src/systemstate/observers.rs deleted file mode 100644 index 233c002399..0000000000 --- a/fuzzers/FRET/src/systemstate/observers.rs +++ /dev/null @@ -1,168 +0,0 @@ -use libafl::prelude::ExitKind; -use libafl::prelude::UsesInput; -use libafl::HasMetadata; -use libafl_bolts::HasLen; -use libafl_bolts::Named; -use libafl::Error; -use libafl::observers::Observer; -use serde::{Deserialize, Serialize}; -use hashbrown::{HashMap, HashSet}; -use crate::systemstate::CaptureEvent; -use crate::time::clock::IcHist; -use crate::time::clock::FUZZ_START_TIMESTAMP; -use std::time::SystemTime; -use std::rc::Rc; -use std::cell::RefCell; -use std::collections::VecDeque; -use std::borrow::Cow; -use itertools::Itertools; - -use super::target_os::TargetSystem; -use super::RTOSJob; -use super::{ AtomicBasicBlock, ExecInterval}; -use crate::systemstate::target_os::SystemState; -use crate::systemstate::target_os::*; - -//============================= Observer - -/// The Qemusystemstate Observer retrieves the systemstate -/// that will get updated by the target. -#[derive(Serialize, Deserialize, Debug)] -#[allow(clippy::unsafe_derive_deserialize)] -pub struct QemuSystemStateObserver -where - SYS: TargetSystem, - for<'de2> SYS: Deserialize<'de2>, -{ - last_run: Vec, - last_states: HashMap, - last_trace: Vec, - last_reads: Vec>, - last_input: I, - job_instances: Vec, - pub do_report: bool, - worst_job_instances: HashMap, - pub select_task: Option, - pub success: bool, - name: Cow<'static, str>, -} - - -impl Observer for QemuSystemStateObserver -where - S: UsesInput + HasMetadata, - S::Input: Default, - I: Clone, - SYS: TargetSystem, -{ - #[inline] - fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { - Ok(()) - } - - #[inline] - fn post_exec(&mut self, state: &mut S, _input: &I, _exit_kind: &ExitKind) -> Result<(), Error> { - // let trace =state.metadata::().expect("TraceData not found"); - - // copy-paste form clock observer - { - let hist = state.metadata_mut::(); - let timestamp = SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis(); - match hist { - Err(_) => { - state.add_metadata(IcHist(vec![(self.last_runtime(), timestamp)], - (self.last_runtime(), timestamp))); - } - Ok(v) => { - v.0.push((self.last_runtime(), timestamp)); - if v.1.0 < self.last_runtime() { - v.1 = (self.last_runtime(), timestamp); - } - } - } - } - Ok(()) - } -} - -impl Named for QemuSystemStateObserver -where - SYS: TargetSystem, - for<'de2> SYS: Deserialize<'de2>, -{ - fn name(&self) -> &Cow<'static, str> { - &self.name - } -} - -impl HasLen for QemuSystemStateObserver -where - SYS: TargetSystem, - for<'de2> SYS: Deserialize<'de2>, -{ - #[inline] - fn len(&self) -> usize { - self.last_run.len() - } -} - -impl QemuSystemStateObserver -where - SYS: TargetSystem, - for<'de2> SYS: Deserialize<'de2>, - I: Default { - pub fn new(select_task: &Option) -> Self { - Self{last_run: vec![], last_trace: vec![], last_reads: vec![], last_input: I::default(), worst_job_instances: HashMap::new(), do_report: false, select_task: select_task.clone(), name: Cow::from("systemstate".to_string()), last_states: HashMap::new(), success: false, job_instances: vec![]} - } - pub fn last_runtime(&self) -> u64 { - self.select_task.as_ref().map(|x| self.worst_job_instances.get(x).map(|y| y.response-y.release).unwrap_or(0).clone()).unwrap_or(unsafe{libafl_qemu::sys::icount_get_raw()}) - } -} -impl Default for QemuSystemStateObserver -where - SYS: TargetSystem, - for<'de2> SYS: Deserialize<'de2>, -I: Default { - fn default() -> Self { - Self::new(&None) - } -} - - - -// /// restore the isr/api begin/end invariant -// fn fix_broken_trace(meta: &mut Vec<(u64, CaptureEvent, String, (Option, Option))>) { -// for i in meta.iter_mut() { -// if i.1 == CaptureEvent::APIStart && i.2.ends_with("FromISR") { -// i.1 = CaptureEvent::ISREnd; -// i.2 = "ISR_0_Handler".to_string(); -// } -// } -// } - -// /// invalidate subsequent intervals of equal states where an ISREnd follows an ISRStart. If the interrupt had no effect on the system we, are not interested. -// fn invalidate_ineffective_isr(trace: &mut Vec) { -// let mut i = 0; -// while i < trace.len() - 1 { -// if trace[i].is_valid() && -// matches!(trace[i].start_capture.0, CaptureEvent::ISRStart) && matches!(trace[i].end_capture.0, CaptureEvent::ISREnd) && -// trace[i].start_capture.1 == trace[i].end_capture.1 && trace[i].start_state == trace[i].end_state -// { -// trace[i].invaildate(); -// } -// } -// } - -// /// merge a sequence of intervals of the same state+abb. jump over all invalid blocks. -// fn merge_subsequent_abbs(trace: &mut Vec) { -// let mut i = 1; -// let mut lst_valid=0; -// while i < trace.len() - 1 { -// if trace[i].is_valid() { -// let mut temp = trace[i].clone(); -// trace[lst_valid].try_unite_with_later_interval(&mut temp); -// trace[i] = temp; -// lst_valid = i; -// } -// } -// } \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/stg.rs b/fuzzers/FRET/src/systemstate/stg.rs index 5dcc4a7065..898b3d3684 100644 --- a/fuzzers/FRET/src/systemstate/stg.rs +++ b/fuzzers/FRET/src/systemstate/stg.rs @@ -4,6 +4,7 @@ use libafl::inputs::Input; /// Feedbacks organizing SystemStates as a graph use libafl_bolts::prelude::SerdeAny; use libafl_bolts::ownedref::OwnedMutSlice; +use log::Metadata; use petgraph::graph::EdgeIndex; use libafl::prelude::UsesInput; use libafl::common::HasNamedMetadata; @@ -27,12 +28,12 @@ use libafl::{executors::ExitKind, observers::ObserversTuple, common::HasMetadata use serde::{Deserialize, Serialize}; use std::marker::PhantomData; +use super::helpers::metadata_insert_or_update_get; use super::target_os::SystemState; use super::AtomicBasicBlock; use super::CaptureEvent; use super::ExecInterval; use super::RTOSJob; -use super::observers::QemuSystemStateObserver; use super::RTOSTask; use petgraph::prelude::DiGraph; use petgraph::graph::NodeIndex; @@ -383,6 +384,7 @@ where last_top_abb_hashes: Option>, // only set, if it was interesting last_job_trace: Option>, // only set, if it was interesting dump_path: Option, + select_task: Option, _phantom_data: PhantomData, } #[cfg(feature = "feed_stg")] @@ -457,10 +459,12 @@ impl StgFeedback where SYS: TargetSystem, { - pub fn new(dump_name: Option) -> Self { + pub fn new(select_task: Option, dump_name: Option) -> Self { // Self {name: String::from("STGFeedback"), last_node_trace: None, last_edge_trace: None, last_intervals: None } let mut s = Self::default(); + unsafe{libafl_bolts::prelude::RegistryBuilder::register::>()}; s.dump_path = dump_name.map(|x| x.with_extension("stgsize")); + s.select_task = select_task; s } @@ -582,40 +586,31 @@ where { // TODO: don't remove metadata. work around ownership issues let trace = state.remove_metadata::().expect("TraceData not found"); - let observer = observers.match_name::::Input, SYS>>("systemstate") - .expect("QemuSystemStateObserver not found"); let clock_observer = observers.match_name::("clocktime") .expect("QemuClockObserver not found"); - #[cfg(feature = "trace_job_response_times")] - let last_runtime = observer.last_runtime(); - #[cfg(not(feature = "trace_job_response_times"))] let last_runtime = clock_observer.last_runtime(); - let feedbackstate = match state - .named_metadata_map_mut() - .get_mut::>("stgfeedbackstate") { - Some(s) => s, - Option::None => { - let n=STGFeedbackState::::default(); - unsafe{libafl_bolts::prelude::RegistryBuilder::register::>()}; - state.named_metadata_map_mut().insert("stgfeedbackstate",n); - state.named_metadata_map_mut().get_mut::>("stgfeedbackstate").unwrap() - } - }; + + #[cfg(feature = "trace_job_response_times")] + let worst_jobs = trace.worst_jobs_per_task_by_response_time(); + #[cfg(feature = "trace_job_response_times")] + let worst_select_job = if let Some(t) = self.select_task.as_ref() {worst_jobs.get(t)} else {None}; + #[cfg(feature = "trace_job_response_times")] + let last_runtime = if let Some(t) = self.select_task.as_ref() {worst_select_job.map_or(0, |x| x.response_time())} else {last_runtime}; + + let feedbackstate = state.metadata_map_mut().get_or_insert_with(||{ + STGFeedbackState::::default() + }); // --------------------------------- Update STG let (mut nodetrace, mut edgetrace, mut interesting, mut updated) = StgFeedback::update_stg_interval(trace.intervals(), &trace.mem_reads(), trace.states_map(), feedbackstate); - let worst_jobs = trace.worst_jobs_per_task_by_time(); #[cfg(feature = "trace_job_response_times")] - let worst_target_instance = worst_jobs.get(observer.select_task.as_ref().unwrap_or(&String::new())); - - #[cfg(feature = "trace_job_response_times")] - if let Some(worst_instance) = worst_target_instance { + if let Some(worst_instance) = worst_select_job { edgetrace = edgetrace.into_iter().filter(|x| x.1 <= worst_instance.response && x.1 >= worst_instance.release ).collect(); nodetrace = nodetrace.into_iter().filter(|x| x.1 <= worst_instance.response && x.1 >= worst_instance.release ).collect(); } else { - if observer.select_task.is_none() { // if nothing was selected, just take the whole trace, otherwise there is nothing interesting here + if self.select_task.is_some() { // if nothing was selected, just take the whole trace, otherwise there is nothing interesting here edgetrace = Vec::new(); nodetrace = Vec::new(); } @@ -625,7 +620,7 @@ where set_observer_map(&edgetrace.iter().map(|x| x.0).collect::>()); // --------------------------------- Update job instances - for i in trace.worst_jobs_per_task_by_time().iter() { + for i in worst_jobs.iter() { interesting |= INTEREST_JOB_INSTANCE && if let Some(x) = feedbackstate.worst_task_jobs.get_mut(&i.1.get_hash_cached()) { // eprintln!("Job instance already present"); x.try_update(i.1) @@ -657,11 +652,11 @@ where let tmp = StgFeedback::abbs_in_exec_order(&observer.last_trace); #[cfg(feature = "trace_job_response_times")] let tmp = { - if let Some(worst_instance) = worst_target_instance { + if let Some(worst_instance) = worst_select_job { let t = trace.intervals().iter().filter(|x| x.start_tick < worst_instance.response && x.end_tick > worst_instance.release ).cloned().collect(); StgFeedback::::abbs_in_exec_order(&t) } else { - if observer.select_task.is_none() { // if nothing was selected, just take the whole trace, otherwise there is nothing interesting here + if self.select_task.is_none() { // if nothing was selected, just take the whole trace, otherwise there is nothing interesting here StgFeedback::::abbs_in_exec_order(trace.intervals()) } else { Vec::new() diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs index 072dfbe5ab..ce62ede601 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs @@ -3,9 +3,10 @@ use libafl_qemu::{elf::EasyElf, GuestAddr}; use crate::{ fuzzer::get_all_fn_symbol_ranges, - systemstate::{self, helpers::{get_function_range, load_symbol}, target_os::freertos::ISR_SYMBOLS}, + systemstate::{helpers::{get_function_range, load_symbol}, target_os::freertos::ISR_SYMBOLS}, }; +// Add os-specific symbols to the target symbol hashmap pub fn add_target_symbols(elf: &EasyElf, addrs: &mut HashMap<&'static str, GuestAddr>) { // required for system state observation addrs.insert("pxCurrentTCB", load_symbol(&elf, "pxCurrentTCB", false)); // loads to the address specified in elf, without respecting program headers @@ -35,50 +36,8 @@ pub fn add_target_symbols(elf: &EasyElf, addrs: &mut HashMap<&'static str, Guest ); } -pub fn add_target_ranges( - elf: &EasyElf, - symbols: &HashMap<&'static str, GuestAddr>, - ranges: &mut HashMap<&'static str, std::ops::Range>, -) { - let api_range = ranges.get("API_CODE").unwrap(); - let app_range = ranges.get("APP_CODE").unwrap(); - - let mut api_fn_ranges = get_all_fn_symbol_ranges(&elf, api_range.clone()); - let mut app_fn_ranges = get_all_fn_symbol_ranges(&elf, app_range.clone()); - - // Regular ISR functions, remove from API functions - let mut isr_fn_ranges: HashMap> = ISR_SYMBOLS - .iter() - .filter_map(|x| { - api_fn_ranges - .remove(&x.to_string()) - .map(|y| (x.to_string(), y.clone())) - }) - .collect(); - // User-defined ISR functions, remove from APP functions - ISR_SYMBOLS.iter().for_each(|x| { - let _ = (app_fn_ranges - .remove(&x.to_string()) - .map(|y| (x.to_string(), y.clone()))) - .map(|z| isr_fn_ranges.insert(z.0, z.1)); - }); - - // Add the rest of the ISR function, if not already found - for i in ISR_SYMBOLS { - if isr_fn_ranges.get(&i.to_string()).is_none() { - if let Some(fr) = get_function_range(&elf, i) { - isr_fn_ranges.insert(i.to_string(), fr); - } - } - } - - let mut groups = HashMap::new(); - - groups.insert("API_FN", api_fn_ranges); - groups.insert("APP_FN", app_fn_ranges); - groups.insert("ISR_FN", isr_fn_ranges); -} +// Group functions into api, app and isr functions pub fn get_range_groups( elf: &EasyElf, _addrs: &HashMap<&'static str, GuestAddr>, diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs index 732e3b2179..d21a0ccc17 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs @@ -453,10 +453,11 @@ pub struct FreeRTOSTraceMetadata trace_length: usize, indices: Vec, // Hashed enumeration of States tcref: isize, + need_to_debug: bool, } impl FreeRTOSTraceMetadata { - pub fn new(trace: Vec<::State>, intervals: Vec, mem_reads: Vec>, jobs: Vec) -> Self { + pub fn new(trace: Vec<::State>, intervals: Vec, mem_reads: Vec>, jobs: Vec, need_to_debug: bool) -> Self { let hashes : Vec<_> = trace .iter() .map(|x| compute_hash(&x) as usize) @@ -470,6 +471,7 @@ impl FreeRTOSTraceMetadata jobs: jobs, indices: hashes, tcref: 0, + need_to_debug: need_to_debug, } } } @@ -512,6 +514,10 @@ impl SystemTraceData for FreeRTOSTraceMetadata fn states_map(&self) -> &HashMap { &self.trace_map } + + fn need_to_debug(&self) -> bool { + self.need_to_debug + } } libafl_bolts::impl_serdeany!(FreeRTOSTraceMetadata); diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs index 27180610fd..e194c3db3f 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs @@ -13,8 +13,6 @@ use libafl_qemu::{ EmulatorModules, GuestAddr, Hook, MemAccessInfo, }; -use libafl_bolts::tuples::Map; - use crate::{fuzzer::MAX_INPUT_SIZE, systemstate::{ helpers::{get_icount, in_any_range, read_rec_return_stackframe}, target_os::{freertos::FreeRTOSStruct::*, *}, @@ -159,6 +157,7 @@ where OT: ObserversTuple, ET: EmulatorModuleTuple, { + let mut need_to_debug = false; if unsafe { CURRENT_SYSTEMSTATE_VEC.len() } == 0 { eprintln!("No system states captured, aborting"); return; @@ -194,6 +193,7 @@ where refine_system_states(unsafe { CURRENT_SYSTEMSTATE_VEC.split_off(0) }); let (intervals, mem_reads, dumped_states, success) = states2intervals(refined_states.clone(), metadata); + need_to_debug |= !success; #[cfg(not(feature = "trace_job_response_times"))] let jobs = Vec::new(); #[cfg(feature = "trace_job_response_times")] @@ -201,6 +201,7 @@ where let releases = get_releases(&intervals, &dumped_states); let responses = unsafe { JOBS_DONE.split_off(0) }; let (job_spans, do_report) = get_release_response_pairs(&releases, &responses); + need_to_debug |= do_report; let jobs : Vec = job_spans .into_iter() @@ -252,7 +253,7 @@ where .collect::>(); jobs }; - _state.add_metadata(FreeRTOSTraceMetadata::new(refined_states, intervals, mem_reads, jobs)); + _state.add_metadata(FreeRTOSTraceMetadata::new(refined_states, intervals, mem_reads, jobs, need_to_debug)); } type ModuleAddressFilter = NopAddressFilter; diff --git a/fuzzers/FRET/src/systemstate/target_os/mod.rs b/fuzzers/FRET/src/systemstate/target_os/mod.rs index a4a325c275..82958ac0f9 100644 --- a/fuzzers/FRET/src/systemstate/target_os/mod.rs +++ b/fuzzers/FRET/src/systemstate/target_os/mod.rs @@ -3,9 +3,7 @@ use std::fmt; use hashbrown::HashSet; use libafl_bolts::prelude::SerdeAny; use libafl_bolts::HasRefCnt; -use libafl_qemu::GuestAddr; use libafl_qemu::Qemu; -use serde::de::DeserializeOwned; use std::hash::Hasher; use std::hash::Hash; use hashbrown::HashMap; @@ -13,15 +11,12 @@ use serde::{Deserialize, Serialize}; use itertools::Itertools; use std::fmt::Debug; -use super::CaptureEvent; +use super::helpers::abb_profile; use super::ExecInterval; use super::RTOSJob; pub mod freertos; -// Constants -const NUM_PRIOS: usize = 15; - //============================= Trait definitions pub trait TargetSystem: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny { @@ -67,9 +62,51 @@ pub trait SystemTraceData: Serialize + Sized + for<'a> Deserialize<'a> + Default }) } #[inline] - fn worst_jobs_per_task_by_time(&self) -> HashMap { + fn worst_jobs_per_task_by_exec_time(&self) -> HashMap { self.worst_jobs_per_task_by(&|old, x| x.exec_ticks > old.exec_ticks) } + #[inline] + fn worst_jobs_per_task_by_response_time(&self) -> HashMap { + self.worst_jobs_per_task_by(&|old, x| x.response_time() > old.response_time()) + } + #[inline] + /// Gives the response time of the worst job of the selected task, or 0 if the task is not found + fn wort_of_task(&self, select_task: &String) -> u64 { + self.worst_jobs_per_task_by_response_time().get(select_task).map_or(0, |job| job.response_time()) + } + + #[inline] + /// extract computation time spent in each task and abb + /// task_name -> (abb_addr -> (interval_count, exec_count, exec_time)) + fn select_abb_profile( + &self, + select_task: Option, + ) -> HashMap> { + if let Some(select_task) = select_task.as_ref() { + // Task selected, only profile this task + if let Some(worst_instance) = self + .worst_jobs_per_task_by_response_time() + .get(select_task) + { + let t: Vec<_> = self + .intervals() + .iter() + .filter(|x| { + x.start_tick < worst_instance.response && x.end_tick > worst_instance.release + }) + .cloned() + .collect(); + abb_profile(t) + } else { + HashMap::new() + } + } else { + // Profile all tasks + abb_profile(self.intervals().clone()) + } + } + + fn need_to_debug(&self) -> bool; } @@ -92,7 +129,7 @@ macro_rules! impl_emu_lookup { fn lookup(emu: &Qemu, addr: ::std::os::raw::c_uint) -> $struct_name { let mut tmp : [u8; std::mem::size_of::<$struct_name>()] = [0u8; std::mem::size_of::<$struct_name>()]; unsafe { - emu.read_mem(addr.into(), &mut tmp); + emu.read_mem(addr.into(), &mut tmp).unwrap(); std::mem::transmute::<[u8; std::mem::size_of::<$struct_name>()], $struct_name>(tmp) } } diff --git a/fuzzers/FRET/src/time/clock.rs b/fuzzers/FRET/src/time/clock.rs index a70e1fc4c9..32f068bdcc 100644 --- a/fuzzers/FRET/src/time/clock.rs +++ b/fuzzers/FRET/src/time/clock.rs @@ -1,52 +1,51 @@ -use hashbrown::HashMap; -use libafl_bolts::Named; use libafl::{ - executors::ExitKind, - observers::Observer, - Error, - common::HasNamedMetadata, - observers::ObserversTuple, prelude::UsesInput, + common::HasNamedMetadata, executors::ExitKind, observers::Observer, observers::ObserversTuple, + prelude::UsesInput, Error, }; +use libafl_bolts::Named; use serde::{Deserialize, Serialize}; use std::{fs::OpenOptions, io::Write}; -use libafl::events::EventFirer; -use libafl::state::MaybeHasClientPerfMonitor; -use libafl::prelude::State; -use libafl::feedbacks::Feedback; -use libafl::SerdeAny; +use core::{fmt::Debug, time::Duration}; use libafl::common::HasMetadata; use libafl::corpus::testcase::Testcase; -use core::{fmt::Debug, time::Duration}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::path::PathBuf; +use libafl::events::EventFirer; +use libafl::feedbacks::Feedback; +use libafl::prelude::State; +use libafl::state::MaybeHasClientPerfMonitor; +use libafl_bolts::tuples::MatchNameRef; +use libafl::SerdeAny; use std::borrow::Cow; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; -use crate::systemstate::observers::QemuSystemStateObserver; +use crate::systemstate::helpers::metadata_insert_or_update_get; use crate::systemstate::target_os::TargetSystem; +use crate::systemstate::target_os::SystemTraceData; -pub static mut FUZZ_START_TIMESTAMP : SystemTime = UNIX_EPOCH; +pub static mut FUZZ_START_TIMESTAMP: SystemTime = UNIX_EPOCH; -pub const QEMU_ICOUNT_SHIFT : u32 = 5; -pub const QEMU_ISNS_PER_SEC : u32 = u32::pow(10, 9) / u32::pow(2, QEMU_ICOUNT_SHIFT); -pub const QEMU_ISNS_PER_USEC : u32 = QEMU_ISNS_PER_SEC / 1000000; -pub const _QEMU_NS_PER_ISN : u32 = 1 << QEMU_ICOUNT_SHIFT; -pub const _TARGET_SYSCLK_FREQ : u32 = 25 * 1000 * 1000; -pub const _TARGET_MHZ_PER_MIPS : f32 = _TARGET_SYSCLK_FREQ as f32 / QEMU_ISNS_PER_SEC as f32; -pub const _TARGET_MIPS_PER_MHZ : f32 = QEMU_ISNS_PER_SEC as f32 / _TARGET_SYSCLK_FREQ as f32; -pub const _TARGET_SYSCLK_PER_QEMU_SEC : u32 = (_TARGET_SYSCLK_FREQ as f32 * _TARGET_MIPS_PER_MHZ) as u32; -pub const _QEMU_SYSCLK_PER_TARGET_SEC : u32 = (_TARGET_SYSCLK_FREQ as f32 * _TARGET_MHZ_PER_MIPS) as u32; +pub const QEMU_ICOUNT_SHIFT: u32 = 5; +pub const QEMU_ISNS_PER_SEC: u32 = u32::pow(10, 9) / u32::pow(2, QEMU_ICOUNT_SHIFT); +pub const QEMU_ISNS_PER_USEC: u32 = QEMU_ISNS_PER_SEC / 1000000; +pub const _QEMU_NS_PER_ISN: u32 = 1 << QEMU_ICOUNT_SHIFT; +pub const _TARGET_SYSCLK_FREQ: u32 = 25 * 1000 * 1000; +pub const _TARGET_MHZ_PER_MIPS: f32 = _TARGET_SYSCLK_FREQ as f32 / QEMU_ISNS_PER_SEC as f32; +pub const _TARGET_MIPS_PER_MHZ: f32 = QEMU_ISNS_PER_SEC as f32 / _TARGET_SYSCLK_FREQ as f32; +pub const _TARGET_SYSCLK_PER_QEMU_SEC: u32 = + (_TARGET_SYSCLK_FREQ as f32 * _TARGET_MIPS_PER_MHZ) as u32; +pub const _QEMU_SYSCLK_PER_TARGET_SEC: u32 = + (_TARGET_SYSCLK_FREQ as f32 * _TARGET_MHZ_PER_MIPS) as u32; pub fn tick_to_time(ticks: u64) -> Duration { Duration::from_nanos((ticks << QEMU_ICOUNT_SHIFT) as u64) } pub fn tick_to_ms(ticks: u64) -> f32 { - (Duration::from_nanos(ticks << QEMU_ICOUNT_SHIFT).as_micros() as f32/10.0).round()/100.0 + (Duration::from_nanos(ticks << QEMU_ICOUNT_SHIFT).as_micros() as f32 / 10.0).round() / 100.0 } use libafl::prelude::StateInitializer; - //========== Metadata #[derive(Debug, SerdeAny, Serialize, Deserialize)] pub struct QemuIcountMetadata { @@ -68,16 +67,14 @@ pub struct MaxIcountMetadata { // } // } -impl Named for MaxIcountMetadata -{ +impl Named for MaxIcountMetadata { #[inline] fn name(&self) -> &Cow<'static, str> { &self.name } } -impl MaxIcountMetadata -{ +impl MaxIcountMetadata { /// Create new `MaxIcountMetadata` #[must_use] pub fn new(name: &'static str) -> Self { @@ -95,8 +92,8 @@ impl Default for MaxIcountMetadata { } /// A piece of metadata tracking all icounts -#[derive(Debug, SerdeAny, Serialize, Deserialize)] -pub struct IcHist (pub Vec<(u64, u128)>, pub (u64,u128)); +#[derive(Debug, Default, SerdeAny, Serialize, Deserialize)] +pub struct IcHist(pub Vec<(u64, u128)>, pub (u64, u128)); //========== Observer @@ -106,18 +103,16 @@ pub struct QemuClockObserver { name: Cow<'static, str>, start_tick: u64, end_tick: u64, - dump_path: Option } impl QemuClockObserver { /// Creates a new [`QemuClockObserver`] with the given name. #[must_use] - pub fn new(name: &'static str, dump_path: Option) -> Self { + pub fn new(name: &'static str) -> Self { Self { name: Cow::from(name), start_tick: 0, end_tick: 0, - dump_path } } @@ -133,59 +128,28 @@ where S: UsesInput + HasMetadata, { fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { + self.start_tick = 0; // Only remember the pre-run ticks if presistent mode ist used #[cfg(not(feature = "snapshot_restore"))] unsafe { - self.start_tick=emu::icount_get_raw(); - self.end_tick=self.start_tick; + self.start_tick = emu::icount_get_raw(); + self.end_tick = self.start_tick; } - // unsafe { - // println!("clock pre {}",emu::icount_get_raw()); - // } Ok(()) } - fn post_exec(&mut self, _state: &mut S, _input: &I, _exit_kind: &ExitKind) -> Result<(), Error> { - unsafe { self.end_tick = libafl_qemu::sys::icount_get_raw() }; - if let Some(td) = &self.dump_path { - // println!("clock post {}", self.end_tick); - // println!("Number of Ticks: {} <- {} {}",self.end_tick - self.start_tick, self.end_tick, self.start_tick); - let metadata =_state.metadata_map_mut(); - let hist = metadata.get_mut::(); - let timestamp = SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis(); - match hist { - Option::None => { - #[cfg(not(feature="trace_job_response_times"))] - { - metadata.insert(IcHist(vec![(self.end_tick - self.start_tick, timestamp)], - (self.end_tick - self.start_tick, timestamp))); - } - #[cfg(feature="trace_job_response_times")] - metadata.insert(IcHist(vec![],(0,timestamp))); - } - Some(v) => { - #[cfg(not(feature="trace_job_response_times"))] - { - v.0.push((self.end_tick - self.start_tick, timestamp)); - if v.1.0 < self.end_tick-self.start_tick { - v.1 = (self.end_tick - self.start_tick, timestamp); - } - } - if v.0.len() >= 100 { - let mut file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .append(true) - .open(td).expect("Could not open timedump"); - let newv : Vec<(u64, u128)> = Vec::with_capacity(110); - for i in std::mem::replace(&mut v.0, newv).into_iter() { - writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed"); - } - } - } - } + fn post_exec( + &mut self, + _state: &mut S, + _input: &I, + _exit_kind: &ExitKind, + ) -> Result<(), Error> { + if _exit_kind != &ExitKind::Ok { + self.start_tick = 0; + self.end_tick = 0; + return Ok(()); } + unsafe { self.end_tick = libafl_qemu::sys::icount_get_raw() }; Ok(()) } } @@ -203,7 +167,6 @@ impl Default for QemuClockObserver { name: Cow::from(String::from("clock")), start_tick: 0, end_tick: 0, - dump_path: None } } } @@ -211,12 +174,12 @@ impl Default for QemuClockObserver { //========== Feedback /// Nop feedback that annotates execution time in the new testcase, if any /// for this Feedback, the testcase is never interesting (use with an OR). -/// It decides, if the given [`QemuClockObserver`] value of a run is interesting. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ClockTimeFeedback { exec_time: Option, select_task: Option, name: Cow<'static, str>, + dump_path: Option, phantom: std::marker::PhantomData, } @@ -233,25 +196,71 @@ where #[allow(clippy::wrong_self_convention)] fn is_interesting( &mut self, - _state: &mut S, + state: &mut S, _manager: &mut EM, _input: &I, observers: &OT, _exit_kind: &ExitKind, ) -> Result - where - { - #[cfg(feature="trace_job_response_times")] - { - if self.select_task.is_some() { - let observer = observers.match_name::>("systemstate").unwrap(); - self.exec_time = Some(Duration::from_nanos(observer.last_runtime())); - return Ok(false) +where { + #[cfg(feature = "trace_job_response_times")] + let icount = { + if let Some(select) = self.select_task.as_ref() { + let trace = state + .metadata::() + .expect("TraceData not found"); + trace.wort_of_task(select) + } else { + let observer = observers + .match_name::(self.name()) + .unwrap(); + observer.last_runtime() + } + }; + #[cfg(not(feature = "trace_job_response_times"))] + let icount = { + let observer = observers + .match_name::(self.name()) + .unwrap(); + observer.last_runtime() + }; + self.exec_time = Some(Duration::from_nanos(icount * _QEMU_NS_PER_ISN as u64)); + + // Dump the icounts to a file + if let Some(td) = &self.dump_path { + let metadata = state.metadata_map_mut(); + let timestamp = SystemTime::now() + .duration_since(unsafe { FUZZ_START_TIMESTAMP }) + .unwrap() + .as_millis(); + let hist = metadata_insert_or_update_get::( + metadata, + || IcHist( + vec![(icount, timestamp)], + (icount, timestamp), + ), + |hist| { + hist.0.push((icount, timestamp)); + if hist.1 .0 < icount { + hist.1 = (icount, timestamp); + } + }, + ); + + if hist.0.len() >= 100 { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .append(true) + .open(td) + .expect("Could not open timedump"); + let newv: Vec<(u64, u128)> = Vec::with_capacity(110); + for i in std::mem::replace(&mut hist.0, newv).into_iter() { + writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed"); + } } } - // TODO Replace with match_name_type when stable - let observer = observers.match_name::(self.name()).unwrap(); - self.exec_time = Some(Duration::from_nanos(observer.last_runtime())); Ok(false) } @@ -287,22 +296,24 @@ impl Named for ClockTimeFeedback { impl ClockTimeFeedback { /// Creates a new [`ClockFeedback`], deciding if the value of a [`QemuClockObserver`] with the given `name` of a run is interesting. #[must_use] - pub fn new(name: &'static str, select_task: Option) -> Self { + pub fn new(name: &'static str, select_task: Option, dump_path: Option) -> Self { Self { exec_time: None, select_task: select_task, name: Cow::from(name.to_string()), + dump_path: dump_path, phantom: std::marker::PhantomData, } } /// Creates a new [`ClockFeedback`], deciding if the given [`QemuClockObserver`] value of a run is interesting. #[must_use] - pub fn new_with_observer(observer: &QemuClockObserver, select_task: &Option) -> Self { + pub fn new_with_observer(observer: &QemuClockObserver, select_task: &Option, dump_path: Option) -> Self { Self { exec_time: None, select_task: select_task.clone(), name: observer.name().clone(), + dump_path: dump_path, phantom: std::marker::PhantomData, } } @@ -330,9 +341,9 @@ where _observers: &OT, _exit_kind: &ExitKind, ) -> Result - where - { - let observer = _observers.match_name::("clock") +where { + let observer = _observers + .match_name::("clock") .expect("QemuClockObserver not found"); let clock_state = state .named_metadata_map_mut() @@ -348,7 +359,13 @@ where /// Append to the testcase the generated metadata in case of a new corpus item #[inline] - fn append_metadata(&mut self, _state: &mut S, _manager: &mut EM, _observers: &OT, _testcase: &mut Testcase) -> Result<(), Error> { + fn append_metadata( + &mut self, + _state: &mut S, + _manager: &mut EM, + _observers: &OT, + _testcase: &mut Testcase, + ) -> Result<(), Error> { // testcase.metadata_mut().insert(QemuIcountMetadata{runtime: self.last_runtime}); Ok(()) } @@ -358,7 +375,6 @@ where fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { Ok(()) } - } impl Named for QemuClockIncreaseFeedback { @@ -372,7 +388,9 @@ impl QemuClockIncreaseFeedback { /// Creates a new [`HitFeedback`] #[must_use] pub fn new(name: &'static str) -> Self { - Self {name: Cow::from(String::from(name))} + Self { + name: Cow::from(String::from(name)), + } } } @@ -380,4 +398,4 @@ impl Default for QemuClockIncreaseFeedback { fn default() -> Self { Self::new("MaxClock") } -} \ No newline at end of file +} diff --git a/fuzzers/FRET/src/time/worst.rs b/fuzzers/FRET/src/time/worst.rs index 2e4744754b..ed77ab17b1 100644 --- a/fuzzers/FRET/src/time/worst.rs +++ b/fuzzers/FRET/src/time/worst.rs @@ -1,47 +1,28 @@ -use core::fmt::Debug; -use core::cmp::Ordering::{Greater,Less,Equal}; -use libafl::inputs::BytesInput; -use libafl::inputs::HasTargetBytes; -use libafl::feedbacks::MapIndexesMetadata; -use libafl::corpus::Testcase; -use libafl::prelude::{ClientStats, Monitor, SimplePrintingMonitor, UsesInput}; -use core::marker::PhantomData; -use libafl::schedulers::{MinimizerScheduler, ProbabilitySamplingScheduler, TestcaseScore}; -use std::path::PathBuf; -use std::fs; -use hashbrown::{HashMap}; -use libafl::observers::ObserversTuple; -use libafl::executors::ExitKind; -use libafl::events::EventFirer; -use libafl::state::{MaybeHasClientPerfMonitor, HasCorpus, UsesState}; -use libafl::prelude::State; -use libafl::inputs::Input; -use libafl::feedbacks::Feedback; -use libafl::common::HasMetadata; -use libafl_qemu::modules::edges::EdgeCoverageModule; -use libafl::observers::MapObserver; -use serde::{Deserialize, Serialize}; -use std::cmp; -use std::time::Duration; -use std::time::Instant; -use std::ops::Sub; +use core::{fmt::Debug, marker::PhantomData}; -use libafl::corpus::Corpus; - -use libafl_bolts::{ - AsSlice, ClientId, HasLen, Named +use std::{ + borrow::Cow, ops::Sub, time::{Duration, Instant} }; + +use serde::{Serialize, Deserialize}; + use libafl::{ - observers::Observer, + common::HasMetadata, + corpus::{Corpus, Testcase}, + events::EventFirer, + executors::ExitKind, + feedbacks::{Feedback, MapIndexesMetadata}, + observers::ObserversTuple, + prelude::{ClientStats, Monitor, SimplePrintingMonitor, State, StateInitializer, UsesInput}, + schedulers::{MinimizerScheduler, ProbabilitySamplingScheduler, TestcaseScore}, + state::{HasCorpus, MaybeHasClientPerfMonitor, UsesState}, Error, }; +use libafl_bolts::{ClientId, HasLen, Named}; use crate::systemstate::target_os::TargetSystem; use crate::time::clock::QemuClockObserver; -use libafl::prelude::StateInitializer; - -use std::borrow::Cow; //=========================== Scheduler pub type TimeMaximizerCorpusScheduler = @@ -50,18 +31,21 @@ pub type TimeMaximizerCorpusScheduler = /// Multiply the testcase size with the execution time. /// This favors small and quick testcases. #[derive(Debug, Clone)] -pub struct MaxTimeFavFactor -{ -} +pub struct MaxTimeFavFactor {} impl TestcaseScore for MaxTimeFavFactor where S: HasCorpus, { - fn compute(state: &S, entry: &mut Testcase<::Input> ) -> Result { + fn compute( + _state: &S, + entry: &mut Testcase<::Input>, + ) -> Result { // TODO maybe enforce entry.exec_time().is_some() - let et = entry.exec_time().expect("testcase.exec_time is needed for scheduler"); - let tns : i64 = et.as_nanos().try_into().expect("failed to convert time"); + let et = entry + .exec_time() + .expect("testcase.exec_time is needed for scheduler"); + let tns: i64 = et.as_nanos().try_into().expect("failed to convert time"); Ok(-tns as f64) } } @@ -86,223 +70,30 @@ where impl TestcaseScore for MaxExecsLenFavFactor where S: HasCorpus + HasMetadata, - <::Corpus as libafl::corpus::Corpus>::Input: HasLen + <::Corpus as libafl::corpus::Corpus>::Input: HasLen, { - fn compute( state: &S, entry: &mut Testcase<::Input>) -> Result { - let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64()); - let execs_times_length_per_hour = execs_per_hour*entry.load_len(state.corpus()).unwrap() as f64; + fn compute( + state: &S, + entry: &mut Testcase<::Input>, + ) -> Result { + let execs_per_hour = (3600.0 + / entry + .exec_time() + .expect("testcase.exec_time is needed for scheduler") + .as_secs_f64()); + let execs_times_length_per_hour = + execs_per_hour * entry.load_len(state.corpus()).unwrap() as f64; Ok(execs_times_length_per_hour) } } //=================================================================== - -/// A Feedback reporting if the Input consists of strictly decreasing bytes. +/// A Feedback which rewards each increase in execution time #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct SortedFeedback { - name: Cow<'static, str> -} - -impl StateInitializer for SortedFeedback {} - -impl Feedback for SortedFeedback -where - S: State + UsesInput + MaybeHasClientPerfMonitor, - S::Input: HasTargetBytes, - EM: EventFirer, - OT: ObserversTuple, -{ - #[allow(clippy::wrong_self_convention)] - fn is_interesting( - &mut self, - _state: &mut S, - _manager: &mut EM, - _input: &S::Input, - _observers: &OT, - _exit_kind: &ExitKind, - ) -> Result - where - { - let t = _input.target_bytes(); - let tmp = t.as_slice(); - if tmp.len()<32 {return Ok(false);} - let tmp = Vec::::from(&tmp[0..32]); - // tmp.reverse(); - // if tmp.is_sorted_by(|a,b| match a.partial_cmp(b).unwrap_or(Less) { - // Less => Some(Greater), - // Equal => Some(Greater), - // Greater => Some(Less), - // }) {return Ok(true)}; - let mut is_sorted = true; - if tmp[0]=tmp[i]; - if !is_sorted {break;} - } - } - return Ok(is_sorted); - } -} - -impl Named for SortedFeedback { - #[inline] - fn name(&self) -> &Cow<'static, str> { - &self.name - } -} - -impl SortedFeedback { - /// Creates a new [`HitFeedback`] - #[must_use] - pub fn new() -> Self { - Self {name: Cow::from("Sorted".to_string()),} - } -} - -impl Default for SortedFeedback { - fn default() -> Self { - Self::new() - } -} - -//=================================================================== -/// A Feedback which expects a certain minimum execution time -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ExecTimeReachedFeedback -{ - name: Cow<'static, str>, - target_time: u64, -} - -impl StateInitializer for ExecTimeReachedFeedback {} - -impl Feedback for ExecTimeReachedFeedback -where - S: State + UsesInput + MaybeHasClientPerfMonitor, - EM: EventFirer, - OT: ObserversTuple, -{ - #[allow(clippy::wrong_self_convention)] - fn is_interesting( - &mut self, - _state: &mut S, - _manager: &mut EM, - _input: &I, - observers: &OT, - _exit_kind: &ExitKind, - ) -> Result - where - { - let observer = observers.match_name::("clock") - .expect("QemuClockObserver not found"); - Ok(observer.last_runtime() >= self.target_time) - } -} - -impl Named for ExecTimeReachedFeedback -{ - #[inline] - fn name(&self) -> &Cow<'static, str> { - &self.name - } -} - -impl ExecTimeReachedFeedback -where -{ - /// Creates a new [`ExecTimeReachedFeedback`] - #[must_use] - pub fn new(target_time : u64) -> Self { - Self {name: Cow::from("ExecTimeReachedFeedback".to_string()), target_time: target_time} - } -} - -pub static mut EXEC_TIME_COLLECTION : Vec = Vec::new(); - -/// A Noop Feedback which records a list of all execution times -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ExecTimeCollectorFeedback -{ - name: Cow<'static, str> -} - -impl StateInitializer for ExecTimeCollectorFeedback {} - -impl Feedback for ExecTimeCollectorFeedback -where - S: State + UsesInput + MaybeHasClientPerfMonitor, - EM: EventFirer, - OT: ObserversTuple, -{ - #[allow(clippy::wrong_self_convention)] - fn is_interesting( - &mut self, - _state: &mut S, - _manager: &mut EM, - _input: &I, - observers: &OT, - _exit_kind: &ExitKind, - ) -> Result - where - { - let observer = observers.match_name::("clock") - .expect("QemuClockObserver not found"); - unsafe { EXEC_TIME_COLLECTION.push(observer.last_runtime().try_into().unwrap()); } - Ok(false) - } -} - -impl Named for ExecTimeCollectorFeedback -{ - #[inline] - fn name(&self) -> &Cow<'static, str> { - &self.name - } -} - -impl ExecTimeCollectorFeedback -where -{ - /// Creates a new [`ExecTimeCollectorFeedback`] - #[must_use] - pub fn new() -> Self { - Self {name: Cow::from("ExecTimeCollectorFeedback".to_string())} - } -} - -/// Shared Metadata for a SysStateFeedback -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ExecTimeCollectorFeedbackState -{ - name: Cow<'static, str>, - collection: Vec, -} -impl Named for ExecTimeCollectorFeedbackState -{ - #[inline] - fn name(&self) -> &Cow<'static, str> { - &self.name - } -} -impl Default for ExecTimeCollectorFeedbackState { - fn default() -> Self { - Self {name: Cow::from("ExecTimeCollectorFeedbackState".to_string()), collection: Vec::new()} - } -} - -//=================================================================== -/// A Feedback which expects a certain minimum execution time -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ExecTimeIncFeedback -{ +pub struct ExecTimeIncFeedback { name: Cow<'static, str>, longest_time: u64, - last_is_longest: bool + last_is_longest: bool, } impl StateInitializer for ExecTimeIncFeedback {} @@ -322,9 +113,9 @@ where observers: &OT, _exit_kind: &ExitKind, ) -> Result - where - { - let observer = observers.match_name::("clocktime") +where { + let observer = observers + .match_name::("clocktime") .expect("QemuClockObserver not found"); if observer.last_runtime() > self.longest_time { self.longest_time = observer.last_runtime(); @@ -344,7 +135,7 @@ where ) -> Result<(), Error> { #[cfg(feature = "feed_afl")] if self.last_is_longest { - let mim : Option<&mut MapIndexesMetadata>= testcase.metadata_map_mut().get_mut(); + let mim: Option<&mut MapIndexesMetadata> = testcase.metadata_map_mut().get_mut(); // pretend that the longest input alone excercises some non-existing edge, to keep it relevant mim.unwrap().list.push(usize::MAX); }; @@ -352,35 +143,34 @@ where } } -impl Named for ExecTimeIncFeedback -{ +impl Named for ExecTimeIncFeedback { #[inline] fn name(&self) -> &Cow<'static, str> { &self.name } } -impl ExecTimeIncFeedback -where -{ +impl ExecTimeIncFeedback { /// Creates a new [`ExecTimeReachedFeedback`] #[must_use] pub fn new() -> Self { - Self {name: Cow::from("ExecTimeReachedFeedback".to_string()), longest_time: 0, last_is_longest: false} + Self { + name: Cow::from("ExecTimeReachedFeedback".to_string()), + longest_time: 0, + last_is_longest: false, + } } } /// A Noop Feedback which records a list of all execution times #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct AlwaysTrueFeedback -{ - name: Cow<'static, str> +pub struct AlwaysTrueFeedback { + name: Cow<'static, str>, } impl StateInitializer for AlwaysTrueFeedback {} impl Feedback for AlwaysTrueFeedback - where S: State + UsesInput + MaybeHasClientPerfMonitor, EM: EventFirer, @@ -395,37 +185,31 @@ where _observers: &OT, _exit_kind: &ExitKind, ) -> Result - where - { +where { Ok(true) } } -impl Named for AlwaysTrueFeedback -{ +impl Named for AlwaysTrueFeedback { #[inline] fn name(&self) -> &Cow<'static, str> { &self.name } } -impl AlwaysTrueFeedback -where -{ +impl AlwaysTrueFeedback { /// Creates a new [`ExecTimeCollectorFeedback`] #[must_use] pub fn new() -> Self { Self { - name: Cow::from("AlwaysTrueFeedback".to_string()) + name: Cow::from("AlwaysTrueFeedback".to_string()), } } } - //=========================== Probability Mass Scheduler -pub type TimeProbMassScheduler = - ProbabilitySamplingScheduler>; +pub type TimeProbMassScheduler = ProbabilitySamplingScheduler>; #[derive(Debug, Clone)] pub struct TimeProbFactor @@ -443,15 +227,19 @@ impl TestcaseScore for TimeProbFactor where S: HasCorpus, { - fn compute(_state: &S, entry: &mut Testcase<::Input>) -> Result { + fn compute( + _state: &S, + entry: &mut Testcase<::Input>, + ) -> Result { // TODO maybe enforce entry.exec_time().is_some() - let et = entry.exec_time().expect("testcase.exec_time is needed for scheduler"); - let tns : i64 = et.as_nanos().try_into().expect("failed to convert time"); - Ok(((tns as f64)/1000.0).powf(2.0)) //microseconds + let et = entry + .exec_time() + .expect("testcase.exec_time is needed for scheduler"); + let tns: i64 = et.as_nanos().try_into().expect("failed to convert time"); + Ok(((tns as f64) / 1000.0).powf(2.0)) //microseconds } } - /// Monitor that prints with a limited rate. #[derive(Debug, Clone)] pub struct RateLimitedMonitor { @@ -483,8 +271,10 @@ impl Monitor for RateLimitedMonitor { #[inline] fn display(&mut self, event_msg: &str, sender_id: ClientId) { let now = Instant::now(); - const RATE : Duration = Duration::from_secs(5); - if (event_msg!="Testcase" && event_msg!="UserStats") || now.duration_since(self.last) > RATE { + const RATE: Duration = Duration::from_secs(5); + if (event_msg != "Testcase" && event_msg != "UserStats") + || now.duration_since(self.last) > RATE + { self.inner.display(event_msg, sender_id); self.last = now; } @@ -506,4 +296,4 @@ impl Default for RateLimitedMonitor { fn default() -> Self { Self::new() } -} \ No newline at end of file +} diff --git a/fuzzers/FRET/tests/run_test.sh b/fuzzers/FRET/tests/run_test.sh index 11c5bf3a33..900c7b7abe 100644 --- a/fuzzers/FRET/tests/run_test.sh +++ b/fuzzers/FRET/tests/run_test.sh @@ -1,14 +1,18 @@ #!/bin/sh +TEST_KERNEL=../benchmark/build/waters_seq_full.elf +TEST_SYMBOLS=../benchmark/target_symbols.csv +DEF_ARGS="-k $TEST_KERNEL -c $TEST_SYMBOLS -n ./dump/test" + # cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_afl,observer_hitcounts # Test basic fuzzing loop -../target/debug/fret -k ../benchmark/build/waters.elf -c ../benchmark/target_symbols.csv -n ./dump/waters -tar fuzz -t 10 -s 123 +# ../target/debug/fret $DEF_ARGS -tar fuzz -t 10 -s 123 # Test reprodcibility -rm -f ./dump/demo.case.time -../target/debug/fret -k ../benchmark/build/waters.elf -c ../benchmark/target_symbols.csv -n ./dump/demo -tr showmap -i ./demo.case -if [[ $(cut -d, -f1 ./dump/demo.case.time) != $(cut -d, -f1 ./demo.example.time) ]]; then echo "Not reproducible!" && exit 1; else echo "Reproducible"; fi +rm -f ./dump/test.time +../target/debug/fret $DEF_ARGS -tr showmap -i ./waters.case.test +if [[ $(cut -d, -f1 ./dump/test.time) != $(cut -d, -f1 ./waters.time.test) ]]; then echo "Not reproducible!" && exit 1; else echo "Reproducible"; fi # Test state dump # cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_afl,observer_hitcounts,systemstate @@ -22,6 +26,6 @@ if [[ -n "$(diff -q demo.example.abb.ron dump/demo.trace.ron)" ]]; then echo "AB # ../target/debug/fret -k ../benchmark/build/minimal.elf -c ../benchmark/target_symbols.csv -n ./dump/minimal_worst -tr showmap -i ./dump/minimal.case # Test fuzzing using systemtraces -cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_systemtrace +cargo build --no-default-features --features std,snapshot_restore,singlecore,config_stg -../target/debug/fret -k ../benchmark/build/waters.elf -c ../benchmark/target_symbols.csv -n ./dump/waters -tar fuzz -t 10 -s 123 \ No newline at end of file +../target/debug/fret -k ../benchmark/build/waters_seq_full.elf -c ../benchmark/target_symbols.csv -n ./dump/waters -tar fuzz -t 10 -s 123