diff --git a/fuzzers/FRET/Cargo.toml b/fuzzers/FRET/Cargo.toml index ef2a0b6750..857125a86e 100644 --- a/fuzzers/FRET/Cargo.toml +++ b/fuzzers/FRET/Cargo.toml @@ -21,3 +21,5 @@ libafl = { path = "../../libafl/" } libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib hashbrown = { version = "0.12", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible +petgraph = { version="0.6.0", features = ["serde-1"] } +ron = "0.7" # write serialized data - including hashmaps \ No newline at end of file diff --git a/fuzzers/FRET/fuzzer.sh b/fuzzers/FRET/fuzzer.sh index 2e49804663..41084db1b7 100755 --- a/fuzzers/FRET/fuzzer.sh +++ b/fuzzers/FRET/fuzzer.sh @@ -5,4 +5,4 @@ [ -n "$4" -a "$4" != "+" -a -z "$BREAKPOINT" ] && export BREAKPOINT="$4" [ -n "$5" -a "$5" != "+" -a -z "$DO_SHOWMAP" ] && export DO_SHOWMAP="$5" [ -n "$6" -a "$6" != "+" -a -z "$SHOWMAP_TEXTINPUT" ] && export SHOWMAP_TEXTINPUT="$6" -target/debug/qemu_systemmode -icount shift=3,align=off,sleep=off -machine mps2-an385 -monitor null -kernel $KERNEL -serial null -nographic -snapshot -drive if=none,format=qcow2,file=dummy.qcow2 -S \ No newline at end of file +target/debug/qemu_systemmode -icount shift=3,align=off,sleep=off -machine mps2-an385 -monitor null -kernel $KERNEL -serial null -nographic -S -semihosting --semihosting-config enable=on,target=native # -snapshot -drive if=none,format=qcow2,file=dummy.qcow2 \ No newline at end of file diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 583c174ae9..f4d5ed9fab 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -28,18 +28,31 @@ use libafl::{ stages::StdMutationalStage, state::{HasCorpus, StdState}, Error, - prelude::{SimpleMonitor, SimpleEventManager}, + prelude::{SimpleMonitor, SimpleEventManager}, Evaluator, }; use libafl_qemu::{ edges, edges::QemuEdgeCoverageHelper, elf::EasyElf, emu::Emulator, GuestPhysAddr, QemuExecutor, - QemuHooks, Regs, + QemuHooks, Regs, QemuInstrumentationFilter, GuestAddr, }; use crate::{ - clock::{QemuClockObserver, ClockTimeFeedback}, + clock::{QemuClockObserver, ClockTimeFeedback, QemuClockIncreaseFeedback}, qemustate::QemuStateRestoreHelper, + systemstate::{helpers::QemuSystemStateHelper, observers::QemuSystemStateObserver, feedbacks::DumpSystraceFeedback}, }; -pub static mut MAX_INPUT_SIZE: usize = 50; +pub static mut MAX_INPUT_SIZE: usize = 32; +/// Read ELF program headers to resolve physical load addresses. +fn virt2phys(vaddr: GuestAddr, tab: &EasyElf) -> GuestAddr { + let ret; + for i in &tab.goblin().program_headers { + if i.vm_range().contains(&vaddr.try_into().unwrap()) { + ret = vaddr - TryInto::::try_into(i.p_vaddr).unwrap() + + TryInto::::try_into(i.p_paddr).unwrap(); + return ret - (ret % 2); + } + } + return vaddr; +} pub fn fuzz() { if let Ok(s) = env::var("FUZZ_SIZE") { @@ -64,7 +77,8 @@ pub fn fuzz() { &env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()), 0, ) - .expect("Symbol or env FUZZ_INPUT not found") as GuestPhysAddr; + .expect("Symbol or env FUZZ_INPUT not found") ; + let input_addr = virt2phys(input_addr,&elf) as GuestPhysAddr; println!("FUZZ_INPUT @ {:#x}", input_addr); let main_addr = elf @@ -72,6 +86,24 @@ pub fn fuzz() { .expect("Symbol main not found"); println!("main address = {:#x}", main_addr); + let curr_tcb_pointer = elf // loads to the address specified in elf, without respecting program headers + .resolve_symbol("pxCurrentTCB", 0) + .expect("Symbol pxCurrentTCBC not found"); + // let curr_tcb_pointer = virt2phys(curr_tcb_pointer,&elf); + println!("TCB pointer at {:#x}", curr_tcb_pointer); + let task_queue_addr = elf + .resolve_symbol("pxReadyTasksLists", 0) + .expect("Symbol pxReadyTasksLists not found"); + // let task_queue_addr = virt2phys(task_queue_addr,&elf.goblin()); + println!("Task Queue at {:#x}", task_queue_addr); + let svh = elf + .resolve_symbol("xPortPendSVHandler", 0) + .expect("Symbol xPortPendSVHandler not found"); + // let svh=virt2phys(svh, &elf); + // let svh = elf + // .resolve_symbol("vPortEnterCritical", 0) + // .expect("Symbol vPortEnterCritical not found"); + let breakpoint = elf .resolve_symbol( &env::var("BREAKPOINT").unwrap_or_else(|_| "BREAKPOINT".to_owned()), @@ -130,11 +162,15 @@ pub fn fuzz() { // Create an observation channel to keep track of the execution time let clock_time_observer = QemuClockObserver::new("clocktime"); + let systemstate_observer = QemuSystemStateObserver::new(); + // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR let mut feedback = feedback_or!( + DumpSystraceFeedback::with_dump(None), // New maximization map feedback linked to the edges observer and the feedback state MaxMapFeedback::new_tracking(&edges_observer, true, true), + // QemuClockIncreaseFeedback::default(), // Time feedback, this one does not need a feedback state ClockTimeFeedback::new_with_observer(&clock_time_observer) ); @@ -166,14 +202,15 @@ pub fn fuzz() { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - - let mut hooks = QemuHooks::new(&emu, tuple_list!(QemuEdgeCoverageHelper::default(),QemuStateRestoreHelper::new())); + let mut hooks = QemuHooks::new(&emu, + tuple_list!(QemuEdgeCoverageHelper::default(),QemuStateRestoreHelper::new(), + QemuSystemStateHelper::new(svh,curr_tcb_pointer,task_queue_addr,0))); // Create a QEMU in-process executor let executor = QemuExecutor::new( &mut hooks, &mut harness, - tuple_list!(edges_observer, clock_time_observer), + tuple_list!(edges_observer, clock_time_observer, systemstate_observer), &mut fuzzer, &mut state, &mut mgr, @@ -198,7 +235,7 @@ pub fn fuzz() { } else { fs::read(s).expect("Input file for DO_SHOWMAP can not be read") }; - fuzzer.execute_input(&mut state, &mut executor, &mut mgr, &BytesInput::new(show_input)) + fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input)) .unwrap(); } else { if state.corpus().count() < 1 { diff --git a/fuzzers/FRET/src/main.rs b/fuzzers/FRET/src/main.rs index ec434fb3e8..d6ca96089a 100644 --- a/fuzzers/FRET/src/main.rs +++ b/fuzzers/FRET/src/main.rs @@ -5,6 +5,8 @@ mod fuzzer; mod clock; #[cfg(target_os = "linux")] mod qemustate; +#[cfg(target_os = "linux")] +mod systemstate; #[cfg(target_os = "linux")] pub fn main() { diff --git a/fuzzers/FRET/src/systemstate/feedbacks.rs b/fuzzers/FRET/src/systemstate/feedbacks.rs new file mode 100644 index 0000000000..b64ee90000 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/feedbacks.rs @@ -0,0 +1,292 @@ +use libafl::SerdeAny; +use libafl::bolts::ownedref::OwnedSlice; +use libafl::inputs::BytesInput; +use libafl::prelude::UsesInput; +use libafl::state::HasNamedMetadata; +use std::path::PathBuf; +use crate::clock::QemuClockObserver; +use libafl::corpus::Testcase; +use libafl::bolts::tuples::MatchName; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::hash::Hash; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::feedbacks::Feedback; +use libafl::bolts::tuples::Named; +use libafl::Error; +use hashbrown::HashMap; +use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use serde::{Deserialize, Serialize}; + +use super::RefinedFreeRTOSSystemState; +use super::FreeRTOSSystemStateMetadata; +use super::observers::QemuSystemStateObserver; +use petgraph::prelude::DiGraph; +use petgraph::graph::NodeIndex; +use petgraph::Direction; +use std::cmp::Ordering; + +//============================= Feedback + +/// Shared Metadata for a systemstateFeedback +#[derive(Debug, Serialize, Deserialize, SerdeAny, Clone, Default)] +pub struct systemstateFeedbackState +{ + known_traces: HashMap, // encounters,ticks,length + longest: Vec, +} +impl Named for systemstateFeedbackState +{ + #[inline] + fn name(&self) -> &str { + "systemstate" + } +} +// impl FeedbackState for systemstateFeedbackState +// { +// fn reset(&mut self) -> Result<(), Error> { +// self.longest.clear(); +// self.known_traces.clear(); +// Ok(()) +// } +// } + +/// A Feedback reporting novel System-State Transitions. Depends on [`QemusystemstateObserver`] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct NovelSystemStateFeedback +{ + last_trace: Option>, + // known_traces: HashMap, +} + +impl Feedback for NovelSystemStateFeedback +where + S: UsesInput + HasClientPerfMonitor + HasNamedMetadata, +{ + fn is_interesting( + &mut self, + state: &mut S, + manager: &mut EM, + input: &S::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + { + let observer = observers.match_name::("systemstate") + .expect("QemusystemstateObserver not found"); + let clock_observer = observers.match_name::("clock") //TODO not fixed + .expect("QemusystemstateObserver not found"); + let feedbackstate = state + .named_metadata_mut() + .get_mut::("systemstate") + .unwrap(); + // 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) { + None => { + is_novel = true; + feedbackstate.known_traces.insert(somehash,(1,clock_observer.last_runtime(),observer.last_run.len())); + } + Some(s) => { + s.0+=1; + if s.1 < clock_observer.last_runtime() { + s.1 = clock_observer.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, testcase: &mut Testcase) -> Result<(), Error> { + let a = self.last_trace.take(); + match a { + Some(s) => testcase.metadata_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: &S::Input) -> Result<(), Error> { + self.last_trace = None; + Ok(()) + } +} + +impl Named for NovelSystemStateFeedback +{ + #[inline] + fn name(&self) -> &str { + "systemstate" + } +} + +//============================= + +pub fn match_traces(target: &Vec, last: &Vec) -> bool { + let mut ret = true; + if target.len() > last.len() {return false;} + for i in 0..target.len() { + ret &= target[i].current_task.task_name==last[i].current_task.task_name; + } + ret +} +pub fn match_traces_name(target: &Vec, last: &Vec) -> bool { + let mut ret = true; + if target.len() > last.len() {return false;} + for i in 0..target.len() { + ret &= target[i]==last[i].current_task.task_name; + } + ret +} + +/// A Feedback reporting novel System-State Transitions. Depends on [`QemusystemstateObserver`] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct HitsystemstateFeedback +{ + target: Option>, +} + +impl Feedback for HitsystemstateFeedback +where + S: UsesInput + HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + state: &mut S, + manager: &mut EM, + input: &S::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + { + let observer = observers.match_name::("systemstate") + .expect("QemusystemstateObserver not found"); + // Do Stuff + match &self.target { + Some(s) => { + // #[cfg(debug_assertions)] eprintln!("Hit systemstate Feedback trigger"); + Ok(match_traces_name(s, &observer.last_run)) + }, + None => Ok(false), + } + } +} + +impl Named for HitsystemstateFeedback +{ + #[inline] + fn name(&self) -> &str { + "hit_systemstate" + } +} + +impl HitsystemstateFeedback { + pub fn new(target: Option>) -> Self { + Self {target: target.map(|x| x.into_iter().map(|y| y.current_task.task_name).collect())} + } +} +//=========================== Debugging Feedback +/// A [`Feedback`] meant to dump the system-traces for debugging. Depends on [`QemusystemstateObserver`] +#[derive(Debug)] +pub struct DumpSystraceFeedback +{ + dumpfile: Option, + dump_metadata: bool, + last_trace: Option>, +} + +impl Feedback for DumpSystraceFeedback +where + S: UsesInput + HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + state: &mut S, + manager: &mut EM, + input: &S::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + { + let observer = observers.match_name::("systemstate") + .expect("QemusystemstateObserver not found"); + match &self.dumpfile { + Some(s) => { + std::fs::write(s,ron::to_string(&observer.last_run).expect("Error serializing hashmap")).expect("Can not dump to file"); + self.dumpfile = None + }, + None => if !self.dump_metadata {println!("{:?}",observer.last_run);} + }; + if self.dump_metadata {self.last_trace=Some(observer.last_run.clone());} + Ok(!self.dump_metadata) + } + /// Append to the testcase the generated metadata in case of a new corpus item + #[inline] + fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase) -> Result<(), Error> { + if !self.dump_metadata {return Ok(());} + let a = self.last_trace.take(); + match a { + Some(s) => testcase.metadata_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: &S::Input) -> Result<(), Error> { + self.last_trace = None; + Ok(()) + } +} + +impl Named for DumpSystraceFeedback +{ + #[inline] + fn name(&self) -> &str { + "Dumpsystemstate" + } +} + +impl DumpSystraceFeedback +{ + /// Creates a new [`DumpSystraceFeedback`] + #[must_use] + pub fn new() -> Self { + Self {dumpfile: None, dump_metadata: false, last_trace: None} + } + pub fn with_dump(dumpfile: Option) -> Self { + Self {dumpfile: dumpfile, dump_metadata: false, last_trace: None} + } + pub fn metadata_only() -> Self { + Self {dumpfile: None, dump_metadata: true, last_trace: None} + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/freertos.rs b/fuzzers/FRET/src/systemstate/freertos.rs new file mode 100644 index 0000000000..04e4338b5c --- /dev/null +++ b/fuzzers/FRET/src/systemstate/freertos.rs @@ -0,0 +1,122 @@ +#![allow(non_camel_case_types,non_snake_case,non_upper_case_globals,deref_nullptr)] +use serde::{Deserialize, Serialize}; +// Manual Types +use libafl_qemu::Emulator; + +/*========== Start of generated Code =============*/ +pub type char_ptr = ::std::os::raw::c_uint; +pub type ListItem_t_ptr = ::std::os::raw::c_uint; +pub type StackType_t_ptr = ::std::os::raw::c_uint; +pub type void_ptr = ::std::os::raw::c_uint; +pub type tskTaskControlBlock_ptr = ::std::os::raw::c_uint; +pub type xLIST_ptr = ::std::os::raw::c_uint; +pub type xLIST_ITEM_ptr = ::std::os::raw::c_uint; +/* automatically generated by rust-bindgen 0.59.2 */ + +pub type __uint8_t = ::std::os::raw::c_uchar; +pub type __uint16_t = ::std::os::raw::c_ushort; +pub type __uint32_t = ::std::os::raw::c_uint; +pub type StackType_t = u32; +pub type UBaseType_t = ::std::os::raw::c_uint; +pub type TickType_t = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] +pub struct xLIST_ITEM { + pub xItemValue: TickType_t, + pub pxNext: xLIST_ITEM_ptr, + pub pxPrevious: xLIST_ITEM_ptr, + pub pvOwner: void_ptr, + pub pvContainer: xLIST_ptr, +} +pub type ListItem_t = xLIST_ITEM; +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] +pub struct xMINI_LIST_ITEM { + pub xItemValue: TickType_t, + pub pxNext: xLIST_ITEM_ptr, + pub pxPrevious: xLIST_ITEM_ptr, +} +pub type MiniListItem_t = xMINI_LIST_ITEM; +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] +pub struct xLIST { + pub uxNumberOfItems: UBaseType_t, + pub pxIndex: ListItem_t_ptr, + pub xListEnd: MiniListItem_t, +} +pub type List_t = xLIST; +pub type TaskHandle_t = tskTaskControlBlock_ptr; +pub const eTaskState_eRunning: eTaskState = 0; +pub const eTaskState_eReady: eTaskState = 1; +pub const eTaskState_eBlocked: eTaskState = 2; +pub const eTaskState_eSuspended: eTaskState = 3; +pub const eTaskState_eDeleted: eTaskState = 4; +pub const eTaskState_eInvalid: eTaskState = 5; +pub type eTaskState = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] +pub struct xTASK_STATUS { + pub xHandle: TaskHandle_t, + pub pcTaskName: char_ptr, + pub xTaskNumber: UBaseType_t, + pub eCurrentState: eTaskState, + pub uxCurrentPriority: UBaseType_t, + pub uxBasePriority: UBaseType_t, + pub ulRunTimeCounter: u32, + pub pxStackBase: StackType_t_ptr, + pub usStackHighWaterMark: u16, +} +pub type TaskStatus_t = xTASK_STATUS; +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] +pub struct tskTaskControlBlock { + pub pxTopOfStack: StackType_t_ptr, + pub xStateListItem: ListItem_t, + pub xEventListItem: ListItem_t, + pub uxPriority: UBaseType_t, + pub pxStack: StackType_t_ptr, + pub pcTaskName: [::std::os::raw::c_char; 10usize], + pub uxBasePriority: UBaseType_t, + pub uxMutexesHeld: UBaseType_t, + pub ulNotifiedValue: [u32; 1usize], + pub ucNotifyState: [u8; 1usize], + pub ucStaticallyAllocated: u8, + pub ucDelayAborted: u8, +} +pub type tskTCB = tskTaskControlBlock; +pub type TCB_t = tskTCB; +/*========== End of generated Code =============*/ + +pub trait emu_lookup { + fn lookup(emu: &Emulator, addr: ::std::os::raw::c_uint) -> Self; +} + + +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum rtos_struct { + TCB_struct(TCB_t), + List_struct(List_t), + List_Item_struct(ListItem_t), + List_MiniItem_struct(MiniListItem_t), +} + +#[macro_export] +macro_rules! impl_emu_lookup { + ($struct_name:ident) => { + impl $crate::systemstate::freertos::emu_lookup for $struct_name { + fn lookup(emu: &Emulator, 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); + std::mem::transmute::<[u8; std::mem::size_of::<$struct_name>()], $struct_name>(tmp) + } + } + } + }; +} +impl_emu_lookup!(TCB_t); +impl_emu_lookup!(List_t); +impl_emu_lookup!(ListItem_t); +impl_emu_lookup!(MiniListItem_t); +impl_emu_lookup!(void_ptr); +impl_emu_lookup!(TaskStatus_t); diff --git a/fuzzers/FRET/src/systemstate/graph.rs b/fuzzers/FRET/src/systemstate/graph.rs new file mode 100644 index 0000000000..72698e993a --- /dev/null +++ b/fuzzers/FRET/src/systemstate/graph.rs @@ -0,0 +1,590 @@ + +/// Feedbacks organizing SystemStates as a graph +use libafl::inputs::HasBytesVec; +use libafl::bolts::rands::RandomSeed; +use libafl::bolts::rands::StdRand; +use libafl::mutators::Mutator; +use libafl::mutators::MutationResult; +use core::marker::PhantomData; +use libafl::state::HasCorpus; +use libafl::state::HasSolutions; +use libafl::state::HasRand; +use crate::worst::MaxExecsLenFavFactor; +use libafl::corpus::MinimizerCorpusScheduler; +use libafl::bolts::HasRefCnt; +use libafl::bolts::AsSlice; +use libafl::bolts::ownedref::OwnedSlice; +use libafl::inputs::BytesInput; +use std::path::PathBuf; +use crate::clock::QemuClockObserver; +use libafl::corpus::Testcase; +use libafl::bolts::tuples::MatchName; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::hash::Hash; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::feedbacks::Feedback; +use libafl::bolts::tuples::Named; +use libafl::Error; +use hashbrown::HashMap; +use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use serde::{Deserialize, Serialize}; + +use super::RefinedFreeRTOSSystemState; +use super::FreeRTOSSystemStateMetadata; +use super::observers::QemusystemstateObserver; +use petgraph::prelude::DiGraph; +use petgraph::graph::NodeIndex; +use petgraph::Direction; +use std::cmp::Ordering; + +use libafl::bolts::rands::Rand; + +//============================= Data Structures +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub struct VariantTuple +{ + pub start_tick: u64, + pub end_tick: u64, + input_counter: u32, + pub input: Vec, // in the end any kind of input are bytes, regardless of type and lifetime +} +impl VariantTuple { + fn from(other: &RefinedFreeRTOSSystemState,input: Vec) -> Self { + VariantTuple{ + start_tick: other.start_tick, + end_tick: other.end_tick, + input_counter: other.input_counter, + input: input, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct SysGraphNode +{ + base: RefinedFreeRTOSSystemState, + pub variants: Vec, +} +impl SysGraphNode { + fn from(base: RefinedFreeRTOSSystemState, input: Vec) -> Self { + SysGraphNode{variants: vec![VariantTuple::from(&base, input)], base:base } + } + /// unites the variants of this value with another, draining the other if the bases are equal + fn unite(&mut self, other: &mut SysGraphNode) -> bool { + if self!=other {return false;} + self.variants.append(&mut other.variants); + self.variants.dedup(); + return true; + } + /// add a Varint from a [`RefinedFreeRTOSSystemState`] + fn unite_raw(&mut self, other: &RefinedFreeRTOSSystemState, input: &Vec) -> bool { + if &self.base!=other {return false;} + self.variants.push(VariantTuple::from(other, input.clone())); + self.variants.dedup(); + return true; + } + /// add a Varint from a [`RefinedFreeRTOSSystemState`], if it's interesting + fn unite_interesting(&mut self, other: &RefinedFreeRTOSSystemState, input: &Vec) -> bool { + if &self.base!=other {return false;} + let interesting = + self.variants.iter().all(|x| x.end_tick-x.start_tickother.end_tick-other.start_tick) || // shortest variant + self.variants.iter().all(|x| x.input_counter>other.input_counter) || // longest input + self.variants.iter().all(|x| x.input_counter &str { + &self.base.current_task.task_name + } + pub fn get_input_counts(&self) -> Vec { + self.variants.iter().map(|x| x.input_counter).collect() + } +} +impl PartialEq for SysGraphNode { + fn eq(&self, other: &SysGraphNode) -> bool { + self.base==other.base + } +} + +// Wrapper around Vec to attach as Metadata +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct SysGraphMetadata { + pub inner: Vec, + indices: Vec, + tcref: isize, +} +impl SysGraphMetadata { + pub fn new(inner: Vec) -> Self{ + Self {indices: inner.iter().map(|x| x.index()).collect(), inner: inner, tcref: 0} + } +} +impl AsSlice for SysGraphMetadata { + /// Convert the slice of system-states to a slice of hashes over enumerated states + fn as_slice(&self) -> &[usize] { + self.indices.as_slice() + } +} + +impl HasRefCnt for SysGraphMetadata { + fn refcnt(&self) -> isize { + self.tcref + } + + fn refcnt_mut(&mut self) -> &mut isize { + &mut self.tcref + } +} + +libafl::impl_serdeany!(SysGraphMetadata); + +pub type GraphMaximizerCorpusScheduler = + MinimizerCorpusScheduler, I, SysGraphMetadata, S>; + +//============================= Graph Feedback + +/// Improved System State Graph +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct SysGraphFeedbackState +{ + pub graph: DiGraph, + entrypoint: NodeIndex, + exit: NodeIndex, + name: String, +} +impl SysGraphFeedbackState +{ + pub fn new() -> Self { + let mut graph = DiGraph::::new(); + let mut entry = SysGraphNode::default(); + entry.base.current_task.task_name="Start".to_string(); + let mut exit = SysGraphNode::default(); + exit.base.current_task.task_name="End".to_string(); + let entry = graph.add_node(entry); + let exit = graph.add_node(exit); + Self {graph: graph, entrypoint: entry, exit: exit, name: String::from("SysMap")} + } + fn insert(&mut self, list: Vec, input: &Vec) { + let mut current_index = self.entrypoint; + for n in list { + let mut done = false; + for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) { + if n == self.graph[i].base { + done = true; + current_index = i; + break; + } + } + if !done { + let j = self.graph.add_node(SysGraphNode::from(n,input.clone())); + self.graph.add_edge(current_index, j, ()); + current_index = j; + } + } + } + /// Try adding a system state path from a [Vec], return true if the path was interesting + fn update(&mut self, list: &Vec, input: &Vec) -> (bool, Vec) { + let mut current_index = self.entrypoint; + let mut novel = false; + let mut trace : Vec = vec![current_index]; + for n in list { + let mut matching : Option = None; + for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) { + let tmp = &self.graph[i]; + if n == &tmp.base { + matching = Some(i); + current_index = i; + break; + } + } + match matching { + None => { + novel = true; + let j = self.graph.add_node(SysGraphNode::from(n.clone(),input.clone())); + self.graph.add_edge(current_index, j, ()); + current_index = j; + }, + Some(i) => { + novel |= self.graph[i].unite_interesting(&n, input); + } + } + trace.push(current_index); + } + self.graph.update_edge(current_index, self.exit, ()); // every path ends in the exit noded + return (novel, trace); + } +} +impl Named for SysGraphFeedbackState +{ + #[inline] + fn name(&self) -> &str { + &self.name + } +} +impl FeedbackState for SysGraphFeedbackState +{ + fn reset(&mut self) -> Result<(), Error> { + self.graph.clear(); + let mut entry = SysGraphNode::default(); + entry.base.current_task.task_name="Start".to_string(); + let mut exit = SysGraphNode::default(); + exit.base.current_task.task_name="End".to_string(); + self.entrypoint = self.graph.add_node(entry); + self.exit = self.graph.add_node(exit); + Ok(()) + } +} + +/// A Feedback reporting novel System-State Transitions. Depends on [`QemusystemstateObserver`] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct SysMapFeedback +{ + name: String, + last_trace: Option>, +} +impl SysMapFeedback { + pub fn new() -> Self { + Self {name: String::from("SysMapFeedback"), last_trace: None } + } +} + +impl Feedback for SysMapFeedback +where + I: Input, + S: HasClientPerfMonitor + HasFeedbackStates, +{ + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = observers.match_name::("systemstate") + .expect("QemusystemstateObserver not found"); + let feedbackstate = state + .feedback_states_mut() + .match_name_mut::("SysMap") + .unwrap(); + let ret = feedbackstate.update(&observer.last_run, &observer.last_input); + self.last_trace = Some(ret.1); + Ok(ret.0) + } + + /// Append to the testcase the generated metadata in case of a new corpus item + #[inline] + fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase) -> Result<(), Error> { + let a = self.last_trace.take(); + match a { + Some(s) => testcase.metadata_mut().insert(SysGraphMetadata::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> { + self.last_trace = None; + Ok(()) + } +} +impl Named for SysMapFeedback +{ + #[inline] + fn name(&self) -> &str { + &self.name + } +} + +//============================= Mutators +//=============================== Snippets +pub struct RandGraphSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + phantom: PhantomData<(I, S)>, +} +impl RandGraphSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + pub fn new() -> Self { + RandGraphSnippetMutator{phantom: PhantomData} + } +} +impl Mutator for RandGraphSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn mutate( + &mut self, + state: &mut S, + input: &mut I, + _stage_idx: i32 + ) -> Result + { + // need our own random generator, because borrowing rules + let mut myrand = StdRand::new(); + let tmp = &mut state.rand_mut(); + myrand.set_seed(tmp.next()); + drop(tmp); + + let feedbackstate = state + .feedback_states() + .match_name::("SysMap") + .unwrap(); + let g = &feedbackstate.graph; + let tmp = state.metadata().get::(); + if tmp.is_none() { // if there are no metadata it was probably not interesting anyways + return Ok(MutationResult::Skipped); + } + let trace =tmp.expect("SysGraphMetadata not found"); + // follow the path, extract snippets from last reads, find common snippets. + // those are likley keys parts. choose random parts from other sibling traces + let sibling_inputs : Vec<&Vec>= g[*trace.inner.last().unwrap()].variants.iter().map(|x| &x.input).collect(); + let mut snippet_collector = vec![]; + let mut per_input_counters = HashMap::<&Vec,usize>::new(); // ugly workaround to track multiple inputs + for t in &trace.inner { + let node = &g[*t]; + let mut per_node_snippets = HashMap::<&Vec,&[u8]>::new(); + for v in &node.variants { + match per_input_counters.get_mut(&v.input) { + None => { + if sibling_inputs.iter().any(|x| *x==&v.input) { // only collect info about siblin inputs from target + per_input_counters.insert(&v.input, v.input_counter.try_into().unwrap()); + } + }, + Some(x) => { + let x_u = *x; + if x_u = vec![]; + for c in snippet_collector { + new_input.extend_from_slice(myrand.choose(c).1); + } + for i in new_input.iter().enumerate() { + input.bytes_mut()[i.0]=*i.1; + } + + Ok(MutationResult::Mutated) + } + + fn post_exec( + &mut self, + _state: &mut S, + _stage_idx: i32, + _corpus_idx: Option + ) -> Result<(), Error> { + Ok(()) + } +} + +impl Named for RandGraphSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn name(&self) -> &str { + "RandGraphSnippetMutator" + } +} +//=============================== Snippets +pub struct RandInputSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + phantom: PhantomData<(I, S)>, +} +impl RandInputSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + pub fn new() -> Self { + RandInputSnippetMutator{phantom: PhantomData} + } +} +impl Mutator for RandInputSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn mutate( + &mut self, + state: &mut S, + input: &mut I, + _stage_idx: i32 + ) -> Result + { + // need our own random generator, because borrowing rules + let mut myrand = StdRand::new(); + let tmp = &mut state.rand_mut(); + myrand.set_seed(tmp.next()); + drop(tmp); + + let feedbackstate = state + .feedback_states() + .match_name::("SysMap") + .unwrap(); + let g = &feedbackstate.graph; + let tmp = state.metadata().get::(); + if tmp.is_none() { // if there are no metadata it was probably not interesting anyways + return Ok(MutationResult::Skipped); + } + let trace = tmp.expect("SysGraphMetadata not found"); + + let mut collection : Vec> = Vec::new(); + let mut current_pointer : usize = 0; + for t in &trace.inner { + let node = &g[*t]; + for v in &node.variants { + if v.input == input.bytes() { + if v.input_counter > current_pointer.try_into().unwrap() { + collection.push(v.input[current_pointer..v.input_counter as usize].to_owned()); + current_pointer = v.input_counter as usize; + } + break; + } + } + } + let index_to_mutate = myrand.below(collection.len() as u64) as usize; + for i in 0..collection[index_to_mutate].len() { + collection[index_to_mutate][i] = myrand.below(0xFF) as u8; + } + for i in collection.concat().iter().enumerate() { + input.bytes_mut()[i.0]=*i.1; + } + + Ok(MutationResult::Mutated) + } + + fn post_exec( + &mut self, + _state: &mut S, + _stage_idx: i32, + _corpus_idx: Option + ) -> Result<(), Error> { + Ok(()) + } +} + +impl Named for RandInputSnippetMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn name(&self) -> &str { + "RandInputSnippetMutator" + } +} +//=============================== Suffix +pub struct RandGraphSuffixMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + phantom: PhantomData<(I, S)>, +} +impl RandGraphSuffixMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + pub fn new() -> Self { + RandGraphSuffixMutator{phantom: PhantomData} + } +} +impl Mutator for RandGraphSuffixMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn mutate( + &mut self, + state: &mut S, + input: &mut I, + _stage_idx: i32 + ) -> Result + { + // need our own random generator, because borrowing rules + let mut myrand = StdRand::new(); + let tmp = &mut state.rand_mut(); + myrand.set_seed(tmp.next()); + drop(tmp); + + let feedbackstate = state + .feedback_states() + .match_name::("SysMap") + .unwrap(); + let g = &feedbackstate.graph; + let tmp = state.metadata().get::(); + if tmp.is_none() { // if there are no metadata it was probably not interesting anyways + return Ok(MutationResult::Skipped); + } + let trace =tmp.expect("SysGraphMetadata not found"); + // follow the path, extract snippets from last reads, find common snippets. + // those are likley keys parts. choose random parts from other sibling traces + let inp_c_end = g[*trace.inner.last().unwrap()].base.input_counter; + let mut num_to_reverse = myrand.below(trace.inner.len().try_into().unwrap()); + for t in trace.inner.iter().rev() { + let int_c_prefix = g[*t].base.input_counter; + if int_c_prefix < inp_c_end { + num_to_reverse-=1; + if num_to_reverse<=0 { + let mut new_input=input.bytes()[..(int_c_prefix as usize)].to_vec(); + let mut ext : Vec = (int_c_prefix..inp_c_end).map(|_| myrand.next().to_le_bytes()).flatten().collect(); + new_input.append(&mut ext); + for i in new_input.iter().enumerate() { + if input.bytes_mut().len()>i.0 { + input.bytes_mut()[i.0]=*i.1; + } + else { break }; + } + break; + } + } + } + Ok(MutationResult::Mutated) + } + + fn post_exec( + &mut self, + _state: &mut S, + _stage_idx: i32, + _corpus_idx: Option + ) -> Result<(), Error> { + Ok(()) + } +} + +impl Named for RandGraphSuffixMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn name(&self) -> &str { + "RandGraphSuffixMutator" + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs new file mode 100644 index 0000000000..55085f93c6 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -0,0 +1,141 @@ +use std::io::Write; +use libafl::prelude::UsesInput; +use libafl_qemu::GuestAddr; +use libafl_qemu::QemuHooks; +use libafl_qemu::edges::QemuEdgesMapMetadata; +use libafl_qemu::emu; +use libafl_qemu::hooks; +use crate::systemstate::RawFreeRTOSSystemState; +use crate::systemstate::CURRENT_SYSTEMSTATE_VEC; +use crate::systemstate::NUM_PRIOS; +use super::freertos::TCB_t; +use super::freertos::rtos_struct::List_Item_struct; +use super::freertos::rtos_struct::*; +use super::freertos; + +use libafl_qemu::{ + helper::{QemuHelper, QemuHelperTuple}, + // edges::SAVED_JUMP, +}; + +//============================= Struct definitions + +pub static mut INTR_OFFSET : Option = None; +pub static mut INTR_DONE : bool = true; + +// only used when inputs are injected +pub static mut NEXT_INPUT : Vec = Vec::new(); + +//============================= Qemu Helper + +/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs +#[derive(Debug)] +pub struct QemuSystemStateHelper { + kerneladdr: u32, + tcb_addr: u32, + ready_queues: u32, + input_counter: u32, +} + +impl QemuSystemStateHelper { + #[must_use] + pub fn new( + kerneladdr: u32, + tcb_addr: u32, + ready_queues: u32, + input_counter: u32, + ) -> Self { + QemuSystemStateHelper { + kerneladdr, + tcb_addr: tcb_addr, + ready_queues: ready_queues, + input_counter: input_counter, + } + } +} + +impl QemuHelper for QemuSystemStateHelper +where + S: UsesInput, +{ + fn first_exec(&self, _hooks: &QemuHooks<'_, QT, S>) + where + QT: QemuHelperTuple, + { + _hooks.instruction(self.kerneladdr, exec_syscall_hook::, false) + } +} + +pub fn exec_syscall_hook( + hooks: &mut QemuHooks<'_, QT, S>, + _state: Option<&mut S>, + _pc: u32, +) +where + S: UsesInput, + QT: QemuHelperTuple, +{ + let emulator = hooks.emulator(); + let h = hooks.helpers().match_first_type::().expect("QemuSystemHelper not found in helper tupel"); + let listbytes : u32 = u32::try_from(std::mem::size_of::()).unwrap(); + let mut systemstate = RawFreeRTOSSystemState::default(); + unsafe { + // TODO: investigate why can_do_io is not set sometimes, as this is just a workaround + let c = emulator.current_cpu().unwrap(); + let can_do_io = (*c.raw_ptr()).can_do_io; + (*c.raw_ptr()).can_do_io = 1; + systemstate.qemu_tick = emu::icount_get_raw(); + (*c.raw_ptr()).can_do_io = can_do_io; + } + let mut buf : [u8; 4] = [0,0,0,0]; + // unsafe { emulator.read_mem(h.input_counter.into(), &mut buf) }; + systemstate.input_counter = u32::from_le_bytes(buf); + + let curr_tcb_addr : freertos::void_ptr = freertos::emu_lookup::lookup(emulator, h.tcb_addr); + if curr_tcb_addr == 0 { + return; + }; + systemstate.current_tcb = freertos::emu_lookup::lookup(emulator,curr_tcb_addr); + + // unsafe { + // match SAVED_JUMP.take() { + // Some(s) => { + // systemstate.last_pc = Some(s.0); + // }, + // None => (), + // } + // } + // println!("{:?}",std::str::from_utf8(¤t_tcb.pcTaskName)); + + for i in 0..NUM_PRIOS { + let target : u32 = listbytes*u32::try_from(i).unwrap()+h.ready_queues; + systemstate.prio_ready_lists[i] = freertos::emu_lookup::lookup(emulator, target); + // println!("List at {}: {:?}",target, systemstate.prio_ready_lists[i]); + let mut next_index = systemstate.prio_ready_lists[i].pxIndex; + for _j in 0..systemstate.prio_ready_lists[i].uxNumberOfItems { + // always jump over the xListEnd marker + if (target..target+listbytes).contains(&next_index) { + let next_item : freertos::MiniListItem_t = freertos::emu_lookup::lookup(emulator, next_index); + let new_next_index=next_item.pxNext; + systemstate.dumping_ground.insert(next_index,List_MiniItem_struct(next_item)); + next_index = new_next_index; + } + let next_item : freertos::ListItem_t = freertos::emu_lookup::lookup(emulator, next_index); + // println!("Item at {}: {:?}",next_index,next_item); + assert_eq!(next_item.pvContainer,target); + let new_next_index=next_item.pxNext; + let next_tcb : TCB_t= freertos::emu_lookup::lookup(emulator,next_item.pvOwner); + // println!("TCB at {}: {:?}",next_item.pvOwner,next_tcb); + systemstate.dumping_ground.insert(next_item.pvOwner,TCB_struct(next_tcb.clone())); + systemstate.dumping_ground.insert(next_index,List_Item_struct(next_item)); + next_index=new_next_index; + } + // Handle edge case where the end marker was not included yet + if (target..target+listbytes).contains(&next_index) { + let next_item : freertos::MiniListItem_t = freertos::emu_lookup::lookup(emulator, next_index); + systemstate.dumping_ground.insert(next_index,List_MiniItem_struct(next_item)); + } + } + + unsafe { CURRENT_SYSTEMSTATE_VEC.push(systemstate); } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/mod.rs b/fuzzers/FRET/src/systemstate/mod.rs new file mode 100644 index 0000000000..d85aaf3a06 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/mod.rs @@ -0,0 +1,165 @@ +//! systemstate referes to the State of a FreeRTOS fuzzing target +use std::collections::hash_map::DefaultHasher; +use libafl::bolts::HasRefCnt; +use libafl::bolts::AsSlice; +use std::hash::Hasher; +use std::hash::Hash; +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; + +use freertos::TCB_t; + +pub mod freertos; +pub mod helpers; +pub mod observers; +pub mod feedbacks; +// pub mod graph; +// pub mod mutators; + +#[cfg(feature = "fuzz_interrupt")] +pub const IRQ_INPUT_BYTES_NUMBER : u32 = 2; // Offset for interrupt bytes +#[cfg(not(feature = "fuzz_interrupt"))] +pub const IRQ_INPUT_BYTES_NUMBER : u32 = 0; // Offset for interrupt bytes +pub const IRQ_INPUT_OFFSET : u32 = 347780; // Tick offset for app code start + +// Constants +const NUM_PRIOS: usize = 5; + +//============================= Struct definitions +/// Raw info Dump from Qemu +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct RawFreeRTOSSystemState { + qemu_tick: u64, + current_tcb: TCB_t, + prio_ready_lists: [freertos::List_t; NUM_PRIOS], + dumping_ground: HashMap, + input_counter: u32, + last_pc: Option, +} +/// List of system state dumps from QemuHelpers +static mut CURRENT_SYSTEMSTATE_VEC: Vec = vec![]; + +/// A reduced version of freertos::TCB_t +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] +pub struct RefinedTCB { + task_name: String, + priority: u32, + base_priority: u32, + mutexes_held: u32, + notify_value: u32, + notify_state: u8, +} + +impl Hash for RefinedTCB { + fn hash(&self, state: &mut H) { + self.task_name.hash(state); + // self.priority.hash(state); + // self.mutexes_held.hash(state); + // self.notify_state.hash(state); + // self.notify_value.hash(state); + } +} + +impl RefinedTCB { + pub fn from_tcb(input: &TCB_t) -> Self { + unsafe { + let tmp = std::mem::transmute::<[i8; 10],[u8; 10]>(input.pcTaskName); + let name : String = std::str::from_utf8(&tmp).expect("TCB name was not utf8").chars().filter(|x| *x != '\0').collect::(); + Self { + task_name: name, + priority: input.uxPriority, + base_priority: input.uxBasePriority, + mutexes_held: input.uxMutexesHeld, + notify_value: input.ulNotifiedValue[0], + notify_state: input.ucNotifyState[0], + } + } + } + pub fn from_tcb_owned(input: TCB_t) -> Self { + unsafe { + let tmp = std::mem::transmute::<[i8; 10],[u8; 10]>(input.pcTaskName); + let name : String = std::str::from_utf8(&tmp).expect("TCB name was not utf8").chars().filter(|x| *x != '\0').collect::(); + Self { + task_name: name, + priority: input.uxPriority, + base_priority: input.uxBasePriority, + mutexes_held: input.uxMutexesHeld, + notify_value: input.ulNotifiedValue[0], + notify_state: input.ucNotifyState[0], + } + } + } +} + +/// Refined information about the states an execution transitioned between +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct RefinedFreeRTOSSystemState { + start_tick: u64, + end_tick: u64, + last_pc: Option, + input_counter: u32, + current_task: RefinedTCB, + ready_list_after: Vec, +} +impl PartialEq for RefinedFreeRTOSSystemState { + fn eq(&self, other: &Self) -> bool { + self.current_task == other.current_task && self.ready_list_after == other.ready_list_after && + self.last_pc == other.last_pc + } +} + +impl Hash for RefinedFreeRTOSSystemState { + fn hash(&self, state: &mut H) { + self.current_task.hash(state); + self.ready_list_after.hash(state); + self.last_pc.hash(state); + } +} +impl RefinedFreeRTOSSystemState { + fn get_time(&self) -> u64 { + self.end_tick-self.start_tick + } +} + +// Wrapper around Vec to attach as Metadata +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct FreeRTOSSystemStateMetadata { + inner: Vec, + indices: Vec, // Hashed enumeration of States + tcref: isize, +} +impl FreeRTOSSystemStateMetadata { + pub fn new(inner: Vec) -> Self{ + let tmp = inner.iter().enumerate().map(|x| compute_hash(x) as usize).collect(); + Self {inner: inner, indices: tmp, tcref: 0} + } +} +pub fn compute_hash(obj: T) -> u64 +where + T: Hash +{ + let mut s = DefaultHasher::new(); + obj.hash(&mut s); + s.finish() +} + +impl AsSlice for FreeRTOSSystemStateMetadata { + /// Convert the slice of system-states to a slice of hashes over enumerated states + fn as_slice(&self) -> &[usize] { + self.indices.as_slice() + } + + type Entry = usize; +} + +impl HasRefCnt for FreeRTOSSystemStateMetadata { + fn refcnt(&self) -> isize { + self.tcref + } + + fn refcnt_mut(&mut self) -> &mut isize { + &mut self.tcref + } +} + +libafl::impl_serdeany!(FreeRTOSSystemStateMetadata); diff --git a/fuzzers/FRET/src/systemstate/mutators.rs b/fuzzers/FRET/src/systemstate/mutators.rs new file mode 100644 index 0000000000..14bc7e40fa --- /dev/null +++ b/fuzzers/FRET/src/systemstate/mutators.rs @@ -0,0 +1,119 @@ +use crate::systemstate::graph::SysGraphMetadata; +use crate::systemstate::graph::SysGraphNode; +use crate::systemstate::IRQ_INPUT_OFFSET; +use crate::systemstate::IRQ_INPUT_BYTES_NUMBER; +use crate::systemstate::graph::SysGraphFeedbackState; +use libafl::inputs::HasBytesVec; +use libafl::bolts::rands::RandomSeed; +use libafl::bolts::rands::StdRand; +use libafl::mutators::Mutator; +use libafl::mutators::MutationResult; +use libafl::prelude::UsesInput; +use core::marker::PhantomData; +use libafl::state::HasCorpus; +use libafl::state::HasSolutions; +use libafl::state::HasRand; + +use libafl::bolts::tuples::MatchName; +use libafl::bolts::tuples::Named; +use libafl::Error; +use libafl::{inputs::Input, state::HasMetadata}; + +use super::FreeRTOSSystemStateMetadata; + +use libafl::bolts::rands::Rand; + + +//=============================== Interrupt +/// Sets up the interrupt to a random block in the trace. Works for both state and graph metadata +pub struct InterruptShifterMutator +where + S: UsesInput, +{ + phantom: PhantomData, +} +impl InterruptShifterMutator +where + S: UsesInput, +{ + pub fn new() -> Self { + InterruptShifterMutator{phantom: PhantomData} + } +} +impl Mutator for InterruptShifterMutator +where + S: UsesInput, +{ + fn mutate( + &mut self, + state: &mut S, + input: &mut S::Input, + _stage_idx: i32 + ) -> Result + { + // need our own random generator, because borrowing rules + let mut myrand = StdRand::new(); + let tmp = &mut state.rand_mut(); + myrand.set_seed(tmp.next()); + drop(tmp); + + let target_bytes = input.bytes_mut(); + let mut target_tick = 0; + + #[cfg(feature = "sched_state")] + { + let tmp = state.metadata().get::(); + if tmp.is_none() { // if there are no metadata it was probably not interesting anyways + return Ok(MutationResult::Skipped); + } + let trace =tmp.expect("FreeRTOSSystemStateMetadata not found"); + let target_block = myrand.choose(trace.inner.iter()); + target_tick = myrand.between(target_block.start_tick,target_block.end_tick)-IRQ_INPUT_OFFSET as u64; + } + #[cfg(feature = "sched_state")] + { + let feedbackstate = state + .feedback_states() + .match_name::("SysMap") + .unwrap(); + let g = &feedbackstate.graph; + let tmp = state.metadata().get::(); + if tmp.is_none() { // if there are no metadata it was probably not interesting anyways + return Ok(MutationResult::Skipped); + } + let trace = tmp.expect("SysGraphMetadata not found"); + let target_block : &SysGraphNode = &g[*myrand.choose(trace.inner.iter())]; + target_tick = match target_block.variants.iter().find(|x| &x.input == target_bytes) { + Some(s) => myrand.between(s.start_tick,s.end_tick)-IRQ_INPUT_OFFSET as u64, + None => myrand.between(target_block.variants[0].start_tick,target_block.variants[0].end_tick)-IRQ_INPUT_OFFSET as u64, + }; + + } + if target_bytes.len() > IRQ_INPUT_BYTES_NUMBER as usize && IRQ_INPUT_BYTES_NUMBER > 0 { + for i in 0..IRQ_INPUT_BYTES_NUMBER as usize { + target_bytes[i] = u64::to_le_bytes(target_tick)[i]; + } + return Ok(MutationResult::Mutated); + } else { + return Ok(MutationResult::Skipped); + } + } + + fn post_exec( + &mut self, + _state: &mut S, + _stage_idx: i32, + _corpus_idx: Option + ) -> Result<(), Error> { + Ok(()) + } +} + +impl Named for InterruptShifterMutator +where + S: UsesInput, +{ + fn name(&self) -> &str { + "InterruptShifterMutator" + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/observers.rs b/fuzzers/FRET/src/systemstate/observers.rs new file mode 100644 index 0000000000..4941f45b5b --- /dev/null +++ b/fuzzers/FRET/src/systemstate/observers.rs @@ -0,0 +1,133 @@ +use crate::systemstate::IRQ_INPUT_BYTES_NUMBER; +use libafl::prelude::{ExitKind, AsSlice}; +use libafl::{inputs::HasTargetBytes, prelude::UsesInput}; +use libafl::bolts::HasLen; +use libafl::bolts::tuples::Named; +use libafl::Error; +use libafl::observers::Observer; +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; + +use super::{ + CURRENT_SYSTEMSTATE_VEC, + RawFreeRTOSSystemState, + RefinedTCB, + RefinedFreeRTOSSystemState, + freertos::{List_t, TCB_t, rtos_struct, rtos_struct::*}, +}; + +//============================= Observer + +/// The Qemusystemstate Observer retrieves the systemstate +/// that will get updated by the target. +#[derive(Serialize, Deserialize, Debug, Default)] +#[allow(clippy::unsafe_derive_deserialize)] +pub struct QemuSystemStateObserver +{ + pub last_run: Vec, + pub last_input: Vec, + name: String, +} + +impl Observer for QemuSystemStateObserver +where + S: UsesInput, + S::Input : HasTargetBytes, +{ + #[inline] + fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { + unsafe {CURRENT_SYSTEMSTATE_VEC.clear(); } + Ok(()) + } + + #[inline] + fn post_exec(&mut self, _state: &mut S, _input: &S::Input, _exit_kind: &ExitKind) -> Result<(), Error> { + unsafe {self.last_run = refine_system_states(&mut CURRENT_SYSTEMSTATE_VEC);} + self.last_input=_input.target_bytes().as_slice().to_owned(); + Ok(()) + } +} + +impl Named for QemuSystemStateObserver +{ + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl HasLen for QemuSystemStateObserver +{ + #[inline] + fn len(&self) -> usize { + self.last_run.len() + } +} + +impl QemuSystemStateObserver { + pub fn new() -> Self { + Self{last_run: vec![], last_input: vec![], name: "systemstate".to_string()} + } + +} + +//============================= Parsing helpers + +/// Parse a List_t containing TCB_t into Vec from cache. Consumes the elements from cache +fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap) -> Vec +{ + let mut ret : Vec = Vec::new(); + if list.uxNumberOfItems == 0 {return ret;} + let last_list_item = match dump.remove(&list.pxIndex).expect("List_t entry was not in Hashmap") { + List_Item_struct(li) => li, + List_MiniItem_struct(mli) => match dump.remove(&mli.pxNext).expect("MiniListItem pointer invaild") { + List_Item_struct(li) => li, + _ => panic!("MiniListItem of a non empty List does not point to ListItem"), + }, + _ => panic!("List_t entry was not a ListItem"), + }; + let mut next_index = last_list_item.pxNext; + let last_tcb = match dump.remove(&last_list_item.pvOwner).expect("ListItem Owner not in Hashmap") { + TCB_struct(t) => t, + _ => panic!("List content does not equal type"), + }; + for _ in 0..list.uxNumberOfItems-1 { + let next_list_item = match dump.remove(&next_index).expect("List_t entry was not in Hashmap") { + List_Item_struct(li) => li, + List_MiniItem_struct(mli) => match dump.remove(&mli.pxNext).expect("MiniListItem pointer invaild") { + List_Item_struct(li) => li, + _ => panic!("MiniListItem of a non empty List does not point to ListItem"), + }, + _ => panic!("List_t entry was not a ListItem"), + }; + match dump.remove(&next_list_item.pvOwner).expect("ListItem Owner not in Hashmap") { + TCB_struct(t) => {ret.push(t)}, + _ => panic!("List content does not equal type"), + } + next_index=next_list_item.pxNext; + } + ret.push(last_tcb); + ret +} +/// Drains a List of raw SystemStates to produce a refined trace +fn refine_system_states(input: &mut Vec) -> Vec { +let mut ret = Vec::::new(); +let mut start_tick : u64 = 0; +for mut i in input.drain(..) { + let mut collector = Vec::::new(); + for j in i.prio_ready_lists.into_iter().rev() { + let mut tmp = tcb_list_to_vec_cached(j,&mut i.dumping_ground).iter().map(|x| RefinedTCB::from_tcb(x)).collect(); + collector.append(&mut tmp); + } + ret.push(RefinedFreeRTOSSystemState { + current_task: RefinedTCB::from_tcb_owned(i.current_tcb), + start_tick: start_tick, + end_tick: i.qemu_tick, + ready_list_after: collector, + input_counter: i.input_counter+IRQ_INPUT_BYTES_NUMBER, + last_pc: i.last_pc, + }); + start_tick=i.qemu_tick; +} +return ret; +} \ No newline at end of file