From 740ce09d31ae43f07955c444460dfe8e4ed1ada4 Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Mon, 9 Sep 2024 10:56:39 +0200 Subject: [PATCH] configurable interrupt sources --- fuzzers/FRET/benchmark/target_symbols.csv | 67 +++++++++++----------- fuzzers/FRET/src/cli.rs | 23 ++++++++ fuzzers/FRET/src/fuzzer.rs | 35 ++++++++--- fuzzers/FRET/src/systemstate/mutational.rs | 41 +++++++------ 4 files changed, 107 insertions(+), 59 deletions(-) diff --git a/fuzzers/FRET/benchmark/target_symbols.csv b/fuzzers/FRET/benchmark/target_symbols.csv index eb1b1b55d4..0aa5ba0260 100644 --- a/fuzzers/FRET/benchmark/target_symbols.csv +++ b/fuzzers/FRET/benchmark/target_symbols.csv @@ -1,33 +1,34 @@ -kernel,main_function,input_symbol,input_size,return_function,select_task -mpeg2,mpeg2_main,mpeg2_oldorgframe,90112,mpeg2_return,NONE -audiobeam,audiobeam_main,audiobeam_input,11520,audiobeam_return,NONE -epic,epic_main,epic_image,4096,epic_return,NONE -dijkstra,dijkstra_main,dijkstra_AdjMatrix,10000,dijkstra_return,NONE -fft,fft_main,fft_twidtable,2046,fft_return,NONE -bsort,bsort_main,bsort_Array,400,bsort_return,NONE -insertsort,insertsort_main,insertsort_a,400,insertsort_return,NONE -g723_enc,g723_enc_main,g723_enc_INPUT,1024,g723_enc_return,NONE -rijndael_dec,rijndael_dec_main,rijndael_dec_data,32768,rijndael_dec_return,NONE -rijndael_enc,rijndael_enc_main,rijndael_enc_data,31369,rijndael_enc_return,NONE -huff_dec,huff_dec_main,huff_dec_encoded,419,huff_dec_return,NONE -huff_enc,huff_enc_main,huff_enc_plaintext,600,huff_enc_return,NONE -gsm_enc,gsm_enc_main,gsm_enc_pcmdata,6400,gsm_enc_return,NONE -tmr,main,FUZZ_INPUT,32,trigger_Qemu_break,NONE -tacle_rtos,prvStage0,FUZZ_INPUT,604,trigger_Qemu_break,NONE -lift,main_lift,FUZZ_INPUT,100,trigger_Qemu_break,NONE -waters,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -watersv2,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -waterspart,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -waterspartv2,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -waters_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -watersv2_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -waterspart_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -waterspartv2_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129 -micro_branchless,main_branchless,FUZZ_INPUT,4,trigger_Qemu_break,NONE -micro_int,main_int,FUZZ_INPUT,16,trigger_Qemu_break,NONE -micro_longint,main_micro_longint,FUZZ_INPUT,16,trigger_Qemu_break,NONE -minimal,main_minimal,FUZZ_INPUT,4096,trigger_Qemu_break,NONE -gen3,main_minimal,FUZZ_INPUT,4096,trigger_Qemu_break,NONE -interact,main_interact,FUZZ_INPUT,4096,trigger_Qemu_break,NONE -interact_int,main_interact,FUZZ_INPUT,4096,trigger_Qemu_break,NONE -release,main_release,FUZZ_INPUT,4096,trigger_Qemu_break,T3 +kernel,main_function,input_symbol,input_size,return_function,select_task,interrupts +mpeg2,mpeg2_main,mpeg2_oldorgframe,90112,mpeg2_return,NONE,0#1000 +audiobeam,audiobeam_main,audiobeam_input,11520,audiobeam_return,NONE,0#1000 +epic,epic_main,epic_image,4096,epic_return,NONE,0#1000 +dijkstra,dijkstra_main,dijkstra_AdjMatrix,10000,dijkstra_return,NONE,0#1000 +fft,fft_main,fft_twidtable,2046,fft_return,NONE,0#1000 +bsort,bsort_main,bsort_Array,400,bsort_return,NONE,0#1000 +insertsort,insertsort_main,insertsort_a,400,insertsort_return,NONE,0#1000 +g723_enc,g723_enc_main,g723_enc_INPUT,1024,g723_enc_return,NONE,0#1000 +rijndael_dec,rijndael_dec_main,rijndael_dec_data,32768,rijndael_dec_return,NONE,0#1000 +rijndael_enc,rijndael_enc_main,rijndael_enc_data,31369,rijndael_enc_return,NONE,0#1000 +huff_dec,huff_dec_main,huff_dec_encoded,419,huff_dec_return,NONE,0#1000 +huff_enc,huff_enc_main,huff_enc_plaintext,600,huff_enc_return,NONE,0#1000 +gsm_enc,gsm_enc_main,gsm_enc_pcmdata,6400,gsm_enc_return,NONE,0#1000 +tmr,main,FUZZ_INPUT,32,trigger_Qemu_break,NONE,0#1000 +tacle_rtos,prvStage0,FUZZ_INPUT,604,trigger_Qemu_break,NONE,0#1000 +lift,main_lift,FUZZ_INPUT,100,trigger_Qemu_break,NONE,0#1000 +waters,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +watersv2,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +waterspart,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +waterspartv2,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +waters_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +watersv2_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +waterspart_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +waterspartv2_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break,1129,0#1000 +micro_branchless,main_branchless,FUZZ_INPUT,4,trigger_Qemu_break,NONE,0#1000 +micro_int,main_int,FUZZ_INPUT,16,trigger_Qemu_break,NONE,0#1000 +micro_longint,main_micro_longint,FUZZ_INPUT,16,trigger_Qemu_break,NONE,0#1000 +minimal,main_minimal,FUZZ_INPUT,4096,trigger_Qemu_break,NONE,0#1000 +gen3,main_minimal,FUZZ_INPUT,4096,trigger_Qemu_break,NONE,0#1000 +interact,main_interact,FUZZ_INPUT,4096,trigger_Qemu_break,NONE,0#1000 +interact_int,main_interact,FUZZ_INPUT,4096,trigger_Qemu_break,NONE,0#1000 +release,main_release,FUZZ_INPUT,4096,trigger_Qemu_break,T3,0#10000;1#1000;2#2000;3#3000 + diff --git a/fuzzers/FRET/src/cli.rs b/fuzzers/FRET/src/cli.rs index 79e32eddd9..64cd9efeb4 100644 --- a/fuzzers/FRET/src/cli.rs +++ b/fuzzers/FRET/src/cli.rs @@ -89,4 +89,27 @@ pub fn set_env_from_config(kernel : &PathBuf, path : &PathBuf) { } } } +} + +pub fn get_interrupt_config(kernel : &PathBuf, path : &PathBuf) -> Vec<(usize,u32)>{ + let is_csv = path.as_path().extension().map_or(false, |x| x=="csv"); + if !is_csv { + panic!("Interrupt config must be inside a CSV file"); + } else { + let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed"); + let p = kernel.as_path(); + let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap(); + for r in reader.records() { + let rec = r.expect("CSV entry error"); + if stem == &rec[0] { + let ret = rec[6].split(';').map(|x| { + let pair = x.split_once('#').expect("Interrupt config error"); + (pair.0.parse().expect("Interrupt config error"), pair.1.parse().expect("Interrupt config error")) + }).collect(); + println!("Interrupt config {:?}", ret); + return ret; + } + } + } + return Vec::new(); } \ No newline at end of file diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 331045b553..bf97a7c26c 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -21,7 +21,7 @@ use crate::{ qemustate::QemuStateRestoreHelper }, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{get_function_range, load_symbol, try_load_symbol, QemuSystemStateHelper}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, - systemstate::mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, + systemstate::mutational::{input_bytes_to_interrupt_times, InterruptShiftStage}, }; use std::time::SystemTime; use petgraph::dot::Dot; @@ -164,6 +164,7 @@ log::set_max_level(log::LevelFilter::Info); SimpleStderrLogger::set_logger().unwrap(); let cli = Cli::parse(); set_env_from_config(&cli.kernel, &cli.config); +let interrupt_config = crate::cli::get_interrupt_config(&cli.kernel, &cli.config); unsafe {FUZZ_START_TIMESTAMP = SystemTime::now();} if cli.dump_name.is_none() && (cli.dump_times || cli.dump_cases || cli.dump_traces || cli.dump_graph) { panic!("Dump name not give but dump is requested"); @@ -272,6 +273,18 @@ let api_ranges : Vec<_> = api_ranges.into_iter().collect(); #[cfg(feature = "observe_systemstate")] let isr_ranges : Vec<_> = isr_ranges.into_iter().collect(); +/// Setup the interrupt inputs. Noop if interrupts are not fuzzed +fn setup_interrupt_inputs(mut input : MultipartInput, interrupt_config : &Vec<(usize,u32)>) -> MultipartInput { + #[cfg(feature = "fuzz_int")] + for (i,_) in interrupt_config { + let name = format!("interrupts_{}",i); + if input.parts_by_name(&name).next().is_none() { + input.add_part(name, BytesInput::new([0; MAX_NUM_INTERRUPT*4].to_vec())); + } + } + input +} + // Client setup ================================================================================ let run_client = |state: Option<_>, mut mgr, _core_id| { @@ -331,10 +344,14 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { unsafe { #[cfg(feature = "fuzz_int")] { - let time_bytes = input.parts_by_name("interrupts").next().map(|x| x.1.bytes()).unwrap_or(&[0u8; MAX_NUM_INTERRUPT*4]); - let t = input_bytes_to_interrupt_times(time_bytes); - for i in 0..t.len() {libafl_interrupt_offsets[0][i]=t[i];} - libafl_num_interrupts[0]=t.len(); + for &c in &interrupt_config { + let (i,_) = c; + let name = format!("interrupts_{}",i); + let input_bytes = input.parts_by_name(&name).next().map(|x| x.1.bytes()).unwrap_or(&[0u8; MAX_NUM_INTERRUPT*4]); + let t = input_bytes_to_interrupt_times(input_bytes, c); + for j in 0..t.len() {libafl_interrupt_offsets[i][j]=t[j];} + libafl_num_interrupts[i]=t.len(); + } // println!("Load: {:?}", libafl_interrupt_offsets[0..libafl_num_interrupts].to_vec()); } @@ -502,7 +519,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { let stages = (systemstate::report::SchedulerStatsStage::default(),()); let mut stages = (StdMutationalStage::new(mutator), stages); #[cfg(feature = "fuzz_int")] - let mut stages = (InterruptShiftStage::new(), stages); + let mut stages = (InterruptShiftStage::new(&interrupt_config), stages); if let Commands::Showmap { input } = cli.command.clone() { let s = input.as_os_str(); @@ -519,7 +536,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { Ok(x) => x, Err(_) => { println!("Interpreting input file as raw input"); - MultipartInput::from([("interrupts",BytesInput::new([0; MAX_NUM_INTERRUPT].to_vec())),("bytes",BytesInput::new(input.as_os_str().as_encoded_bytes().to_vec()))]) + setup_interrupt_inputs(MultipartInput::from([("bytes",BytesInput::new(input.as_os_str().as_encoded_bytes().to_vec()))]), &interrupt_config) } }; fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, show_input) @@ -533,7 +550,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { for _ in 0..100 { let inp1 = BytesInput::new(vec![rng.gen::(); MAX_NUM_INTERRUPT*4]); let inp2 = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); - let inp = MultipartInput::from([("interrupts",inp1),("bytes",inp2)]); + let inp = setup_interrupt_inputs(MultipartInput::from([("bytes",inp2)]), &interrupt_config); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); } } @@ -576,7 +593,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { // libafl's generator is too slow let inp1 = BytesInput::new(vec![rng.gen::(); MAX_NUM_INTERRUPT*4]); let inp2 = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); - let inp = MultipartInput::from([("interrupts",inp1),("bytes",inp2)]); + let inp = setup_interrupt_inputs(MultipartInput::from([("bytes",inp2)]), &interrupt_config); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); } }} else { diff --git a/fuzzers/FRET/src/systemstate/mutational.rs b/fuzzers/FRET/src/systemstate/mutational.rs index f10f86aea6..1f5187d486 100644 --- a/fuzzers/FRET/src/systemstate/mutational.rs +++ b/fuzzers/FRET/src/systemstate/mutational.rs @@ -19,13 +19,13 @@ use std::borrow::Cow; use super::stg::{STGEdge, STGNode}; -pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*us*/ * QEMU_ISNS_PER_USEC; +// pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*us*/ * QEMU_ISNS_PER_USEC; // one isn per 2**4 ns // virtual insn/sec 62500000 = 1/16 GHz // 1ms = 62500 insn // 1us = 62.5 insn -pub fn input_bytes_to_interrupt_times(buf: &[u8]) -> Vec { +pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize,u32)) -> Vec { let len = buf.len(); let mut start_tick : u32 = 0; let mut ret = Vec::with_capacity(DO_NUM_INTERRUPT); @@ -45,8 +45,8 @@ pub fn input_bytes_to_interrupt_times(buf: &[u8]) -> Vec { for i in 0..ret.len() { if ret[i]==0 {continue;} 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}); + 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); } else {break;} } } @@ -70,11 +70,11 @@ fn is_candidate_for_new_branches(graph: &DiGraph, node: NodeIn // TODO: this can be much more efficient, if the graph stored snapshots of the state and input progress was tracked /// Determines if a given node in the state transition graph (STG) is a candidate for introducing new branches. -pub fn try_force_new_branches(interrupt_ticks : &[u32], fbs: &STGFeedbackState, meta: &STGNodeMetadata) -> Option> { +pub fn try_force_new_branches(interrupt_ticks : &[u32], fbs: &STGFeedbackState, meta: &STGNodeMetadata, config: (usize, u32)) -> Option> { let mut new = false; let mut new_interrupt_times = Vec::new(); for (num,&interrupt_time) in interrupt_ticks.iter().enumerate() { - let lower_bound = if num==0 {FIRST_INT} else {interrupt_ticks[num-1].saturating_add(unsafe{MINIMUM_INTER_ARRIVAL_TIME})}; + let lower_bound = if num==0 {FIRST_INT} else {interrupt_ticks[num-1].saturating_add(config.1 * QEMU_ISNS_PER_USEC)}; let next = if interrupt_ticks.len()>num {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 @@ -98,6 +98,7 @@ pub fn try_force_new_branches(interrupt_ticks : &[u32], fbs: &STGFeedbackState, pub struct InterruptShiftStage { #[allow(clippy::type_complexity)] phantom: PhantomData<(E, EM, Z)>, + interrup_config: Vec<(usize,u32)> } impl InterruptShiftStage @@ -107,8 +108,8 @@ where Z: Evaluator, Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand, { - pub fn new() -> Self { - Self { phantom: PhantomData } + pub fn new(config : &Vec<(usize,u32)>) -> Self { + Self { phantom: PhantomData, interrup_config: config.clone() } } } @@ -133,20 +134,26 @@ where let mut myrand = StdRand::new(); myrand.set_seed(state.rand_mut().next()); + let mut loopcount = 0; let mut loopbound = 50; loop { + let interrup_config = match myrand.choose(&self.interrup_config) { + Some(s) => s, + Option::None => return Ok(()) + }; + let name = format!("interrupts_{}", interrup_config.0); // manager.log(state, LogSeverity::Info, format!("Mutation {}/{}", loopbound, loopcount))?; loopbound-=1; let current_case = state.current_testcase()?; let old_input = current_case.input().as_ref().unwrap(); - let old_interrupt_times = old_input.parts_by_name("interrupts").next(); + let old_interrupt_times = old_input.parts_by_name(&name).next(); let mut new_input = old_input.clone(); - let mut new_interrupt_times : &mut I = if new_input.parts_by_name("interrupts").next().is_some() { - new_input.parts_by_name_mut("interrupts").next().unwrap() + let mut new_interrupt_times : &mut I = if new_input.parts_by_name(&name).next().is_some() { + new_input.parts_by_name_mut(&name).next().unwrap() } else { - new_input.add_part(String::from("interrupts"), I::default()); new_input.parts_by_name_mut("interrupts").next().unwrap() + new_input.add_part(String::from(&name), I::default()); new_input.parts_by_name_mut(&name).next().unwrap() }.1; let mut do_rerun = false; // if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time @@ -156,7 +163,7 @@ where let mut interrupt_offsets : [u32; MAX_NUM_INTERRUPT] = [u32::MAX; MAX_NUM_INTERRUPT]; let mut num_interrupts : usize = 0; { - let t = input_bytes_to_interrupt_times(new_interrupt_times.bytes()); + let t = input_bytes_to_interrupt_times(new_interrupt_times.bytes(), *interrup_config); for i in 0..t.len() {interrupt_offsets[i]=t[i];} num_interrupts=t.len(); } @@ -180,7 +187,7 @@ where 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..myrand.between(0,2*min(MAX_NUM_INTERRUPT, maxtick as usize / unsafe{MINIMUM_INTER_ARRIVAL_TIME as usize})) { + for i in 0..myrand.between(0,2*min(MAX_NUM_INTERRUPT, maxtick as usize / (interrup_config.1 as usize * QEMU_ISNS_PER_USEC as usize))) { prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64) as usize).try_into().expect("ticks > u32"))); } } @@ -194,7 +201,7 @@ where } }; if let Some(meta) = current_case.metadata_map().get::() { - if let Some(t) = try_force_new_branches(&interrupt_offsets, feedbackstate, meta) { + if let Some(t) = try_force_new_branches(&interrupt_offsets, feedbackstate, meta, *interrup_config) { do_rerun = true; for i in 0..t.len() { if i u32"); if i > 0 { - lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); + lb = u32::saturating_add(interrupt_offsets[i-1], interrup_config.1 * QEMU_ISNS_PER_USEC); } if i < num_interrupts-1 { - ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); + ub = u32::saturating_sub(interrupt_offsets[i+1], interrup_config.1 * QEMU_ISNS_PER_USEC); } // get old hit and handler let old_hit = marks.iter().filter(