From 25d598f30b342706e63fc2f0b17388c2824ef17f Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Sat, 16 Aug 2025 07:58:58 +0000 Subject: [PATCH] documentation --- fuzzers/FRET/src/systemstate/helpers.rs | 105 ++++++++++++++++-- fuzzers/FRET/src/systemstate/mod.rs | 32 +++++- .../src/systemstate/target_os/freertos/mod.rs | 62 ++++++++++- fuzzers/FRET/src/systemstate/target_os/mod.rs | 13 +++ 4 files changed, 196 insertions(+), 16 deletions(-) diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs index 58e4a40b58..4082a97017 100644 --- a/fuzzers/FRET/src/systemstate/helpers.rs +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -12,7 +12,14 @@ use super::ExecInterval; //============================= API symbols -/// Read ELF program headers to resolve physical load addresses. +/// Resolves a virtual address to a physical address using ELF program headers. +/// +/// # Arguments +/// * `vaddr` - The virtual address to resolve. +/// * `tab` - The ELF file containing program headers. +/// +/// # Returns +/// The corresponding physical address, or the original address if not found. fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { let ret; for i in &tab.goblin().program_headers { @@ -25,12 +32,31 @@ fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { return vaddr; } -/// Lookup a symbol in the ELF file, optionally resolve segment offsets +/// Looks up a symbol in the ELF file and returns its address, optionally translating to a physical address. +/// +/// # Arguments +/// * `elf` - The ELF file to search. +/// * `symbol` - The symbol name to look up. +/// * `do_translation` - Whether to translate the address to a physical address. +/// +/// # Panics +/// Panics if the symbol is not found. +/// +/// # Returns +/// The address of the symbol. pub fn load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAddr { try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol)) } -/// Lookup a symbol in the ELF file, optionally resolve segment offsets +/// Looks up a symbol in the ELF file and returns its address, optionally translating to a physical address. +/// +/// # Arguments +/// * `elf` - The ELF file to search. +/// * `symbol` - The symbol name to look up. +/// * `do_translation` - Whether to translate the address to a physical address. +/// +/// # Returns +/// Some(address) if found, None otherwise. pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option { let ret = elf.resolve_symbol(symbol, 0); if do_translation { @@ -42,7 +68,14 @@ pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Opt } } -/// Try looking up the address range of a function in the ELF file +/// Returns the address range of a function symbol in the ELF file. +/// +/// # Arguments +/// * `elf` - The ELF file to search. +/// * `symbol` - The function symbol name. +/// +/// # Returns +/// Some(range) if found, None otherwise. pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option> { let gob = elf.goblin(); @@ -74,7 +107,14 @@ pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option( ranges: &'a Vec<(Cow<'static, str>, Range)>, addr: GuestAddr, @@ -89,6 +129,13 @@ pub fn in_any_range<'a>( //============================= QEMU related utility functions +/// Retrieves the current QEMU instruction count. +/// +/// # Arguments +/// * `emulator` - The QEMU emulator instance. +/// +/// # Returns +/// The current instruction count as a u64. pub fn get_icount(emulator: &libafl_qemu::Qemu) -> u64 { unsafe { // TODO: investigate why can_do_io is not set sometimes, as this is just a workaround @@ -101,6 +148,14 @@ pub fn get_icount(emulator: &libafl_qemu::Qemu) -> u64 { } } +/// Converts input bytes to a vector of interrupt times, enforcing minimum inter-arrival time. +/// +/// # Arguments +/// * `buf` - The input byte buffer. +/// * `config` - Tuple of (number of interrupts, minimum inter-arrival time). +/// +/// # Returns +/// A sorted vector of interrupt times. pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize, u32)) -> Vec { let len = buf.len(); let mut start_tick; @@ -140,6 +195,13 @@ pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize, u32)) -> Vec Vec { let mut ret = Vec::with_capacity(interrupt_times.len() * 4); for i in interrupt_times { @@ -148,6 +210,14 @@ pub fn interrupt_times_to_input_bytes(interrupt_times: &[u32]) -> Vec { ret } +/// Reads the return address from the stack frame, handling ARM exception return conventions. +/// +/// # Arguments +/// * `emu` - The QEMU emulator instance. +/// * `lr` - The link register value. +/// +/// # Returns +/// The return address from the stack frame. pub fn read_rec_return_stackframe(emu: &libafl_qemu::Qemu, lr: GuestAddr) -> GuestAddr { let lr_ = lr & u32::MAX - 1; if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF8 || lr_ == 0xFFFFFFF0 { @@ -171,6 +241,15 @@ pub fn read_rec_return_stackframe(emu: &libafl_qemu::Qemu, lr: GuestAddr) -> Gue //============================= Tracing related utility functions +/// Inserts or updates metadata in a map, returning a mutable reference. +/// +/// # Arguments +/// * `metadata` - The metadata map. +/// * `default` - Function to create a default value if not present. +/// * `update` - Function to update the value if present. +/// +/// # Returns +/// A mutable reference to the metadata value. pub fn metadata_insert_or_update_get( metadata: &mut SerdeAnyMap, default: impl FnOnce() -> T, @@ -188,8 +267,13 @@ where } } -/// Build an ABB-profile from a stretch of intervals -/// returns mapping: task_name -> (abb_addr -> (interval_count, exec_count, exec_time, woet)) +/// Builds an ABB (atomic basic block) profile from execution intervals. +/// +/// # Arguments +/// * `intervals` - A vector of execution intervals. +/// +/// # Returns +/// A mapping from task name to ABB address to (interval count, exec count, exec time, woet). #[allow(unused)] pub fn abb_profile( mut intervals: Vec, @@ -270,6 +354,13 @@ pub fn abb_profile( ret } +/// Returns an immutable reference from a mutable one. +/// +/// # Arguments +/// * `x` - A mutable reference. +/// +/// # Returns +/// An immutable reference to the same value. pub fn unmut(x: &mut T) -> &T { &(*x) } diff --git a/fuzzers/FRET/src/systemstate/mod.rs b/fuzzers/FRET/src/systemstate/mod.rs index 3ddd8e0f48..b30fbd8d7b 100644 --- a/fuzzers/FRET/src/systemstate/mod.rs +++ b/fuzzers/FRET/src/systemstate/mod.rs @@ -55,10 +55,15 @@ pub enum CaptureEvent { pub struct ExecInterval { pub start_tick: u64, pub end_tick: u64, + /// Hash of the start state pub start_state: u64, + /// Hash of the end state pub end_state: u64, + /// The event that started this interval pub start_capture: (CaptureEvent, Cow<'static, str>), + /// The event that ended this interval pub end_capture: (CaptureEvent, Cow<'static, str>), + /// Execution level: 0 = APP, 1 = API, 2 = ISR pub level: u8, // tick_spend_preempted: u64, pub abb: Option @@ -217,6 +222,7 @@ libafl_bolts::impl_serdeany!(AtomicBasicBlock); // ============================= Job instances +/// Represents a single execution of a task, recording the place and input read. #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct RTOSJob { pub name: String, @@ -288,6 +294,7 @@ impl Hash for RTOSTask { } } impl RTOSTask { + /// Returns the hash value for the task, computing it if not cached. pub fn get_hash(&mut self) -> u64 { if self.hash_cache == 0 { let mut s = DefaultHasher::new(); @@ -296,6 +303,7 @@ impl RTOSTask { } self.hash_cache } + /// Returns the cached hash value for the task. pub fn get_hash_cached(&self) -> u64 { if self.hash_cache == 0 { let mut s = DefaultHasher::new(); @@ -305,7 +313,7 @@ impl RTOSTask { self.hash_cache } } - /// Update woet (time, inputs) and wort (time only) if the new instance is better + /// Update WOET (time, inputs) and WORT (time only) if the new instance is better pub fn try_update(&mut self, other: &RTOSJob) -> bool { assert_eq!(self.get_hash(), other.get_hash_cached()); let mut ret = false; @@ -321,6 +329,7 @@ impl RTOSTask { } ret } + /// Creates a RTOSTask instance from a given RTOSJob instance. pub fn from_instance(input: &RTOSJob) -> Self { let c = input.get_hash_cached(); Self { @@ -333,9 +342,24 @@ impl RTOSTask { hash_cache: c } } - pub fn map_bytes_onto(&self, input: &RTOSJob, offset: Option) -> Vec<(u32,u8)> { - if input.mem_reads.len() == 0 {return vec![];} - let ret = input.mem_reads.iter().take(self.woet_bytes.len()).enumerate().filter_map(|(idx,(addr,oldbyte))| if self.woet_bytes[idx]!=*oldbyte {Some((*addr-offset.unwrap_or_default(), self.woet_bytes[idx]))} else {None}).collect(); + /// Maps bytes onto a given RTOSJob instance, returning the differences. + pub fn map_bytes_onto(&self, input: &RTOSJob, offset: Option) -> Vec<(u32, u8)> { + if input.mem_reads.len() == 0 { + return vec![]; + } + let ret = input + .mem_reads + .iter() + .take(self.woet_bytes.len()) + .enumerate() + .filter_map(|(idx, (addr, oldbyte))| { + if self.woet_bytes[idx] != *oldbyte { + Some((*addr - offset.unwrap_or_default(), self.woet_bytes[idx])) + } else { + None + } + }) + .collect(); // eprintln!("Mapped: {:?}", ret); ret } diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs index cf378f00d3..bf1d984abc 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs @@ -128,6 +128,15 @@ pub const USR_ISR_SYMBOLS: &'static [&'static str] = &[ //============================================================================= Helper functions +/// Reads a FreeRTOS list from the target and populates the system state. +/// +/// # Arguments +/// * `systemstate` - The mutable system state to populate. +/// * `emulator` - The QEMU emulator instance. +/// * `target` - The address of the list to read. +/// +/// # Returns +/// A tuple containing the read list and a boolean indicating if the read was valid. fn read_freertos_list( systemstate: &mut RawFreeRTOSSystemState, emulator: &libafl_qemu::Qemu, @@ -179,6 +188,13 @@ fn read_freertos_list( return (read, true); } +/// Triggers the collection of a FreeRTOS system state snapshot at a given event. +/// +/// # Arguments +/// * `emulator` - The QEMU emulator instance. +/// * `edge` - A tuple of (from, to) addresses representing the edge. +/// * `event` - The capture event type. +/// * `h` - The FreeRTOS system state helper. #[inline] fn trigger_collection( emulator: &libafl_qemu::Qemu, @@ -327,6 +343,13 @@ impl Hash for RefinedTCB { } impl RefinedTCB { + /// Constructs a `RefinedTCB` from a raw FreeRTOS TCB struct reference. + /// + /// # Arguments + /// * `input` - Reference to a raw TCB_t struct. + /// + /// # Returns + /// A new `RefinedTCB` instance. pub fn from_tcb(input: &TCB_t) -> Self { unsafe { let tmp = std::mem::transmute::<[i8; 10], [u8; 10]>(input.pcTaskName); @@ -345,6 +368,13 @@ impl RefinedTCB { } } } + /// Constructs a `RefinedTCB` from a raw FreeRTOS TCB struct (by value). + /// + /// # Arguments + /// * `input` - The TCB_t struct. + /// + /// # Returns + /// A new `RefinedTCB` instance. pub fn from_tcb_owned(input: TCB_t) -> Self { unsafe { let tmp = std::mem::transmute::<[i8; 10], [u8; 10]>(input.pcTaskName); @@ -391,10 +421,10 @@ impl Hash for FreeRTOSSystemState { } } impl FreeRTOSSystemState { - // fn get_tick(&self) -> u64 { - // self.tick - // } - + /// Prints the ready and delay lists as a formatted string. + /// + /// # Returns + /// A string representation of the ready and delay lists. pub fn print_lists(&self) -> String { let mut ret = String::from("+"); for j in self.ready_list_after.iter() { @@ -406,6 +436,10 @@ impl FreeRTOSSystemState { } ret } + /// Computes a hash for the system state. + /// + /// # Returns + /// The hash value as a u64. pub fn get_hash(&self) -> u64 { let mut h = DefaultHasher::new(); self.hash(&mut h); @@ -461,6 +495,17 @@ pub struct FreeRTOSTraceMetadata } impl FreeRTOSTraceMetadata { + /// Constructs a new `FreeRTOSTraceMetadata` from trace data. + /// + /// # Arguments + /// * `trace` - Vector of system states. + /// * `intervals` - Vector of execution intervals. + /// * `mem_reads` - Vector of memory reads. + /// * `jobs` - Vector of RTOS jobs. + /// * `need_to_debug` - Whether the current trace should be dumped for debugging purposes. + /// + /// # Returns + /// A new `FreeRTOSTraceMetadata` instance. pub fn new(trace: Vec<::State>, intervals: Vec, mem_reads: Vec>, jobs: Vec, need_to_debug: bool) -> Self { let hashes : Vec<_> = trace .iter() @@ -529,7 +574,14 @@ libafl_bolts::impl_serdeany!(RefinedTCB); libafl_bolts::impl_serdeany!(FreeRTOSSystemState); libafl_bolts::impl_serdeany!(FreeRTOSSystem); -fn get_task_names(trace: &Vec) -> HashSet { +/// Returns a set of all task names present in the given trace. +/// +/// # Arguments +/// * `trace` - A vector of FreeRTOS system states. +/// +/// # Returns +/// A set of unique task names as strings. +pub(crate) fn get_task_names(trace: &Vec) -> HashSet { let mut ret: HashSet<_, _> = HashSet::new(); for state in trace { ret.insert(state.current_task.task_name.to_string()); diff --git a/fuzzers/FRET/src/systemstate/target_os/mod.rs b/fuzzers/FRET/src/systemstate/target_os/mod.rs index 6f1136b1c3..a464ed0be2 100644 --- a/fuzzers/FRET/src/systemstate/target_os/mod.rs +++ b/fuzzers/FRET/src/systemstate/target_os/mod.rs @@ -20,12 +20,16 @@ pub mod freertos; //============================= Trait definitions +/// A trait representing a target system, which includes a system state, task control block, and trace data. pub trait TargetSystem: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny { type State: SystemState; + /// The type of a task control block used in the system state. type TCB: TaskControlBlock; + /// The type used to store trace data for the system. type TraceData: SystemTraceData; } +/// A trait representing the system state of a target system, which includes methods to access the current task. pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Hash + PartialEq + Clone + SerdeAny { type TCB: TaskControlBlock; @@ -39,14 +43,20 @@ pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + D pub trait SystemTraceData: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny + HasRefCnt { type State: SystemState; + /// Returns a vector of all system states in the trace. fn states(&self) -> Vec<&Self::State>; + /// Returns hash map of system states, where the key is the hash value of the state. fn states_map(&self) -> &HashMap; + /// Returns a vector of execution intervals in the trace. fn intervals(&self) -> &Vec; + /// Returns a vector of memory reads, where each read is represented as a tuple of (address, value). fn mem_reads(&self) -> &Vec>; + /// Returns a vector of RTOS jobs which were executed during the trace. fn jobs(&self) -> &Vec; fn trace_length(&self) -> usize; #[inline] + /// Returns the worst job of each task by a given predicate. fn worst_jobs_per_task_by(&self, pred: &dyn Fn(&RTOSJob,&RTOSJob) -> bool) -> HashMap { self.jobs().iter().fold(HashMap::new(), |mut acc, next| { match acc.get_mut(&next.name) { @@ -63,10 +73,12 @@ pub trait SystemTraceData: Serialize + Sized + for<'a> Deserialize<'a> + Default }) } #[inline] + /// Gives the worst job of each task by execution time. fn worst_jobs_per_task_by_exec_time(&self) -> HashMap { self.worst_jobs_per_task_by(&|old, x| x.exec_ticks > old.exec_ticks) } #[inline] + /// Gives the worst job of each task by response time. fn worst_jobs_per_task_by_response_time(&self) -> HashMap { self.worst_jobs_per_task_by(&|old, x| x.response_time() > old.response_time()) } @@ -118,6 +130,7 @@ pub trait TaskControlBlock: Serialize + for<'a> Deserialize<'a> + Default + Debu //============================= +/// A trait for looking up data in a QEMU emulation environment. pub trait QemuLookup { fn lookup(emu: &Qemu, addr: ::std::os::raw::c_uint) -> Self; }