diff --git a/fuzzers/FRET/Cargo.toml b/fuzzers/FRET/Cargo.toml index d4dfaf9e9c..22dcd7231d 100644 --- a/fuzzers/FRET/Cargo.toml +++ b/fuzzers/FRET/Cargo.toml @@ -22,6 +22,10 @@ do_hash_notify_state = [] trace_stg = [ "observe_systemstate" ] # feedbacks feed_stg = [ "trace_stg", "observe_systemstate" ] +# feed_stg_edge = [ "feed_stg"] +feed_stg_pathhash = [ "feed_stg"] +feed_stg_abbhash = [ "feed_stg"] +feed_stg_aggregatehash = [ "feed_stg"] feed_longest = [ ] feed_afl = [ "observe_edges" ] feed_genetic = [] @@ -32,6 +36,10 @@ gensize_100 = [ ] sched_genetic = [] sched_afl = [] sched_stg = [] +# sched_stg_edge = ['sched_stg'] # every edge in the stg +sched_stg_pathhash = ['sched_stg'] # every path in the stg +sched_stg_abbhash = ['sched_stg'] # every path of abbs +sched_stg_aggregatehash = ['sched_stg'] # every aggregated path (order independent) # overall_configs config_genetic = ["gensize_100","feed_genetic","sched_genetic"] config_afl = ["feed_afl","sched_afl","observe_hitcounts"] diff --git a/fuzzers/FRET/benchmark/Snakefile b/fuzzers/FRET/benchmark/Snakefile index e372779d70..c08d0892ac 100644 --- a/fuzzers/FRET/benchmark/Snakefile +++ b/fuzzers/FRET/benchmark/Snakefile @@ -2,7 +2,7 @@ import csv import os def_flags="--no-default-features --features std,snapshot_restore,singlecore,restarting,do_hash_notify_state" remote="timedump_253048_1873f6_all/" -RUNTIME=7600 +RUNTIME=1800 TARGET_REPS_A=2 TARGET_REPS_B=2 NUM_NODES=2 @@ -243,6 +243,47 @@ rule all_new: input: expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['random', 'feedgeneration100', 'frafl', 'stg'], target=['waters', 'watersv2', 'interact'],num=range(0,2)), expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['random_int', 'feedgeneration100_int', 'frafl_int', 'stg_int'], target=['waters_int', 'watersv2_int', 'interact_int'],num=range(0,3)) + +rule build_stgpath: + output: + directory("bins/target_stgpath_int") + shell: + "cargo build --target-dir {output} {def_flags},config_stg,sched_stg_pathhash,feed_stg_pathhash" + +rule build_stgabb: + output: + directory("bins/target_stgabb_int") + shell: + "cargo build --target-dir {output} {def_flags},config_stg,sched_stg_abbhash,feed_stg_abbhash" + +rule build_stgaggregate: + output: + directory("bins/target_stgaggregate_int") + shell: + "cargo build --target-dir {output} {def_flags},config_stg,sched_stg_aggregatehash,feed_stg_aggregatehash" + +rule build_stgpath_int: + output: + directory("bins/target_stgpath_int") + shell: + "cargo build --target-dir {output} {def_flags},config_stg,fuzz_int,sched_stg_pathhash,feed_stg_pathhash" + +rule build_stgabb_int: + output: + directory("bins/target_stgabb_int") + shell: + "cargo build --target-dir {output} {def_flags},config_stg,fuzz_int,sched_stg_abbhash,feed_stg_abbhash" + +rule build_stgaggregate_int: + output: + directory("bins/target_stgaggregate_int") + shell: + "cargo build --target-dir {output} {def_flags},config_stg,fuzz_int,sched_stg_aggregatehash,feed_stg_aggregatehash" + +rule custom_test: + input: + expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['stgpath', 'stgabb', 'stgaggregate'], target=['waters','watersv2','interact'],num=range(0,2)), + expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['stgpath_int', 'stgabb_int', 'stgaggregate_int'], target=['waters_int','watersv2_int','interact_int'],num=range(0,2)), rule all_bins: diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 573bd1cd3c..8df3600c8c 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -14,7 +14,7 @@ use libafl_qemu::{ }; use rand::{SeedableRng, StdRng, Rng}; use crate::{ - clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback}, stg::{GraphMaximizerCorpusScheduler}, helpers::{QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeStateMaximizerCorpusScheduler} + clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME, input_bytes_to_interrupt_times}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback}, stg::{GraphMaximizerCorpusScheduler}, helpers::{QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeStateMaximizerCorpusScheduler} }; use std::time::{SystemTime, UNIX_EPOCH}; use clap::{Parser, Subcommand}; @@ -29,7 +29,7 @@ use crate::systemstate::stg::STGFeedbackState; pub static mut RNG_SEED: u64 = 1; -pub static mut LIMIT : u32 = u32::MAX>>1; +pub static mut LIMIT : u32 = u32::MAX; pub const FIRST_INT : u32 = 500000; pub const MAX_NUM_INTERRUPT: usize = 32; @@ -439,22 +439,12 @@ pub fn fuzz() { unsafe { #[cfg(feature = "fuzz_int")] { - let mut start_tick : u32 = 0; - for i in 0..DO_NUM_INTERRUPT { - let mut t : [u8; 4] = [0,0,0,0]; - if len > (i+1)*4 { - for j in 0 as usize..4 as usize { - t[j]=buf[i*4+j]; - } - if i == 0 || true { - unsafe {start_tick = u32::from_le_bytes(t) % LIMIT + FIRST_INT;} - } else { - start_tick = u32::saturating_add(start_tick,max(unsafe{MINIMUM_INTER_ARRIVAL_TIME},u32::from_le_bytes(t))); - } - libafl_interrupt_offsets[i] = start_tick; - libafl_num_interrupts = i+1; - } + let times = input_bytes_to_interrupt_times(buf); + for (i,t) in times.iter().enumerate() { + libafl_interrupt_offsets[i] = *t; } + libafl_num_interrupts = times.len(); + if buf.len() > libafl_num_interrupts*4 { buf = &buf[libafl_num_interrupts*4..]; len = buf.len(); @@ -500,7 +490,7 @@ pub fn fuzz() { #[cfg(feature = "observer_hitcounts")] let edges_observer = HitcountsMapObserver::new(edges_observer); - #[cfg(feature = "feed_stg")] + #[cfg(feature = "observe_systemstate")] let stg_coverage_observer = unsafe { VariableMapObserver::from_mut_slice( "stg", stg_map_mut_slice(), @@ -542,9 +532,9 @@ pub fn fuzz() { #[cfg(feature = "trace_stg")] let mut feedback = feedback_or!( feedback, - StgFeedback::default() + StgFeedback::new(if cli.dump_graph {cli.dump_name.clone()} else {None}) ); - #[cfg(feature = "feed_stg")] + #[cfg(feature = "feed_stg_edge")] let mut feedback = feedback_or!( feedback, MaxMapFeedback::tracking(&stg_coverage_observer, true, true) @@ -642,7 +632,7 @@ pub fn fuzz() { if let Some(se) = seed { unsafe { let mut rng = StdRng::seed_from_u64(se); - for i in 0..20 { + for i in 0..1000 { let inp = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); } diff --git a/fuzzers/FRET/src/mutational.rs b/fuzzers/FRET/src/mutational.rs index 9414ccbed8..f9e4ac751b 100644 --- a/fuzzers/FRET/src/mutational.rs +++ b/fuzzers/FRET/src/mutational.rs @@ -20,6 +20,33 @@ pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*ms*/ * 62500; // 1ms = 62500 insn // 1us = 62.5 insn +pub fn input_bytes_to_interrupt_times(buf: &[u8]) -> Vec { + let len = buf.len(); + let mut start_tick : u32 = 0; + let mut ret = Vec::with_capacity(DO_NUM_INTERRUPT); + for i in 0..DO_NUM_INTERRUPT { + let mut t : [u8; 4] = [0,0,0,0]; + if len > (i+1)*4 { + for j in 0usize..4usize { + t[j]=buf[i*4+j]; + } + unsafe {start_tick = max(u32::from_le_bytes(t), FIRST_INT);} + 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()-1 { + for j in i+1..ret.len()-1 { + if ret[j]-ret[i] < unsafe{MINIMUM_INTER_ARRIVAL_TIME} { + ret[j] = u32::saturating_add(ret[i],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); + } else {break;} + } + } + ret +} + + //======================= Custom mutator /// The default mutational stage @@ -64,39 +91,29 @@ where let mut newinput = _input.input_mut().as_mut().unwrap().clone(); // let mut tmpinput = _input.input_mut().as_mut().unwrap().clone(); let mut do_rerun = false; + if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time { // need our own random generator, because borrowing rules let mut myrand = StdRand::new(); let mut target_bytes : Vec = vec![]; { let input = _input.input_mut().as_ref().unwrap(); + target_bytes = input.bytes().to_vec(); let tmp = &mut state.rand_mut(); myrand.set_seed(tmp.next()); - target_bytes = input.bytes().to_vec(); } // produce a slice of absolute interrupt times - let mut interrupt_offsets : [u32; 32] = [0u32; 32]; + let mut interrupt_offsets : [u32; 32] = [u32::MAX; 32]; let mut num_interrupts : usize = 0; { - let mut start_tick : u32 = 0; - for i in 0..DO_NUM_INTERRUPT { - let mut t : [u8; 4] = [0,0,0,0]; - if target_bytes.len() > (i+1)*4 { - for j in 0 as usize..4 as usize { - t[j]=target_bytes[i*4+j]; - } - if i == 0 || true { - start_tick = u32::saturating_add(u32::from_le_bytes(t),FIRST_INT); - } else { - start_tick = u32::saturating_add(start_tick,max(unsafe{MINIMUM_INTER_ARRIVAL_TIME},u32::from_le_bytes(t))); - } - interrupt_offsets[i] = start_tick; - num_interrupts = i+1; - } + let times = input_bytes_to_interrupt_times(&target_bytes); + for (i,t) in times.iter().enumerate() { + interrupt_offsets[i] = *t; } + num_interrupts = times.len(); } - interrupt_offsets.sort(); + interrupt_offsets.sort_unstable(); // println!("Vor Mutator: {:?}", interrupt_offsets[0..num_interrupts].to_vec()); // let num_i = min(target_bytes.len() / 4, DO_NUM_INTERRUPT); @@ -105,125 +122,140 @@ where // let mut suffix : Vec = vec![]; #[cfg(feature = "trace_stg")] { - let feedbackstate = match state - .named_metadata_map_mut() - .get_mut::("stgfeedbackstate") { - Some(s) => s, - None => { - panic!("STGfeedbackstate not visible") - } - }; - - let tmp = _input.metadata_map().get::(); - if tmp.is_some() { - let trace = tmp.expect("STGNodeMetadata not found"); - - // calculate hits and identify snippets - let mut last_m = false; - let mut marks : Vec<(&ExecInterval, usize, usize)>= vec![]; // 1: got interrupted, 2: interrupt handler - for i in 0..trace.intervals.len() { - let curr = &trace.intervals[i]; - let m = interrupt_offsets[0..num_interrupts].iter().any(|x| (curr.start_tick..curr.end_tick).contains(&(*x as u64))); - if m { - marks.push((curr, i, 1)); - // println!("1: {}",curr.current_task.0.task_name); - } else if last_m { - marks.push((curr, i, 2)); - // println!("2: {}",curr.current_task.0.task_name); - } else { - marks.push((curr, i, 0)); + if myrand.between(1,100) <= 25 { // 0.5*0.25 = 12.5% of the time fully randomize all interrupts + do_rerun = true; + let metadata = state.metadata_map(); + let hist = metadata.get::().unwrap(); + let maxtick : u64 = hist.1.0; + // let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap(); + let mut numbers : Vec = vec![]; + for i in 0..num_interrupts { + prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32"))); } - last_m = m; - } - for i in 0..num_interrupts { - // bounds based on minimum inter-arrival time - let mut lb = 0; - let mut ub : u32 = marks[marks.len()-1].0.end_tick.try_into().expect("ticks > u32"); - if i > 0 { - lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); - } - if i < num_interrupts-1 { - ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); - } - // get old hit and handler - let old_hit = marks.iter().filter( - |x| x.0.start_tick < (interrupt_offsets[i] as u64) && (interrupt_offsets[i] as u64) < x.0.end_tick - ).next(); - let old_handler = match old_hit { - Some(s) => if s.1 < num_interrupts-1 && s.1 < marks.len()-1 { - Some(marks[s.1+1]) - } else {None}, - None => None - }; - // find reachable alternatives - let alternatives : Vec<_> = marks.iter().filter(|x| - x.2 != 2 && - ( - x.0.start_tick < (lb as u64) && (lb as u64) < x.0.end_tick - || x.0.start_tick < (ub as u64) && (ub as u64) < x.0.end_tick ) - ).collect(); - // in cases there are no alternatives - if alternatives.len() == 0 { - if old_hit.is_none() { - // choose something random - let untouched : Vec<_> = marks.iter().filter( - |x| x.2 == 0 - ).collect(); - if untouched.len() > 0 { - let tmp = interrupt_offsets[i]; - let choice = myrand.choose(untouched); - interrupt_offsets[i] = myrand.between(choice.0.start_tick, choice.0.end_tick) - .try_into().expect("tick > u32"); - do_rerun = true; + } else { + let feedbackstate = match state + .named_metadata_map_mut() + .get_mut::("stgfeedbackstate") { + Some(s) => s, + None => { + panic!("STGfeedbackstate not visible") } - // println!("no alternatives, choose random i: {} {} -> {}",i,tmp,interrupt_offsets[i]); + }; + + let tmp = _input.metadata_map().get::(); + if tmp.is_some() { + let trace = tmp.expect("STGNodeMetadata not found"); + + // calculate hits and identify snippets + let mut last_m = false; + let mut marks : Vec<(&ExecInterval, usize, usize)>= vec![]; // 1: got interrupted, 2: interrupt handler + for i in 0..trace.intervals.len() { + let curr = &trace.intervals[i]; + let m = interrupt_offsets[0..num_interrupts].iter().any(|x| (curr.start_tick..curr.end_tick).contains(&(*x as u64))); + if m { + marks.push((curr, i, 1)); + // println!("1: {}",curr.current_task.0.task_name); + } else if last_m { + marks.push((curr, i, 2)); + // println!("2: {}",curr.current_task.0.task_name); + } else { + marks.push((curr, i, 0)); + } + last_m = m; + } + for i in 0..num_interrupts { + // bounds based on minimum inter-arrival time + let mut lb = 0; + let mut ub : u32 = marks[marks.len()-1].0.end_tick.try_into().expect("ticks > u32"); + if i > 0 { + lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); + } + if i < num_interrupts-1 { + ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); + } + // get old hit and handler + let old_hit = marks.iter().filter( + |x| x.0.start_tick < (interrupt_offsets[i] as u64) && (interrupt_offsets[i] as u64) < x.0.end_tick + ).next(); + let old_handler = match old_hit { + Some(s) => if s.1 < num_interrupts-1 && s.1 < marks.len()-1 { + Some(marks[s.1+1]) + } else {None}, + None => None + }; + // find reachable alternatives + let alternatives : Vec<_> = marks.iter().filter(|x| + x.2 != 2 && + ( + x.0.start_tick < (lb as u64) && (lb as u64) < x.0.end_tick + || x.0.start_tick < (ub as u64) && (ub as u64) < x.0.end_tick ) + ).collect(); + // in cases there are no alternatives + if alternatives.len() == 0 { + if old_hit.is_none() { + // choose something random + let untouched : Vec<_> = marks.iter().filter( + |x| x.2 == 0 + ).collect(); + if untouched.len() > 0 { + let tmp = interrupt_offsets[i]; + let choice = myrand.choose(untouched); + interrupt_offsets[i] = myrand.between(choice.0.start_tick, choice.0.end_tick) + .try_into().expect("tick > u32"); + do_rerun = true; + } + // println!("no alternatives, choose random i: {} {} -> {}",i,tmp,interrupt_offsets[i]); + continue; + } else { + // do nothing + // println!("no alternatives, do nothing i: {} {}",i,interrupt_offsets[i]); + continue; + } + } + let replacement = myrand.choose(alternatives); + if (old_hit.map_or(false, |x| x == replacement)) { + // use the old value + // println!("chose old value, do nothing i: {} {}",i,interrupt_offsets[i]); continue; } else { - // do nothing - // println!("no alternatives, do nothing i: {} {}",i,interrupt_offsets[i]); - continue; + let extra = if (old_hit.map_or(false, |x| x.1 < replacement.1)) { + // move futher back, respect old_handler + old_handler.map_or(0, |x| x.0.end_tick - x.0.start_tick) + } else { 0 }; + let tmp = interrupt_offsets[i]; + interrupt_offsets[i] = (myrand.between(replacement.0.start_tick, + replacement.0.end_tick) + extra).try_into().expect("ticks > u32"); + // println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]); + do_rerun = true; } } - let replacement = myrand.choose(alternatives); - if (old_hit.map_or(false, |x| x == replacement)) { - // use the old value - // println!("chose old value, do nothing i: {} {}",i,interrupt_offsets[i]); - continue; - } else { - let extra = if (old_hit.map_or(false, |x| x.1 < replacement.1)) { - // move futher back, respect old_handler - old_handler.map_or(0, |x| x.0.end_tick - x.0.start_tick) - } else { 0 }; - let tmp = interrupt_offsets[i]; - interrupt_offsets[i] = (myrand.between(replacement.0.start_tick, - replacement.0.end_tick) + extra).try_into().expect("ticks > u32"); - // println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]); - do_rerun = true; + let mut numbers : Vec = interrupt_offsets[0..num_interrupts].to_vec(); + numbers.sort(); + // println!("Mutator: {:?}", numbers); + // let mut start : u32 = 0; + // for i in 0..numbers.len() { + // let tmp = numbers[i]; + // numbers[i] = numbers[i]-start; + // start = tmp; + // } + for i in 0..numbers.len() { + prefix.push(u32::to_le_bytes(numbers[i])); + } } - } - let mut numbers : Vec = interrupt_offsets[0..num_interrupts].to_vec(); - numbers.sort(); - // println!("Mutator: {:?}", numbers); - // let mut start : u32 = 0; - // for i in 0..numbers.len() { - // let tmp = numbers[i]; - // numbers[i] = numbers[i]-start; - // start = tmp; - // } - for i in 0..numbers.len() { - prefix.push(u32::to_le_bytes(numbers[i])); - } } } #[cfg(not(feature = "trace_stg"))] { - let metadata = state.metadata_map(); - let hist = metadata.get::().unwrap(); - let maxtick : u64 = hist.1.0; - // let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap(); - let mut numbers : Vec = vec![]; - for i in 0..num_interrupts { - prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32"))); + if myrand.between(1,100) <= 25 { // we have no hint if interrupt times will change anything + do_rerun = true; + let metadata = state.metadata_map(); + let hist = metadata.get::().unwrap(); + let maxtick : u64 = hist.1.0; + // let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap(); + let mut numbers : Vec = vec![]; + for i in 0..num_interrupts { + prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32"))); + } } } diff --git a/fuzzers/FRET/src/systemstate/stg.rs b/fuzzers/FRET/src/systemstate/stg.rs index b1296f367e..616257b785 100644 --- a/fuzzers/FRET/src/systemstate/stg.rs +++ b/fuzzers/FRET/src/systemstate/stg.rs @@ -59,6 +59,10 @@ use std::rc::Rc; use libafl_bolts::rands::Rand; +use crate::clock::FUZZ_START_TIMESTAMP; +use std::time::SystemTime; +use std::{fs::OpenOptions, io::Write}; + //============================= Data Structures #[derive(Serialize, Deserialize, Clone, Debug, Default, Hash)] pub struct STGNode @@ -147,7 +151,9 @@ pub struct STGFeedbackState entrypoint: NodeIndex, exitpoint: NodeIndex, // Metadata about aggregated traces. aggegated meaning, order has been removed - worst_observed_per_aggegated_path: HashMap,u64> + worst_observed_per_aggegated_path: HashMap,u64>, + worst_observed_per_abb_path: HashMap, + worst_observed_per_stg_path: HashMap } impl Default for STGFeedbackState { @@ -176,6 +182,8 @@ impl Default for STGFeedbackState { entrypoint, exitpoint, worst_observed_per_aggegated_path: HashMap::new(), + worst_observed_per_abb_path: HashMap::new(), + worst_observed_per_stg_path: HashMap::new(), systemstate_index, state_abb_hash_index } @@ -195,16 +203,35 @@ impl Named for STGFeedbackState pub struct STGNodeMetadata { pub nodes: Vec, pub edges: Vec, + pub abbs: Vec, pub intervals: Vec, indices: Vec, tcref: isize, } impl STGNodeMetadata { - pub fn new(nodes: Vec, edges: Vec, intervals: Vec) -> Self{ - let mut indices : Vec<_> = edges.iter().map(|x| x.index()).collect(); - indices.sort_unstable(); - indices.dedup(); - Self {indices, intervals, nodes, edges, tcref: 0} + pub fn new(nodes: Vec, edges: Vec, abbs: Vec, intervals: Vec) -> Self{ + let mut indices : Vec<_> = vec![]; + #[cfg(all(feature = "sched_stg",not(any(feature = "sched_stg_pathhash",feature = "sched_stg_abbhash",feature = "sched_stg_aggregatehash"))))] + { + indices = edges.iter().map(|x| x.index()).collect(); + indices.sort_unstable(); + indices.dedup(); + } + #[cfg(feature = "sched_stg_pathhash")] + { + indices.push(get_generic_hash(&edges) as usize); + } + #[cfg(feature = "sched_stg_abbhash")] + { + indices.push(get_generic_hash(&abbs) as usize); + } + #[cfg(feature = "sched_stg_aggregatehash")] + { + let mut _abbs = abbs.clone(); + _abbs.sort_unstable(); + indices.push(get_generic_hash(&_abbs) as usize); + } + Self {indices, intervals, nodes, abbs, edges, tcref: 0} } } impl AsSlice for STGNodeMetadata { @@ -247,18 +274,29 @@ pub struct StgFeedback last_node_trace: Option>, last_edge_trace: Option>, last_intervals: Option>, + last_abbs: Option>, + dump_path: Option } #[cfg(feature = "feed_stg")] const INTEREST_EDGE : bool = true; #[cfg(feature = "feed_stg")] const INTEREST_NODE : bool = true; -#[cfg(feature = "feed_stg")] +#[cfg(feature = "feed_stg_pathhash")] +const INTEREST_PATH : bool = true; +#[cfg(feature = "feed_stg_abbhash")] +const INTEREST_ABBPATH : bool = true; +#[cfg(feature = "feed_stg_aggregatehash")] const INTEREST_AGGREGATE : bool = true; + #[cfg(not(feature = "feed_stg"))] const INTEREST_EDGE : bool = false; #[cfg(not(feature = "feed_stg"))] const INTEREST_NODE : bool = false; -#[cfg(not(feature = "feed_stg"))] +#[cfg(not(feature = "feed_stg_pathhash"))] +const INTEREST_PATH : bool = false; +#[cfg(not(feature = "feed_stg_abbhash"))] +const INTEREST_ABBPATH : bool = false; +#[cfg(not(feature = "feed_stg_aggregatehash"))] const INTEREST_AGGREGATE : bool = false; fn set_observer_map(trace : &Vec) { unsafe { @@ -273,9 +311,22 @@ fn set_observer_map(trace : &Vec) { } } } + +fn get_generic_hash(input: &H) -> u64 + where + H: Hash, +{ + let mut s = DefaultHasher::new(); + input.hash(&mut s); + s.finish() +} + impl StgFeedback { - pub fn new() -> Self { - Self {name: String::from("STGFeedback"), last_node_trace: None, last_edge_trace: None, last_intervals: None } + pub fn new(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(); + s.dump_path = dump_name.map(|x| x.with_extension("stgsize")); + s } /// params: @@ -287,10 +338,11 @@ impl StgFeedback { /// newly discovered node? /// side effect: /// the graph gets new nodes and edge - fn update_stg_interval(trace: &Vec, table: &HashMap, fbs: &mut STGFeedbackState) -> (Vec, Vec, bool) { + fn update_stg_interval(trace: &Vec, table: &HashMap, fbs: &mut STGFeedbackState) -> (Vec, Vec, bool, bool) { let mut return_node_trace = vec![fbs.entrypoint]; let mut return_edge_trace = vec![]; let mut interesting = false; + let mut updated = false; // add all missing state+abb combinations to the graph for (i,interval) in trace.iter().enumerate() { // Iterate intervals let node = STGNode {base: table[&interval.start_state].clone(), abb: interval.abb.as_ref().unwrap().clone()}; @@ -305,6 +357,7 @@ impl StgFeedback { fbs.stgnode_index.insert(h_node, idx); fbs.state_abb_hash_index.insert(h, idx); interesting |= INTEREST_NODE; + updated = true; idx }; // connect in graph if edge not present @@ -315,6 +368,7 @@ impl StgFeedback { let e_ = fbs.graph.add_edge(return_node_trace[return_node_trace.len()-1], next_idx, STGEdge{event: interval.start_capture.0, name: interval.start_capture.1.clone()}); return_edge_trace.push(e_); interesting |= INTEREST_EDGE; + updated = true; } return_node_trace.push(next_idx); /* @@ -330,11 +384,12 @@ impl StgFeedback { let e_ = fbs.graph.add_edge(return_node_trace[return_node_trace.len()-1], fbs.exitpoint, STGEdge { event: CaptureEvent::End, name: String::from("End") }); return_edge_trace.push(e_); interesting |= INTEREST_EDGE; + updated = true; } return_node_trace.push(fbs.exitpoint); #[cfg(feature = "feed_stg")] set_observer_map(&return_edge_trace); - (return_node_trace, return_edge_trace, interesting) + (return_node_trace, return_edge_trace, interesting, updated) } fn abbs_in_exec_order(trace: &Vec) -> Vec { @@ -382,21 +437,53 @@ where } }; - let (nodetrace, edgetrace, mut interesting) = StgFeedback::update_stg_interval(&observer.last_trace, &observer.last_states, feedbackstate); + let (nodetrace, edgetrace, mut interesting, mut updated) = StgFeedback::update_stg_interval(&observer.last_trace, &observer.last_states, feedbackstate); - if INTEREST_AGGREGATE { - let mut tmp = StgFeedback::abbs_in_exec_order(&observer.last_trace); - // aggegation by sorting, order of states is not relevant - tmp.sort(); - if let Some(x) = feedbackstate.worst_observed_per_aggegated_path.get_mut(&tmp) { + { + let h = get_generic_hash(&edgetrace); + if let Some(x) = feedbackstate.worst_observed_per_stg_path.get_mut(&h) { let t = clock_observer.last_runtime(); if t > *x { *x = t; - interesting |= INTEREST_AGGREGATE; + interesting |= INTEREST_PATH; } } else { - feedbackstate.worst_observed_per_aggegated_path.insert(tmp, clock_observer.last_runtime()); - interesting |= INTEREST_AGGREGATE; + feedbackstate.worst_observed_per_stg_path.insert(h, clock_observer.last_runtime()); + updated = true; + interesting |= INTEREST_PATH; + } + } + + let mut tmp = StgFeedback::abbs_in_exec_order(&observer.last_trace); + if INTEREST_AGGREGATE || INTEREST_ABBPATH { + if INTEREST_ABBPATH { + let h = get_generic_hash(&tmp); + // order of execution is relevant + if let Some(x) = feedbackstate.worst_observed_per_abb_path.get_mut(&h) { + let t = clock_observer.last_runtime(); + if t > *x { + *x = t; + interesting |= INTEREST_ABBPATH; + } + } else { + feedbackstate.worst_observed_per_abb_path.insert(h, clock_observer.last_runtime()); + interesting |= INTEREST_ABBPATH; + } + } + if INTEREST_AGGREGATE { + // aggegation by sorting, order of states is not relevant + let mut _tmp = tmp.clone(); + _tmp.sort(); + if let Some(x) = feedbackstate.worst_observed_per_aggegated_path.get_mut(&_tmp) { + let t = clock_observer.last_runtime(); + if t > *x { + *x = t; + interesting |= INTEREST_AGGREGATE; + } + } else { + feedbackstate.worst_observed_per_aggegated_path.insert(_tmp, clock_observer.last_runtime()); + interesting |= INTEREST_AGGREGATE; + } } } @@ -407,17 +494,31 @@ where self.last_node_trace = Some(nodetrace); self.last_edge_trace = Some(edgetrace); self.last_intervals = Some(observer.last_trace.clone()); + self.last_abbs = Some(tmp); + if let Some(dp) = &self.dump_path { + if updated { + let timestamp = SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis(); + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .append(true) + .open(dp).expect("Could not open stgsize"); + writeln!(file, "{},{},{},{},{}", feedbackstate.graph.edge_count(), feedbackstate.graph.node_count(), feedbackstate.worst_observed_per_aggegated_path.len(),feedbackstate.worst_observed_per_stg_path.len(), timestamp).expect("Write to dump failed"); + } + } Ok(interesting) } /// Append to the testcase the generated metadata in case of a new corpus item #[inline] - fn append_metadata(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase) -> Result<(), Error> { + fn append_metadata(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase) -> Result<(), Error> { let nodes = self.last_node_trace.take(); let edges = self.last_edge_trace.take(); + let abbs = self.last_abbs.take(); match nodes { - Some(s) => testcase.metadata_map_mut().insert(STGNodeMetadata::new(s, edges.unwrap(), self.last_intervals.take().unwrap())), + Some(s) => testcase.metadata_map_mut().insert(STGNodeMetadata::new(s, edges.unwrap(), abbs.unwrap(), self.last_intervals.take().unwrap())), None => (), } Ok(())