diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index dc7e4544a3..078d4081ad 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -16,7 +16,7 @@ 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, 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} + clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP, QEMU_ICOUNT_SHIFT, QEMU_ISNS_PER_MSEC, QEMU_ISNS_PER_USEC}, qemustate::QemuStateRestoreHelper, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, RateLimitedMonitor, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler} } }; use std::time::SystemTime; @@ -209,7 +209,7 @@ fn setup_interrupt_inputs(mut input : MultipartInput, interrupt_conf let name = format!("isr_{}_times",i); if input.parts_by_name(&name).next().is_none() { if let Some(random) = random.as_mut() { - input.add_part(name, BytesInput::new((0..MAX_NUM_INTERRUPT).map(|_| (random.next_u32()%(100*1000*QEMU_ISNS_PER_USEC)).to_le_bytes()).flatten().collect())); + input.add_part(name, BytesInput::new((0..MAX_NUM_INTERRUPT).map(|_| (random.next_u32()%(100*QEMU_ISNS_PER_MSEC)).to_le_bytes()).flatten().collect())); } else { input.add_part(name, BytesInput::new([0; MAX_NUM_INTERRUPT*4].to_vec())); } @@ -323,7 +323,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", &cli.select_task); // 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")] @@ -366,7 +366,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { // afl feedback needs to be activated first for MapIndexesMetadata feedback, // Feedback to reward any input which increses the execution time - ExecTimeIncFeedback::new() + ExecTimeIncFeedback::::new() ); #[cfg(all(feature = "observe_systemstate"))] let mut feedback = feedback_or!( diff --git a/fuzzers/FRET/src/systemstate/feedbacks.rs b/fuzzers/FRET/src/systemstate/feedbacks.rs index a86596bf2d..21a1c871b5 100644 --- a/fuzzers/FRET/src/systemstate/feedbacks.rs +++ b/fuzzers/FRET/src/systemstate/feedbacks.rs @@ -72,7 +72,7 @@ where { } } if let Some(wi) = worst_input { - wi.to_file(casename); + wi.to_file(casename).expect("Could not dump testcase"); } // Try dumping the current case diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs index 4cd1eddcae..d618e28ca0 100644 --- a/fuzzers/FRET/src/systemstate/helpers.rs +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -128,7 +128,7 @@ pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize, u32)) -> Vecnum+1 {interrupt_ticks[num+1]} else {u32::MAX}; for exec_interval in meta.intervals().iter().filter(|x| x.start_tick >= lower_bound as u64 && x.start_tick < next as u64) { if !(exec_interval.start_capture.0==CaptureEvent::ISRStart) { // shortcut to skip interrupt handers without node lookup @@ -302,10 +302,10 @@ where let mut ub : u32 = trace.intervals()[trace.intervals().len()-1].end_tick.try_into().expect("ticks > u32"); if i > 0 { // use the new times, because changes to preceding timings are not accounted for yet - lb = u32::saturating_add(new_interrupt_times[i-1], interrup_config.1 * QEMU_ISNS_PER_USEC); + lb = u32::saturating_add(new_interrupt_times[i-1], (interrup_config.1 as f32 * QEMU_ISNS_PER_USEC) as u32); } if i < old_interrupt_times.len()-1 { - ub = u32::saturating_sub(new_interrupt_times[i+1], interrup_config.1 * QEMU_ISNS_PER_USEC); + ub = u32::saturating_sub(new_interrupt_times[i+1], (interrup_config.1 as f32 * QEMU_ISNS_PER_USEC) as u32); } // get old hit and handler let old_hit = marks.iter().filter( diff --git a/fuzzers/FRET/src/systemstate/report.rs b/fuzzers/FRET/src/systemstate/report.rs index db5579bb38..849d746f4f 100644 --- a/fuzzers/FRET/src/systemstate/report.rs +++ b/fuzzers/FRET/src/systemstate/report.rs @@ -37,6 +37,8 @@ use libafl::HasFeedback; use libafl::ExecutesInput; use libafl::ExecutionProcessor; +use crate::time::clock::{tick_to_time, time_to_tick, IcHist}; + /// The [`AflStatsStage`] is a simple stage that computes and reports some stats. #[derive(Debug, Clone)] pub struct SchedulerStatsStage { @@ -103,6 +105,7 @@ where let cur = current_time(); if cur.checked_sub(self.last_report_time).unwrap_or_default() > self.stats_report_interval { + let wort = tick_to_time(state.metadata_map().get::().unwrap_or(&IcHist::default()).1.0); if let Some(meta) = state.metadata_map().get::() { let kc = meta.map.keys().count(); let mut v : Vec<_> = meta.map.values().cloned().collect(); @@ -146,6 +149,7 @@ where let cc = state.corpus().count(); let to_keep = usize::max(vc*MULTI, PRUNE_MIN_KEEP); let activate = cc > PRUNE_MAX_KEEP || cc > usize::max(vc*PRUNE_THRESHOLD, PRUNE_MIN_KEEP*2); + let mut wort_preserved = false; if activate { println!("Pruning corpus, keeping {} / {}", to_keep, cc); let corpus = state.corpus_mut(); @@ -153,10 +157,15 @@ where let ids : Vec<_> = corpus.ids().filter_map(|x| { let tc = corpus.get(x).unwrap().borrow(); let md = tc.metadata_map(); - if vc < PRUNE_MAX_KEEP && (md.get::().is_some() || &Some(x) == currid || v.contains(&&x)) { - None + if !wort_preserved && tc.exec_time() == &Some(wort) && wort>Duration::ZERO { + wort_preserved = true; // Keep the worst observed under all circumstances + Some((x, tc.exec_time().clone())) } else { - Some((x, tc.exec_time().clone())) + if vc < PRUNE_MAX_KEEP && (md.get::().is_some() || &Some(x) == currid || v.contains(&&x)) { + None + } else { + Some((x, tc.exec_time().clone())) + } } }).sorted_by_key(|x| x.1).take(usize::saturating_sub(corpus.count(),to_keep)).sorted_by_key(|x| x.0).unique().rev().collect(); for (cid, _) in ids { diff --git a/fuzzers/FRET/src/systemstate/stg.rs b/fuzzers/FRET/src/systemstate/stg.rs index 0096df6d3d..a188e075c7 100644 --- a/fuzzers/FRET/src/systemstate/stg.rs +++ b/fuzzers/FRET/src/systemstate/stg.rs @@ -167,6 +167,7 @@ where entrypoint: NodeIndex, exitpoint: NodeIndex, // Metadata about aggregated traces. aggegated meaning, order has been removed + wort: u64, wort_per_aggegated_path: HashMap,u64>, wort_per_abb_path: HashMap, wort_per_stg_path: HashMap, @@ -207,6 +208,7 @@ where stgnode_index: index, entrypoint, exitpoint, + wort: 0, wort_per_aggegated_path: HashMap::new(), wort_per_abb_path: HashMap::new(), wort_per_stg_path: HashMap::new(), @@ -598,8 +600,8 @@ where ::Input: Default, { // TODO: don't remove metadata. work around ownership issues - let trace = state.remove_metadata::().expect("TraceData not found"); - let clock_observer = observers.match_name::("clocktime") + let trace : SYS::TraceData = *state.remove_metadata::().expect("TraceData not found"); + let clock_observer = observers.match_name::>("clocktime") .expect("QemuClockObserver not found"); let last_runtime = clock_observer.last_runtime(); @@ -619,6 +621,11 @@ where // --------------------------------- Update STG let (mut nodetrace, mut edgetrace, mut interesting, mut updated) = StgFeedback::update_stg_interval(trace.intervals(), &trace.mem_reads(), trace.states_map(), feedbackstate); + // the longest running case is always intersting + if last_runtime > feedbackstate.wort { + feedbackstate.wort = last_runtime; + interesting |= true; + } #[cfg(feature = "trace_job_response_times")] if let Some(worst_instance) = worst_select_job { @@ -710,6 +717,9 @@ where _tmp.sort(); // use sort+count, because we need the sorted trace anyways let counts = count_occurrences_sorted(&_tmp); let mut top_indices = Vec::new(); + if last_runtime >= feedbackstate.wort { + top_indices.push(0xFFFF_FFFF_FFFF_FFFF); // pseudo trace to keep worts + } for (k,c) in counts { if let Some(reference) = feedbackstate.worst_abb_exec_count.get_mut(k) { if *reference < c { @@ -758,6 +768,8 @@ where writeln!(file, "{},{},{},{},{}", feedbackstate.graph.edge_count(), feedbackstate.graph.node_count(), feedbackstate.wort_per_aggegated_path.len(),feedbackstate.wort_per_stg_path.len(), timestamp).expect("Write to dump failed"); } } + // Re-add trace data + state.add_metadata(trace); Ok(interesting) } diff --git a/fuzzers/FRET/src/time/clock.rs b/fuzzers/FRET/src/time/clock.rs index 32f068bdcc..9ebc3be150 100644 --- a/fuzzers/FRET/src/time/clock.rs +++ b/fuzzers/FRET/src/time/clock.rs @@ -23,11 +23,14 @@ use crate::systemstate::helpers::metadata_insert_or_update_get; use crate::systemstate::target_os::TargetSystem; use crate::systemstate::target_os::SystemTraceData; +use libafl::prelude::StateInitializer; + 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_ISNS_PER_MSEC: u32 = QEMU_ISNS_PER_SEC / 1000; +pub const QEMU_ISNS_PER_USEC: f32 = QEMU_ISNS_PER_SEC as f32 / 1000000.0; 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; @@ -38,13 +41,16 @@ 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) + Duration::from_nanos((ticks * _QEMU_NS_PER_ISN 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 + (tick_to_time(ticks).as_micros() as f32 / 10.0).round() / 100.0 +} + +pub fn time_to_tick(time: Duration) -> u64 { + time.as_nanos() as u64 / _QEMU_NS_PER_ISN as u64 } -use libafl::prelude::StateInitializer; //========== Metadata #[derive(Debug, SerdeAny, Serialize, Deserialize)] @@ -99,20 +105,24 @@ pub struct IcHist(pub Vec<(u64, u128)>, pub (u64, u128)); /// A simple observer, just overlooking the runtime of the target. #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct QemuClockObserver { +pub struct QemuClockObserver { name: Cow<'static, str>, start_tick: u64, end_tick: u64, + select_task: Option, + phantom: std::marker::PhantomData, } -impl QemuClockObserver { +impl QemuClockObserver { /// Creates a new [`QemuClockObserver`] with the given name. #[must_use] - pub fn new(name: &'static str) -> Self { + pub fn new(name: &'static str, select_task: &Option) -> Self { Self { name: Cow::from(name), start_tick: 0, end_tick: 0, + select_task: select_task.clone(), + phantom: std::marker::PhantomData, } } @@ -123,9 +133,10 @@ impl QemuClockObserver { } } -impl Observer for QemuClockObserver +impl Observer for QemuClockObserver where S: UsesInput + HasMetadata, + SYS: TargetSystem, { fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { self.start_tick = 0; @@ -140,7 +151,7 @@ where fn post_exec( &mut self, - _state: &mut S, + state: &mut S, _input: &I, _exit_kind: &ExitKind, ) -> Result<(), Error> { @@ -149,24 +160,40 @@ where self.end_tick = 0; return Ok(()); } - unsafe { self.end_tick = libafl_qemu::sys::icount_get_raw() }; + #[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 { + unsafe {libafl_qemu::sys::icount_get_raw()} + } + }; + #[cfg(not(feature = "trace_job_response_times"))] + let icount = unsafe {libafl_qemu::sys::icount_get_raw()}; + + self.end_tick = icount; Ok(()) } } -impl Named for QemuClockObserver { +impl Named for QemuClockObserver { #[inline] fn name(&self) -> &Cow<'static, str> { &self.name } } -impl Default for QemuClockObserver { +impl Default for QemuClockObserver { fn default() -> Self { Self { name: Cow::from(String::from("clock")), start_tick: 0, end_tick: 0, + select_task: None, + phantom: std::marker::PhantomData, } } } @@ -212,7 +239,7 @@ where { trace.wort_of_task(select) } else { let observer = observers - .match_name::(self.name()) + .match_name::>(self.name()) .unwrap(); observer.last_runtime() } @@ -220,11 +247,11 @@ where { #[cfg(not(feature = "trace_job_response_times"))] let icount = { let observer = observers - .match_name::(self.name()) + .match_name::>(self.name()) .unwrap(); observer.last_runtime() }; - self.exec_time = Some(Duration::from_nanos(icount * _QEMU_NS_PER_ISN as u64)); + self.exec_time = Some(tick_to_time(icount)); // Dump the icounts to a file if let Some(td) = &self.dump_path { @@ -293,7 +320,7 @@ impl Named for ClockTimeFeedback { } } -impl 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, dump_path: Option) -> Self { @@ -308,7 +335,7 @@ impl ClockTimeFeedback { /// 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, dump_path: 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(), @@ -321,13 +348,14 @@ impl ClockTimeFeedback { /// A [`Feedback`] rewarding increasing the execution cycles on Qemu. #[derive(Debug)] -pub struct QemuClockIncreaseFeedback { +pub struct QemuClockIncreaseFeedback { name: Cow<'static, str>, + phantom: std::marker::PhantomData, } -impl StateInitializer for QemuClockIncreaseFeedback {} +impl StateInitializer for QemuClockIncreaseFeedback {} -impl Feedback for QemuClockIncreaseFeedback +impl Feedback for QemuClockIncreaseFeedback where S: State + UsesInput + HasNamedMetadata + MaybeHasClientPerfMonitor + Debug, EM: EventFirer, @@ -343,7 +371,7 @@ where ) -> Result where { let observer = _observers - .match_name::("clock") + .match_name::>("clock") .expect("QemuClockObserver not found"); let clock_state = state .named_metadata_map_mut() @@ -377,24 +405,25 @@ where { } } -impl Named for QemuClockIncreaseFeedback { +impl Named for QemuClockIncreaseFeedback { #[inline] fn name(&self) -> &Cow<'static, str> { &self.name } } -impl QemuClockIncreaseFeedback { +impl QemuClockIncreaseFeedback { /// Creates a new [`HitFeedback`] #[must_use] pub fn new(name: &'static str) -> Self { Self { name: Cow::from(String::from(name)), + phantom: std::marker::PhantomData, } } } -impl Default for QemuClockIncreaseFeedback { +impl Default for QemuClockIncreaseFeedback { fn default() -> Self { Self::new("MaxClock") } diff --git a/fuzzers/FRET/src/time/worst.rs b/fuzzers/FRET/src/time/worst.rs index ed77ab17b1..853698ca86 100644 --- a/fuzzers/FRET/src/time/worst.rs +++ b/fuzzers/FRET/src/time/worst.rs @@ -90,15 +90,16 @@ where //=================================================================== /// A Feedback which rewards each increase in execution time #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ExecTimeIncFeedback { +pub struct ExecTimeIncFeedback { name: Cow<'static, str>, longest_time: u64, last_is_longest: bool, + phantom: PhantomData, } -impl StateInitializer for ExecTimeIncFeedback {} +impl StateInitializer for ExecTimeIncFeedback {} -impl Feedback for ExecTimeIncFeedback +impl Feedback for ExecTimeIncFeedback where S: State + UsesInput + MaybeHasClientPerfMonitor, EM: EventFirer, @@ -115,7 +116,7 @@ where ) -> Result where { let observer = observers - .match_name::("clocktime") + .match_name::>("clocktime") .expect("QemuClockObserver not found"); if observer.last_runtime() > self.longest_time { self.longest_time = observer.last_runtime(); @@ -143,14 +144,14 @@ where { } } -impl Named for ExecTimeIncFeedback { +impl Named for ExecTimeIncFeedback { #[inline] fn name(&self) -> &Cow<'static, str> { &self.name } } -impl ExecTimeIncFeedback { +impl ExecTimeIncFeedback { /// Creates a new [`ExecTimeReachedFeedback`] #[must_use] pub fn new() -> Self { @@ -158,6 +159,7 @@ impl ExecTimeIncFeedback { name: Cow::from("ExecTimeReachedFeedback".to_string()), longest_time: 0, last_is_longest: false, + phantom: PhantomData, } } } diff --git a/fuzzers/FRET/tests/times.py b/fuzzers/FRET/tests/times.py new file mode 100755 index 0000000000..bc0d94e9c5 --- /dev/null +++ b/fuzzers/FRET/tests/times.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import sys + +if len(sys.argv) < 2: + print("Usage: time.py ") + sys.exit(1) + +try: + number = float(sys.argv[1]) +except ValueError: + print("The first argument must be a number.") + sys.exit(1) + +QEMU_SHIFT=5 +ISNS_PER_US=10**3 / (2**QEMU_SHIFT) +print("Time span") +print("ISNS -> µs", f"{number / ISNS_PER_US:.2f} us") +print("µs -> ISNS", f"{number * ISNS_PER_US:.2f}") + +int_offset=53430 +print("Interrupt offset") +print("ISNS -> µs", f"{((number + int_offset) / ISNS_PER_US):.2f} us") +print("µs -> ISNS", f"{((number * ISNS_PER_US)-int_offset):.2f}")