From 1caa49f470eb765947514fd59d3a38fb426c04e3 Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Mon, 25 Aug 2025 12:06:08 +0000 Subject: [PATCH] systemstate::target_os comments and refactoring --- fuzzers/FRET/src/config.rs | 22 +- fuzzers/FRET/src/fuzzer.rs | 29 +-- fuzzers/FRET/src/systemstate/helpers.rs | 83 +++++- fuzzers/FRET/src/systemstate/mutational.rs | 64 +---- fuzzers/FRET/src/systemstate/schedulers.rs | 20 +- .../target_os/freertos/bindings.rs | 1 + .../systemstate/target_os/freertos/config.rs | 152 +++++++++-- .../{qemu_module.rs => extraction.rs} | 243 ++++++++++++++++-- .../src/systemstate/target_os/freertos/mod.rs | 242 +---------------- .../target_os/freertos/post_processing.rs | 93 +++++-- fuzzers/FRET/src/systemstate/target_os/mod.rs | 2 +- 11 files changed, 543 insertions(+), 408 deletions(-) rename fuzzers/FRET/src/systemstate/target_os/freertos/{qemu_module.rs => extraction.rs} (62%) diff --git a/fuzzers/FRET/src/config.rs b/fuzzers/FRET/src/config.rs index 3967ac3baa..9ec2e4e59b 100644 --- a/fuzzers/FRET/src/config.rs +++ b/fuzzers/FRET/src/config.rs @@ -2,33 +2,33 @@ use hashbrown::HashMap; use libafl_qemu::{elf::EasyElf, GuestAddr}; use std::env; -use crate::systemstate::helpers::{load_symbol, try_load_symbol}; +use crate::systemstate::helpers::symbol_helpers::{load_elf_symbol, try_load_elf_symbol}; -pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> { +pub fn get_target_symbols(elf: &EasyElf, add_target_specific_symbols: fn (elf: &EasyElf, addrs: &mut HashMap<&'static str, GuestAddr>)) -> HashMap<&'static str, GuestAddr> { let mut addrs = HashMap::new(); addrs.insert( "__APP_CODE_START__", - load_symbol(&elf, "__APP_CODE_START__", false), + load_elf_symbol(&elf, "__APP_CODE_START__", false), ); addrs.insert( "__APP_CODE_END__", - load_symbol(&elf, "__APP_CODE_END__", false), + load_elf_symbol(&elf, "__APP_CODE_END__", false), ); addrs.insert( "__API_CODE_START__", - load_symbol(&elf, "__API_CODE_START__", false), + load_elf_symbol(&elf, "__API_CODE_START__", false), ); addrs.insert( "__API_CODE_END__", - load_symbol(&elf, "__API_CODE_END__", false), + load_elf_symbol(&elf, "__API_CODE_END__", false), ); addrs.insert( "trigger_job_done", - load_symbol(&elf, "trigger_job_done", false), + load_elf_symbol(&elf, "trigger_job_done", false), ); - crate::systemstate::target_os::freertos::config::add_target_symbols(elf, &mut addrs); + add_target_specific_symbols(elf, &mut addrs); // the main address where the fuzzer starts // if this is set for freeRTOS it has an influence on where the data will have to be written, @@ -41,14 +41,14 @@ pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> { addrs.insert("FUZZ_MAIN", main_addr); } - let input_addr = load_symbol( + let input_addr = load_elf_symbol( &elf, &env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()), true, ); addrs.insert("FUZZ_INPUT", input_addr); - let input_length_ptr = try_load_symbol( + let input_length_ptr = try_load_elf_symbol( &elf, &env::var("FUZZ_LENGTH").unwrap_or_else(|_| "FUZZ_LENGTH".to_owned()), true, @@ -56,7 +56,7 @@ pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> { if let Some(input_length_ptr) = input_length_ptr { addrs.insert("FUZZ_LENGTH", input_length_ptr); } - let input_counter_ptr = try_load_symbol( + let input_counter_ptr = try_load_elf_symbol( &elf, &env::var("FUZZ_POINTER").unwrap_or_else(|_| "FUZZ_POINTER".to_owned()), true, diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 59ffc087ed..5ff7e46d64 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -16,7 +16,7 @@ elf::EasyElf, emu::Emulator, modules::{edges::{self}, EdgeCoverageModule, Filter use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; use rand::{SeedableRng, StdRng, Rng}; use crate::{ - config::{get_target_ranges, get_target_symbols}, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{get_function_range, input_bytes_to_interrupt_times, load_symbol, try_load_symbol}, mutational::{InterruptShiftStage, STGSnippetStage}, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}, target_os::freertos::{config::get_range_groups, qemu_module::FreeRTOSSystemStateHelper, FreeRTOSSystem}}, time::{ + config::{get_target_ranges, get_target_symbols}, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{ input_bytes_to_interrupt_times}, mutational::{InterruptShiftStage, STGSnippetStage}, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}, target_os::freertos::{config::get_range_groups, extraction::qemu_module::FreeRTOSSystemStateModule, FreeRTOSSystem}}, time::{ clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP, QEMU_ICOUNT_SHIFT, QEMU_ISNS_PER_MSEC, QEMU_ISNS_PER_USEC}, qemustate::QemuStateRestoreHelper, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, RateLimitedMonitor, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler} } }; @@ -45,27 +45,6 @@ pub const NUM_INTERRUPT_SOURCES: usize = 6; // Keep in sync with qemu-libafl-bri pub const DO_NUM_INTERRUPT: usize = 128; pub static mut MAX_INPUT_SIZE: usize = 1024; -pub fn get_all_fn_symbol_ranges(elf: &EasyElf, range: std::ops::Range) -> HashMap> { - let mut ret : HashMap> = HashMap::new(); - - let gob = elf.goblin(); - - let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function() && range.contains(&x.st_value.try_into().unwrap())).collect(); - funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value)); - - for sym in &funcs { - let sym_name = gob.strtab.get_at(sym.st_name); - if let Some(sym_name) = sym_name { - // if ISR_SYMBOLS.contains(&sym_name) {continue;}; // skip select symbols, which correspond to ISR-safe system calls - if let Some(r) = get_function_range(elf, sym_name) { - ret.insert(sym_name.to_string(), r); - } - } - } - - return ret; -} - #[allow(unused)] extern "C" { static mut libafl_interrupt_offsets : [[u32; MAX_NUM_INTERRUPT]; NUM_INTERRUPT_SOURCES]; @@ -182,7 +161,7 @@ let elf = EasyElf::from_file( ) .unwrap(); -let TARGET_SYMBOLS: HashMap<&'static str, GuestAddr> = get_target_symbols(&elf); +let TARGET_SYMBOLS: HashMap<&'static str, GuestAddr> = get_target_symbols(&elf, crate::systemstate::target_os::freertos::config::add_target_symbols); let TARGET_RANGES: HashMap<&'static str, Range> = get_target_ranges(&elf, &TARGET_SYMBOLS); let TARGET_GROUPS: HashMap<&'static str, HashMap>> = get_range_groups(&elf, &TARGET_SYMBOLS, &TARGET_RANGES); @@ -426,7 +405,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { let qhelpers = tuple_list!(); #[cfg(feature = "observe_systemstate")] - let qhelpers = (FreeRTOSSystemStateHelper::new(&TARGET_SYMBOLS,&TARGET_RANGES,&TARGET_GROUPS), qhelpers); + let qhelpers = (FreeRTOSSystemStateModule::new(&TARGET_SYMBOLS,&TARGET_RANGES,&TARGET_GROUPS), qhelpers); #[cfg(feature = "observe_edges")] let qhelpers = ( StdEdgeCoverageModule::builder() @@ -498,7 +477,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| { let mut rng = StdRng::seed_from_u64(se); let bound = 10000; #[cfg(feature = "shortcut")] - let bound = 100; + let bound = 5; for _ in 0..bound { let inp2 = BytesInput::new((0..MAX_INPUT_SIZE).map(|_| rng.gen::()).collect()); let inp = setup_interrupt_inputs(MultipartInput::from([("bytes",inp2)]), &interrupt_config, Some(&mut rng)); diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs index 98abbf7843..54441cf3d2 100644 --- a/fuzzers/FRET/src/systemstate/helpers.rs +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -1,7 +1,7 @@ use hashbrown::HashMap; use libafl_bolts::prelude::{SerdeAny, SerdeAnyMap}; -use libafl_qemu::{elf::EasyElf, read_user_reg_unchecked, GuestAddr, GuestPhysAddr}; -use std::{borrow::Cow, cmp::min, hash::{DefaultHasher, Hash, Hasher}, ops::Range}; +use libafl_qemu::{read_user_reg_unchecked, GuestAddr}; +use std::{borrow::Cow, cmp::min, hash::{DefaultHasher, Hash, Hasher}}; use crate::{ fuzzer::{DO_NUM_INTERRUPT, FIRST_INT}, @@ -10,8 +10,31 @@ use crate::{ use super::ExecInterval; +pub mod symbol_helpers { +//! Helper functions for working with ELF symbols and address translation. +//! +//! This module provides utilities for: +//! - Translating virtual addresses to physical addresses using ELF program headers. +//! - Looking up symbol addresses in an ELF file, with optional address translation. +//! - Determining the address range of a function symbol. +//! - Retrieving all function symbol ranges within a specified address range from an ELF file. +//! - Checking if an address falls within any of a set of address ranges. +//! +//! # Functions +//! - `virt2phys`: Resolves a virtual address to a physical address using ELF program headers. +//! - `load_elf_symbol`: Looks up a symbol and returns its address, panicking if not found. +//! - `try_load_elf_symbol`: Looks up a symbol and returns its address if found. +//! - `get_function_range`: Returns the address range of a function symbol. +//! - `get_all_fn_symbols_in_range`: Returns a map of function names to their address ranges within a given range. +//! - `in_any_range`: Checks if an address is within any of the provided ranges. + //============================= API symbols +use std::{borrow::Cow, ops::Range}; + +use hashbrown::HashMap; +use libafl_qemu::{elf::EasyElf, GuestAddr, GuestPhysAddr}; + /// Resolves a virtual address to a physical address using ELF program headers. /// /// # Arguments @@ -44,8 +67,8 @@ fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { /// /// # 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)) +pub fn load_elf_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAddr { + try_load_elf_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol)) } /// Looks up a symbol in the ELF file and returns its address, optionally translating to a physical address. @@ -57,7 +80,7 @@ pub fn load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAd /// /// # Returns /// Some(address) if found, None otherwise. -pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option { +pub fn try_load_elf_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option { let ret = elf.resolve_symbol(symbol, 0); if do_translation { Option::map_or(ret, None, |x| { @@ -76,7 +99,7 @@ pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Opt /// /// # Returns /// Some(range) if found, None otherwise. -pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option> { +pub(crate) fn get_function_range(elf: &EasyElf, symbol: &str) -> Option> { let gob = elf.goblin(); let mut funcs: Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect(); @@ -107,6 +130,50 @@ pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option`). +/// Only functions that have valid ranges (as determined by `get_function_range`) are included. +/// +/// # Example +/// ```rust +/// let api_functions = get_all_fn_symbols_in_range(&elf, 0x1000..0x2000); +/// for (name, range) in &api_functions { +/// println!("Function {} spans {:#x}..{:#x}", name, range.start, range.end); +/// } +/// ``` +pub(crate) fn get_all_fn_symbols_in_range(elf: &EasyElf, range: std::ops::Range) -> HashMap> { + let mut ret : HashMap> = HashMap::new(); + + let gob = elf.goblin(); + + let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function() && range.contains(&x.st_value.try_into().unwrap())).collect(); + funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value)); + + for sym in &funcs { + let sym_name = gob.strtab.get_at(sym.st_name); + if let Some(sym_name) = sym_name { + // if ISR_SYMBOLS.contains(&sym_name) {continue;}; // skip select symbols, which correspond to ISR-safe system calls + if let Some(r) = get_function_range(elf, sym_name) { + ret.insert(sym_name.to_string(), r); + } + } + } + + return ret; +} + /// Checks if an address is within any of the provided ranges. /// /// # Arguments @@ -127,6 +194,10 @@ pub fn in_any_range<'a>( return None; } +} + + + //============================= QEMU related utility functions /// Retrieves the current QEMU instruction count. diff --git a/fuzzers/FRET/src/systemstate/mutational.rs b/fuzzers/FRET/src/systemstate/mutational.rs index e80b6098f9..f3154ccccc 100644 --- a/fuzzers/FRET/src/systemstate/mutational.rs +++ b/fuzzers/FRET/src/systemstate/mutational.rs @@ -9,7 +9,7 @@ use libafl_bolts::{rands::{ random_seed, Rand, StdRand }, Named}; use libafl::{ - common::{HasMetadata, HasNamedMetadata}, corpus::{self, Corpus, HasCurrentCorpusId, Testcase}, events::{Event, EventFirer, EventProcessor, LogSeverity}, fuzzer::Evaluator, inputs::{HasMutatorBytes, HasTargetBytes, Input, MultipartInput}, mark_feature_time, prelude::{new_hash_feedback, AggregatorOps, CorpusId, MutationResult, Mutator, UserStats, UserStatsValue, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error + common::{HasMetadata, HasNamedMetadata}, corpus::{self, HasCurrentCorpusId, Testcase}, events::{Event, EventFirer, EventProcessor, LogSeverity}, fuzzer::Evaluator, inputs::{HasMutatorBytes, HasTargetBytes, Input, MultipartInput}, mark_feature_time, prelude::{new_hash_feedback, AggregatorOps, MutationResult, Mutator, UserStats, UserStatsValue, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error }; use libafl::prelude::State; use petgraph::{graph::NodeIndex, graph::{self, DiGraph}}; @@ -145,7 +145,7 @@ where EM: UsesState, Z: Evaluator, S: State> + HasRand + HasCorpus + HasCurrentTestcase + HasMetadata + HasNamedMetadata, - <::State as HasCorpus>::Corpus: Corpus, //delete me + <::State as HasCorpus>::Corpus: libafl::prelude::Corpus, EM: EventFirer, I: Default + Input + HasMutatorBytes, SYS: TargetSystem, @@ -189,7 +189,7 @@ where } else { new_input.add_part(String::from(&name), I::default()); new_input.parts_by_name_mut(&name).next().unwrap() }.1; - let old_interrupt_times = input_bytes_to_interrupt_times(new_interrupt_part.bytes(), *interrup_config); + let old_interrupt_times : Vec = input_bytes_to_interrupt_times(new_interrupt_part.bytes(), *interrup_config); let mut new_interrupt_times = Vec::with_capacity(MAX_NUM_INTERRUPT); let mut do_rerun = false; // if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time @@ -224,54 +224,6 @@ where new_interrupt_times=t; } } - // let tmp = current_case.metadata_map().get::(); - // if tmp.is_some() { - // let trace = tmp.expect("STGNodeMetadata not found"); - // let mut node_indices = vec![]; - // for i in (0..trace.intervals.len()).into_iter() { - // if let Some(abb) = &trace.intervals[i].abb { - // if let Some(idx) = feedbackstate.state_abb_hash_index.get(&(trace.intervals[i].start_state,abb.get_hash())) { - // node_indices.push(Some(idx)); - // continue; - // } - // } - // node_indices.push(None); - // } - // // let mut marks : HashMap= HashMap::new(); // interrupt -> block hit - // // for i in 0..trace.intervals.len() { - // // let curr = &trace.intervals[i]; - // // let m = interrupt_offsets[0..num_interrupts].iter().filter(|x| (curr.start_tick..curr.end_tick).contains(&((**x) as u64))); - // // for k in m { - // // marks.insert(*k,i); - // // } - // // } - // // walk backwards trough the trace and try moving the interrupt to a block that does not have an outgoing interrupt edge or ist already hit by a predecessor - // for i in (0..num_interrupts).rev() { - // let mut lb = FIRST_INT; - // let mut ub : u32 = trace.intervals[trace.intervals.len()-1].end_tick.try_into().expect("ticks > u32"); - // if i > 0 { - // lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); - // } - // if i < num_interrupts-1 { - // ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); - // } - // let alternatives : Vec<_> = (0..trace.intervals.len()).filter(|x| - // node_indices[*x].is_some() && - // (trace.intervals[*x].start_tick < (lb as u64) && (lb as u64) < trace.intervals[*x].end_tick - // || trace.intervals[*x].start_tick > (lb as u64) && trace.intervals[*x].start_tick < (ub as u64)) - // ).collect(); - // let not_yet_hit : Vec<_> = alternatives.iter().filter( - // |x| feedbackstate.graph.edges_directed(*node_indices[**x].unwrap(), petgraph::Direction::Outgoing).any(|y| y.weight().event != CaptureEvent::ISRStart)).collect(); - // if not_yet_hit.len() > 0 { - // let replacement = &trace.intervals[*myrand.choose(not_yet_hit).unwrap()]; - // interrupt_offsets[i] = (myrand.between(replacement.start_tick as usize, - // replacement.end_tick as usize)).try_into().expect("ticks > u32"); - // // println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]); - // do_rerun = true; - // break; - // } - // } - // } } else { // old version of the alternative search new_interrupt_times = old_interrupt_times.clone(); @@ -406,11 +358,11 @@ where Ok(()) } - fn should_restart(&mut self, state: &mut Self::State) -> Result { + fn should_restart(&mut self, _state: &mut Self::State) -> Result { Ok(true) } - fn clear_progress(&mut self, state: &mut Self::State) -> Result<(), Error> { + fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { Ok(()) } } @@ -504,7 +456,7 @@ where Z::State: UsesInput>, I: HasMutatorBytes + Default, Z::State: HasCurrentTestcase+HasCorpus+HasCurrentCorpusId, - ::Corpus: Corpus>, + ::Corpus: libafl::prelude::Corpus>, SYS: TargetSystem, { fn perform( @@ -562,11 +514,11 @@ where Ok(()) } - fn should_restart(&mut self, state: &mut Self::State) -> Result { + fn should_restart(&mut self, _state: &mut Self::State) -> Result { Ok(true) } - fn clear_progress(&mut self, state: &mut Self::State) -> Result<(), Error> { + fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { Ok(()) } } diff --git a/fuzzers/FRET/src/systemstate/schedulers.rs b/fuzzers/FRET/src/systemstate/schedulers.rs index f6b352c754..eb5feab304 100644 --- a/fuzzers/FRET/src/systemstate/schedulers.rs +++ b/fuzzers/FRET/src/systemstate/schedulers.rs @@ -6,9 +6,9 @@ use std::{cmp::{max, min}, mem::swap}; use serde::{Deserialize, Serialize}; -use libafl_bolts::{rands::Rand, AsIter, HasLen}; +use libafl_bolts::{rands::Rand}; use libafl::{ - common::HasMetadata, corpus::{Corpus, Testcase}, inputs::UsesInput, prelude::{CanTrack, CorpusId, RemovableScheduler}, schedulers::{minimizer::DEFAULT_SKIP_NON_FAVORED_PROB, Scheduler, TestcaseScore }, state::{HasCorpus, HasRand, State, UsesState}, Error, SerdeAny + common::HasMetadata, corpus::{Corpus, Testcase}, inputs::UsesInput, prelude::{CorpusId, RemovableScheduler}, schedulers::{minimizer::DEFAULT_SKIP_NON_FAVORED_PROB, Scheduler, TestcaseScore }, state::{HasCorpus, HasRand, State, UsesState}, Error, SerdeAny }; @@ -249,8 +249,8 @@ where fn set_current_scheduled( &mut self, - state: &mut S, - next_id: Option, + _state: &mut S, + _next_id: Option, ) -> Result<(), Error> { Ok(()) } @@ -288,9 +288,9 @@ where /// Replaces the testcase at the given idx fn on_replace( &mut self, - state: &mut ::State, - idx: CorpusId, - testcase: &Testcase, + _state: &mut ::State, + _idx: CorpusId, + _testcase: &Testcase, ) -> Result<(), Error> { Ok(()) } @@ -298,9 +298,9 @@ where /// Removes an entry from the corpus fn on_remove( &mut self, - state: &mut ::State, - idx: CorpusId, - testcase: &Option>, + _state: &mut ::State, + _idx: CorpusId, + _testcase: &Option>, ) -> Result<(), Error> { Ok(()) } diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/bindings.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/bindings.rs index be2855befb..6c5e910fd2 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/bindings.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/bindings.rs @@ -1,3 +1,4 @@ +//! Bindings for FreeRTOS types and constants. #![allow(non_camel_case_types,non_snake_case,non_upper_case_globals,deref_nullptr,unused)] use serde::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs index 22bbeff122..a885188e6a 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/config.rs @@ -1,47 +1,159 @@ +//! FreeRTOS-specific configuration for system state observation. +//! +//! This module provides functionality to extract and organize FreeRTOS kernel symbols +//! and function ranges for fuzzing and system state monitoring. +//! +//! # Overview +//! +//! The module handles: +//! - Loading FreeRTOS kernel symbols required for system state observation +//! - Categorizing functions into API, application, and ISR (Interrupt Service Routine) groups +//! - Managing symbol addresses and function ranges for fuzzing instrumentation +//! +//! # Functions +//! +//! - [`add_target_symbols`]: Adds FreeRTOS-specific symbols to the target symbol map +//! - [`get_range_groups`]: Groups functions by type (API, APP, ISR) for targeted fuzzing + use hashbrown::HashMap; use libafl_qemu::{elf::EasyElf, GuestAddr}; -use crate::{ - fuzzer::get_all_fn_symbol_ranges, - systemstate::{helpers::{get_function_range, load_symbol}, target_os::freertos::ISR_SYMBOLS}, -}; +use crate::systemstate::{helpers::symbol_helpers::{get_all_fn_symbols_in_range, get_function_range, load_elf_symbol}}; -// Add os-specific symbols to the target symbol hashmap +/// List of ISR handler symbols in FreeRTOS, including default handlers and user-defined ISRs. +pub(crate) const ISR_SYMBOLS: &'static [&'static str] = &[ + "Reset_Handler", + "Default_Handler", + "Default_Handler2", + "Default_Handler3", + "Default_Handler4", + "Default_Handler5", + "Default_Handler6", + "vPortSVCHandler", + "xPortPendSVHandler", + "xPortSysTickHandler", + "ISR_0_Handler", + "ISR_1_Handler", + "ISR_2_Handler", + "ISR_3_Handler", + "ISR_4_Handler", + "ISR_5_Handler", + "ISR_6_Handler", + "ISR_7_Handler", + "ISR_8_Handler", + "ISR_9_Handler", + "ISR_10_Handler", + "ISR_11_Handler", + "ISR_12_Handler", + "ISR_13_Handler", +]; +/// User-defined ISR handler symbols in FreeRTOS. +pub(crate) const USR_ISR_SYMBOLS: &'static [&'static str] = &[ + "ISR_0_Handler", + "ISR_1_Handler", + "ISR_2_Handler", + "ISR_3_Handler", + "ISR_4_Handler", + "ISR_5_Handler", + "ISR_6_Handler", + "ISR_7_Handler", + "ISR_8_Handler", + "ISR_9_Handler", + "ISR_10_Handler", + "ISR_11_Handler", + "ISR_12_Handler", + "ISR_13_Handler", +]; + +/// Adds FreeRTOS-specific symbols to the target symbol hashmap. +/// +/// This function extracts essential FreeRTOS kernel symbols from the ELF file +/// and adds them to the provided address hashmap. These symbols are required +/// for system state observation during fuzzing. +/// +/// # Arguments +/// +/// * `elf` - Reference to the parsed ELF file containing the FreeRTOS kernel +/// * `addrs` - Mutable reference to the hashmap where symbol addresses will be stored +/// +/// # Symbols Added +/// +/// The function adds the following FreeRTOS kernel symbols: +/// - `pxCurrentTCB` - Pointer to the currently executing task control block +/// - `pxReadyTasksLists` - Array of ready task lists for each priority level +/// - `pxDelayedTaskList` - List of tasks waiting for a timeout +/// - `pxOverflowDelayedTaskList` - Overflow list for delayed tasks +/// - `uxSchedulerSuspended` - Counter for scheduler suspension nesting +/// - `xSchedulerRunning` - Flag indicating if the scheduler is running +/// - `uxCriticalNesting` - Critical section nesting counter +/// - `xQueueRegistry` - Registry of queues in the system pub fn add_target_symbols(elf: &EasyElf, addrs: &mut HashMap<&'static str, GuestAddr>) { // required for system state observation - addrs.insert("pxCurrentTCB", load_symbol(&elf, "pxCurrentTCB", false)); // loads to the address specified in elf, without respecting program headers + addrs.insert("pxCurrentTCB", load_elf_symbol(&elf, "pxCurrentTCB", false)); // loads to the address specified in elf, without respecting program headers addrs.insert( "pxReadyTasksLists", - load_symbol(&elf, "pxReadyTasksLists", false), + load_elf_symbol(&elf, "pxReadyTasksLists", false), ); addrs.insert( "pxDelayedTaskList", - load_symbol(&elf, "pxDelayedTaskList", false), + load_elf_symbol(&elf, "pxDelayedTaskList", false), ); addrs.insert( "pxOverflowDelayedTaskList", - load_symbol(&elf, "pxOverflowDelayedTaskList", false), + load_elf_symbol(&elf, "pxOverflowDelayedTaskList", false), ); addrs.insert( "uxSchedulerSuspended", - load_symbol(&elf, "uxSchedulerSuspended", false), + load_elf_symbol(&elf, "uxSchedulerSuspended", false), ); addrs.insert( "xSchedulerRunning", - load_symbol(&elf, "xSchedulerRunning", false), + load_elf_symbol(&elf, "xSchedulerRunning", false), ); addrs.insert( "uxCriticalNesting", - load_symbol(&elf, "uxCriticalNesting", false), + load_elf_symbol(&elf, "uxCriticalNesting", false), ); addrs.insert( "xQueueRegistry", - load_symbol(&elf, "xQueueRegistry", false), + load_elf_symbol(&elf, "xQueueRegistry", false), ); } -// Group functions into api, app and isr functions +/// Groups functions into API, application, and ISR function categories. +/// +/// This function categorizes all functions in the FreeRTOS binary into three groups: +/// API functions, application functions, and interrupt service routine (ISR) functions. +/// This categorization is used for targeted fuzzing and system state monitoring. +/// +/// # Arguments +/// +/// * `elf` - Reference to the parsed ELF file containing the FreeRTOS kernel and application +/// * `_addrs` - Reference to the symbol addresses hashmap (currently unused) +/// * `ranges` - Reference to a hashmap containing memory ranges for different code sections +/// +/// # Returns +/// +/// A hashmap containing three groups of functions: +/// - `"API_FN"` - FreeRTOS API functions (kernel functions excluding ISRs) +/// - `"APP_FN"` - Application-specific functions +/// - `"ISR_FN"` - Interrupt Service Routine functions +/// +/// Each group maps function names to their memory address ranges. +/// +/// # Behavior +/// +/// 1. Extracts all functions from the API_CODE and APP_CODE memory ranges +/// 2. Identifies and removes ISR functions from both API and application function lists +/// 3. Creates a separate ISR function group containing all interrupt handlers +/// 4. Ensures all ISR symbols are properly categorized, even if not found in the initial ranges +/// +/// # Note +/// +/// The function uses predefined ISR symbol lists (`ISR_SYMBOLS`) to identify interrupt handlers. +/// If an ISR symbol is not found in the existing ranges, it attempts to locate it elsewhere +/// in the ELF file. pub fn get_range_groups( elf: &EasyElf, _addrs: &HashMap<&'static str, GuestAddr>, @@ -50,10 +162,11 @@ pub fn get_range_groups( let api_range = ranges.get("API_CODE").unwrap(); let app_range = ranges.get("APP_CODE").unwrap(); - let mut api_fn_ranges = get_all_fn_symbol_ranges(&elf, api_range.clone()); - let mut app_fn_ranges = get_all_fn_symbol_ranges(&elf, app_range.clone()); + // Extract function symbols from API and application memory ranges + let mut api_fn_ranges = get_all_fn_symbols_in_range(&elf, api_range.clone()); + let mut app_fn_ranges = get_all_fn_symbols_in_range(&elf, app_range.clone()); - // Regular ISR functions, remove from API functions + // Create ISR function map by removing ISR symbols from API functions let mut isr_fn_ranges: HashMap> = ISR_SYMBOLS .iter() .filter_map(|x| { @@ -62,7 +175,8 @@ pub fn get_range_groups( .map(|y| (x.to_string(), y.clone())) }) .collect(); - // User-defined ISR functions, remove from APP functions + + // Remove user-defined ISR functions from application functions and add to ISR group ISR_SYMBOLS.iter().for_each(|x| { let _ = (app_fn_ranges .remove(&x.to_string()) @@ -70,7 +184,7 @@ pub fn get_range_groups( .map(|z| isr_fn_ranges.insert(z.0, z.1)); }); - // Add the rest of the ISR function, if not already found + // Find any remaining ISR symbols that weren't in the initial ranges for i in ISR_SYMBOLS { if isr_fn_ranges.get(&i.to_string()).is_none() { if let Some(fr) = get_function_range(&elf, i) { diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/extraction.rs similarity index 62% rename from fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs rename to fuzzers/FRET/src/systemstate/target_os/freertos/extraction.rs index 99b306a152..46d349fdf5 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/qemu_module.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/extraction.rs @@ -1,4 +1,18 @@ use std::borrow::Cow; +use libafl_qemu::GuestAddr; + +use crate::systemstate::{ + helpers::get_icount, + target_os::{freertos::{extraction::qemu_module::{FreeRTOSSystemStateModule, MEM_READ}, FreeRTOSStruct, RawFreeRTOSSystemState}, *}, + CaptureEvent, +}; + +use super::{ + bindings::{self, *}, CURRENT_SYSTEMSTATE_VEC +}; + +pub mod qemu_module { +use std::borrow::Cow; use std::ops::Range; use freertos::FreeRTOSTraceMetadata; @@ -15,43 +29,53 @@ use libafl_qemu::{ }; use crate::{fuzzer::MAX_INPUT_SIZE, systemstate::{ - helpers::{get_icount, in_any_range, read_rec_return_stackframe}, - target_os::*, - CaptureEvent, RTOSJob, -}}; + helpers::{get_icount, read_rec_return_stackframe, symbol_helpers::in_any_range}, + target_os::{freertos::{extraction::trigger_collection, post_processing::{refine_system_states, states2intervals,get_releases,get_release_response_pairs}, *}, RTOSJob, +}}}; use super::{ - bindings::{self, *}, post_processing::{get_release_response_pairs, get_releases, refine_system_states, states2intervals}, trigger_collection, CURRENT_SYSTEMSTATE_VEC + bindings::{self, *}, CURRENT_SYSTEMSTATE_VEC }; -//============================= Qemu Helper +//============================= Qemu Module /// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs #[derive(Debug)] -pub struct FreeRTOSSystemStateHelper { - // Address of the application code +pub struct FreeRTOSSystemStateModule { + /// Address of the application code pub app_range: Range, - // Address of API functions + /// Address of API functions pub api_fn_addrs: HashMap>, + /// Ranges of API functions pub api_fn_ranges: Vec<(Cow<'static, str>, std::ops::Range)>, // Address of interrupt routines pub isr_fn_addrs: HashMap>, + /// Ranges of interrupt routines pub isr_fn_ranges: Vec<(Cow<'static, str>, std::ops::Range)>, - // Address of input memory + /// Range of input memory pub input_mem: Range, // FreeRTOS specific addresses + /// Address of the current TCB pointer pub tcb_addr: GuestAddr, + /// Address of the ready queues pub ready_queues: GuestAddr, + /// Address of the delay queue pub delay_queue: GuestAddr, + /// Address of the delay overflow queue pub delay_queue_overflow: GuestAddr, + /// Address of the scheduler lock variable pub scheduler_lock_addr: GuestAddr, + /// Address of the scheduler running variable pub scheduler_running_addr: GuestAddr, + /// Address of the critical nesting variable pub critical_addr: GuestAddr, + /// Address of the function which signals that a job is done pub job_done_addrs: GuestAddr, - pub queue_registry_addrs: GuestAddr, + /// Address of the queue registry (not used, but kept for possible future use) + pub _queue_registry_addrs: GuestAddr, } -impl FreeRTOSSystemStateHelper { +impl FreeRTOSSystemStateModule { #[must_use] pub fn new( target_symbols: &HashMap<&'static str, GuestAddr>, @@ -77,7 +101,7 @@ impl FreeRTOSSystemStateHelper { let job_done_addrs = *target_symbols.get("trigger_job_done").unwrap(); let queue_registry_addrs = *target_symbols.get("xQueueRegistry").unwrap(); - FreeRTOSSystemStateHelper { + FreeRTOSSystemStateModule { app_range, api_fn_addrs, api_fn_ranges, @@ -92,12 +116,12 @@ impl FreeRTOSSystemStateHelper { scheduler_running_addr, critical_addr, job_done_addrs, - queue_registry_addrs, + _queue_registry_addrs: queue_registry_addrs, } } } -impl EmulatorModule for FreeRTOSSystemStateHelper +impl EmulatorModule for FreeRTOSSystemStateModule where S: UsesInput + Unpin + HasMetadata, { @@ -280,6 +304,7 @@ where //============================= Trace job response times +/// List of all finished jobs (icount, task name) pub static mut JOBS_DONE: Vec<(u64, String)> = vec![]; pub fn job_done_hook( @@ -293,7 +318,7 @@ pub fn job_done_hook( let emulator = hooks.qemu(); let h = hooks .modules() - .match_first_type::() + .match_first_type::() .expect("QemuSystemHelper not found in helper tupel"); let curr_tcb_addr: bindings::void_ptr = super::QemuLookup::lookup(&emulator, h.tcb_addr); if curr_tcb_addr == 0 { @@ -324,7 +349,7 @@ pub fn exec_isr_hook( let emulator = hooks.qemu(); let h = hooks .modules() - .match_first_type::() + .match_first_type::() .expect("QemuSystemHelper not found in helper tupel"); let src = read_rec_return_stackframe(&emulator, 0xfffffffc); trigger_collection(&emulator, (src, pc), CaptureEvent::ISRStart, h); @@ -345,7 +370,7 @@ where { if let Some(h) = hooks .modules() - .match_first_type::() + .match_first_type::() { if h.app_range.contains(&src) && !h.app_range.contains(&dest) @@ -385,7 +410,7 @@ pub fn trace_jmp( { let h = hooks .modules() - .match_first_type::() + .match_first_type::() .expect("QemuSystemHelper not found in helper tupel"); let emulator = hooks.qemu(); if id == 1 { @@ -433,7 +458,7 @@ where { if let Some(h) = hooks .modules() - .match_first_type::() + .match_first_type::() { if h.app_range.contains(&pc) { // println!("gen_read {:x}", pc); @@ -472,4 +497,182 @@ pub fn trace_reads( } } } +} +} + +// ============================= FreeRTOS State Reading + +/// 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, + target: GuestAddr, +) -> (List_t, bool) { + let read: List_t = QemuLookup::lookup(emulator, target); + let listbytes: GuestAddr = GuestAddr::try_from(std::mem::size_of::()).unwrap(); + + let mut next_index = read.pxIndex; + for _j in 0..read.uxNumberOfItems { + // always jump over the xListEnd marker + if (target..target + listbytes).contains(&next_index) { + let next_item: MiniListItem_t = QemuLookup::lookup(emulator, next_index); + let new_next_index = next_item.pxNext; + systemstate + .dumping_ground + .insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item)); + next_index = new_next_index; + } + let next_item: ListItem_t = QemuLookup::lookup(emulator, next_index); + // println!("Item at {}: {:?}",next_index,next_item); + if next_item.pvContainer != target { + // the list is being modified, abort by setting the list empty + eprintln!("Warning: attempted to read a list that is being modified"); + let mut read = read; + read.uxNumberOfItems = 0; + return (read, false); + } + // assert_eq!(next_item.pvContainer,target); + let new_next_index = next_item.pxNext; + let next_tcb: TCB_t = QemuLookup::lookup(emulator, next_item.pvOwner); + // println!("TCB at {}: {:?}",next_item.pvOwner,next_tcb); + systemstate.dumping_ground.insert( + next_item.pvOwner, + FreeRTOSStruct::TCB_struct(next_tcb.clone()), + ); + systemstate + .dumping_ground + .insert(next_index, FreeRTOSStruct::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 = QemuLookup::lookup(emulator, next_index); + systemstate + .dumping_ground + .insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item)); + } + 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, + edge: (GuestAddr, GuestAddr), + event: CaptureEvent, + h: &FreeRTOSSystemStateModule, +) { + let listbytes: GuestAddr = + GuestAddr::try_from(std::mem::size_of::()).unwrap(); + let mut systemstate = RawFreeRTOSSystemState::default(); + + match event { + CaptureEvent::APIStart => { + let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.1).unwrap(); + systemstate.capture_point = (CaptureEvent::APIStart, s.clone()); + } + CaptureEvent::APIEnd => { + let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.0).unwrap(); + systemstate.capture_point = (CaptureEvent::APIEnd, s.clone()); + } + CaptureEvent::ISRStart => { + let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.1).unwrap(); + systemstate.capture_point = (CaptureEvent::ISRStart, s.clone()); + } + CaptureEvent::ISREnd => { + let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.0).unwrap(); + systemstate.capture_point = (CaptureEvent::ISREnd, s.clone()); + } + CaptureEvent::End => { + systemstate.capture_point = (CaptureEvent::End, Cow::Borrowed("")); + } + CaptureEvent::Undefined => (), + } + + if systemstate.capture_point.0 == CaptureEvent::Undefined { + // println!("Not found: {:#x} {:#x}", edge.0.unwrap_or(0), edge.1.unwrap_or(0)); + } + systemstate.edge = ((edge.0), (edge.1)); + + systemstate.qemu_tick = get_icount(emulator); + + let curr_tcb_addr: freertos::void_ptr = QemuLookup::lookup(emulator, h.tcb_addr); + if curr_tcb_addr == 0 { + return; + }; + + /* + let mut queue_registry : Vec = QemuLookup::lookup_slice(emulator, h.queue_registry_addrs, configQUEUE_REGISTRY_SIZE as usize); + let queue_registry = queue_registry.into_iter().filter(|x| x.xHandle != 0).map(|x| { + let queue_def: freertos::QueueDefinition = QemuLookup::lookup(emulator, x.xHandle); + let queue_name: String = emu_lookup_string(emulator, x.pcQueueName, None); + if queue_def.cRxLock == 0xFF && queue_def.cTxLock == 0xFF { + let sending = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend); + let recieving = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend); + } + (queue_def, queue_name) + } + ).collect::>(); + dbg!(&queue_registry); + */ + + // println!("{:?}",std::str::from_utf8(¤t_tcb.pcTaskName)); + let critical: void_ptr = QemuLookup::lookup(emulator, h.critical_addr); + let suspended: void_ptr = QemuLookup::lookup(emulator, h.scheduler_lock_addr); + let _running: void_ptr = QemuLookup::lookup(emulator, h.scheduler_running_addr); + + systemstate.current_tcb = QemuLookup::lookup(emulator, curr_tcb_addr); + // During ISRs it is only safe to extract structs if they are not currently being modified + if systemstate.capture_point.0 == CaptureEvent::APIStart + || systemstate.capture_point.0 == CaptureEvent::APIEnd + || (critical == 0 && suspended == 0) + { + // Extract delay list + let mut target: GuestAddr = h.delay_queue; + target = QemuLookup::lookup(emulator, target); + let _temp = read_freertos_list(&mut systemstate, emulator, target); + systemstate.delay_list = _temp.0; + systemstate.read_invalid |= !_temp.1; + + // Extract delay list overflow + let mut target: GuestAddr = h.delay_queue_overflow; + target = QemuLookup::lookup(emulator, target); + let _temp = read_freertos_list(&mut systemstate, emulator, target); + systemstate.delay_list_overflow = _temp.0; + systemstate.read_invalid |= !_temp.1; + + // Extract suspended tasks (infinite wait), seems broken, always appreas to be modified + // let mut target : GuestAddr = h.suspended_queue; + // target = QemuLookup::lookup(emulator, target); + // systemstate.suspended_list = read_freertos_list(&mut systemstate, emulator, target); + + // Extract priority lists + for i in 0..configMAX_PRIORITIES as usize { + let target: GuestAddr = listbytes * GuestAddr::try_from(i).unwrap() + h.ready_queues; + let _temp = read_freertos_list(&mut systemstate, emulator, target); + systemstate.prio_ready_lists[i] = _temp.0; + systemstate.read_invalid |= !_temp.1; + } + } else { + systemstate.read_invalid = true; + } + systemstate.mem_reads = unsafe { std::mem::replace((&raw mut MEM_READ).as_mut().unwrap(), vec![])}; + + unsafe { + (&raw mut CURRENT_SYSTEMSTATE_VEC).as_mut().unwrap().push(systemstate); + } } \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs index 1038fac6a7..cac6ae4b16 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/mod.rs @@ -1,32 +1,25 @@ #![allow(non_camel_case_types)] use libafl_qemu::GuestAddr; -use qemu_module::{FreeRTOSSystemStateHelper, MEM_READ}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use crate::{ impl_emu_lookup, - systemstate::{helpers::get_icount, CaptureEvent}, + systemstate::CaptureEvent, }; pub mod bindings; -pub mod qemu_module; +pub mod extraction; pub mod config; pub mod post_processing; use bindings::*; -use super::QemuLookup; use crate::systemstate::target_os::*; -// Constants -const NUM_PRIOS: usize = 15; - //============================================================================= Outside interface #[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct FreeRTOSSystem { - pub raw_trace: Vec, -} +pub struct FreeRTOSSystem {} impl TargetSystem for FreeRTOSSystem { type State = FreeRTOSSystemState; @@ -54,7 +47,7 @@ impl SystemState for FreeRTOSSystemState { &self.ready_list_after } - fn get_delay_list(&self) -> &Vec { + fn _get_delay_list(&self) -> &Vec { &self.delay_list_after } @@ -70,7 +63,7 @@ impl SystemState for FreeRTOSSystemState { //============================================================================= Data structures #[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub enum FreeRTOSStruct { +enum FreeRTOSStruct { TCB_struct(TCB_t), List_struct(List_t), List_Item_struct(ListItem_t), @@ -86,233 +79,16 @@ impl_emu_lookup!(TaskStatus_t); impl_emu_lookup!(QueueRegistryItem_t); impl_emu_lookup!(Queue_t); -pub const ISR_SYMBOLS: &'static [&'static str] = &[ - // ISRs - "Reset_Handler", - "Default_Handler", - "Default_Handler2", - "Default_Handler3", - "Default_Handler4", - "Default_Handler5", - "Default_Handler6", - "vPortSVCHandler", - "xPortPendSVHandler", - "xPortSysTickHandler", - "ISR_0_Handler", - "ISR_1_Handler", - "ISR_2_Handler", - "ISR_3_Handler", - "ISR_4_Handler", - "ISR_5_Handler", - "ISR_6_Handler", - "ISR_7_Handler", - "ISR_8_Handler", - "ISR_9_Handler", - "ISR_10_Handler", - "ISR_11_Handler", - "ISR_12_Handler", - "ISR_13_Handler", -]; -pub const USR_ISR_SYMBOLS: &'static [&'static str] = &[ - "ISR_0_Handler", - "ISR_1_Handler", - "ISR_2_Handler", - "ISR_3_Handler", - "ISR_4_Handler", - "ISR_5_Handler", - "ISR_6_Handler", - "ISR_7_Handler", - "ISR_8_Handler", - "ISR_9_Handler", - "ISR_10_Handler", - "ISR_11_Handler", - "ISR_12_Handler", - "ISR_13_Handler", -]; + //============================================================================= 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, - target: GuestAddr, -) -> (List_t, bool) { - let read: List_t = QemuLookup::lookup(emulator, target); - let listbytes: GuestAddr = GuestAddr::try_from(std::mem::size_of::()).unwrap(); - - let mut next_index = read.pxIndex; - for _j in 0..read.uxNumberOfItems { - // always jump over the xListEnd marker - if (target..target + listbytes).contains(&next_index) { - let next_item: MiniListItem_t = QemuLookup::lookup(emulator, next_index); - let new_next_index = next_item.pxNext; - systemstate - .dumping_ground - .insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item)); - next_index = new_next_index; - } - let next_item: ListItem_t = QemuLookup::lookup(emulator, next_index); - // println!("Item at {}: {:?}",next_index,next_item); - if next_item.pvContainer != target { - // the list is being modified, abort by setting the list empty - eprintln!("Warning: attempted to read a list that is being modified"); - let mut read = read; - read.uxNumberOfItems = 0; - return (read, false); - } - // assert_eq!(next_item.pvContainer,target); - let new_next_index = next_item.pxNext; - let next_tcb: TCB_t = QemuLookup::lookup(emulator, next_item.pvOwner); - // println!("TCB at {}: {:?}",next_item.pvOwner,next_tcb); - systemstate.dumping_ground.insert( - next_item.pvOwner, - FreeRTOSStruct::TCB_struct(next_tcb.clone()), - ); - systemstate - .dumping_ground - .insert(next_index, FreeRTOSStruct::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 = QemuLookup::lookup(emulator, next_index); - systemstate - .dumping_ground - .insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item)); - } - 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, - edge: (GuestAddr, GuestAddr), - event: CaptureEvent, - h: &FreeRTOSSystemStateHelper, -) { - let listbytes: GuestAddr = - GuestAddr::try_from(std::mem::size_of::()).unwrap(); - let mut systemstate = RawFreeRTOSSystemState::default(); - - match event { - CaptureEvent::APIStart => { - let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.1).unwrap(); - systemstate.capture_point = (CaptureEvent::APIStart, s.clone()); - } - CaptureEvent::APIEnd => { - let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.0).unwrap(); - systemstate.capture_point = (CaptureEvent::APIEnd, s.clone()); - } - CaptureEvent::ISRStart => { - let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.1).unwrap(); - systemstate.capture_point = (CaptureEvent::ISRStart, s.clone()); - } - CaptureEvent::ISREnd => { - let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.0).unwrap(); - systemstate.capture_point = (CaptureEvent::ISREnd, s.clone()); - } - CaptureEvent::End => { - systemstate.capture_point = (CaptureEvent::End, Cow::Borrowed("")); - } - CaptureEvent::Undefined => (), - } - - if systemstate.capture_point.0 == CaptureEvent::Undefined { - // println!("Not found: {:#x} {:#x}", edge.0.unwrap_or(0), edge.1.unwrap_or(0)); - } - systemstate.edge = ((edge.0), (edge.1)); - - systemstate.qemu_tick = get_icount(emulator); - - let curr_tcb_addr: freertos::void_ptr = QemuLookup::lookup(emulator, h.tcb_addr); - if curr_tcb_addr == 0 { - return; - }; - - /* - let mut queue_registry : Vec = QemuLookup::lookup_slice(emulator, h.queue_registry_addrs, configQUEUE_REGISTRY_SIZE as usize); - let queue_registry = queue_registry.into_iter().filter(|x| x.xHandle != 0).map(|x| { - let queue_def: freertos::QueueDefinition = QemuLookup::lookup(emulator, x.xHandle); - let queue_name: String = emu_lookup_string(emulator, x.pcQueueName, None); - if queue_def.cRxLock == 0xFF && queue_def.cTxLock == 0xFF { - let sending = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend); - let recieving = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend); - } - (queue_def, queue_name) - } - ).collect::>(); - dbg!(&queue_registry); - */ - - // println!("{:?}",std::str::from_utf8(¤t_tcb.pcTaskName)); - let critical: void_ptr = QemuLookup::lookup(emulator, h.critical_addr); - let suspended: void_ptr = QemuLookup::lookup(emulator, h.scheduler_lock_addr); - let _running: void_ptr = QemuLookup::lookup(emulator, h.scheduler_running_addr); - - systemstate.current_tcb = QemuLookup::lookup(emulator, curr_tcb_addr); - // During ISRs it is only safe to extract structs if they are not currently being modified - if systemstate.capture_point.0 == CaptureEvent::APIStart - || systemstate.capture_point.0 == CaptureEvent::APIEnd - || (critical == 0 && suspended == 0) - { - // Extract delay list - let mut target: GuestAddr = h.delay_queue; - target = QemuLookup::lookup(emulator, target); - let _temp = read_freertos_list(&mut systemstate, emulator, target); - systemstate.delay_list = _temp.0; - systemstate.read_invalid |= !_temp.1; - - // Extract delay list overflow - let mut target: GuestAddr = h.delay_queue_overflow; - target = QemuLookup::lookup(emulator, target); - let _temp = read_freertos_list(&mut systemstate, emulator, target); - systemstate.delay_list_overflow = _temp.0; - systemstate.read_invalid |= !_temp.1; - - // Extract suspended tasks (infinite wait), seems broken, always appreas to be modified - // let mut target : GuestAddr = h.suspended_queue; - // target = QemuLookup::lookup(emulator, target); - // systemstate.suspended_list = read_freertos_list(&mut systemstate, emulator, target); - - // Extract priority lists - for i in 0..NUM_PRIOS { - let target: GuestAddr = listbytes * GuestAddr::try_from(i).unwrap() + h.ready_queues; - let _temp = read_freertos_list(&mut systemstate, emulator, target); - systemstate.prio_ready_lists[i] = _temp.0; - systemstate.read_invalid |= !_temp.1; - } - } else { - systemstate.read_invalid = true; - } - systemstate.mem_reads = unsafe { std::mem::replace((&raw mut MEM_READ).as_mut().unwrap(), vec![])}; - - unsafe { - (&raw mut CURRENT_SYSTEMSTATE_VEC).as_mut().unwrap().push(systemstate); - } -} - /// Raw info Dump from Qemu #[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RawFreeRTOSSystemState { +pub(super) struct RawFreeRTOSSystemState { qemu_tick: u64, current_tcb: TCB_t, - prio_ready_lists: [freertos::List_t; NUM_PRIOS], + prio_ready_lists: [freertos::List_t; configMAX_PRIORITIES as usize], delay_list: freertos::List_t, delay_list_overflow: freertos::List_t, dumping_ground: HashMap, @@ -492,7 +268,7 @@ impl fmt::Display for FreeRTOSSystemState { } #[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub(crate)struct FreeRTOSSystemStateContext { +pub(super)struct FreeRTOSSystemStateContext { pub qemu_tick: u64, pub capture_point: (CaptureEvent, Cow<'static, str>), pub edge: (GuestAddr, GuestAddr), diff --git a/fuzzers/FRET/src/systemstate/target_os/freertos/post_processing.rs b/fuzzers/FRET/src/systemstate/target_os/freertos/post_processing.rs index b790ef9faf..0e22d54eec 100644 --- a/fuzzers/FRET/src/systemstate/target_os/freertos/post_processing.rs +++ b/fuzzers/FRET/src/systemstate/target_os/freertos/post_processing.rs @@ -1,6 +1,45 @@ +//! Utilities to refine and analyze raw FreeRTOS captures into higher-level +//! runtime artifacts. +//! +//! This module performs several post-processing steps on traces produced by +//! the FreeRTOS target collector: +//! +//! - Parsing helpers to extract TCB lists from the dumped memory cache +//! (tcb_list_to_vec_cached). +//! - State refinement that converts RawFreeRTOSSystemState into +//! FreeRTOSSystemState and FreeRTOSSystemStateContext pairs +//! (refine_system_states). +//! - Conversion of a sequence of states + metadata into execution +//! intervals (ExecInterval), tracking state hashes and memory reads +//! (states2intervals). +//! - Annotation of intervals with executed Atomic Basic Blocks (ABBs) +//! by matching capture events and trace edges (add_abb_to_interval). +//! - Extraction of task release events from interval transitions +//! (get_releases) and pairing releases with responses +//! (get_release_response_pairs). +//! +//! Key assumptions and behavior +//! - The input traces are sequences of captured system states along with +//! capture events (API/ISR start/end). Many algorithms rely on the +//! availability of valid reads in neighboring states when the direct +//! state is marked read_invalid. +//! - ABB annotation assumes consistent capture events and edge tracing — +//! unexpected or missing starts/ends will be tolerated where possible +//! but may set return flags indicating imperfect reconstruction. +//! - get_releases attempts to detect releases from timer ISRs and API +//! calls and employs heuristics to tolerate nested interrupts and +//! transient invalid reads. +//! - get_release_response_pairs uses simple bookkeeping to match releases +//! to responses; it flags potential ambiguities via the returned bool. +//! +//! Note: The module focuses on deterministic and conservative +//! reconstruction; the heuristics present are tuned to real-world trace +//! imperfections and prioritize producing usable release/response pairs +//! over strict completeness. + use std::{cell::RefCell, collections::VecDeque, rc::Rc}; -use freertos::USR_ISR_SYMBOLS; +use freertos::config::USR_ISR_SYMBOLS; use hashbrown::HashMap; use crate::systemstate::{ @@ -14,6 +53,22 @@ use super::{ FreeRTOSSystemStateContext, RawFreeRTOSSystemState, RefinedTCB, }; +/* Call-graph + +States: +refine_system_states + -> tcb_list_to_vec_cached + +Intervals: +states2intervals + -> add_abb_to_interval + +Jobs: +get_releases +get_release_response_pairs + +*/ + //============================= Parsing helpers /// Parse a List_t containing TCB_t into Vec from cache. Consumes the elements from cache @@ -26,7 +81,7 @@ use super::{ /// # Returns /// /// A Vec of TCB_t extracted from the list. -pub fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap) -> Vec { +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; @@ -94,7 +149,7 @@ pub fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap, ) -> (Vec, Vec) { let mut ret = (Vec::<_>::new(), Vec::<_>::new()); @@ -113,8 +168,8 @@ pub(crate) fn refine_system_states( #[cfg(feature = "observe_systemstate_unordered")] { // respect the order of the first ``lookahead`` tasks and sort the rest by task name - const lookahead : usize = 2; - collector.get_mut(lookahead..).map(|x| x.sort_by(|a, b| a.task_name.cmp(&b.task_name))); + const LOOKAHEAD : usize = 2; + collector.get_mut(LOOKAHEAD..).map(|x| x.sort_by(|a, b| a.task_name.cmp(&b.task_name))); } // collect delay list @@ -162,7 +217,7 @@ pub(crate) fn refine_system_states( /// - Vec of Vec<(u32, u8)> marking memory reads during these intervals /// - HashMap by hash /// - bool indicating success -pub(crate) fn states2intervals( +pub(super) fn states2intervals( trace: Vec, meta: Vec, ) -> ( @@ -263,7 +318,7 @@ pub(crate) fn states2intervals( last_hash = next_hash; edges.push((meta[i].edge.1, meta[i + 1].edge.0)); } - let t = add_abb_info(&mut ret, &table, &edges); + let t = add_abb_to_interval(&mut ret, &table, &edges); (ret, reads, table, t) } @@ -278,7 +333,7 @@ pub(crate) fn states2intervals( /// # Returns /// /// A bool indicating whether ABB annotation was successful. -pub(crate) fn add_abb_info( +fn add_abb_to_interval( trace: &mut Vec, state_table: &HashMap, edges: &Vec<(u32, u32)>, @@ -485,7 +540,7 @@ pub(crate) fn add_abb_info( //============================================= Task release times -// Find all task release times. +/// Find all task release times by looking for chanegs in the ready list /// /// # Arguments /// @@ -495,7 +550,7 @@ pub(crate) fn add_abb_info( /// # Returns /// /// A Vec of (u64, String) tuples, where each tuple is (tick, task_name) for each release event. -pub(crate) fn get_releases( +pub(super) fn get_releases( trace: &Vec, states: &HashMap, ) -> Vec<(u64, String)> { @@ -584,11 +639,6 @@ pub(crate) fn get_releases( ret.push((i.end_tick, x.task_name.clone())); } }); - // start_state.delay_list_after.iter().for_each(|x| { - // if !end_state.delay_list_after.iter().any(|y| x.task_name == y.task_name) { - // ret.push((i.end_tick, x.task_name.clone())); - // } - // }); } else if i.end_capture.0 == CaptureEvent::ISRStart { // Nested interrupts. Fast-forward to the end of the original interrupt, or the first valid state thereafter // TODO: this may cause the same release to be registered multiple times @@ -618,17 +668,6 @@ pub(crate) fn get_releases( break; } } - // if let Some(interval_end) = trace.get(_n+2) { - // if interval_end.start_capture.0 == CaptureEvent::ISREnd && interval_end.end_capture.0 == CaptureEvent::ISREnd && interval_end.end_capture.1 == i.start_capture.1 { - // let start_state = states.get(&i.start_state).expect("State not found"); - // let end_state = states.get(&interval_end.end_state).expect("State not found"); - // end_state.ready_list_after.iter().for_each(|x| { - // if x.task_name != end_state.current_task.task_name && x.task_name != start_state.current_task.task_name && !start_state.ready_list_after.iter().any(|y| x.task_name == y.task_name) { - // ret.push((i.end_tick, x.task_name.clone())); - // } - // }); - // } - // } } } // Release driven by an API call. This produces a lot of false positives, as a job may block multiple times per instance. Despite this, aperiodic jobs not be modeled otherwise. If we assume the first release is the real one, we can filter out the rest. @@ -680,7 +719,7 @@ pub(crate) fn get_releases( /// A tuple containing: /// - Vec of (u64, u64, String) tuples for (release_tick, response_tick, task_name) /// - bool indicating if there was a possible error in pairing -pub(crate) fn get_release_response_pairs( +pub(super) fn get_release_response_pairs( rel: &Vec<(u64, String)>, resp: &Vec<(u64, String)>, ) -> (Vec<(u64, u64, String)>, bool) { diff --git a/fuzzers/FRET/src/systemstate/target_os/mod.rs b/fuzzers/FRET/src/systemstate/target_os/mod.rs index fbeaf557cc..d7aa40150f 100644 --- a/fuzzers/FRET/src/systemstate/target_os/mod.rs +++ b/fuzzers/FRET/src/systemstate/target_os/mod.rs @@ -36,7 +36,7 @@ pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + D fn current_task(&self) -> &Self::TCB; fn current_task_mut(&mut self) -> &mut Self::TCB; fn get_ready_lists(&self) -> &Vec; - fn get_delay_list(&self) -> &Vec; + fn _get_delay_list(&self) -> &Vec; fn print_lists(&self) -> String; }