From d93ed809f1554757cccc36143252d1baa017667c Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Fri, 3 May 2024 13:28:15 +0200 Subject: [PATCH] improve stg parsing --- fuzzers/FRET/src/fuzzer.rs | 45 +++++---- fuzzers/FRET/src/systemstate/stg.rs | 138 +++++++++++++++++++++++----- 2 files changed, 141 insertions(+), 42 deletions(-) diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 6a2802e10f..a1f5f8b45e 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -43,6 +43,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use clap::{Parser, Subcommand}; use csv::Reader; use petgraph::dot::{Dot, Config}; +use crate::systemstate::stg::STGFeedbackState; // Constants ================================================================================ @@ -173,7 +174,7 @@ struct Cli { #[command(subcommand)] command: Commands, } -#[derive(Subcommand)] +#[derive(Subcommand,Clone)] enum Commands { /// run a single input Showmap { @@ -255,6 +256,23 @@ macro_rules! do_dump_graph { }; } +/// Takes a state and a bool, writes out the current graph +macro_rules! do_dump_stg { + ($state:expr, $cli:expr, $c:expr) => { + #[cfg(feature = "trace_abbs")] + if $cli.dump_graph { + let dump_path = $cli.dump_name.clone().unwrap().with_extension(if $c=="" {"stg"} else {$c}); + println!("Dumping graph to {:?}", &dump_path); + if let Some(md) = $state.named_metadata_map_mut().get_mut::("stgfeedbackstate") { + let out = md.graph.map(|i,x| x.pretty_print(), |_,_| ""); + let outs = Dot::with_config(&out, &[Config::EdgeNoLabel]).to_string(); + let outs = outs.replace(';',"\\n"); + fs::write(dump_path,outs).expect("Failed to write graph"); + } + } + }; +} + /// Takes a state and a bool, writes out top rated inputs macro_rules! do_dump_toprated { ($state:expr, $cli:expr, $c:expr) => { @@ -656,7 +674,7 @@ pub fn fuzz() { #[cfg(not(feature = "fuzz_int"))] let mut stages = tuple_list!(StdMutationalStage::new(mutator)); - if let Commands::Showmap { input } = cli.command { + if let Commands::Showmap { input } = cli.command.clone() { let s = input.as_os_str(); let show_input = if s=="-" { let mut buf = Vec::::new(); @@ -669,25 +687,14 @@ pub fn fuzz() { }; fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input)) .unwrap(); - if cli.dump_times { - let td = cli.dump_name.clone().expect("Dump name not give").with_extension("case.time"); - let mut file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .append(true) - .open(td).expect("Could not open timedump"); - if let Ok(ichist) = state.metadata_mut::() { - for i in ichist.0.drain(..) { - writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed"); - } - } - } + do_dump_times!(state, &cli, ""); + do_dump_graph!(state, &cli, ""); + do_dump_stg!(state, &cli, ""); } else if let Commands::Fuzz { random, time, seed } = cli.command { if let Some(se) = seed { unsafe { let mut rng = StdRng::seed_from_u64(se); - for i in 0..100 { + for i in 0..20 { let inp = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); } @@ -746,6 +753,8 @@ pub fn fuzz() { do_dump_case!(state, &cli, &d); let _d = format!("{}.graph",marker); do_dump_graph!(state, &cli, &_d); + let _d = format!("{}.stg",marker); + do_dump_stg!(state, &cli, &_d); let d = format!("{}.toprated",marker); do_dump_toprated!(state, &cli, &d); }; @@ -766,6 +775,7 @@ pub fn fuzz() { } do_dump_case!(state, &cli, ""); do_dump_graph!(state, &cli, ""); + do_dump_stg!(state, &cli, ""); do_dump_toprated!(state, &cli, ""); } } @@ -773,6 +783,7 @@ pub fn fuzz() { do_dump_times!(state, &cli, ""); do_dump_case!(state, &cli, ""); do_dump_graph!(state, &cli, ""); + do_dump_stg!(state, &cli, ""); do_dump_toprated!(state, &cli, ""); }, } diff --git a/fuzzers/FRET/src/systemstate/stg.rs b/fuzzers/FRET/src/systemstate/stg.rs index 8ee07679e4..dff89be442 100644 --- a/fuzzers/FRET/src/systemstate/stg.rs +++ b/fuzzers/FRET/src/systemstate/stg.rs @@ -56,16 +56,22 @@ use std::rc::Rc; use libafl_bolts::rands::Rand; //============================= Data Structures -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash)] pub struct STGNode { base: RefinedFreeRTOSSystemState, abb: AtomicBasicBlock, } impl STGNode { - fn pretty_print(&self) -> String { + pub fn pretty_print(&self) -> String { format!("{}\n{:x}-{:x}", self.base.current_task.0.task_name, self.abb.start, self.abb.ends.iter().next().unwrap_or_else(||&0)) } + fn calculate_hash(&self) -> u64 { + let mut s = DefaultHasher::new(); + self.base.hash(&mut s); + self.abb.hash(&mut s); + s.finish() + } } impl PartialEq for STGNode { fn eq(&self, other: &STGNode) -> bool { @@ -73,6 +79,49 @@ impl PartialEq for STGNode { } } +/// Shared Metadata for a systemstateFeedback +#[derive(Debug, Serialize, Deserialize, SerdeAny, Clone)] +pub struct STGFeedbackState +{ + pub graph: DiGraph, + index: HashMap, + entrypoint: NodeIndex, + exit: NodeIndex, +} + +impl Default for STGFeedbackState { + fn default() -> STGFeedbackState { + let mut graph = DiGraph::new(); + let mut entry = STGNode::default(); + entry.base.current_task.0.task_name="Start".to_string(); + let mut exit = STGNode::default(); + exit.base.current_task.0.task_name="End".to_string(); + + let h_entry = entry.calculate_hash(); + let h_exit = exit.calculate_hash(); + + let entrypoint = graph.add_node(entry); + let exit = graph.add_node(exit); + + let index = HashMap::from([(h_entry, entrypoint), (h_exit, exit)]); + + STGFeedbackState { + graph, + index, + entrypoint, + exit + } + } +} + +impl Named for STGFeedbackState +{ + #[inline] + fn name(&self) -> &str { + "stgfeedbackstate" + } +} + //============================= Graph Feedback /// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`] @@ -86,24 +135,52 @@ impl StgFeedback { pub fn new() -> Self { Self {name: String::from("SysMapFeedback"), last_trace: None } } - fn trace_to_stg(trace: &Vec, map: Vec<(String, Rc, usize, u64)>) -> DiGraph{ - let mut graph = DiGraph::new(); - let mut entry = STGNode::default(); - entry.base.current_task.0.task_name="Start".to_string(); - let mut exit = STGNode::default(); - exit.base.current_task.0.task_name="End".to_string(); - let entry = graph.add_node(entry); - let exit = graph.add_node(exit); - - let mut last = entry; - - for (i,state) in trace.iter().enumerate() { - if let Some(marker) = map.iter().filter(|(_,_,index,_)| index==&i).next() { - graph.add_node(STGNode {base: state.clone(), abb: (*marker.1).clone()}); + /// params: + /// tarce of system states + /// list of all atomic basic blocks with asociated state index and ticks + /// produces: + /// tarce of node indexes representing the path trough the graph + /// newly discovered node? + /// side effect: + /// the graph gets new nodes + fn update_stg(trace: &Vec, map: Vec<(String, Rc, usize, u64)>, fbs: &mut STGFeedbackState) -> (Vec, bool) { + let mut returntrace = vec![fbs.entrypoint]; + let mut new_stg_edge = false; + // add all missing state+abb combinations to the graph + for (i,state) in trace.iter().enumerate() { // Iterate states first, keep the trace order intact + if let Some(marker) = map.iter().filter(|(_,_,index,_)| index==&i).next() { // + let node = STGNode {base: state.clone(), abb: (*marker.1).clone()}; + let h_node = node.calculate_hash(); + let next_idx = if let Some(idx) = fbs.index.get(&h_node) { + // alredy present + *idx + } else { + // not present + let idx = fbs.graph.add_node(node); + fbs.index.insert(h_node, idx); + idx + }; + // connect in graph if edge not present + if !fbs.graph.neighbors_directed(returntrace[returntrace.len()-1],Direction::Outgoing).any(|x| x == next_idx) { + fbs.graph.add_edge(returntrace[returntrace.len()-1], next_idx, ()); + new_stg_edge = true; + } + returntrace.push(next_idx); + /* + Ideas: + Mark edges triggered by interrupts + Specify path with edges instead of nodes? + Form a coverage map over edges? + Sum up execution time per ABB + */ } } - - graph + if !fbs.graph.neighbors_directed(returntrace[returntrace.len()-1],Direction::Outgoing).any(|x| x == fbs.exit) { + fbs.graph.add_edge(returntrace[returntrace.len()-1], fbs.exit, ()); + new_stg_edge = true; + } + returntrace.push(fbs.exit); + (returntrace, new_stg_edge) } } @@ -127,14 +204,25 @@ where { let observer = observers.match_name::("systemstate") .expect("QemuSystemStateObserver not found"); - let abbs = trace_to_state_abb(&observer.last_run); - println!("{:?}",abbs); - let res =StgFeedback::trace_to_stg(&observer.last_run, abbs); + let feedbackstate = match state + .named_metadata_map_mut() + .get_mut::("stgfeedbackstate") { + Some(s) => s, + None => { + let n=STGFeedbackState::default(); + state.named_metadata_map_mut().insert(n, "stgfeedbackstate"); + state.named_metadata_map_mut().get_mut::("stgfeedbackstate").unwrap() + } + }; - let out = res.map(|i,x| x.pretty_print(), |_,_| ""); - let outs = Dot::with_config(&out, &[Config::EdgeNoLabel]).to_string(); - let outs = outs.replace(';',"\\n"); - fs::write("./mystg.dot",outs).expect("Failed to write graph"); + let abbs = trace_to_state_abb(&observer.last_run); + // println!("{:?}",abbs); + let (trace, new_edge) = StgFeedback::update_stg(&observer.last_run, abbs, feedbackstate); + + // let out = feedbackstate.graph.map(|i,x| x.pretty_print(), |_,_| ""); + // let outs = Dot::with_config(&out, &[Config::EdgeNoLabel]).to_string(); + // let outs = outs.replace(';',"\\n"); + // fs::write("./mystg.dot",outs).expect("Failed to write graph"); Ok(false) }