diff --git a/fuzzers/wcet_qemu_sys/.gitignore b/fuzzers/wcet_qemu_sys/.gitignore new file mode 100644 index 0000000000..0daf8ec693 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/.gitignore @@ -0,0 +1,6 @@ +*.axf +*.qcow2 +demo +*.ron +*.bsp +tmp* diff --git a/fuzzers/wcet_qemu_sys/Cargo.toml b/fuzzers/wcet_qemu_sys/Cargo.toml new file mode 100644 index 0000000000..d6dd0056bd --- /dev/null +++ b/fuzzers/wcet_qemu_sys/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "wcet_qemu_sys" +version = "0.1.2" +authors = [ "Alwin Berger " ] +edition = "2021" + +[features] +default = ["std", "obj_collect"] +std = [] +multicore = [] + +fuzz_interrupt = [] # use the first bytes of the input for a timed interrupts +fuzz_random = [] # just process random inputs + +# select which feedbacks to use. enable at least one. +feed_known_edges = [] +feed_afl = [] +feed_clock = [] +feed_state = [] +feed_graph = [] + +# choose exactly one scheduler +sched_queue = [] +sched_mapmax = [] +sched_state = [] +sched_graph = [] + +# objective selection, additive +obj_collect = [] +obj_trace = [] +obj_edges = [] +obj_ticks = [] + +muta_input = [ "sched_graph" ] +muta_snip = [ "sched_graph" ] +muta_suffix = [ "sched_graph" ] +muta_interrupt = [] + +benchmark = [] # don't save corpus to disk, easy parallelizable +dump_infos = [] # dump select corpus items for analysis + +[profile.release] +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["systemmode", "arm", "jmp_as_edge"] } +clap = { version = "3.0.0-beta.2", features = ["default"] } +serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib +ron = "0.7" # write serialized data - including hashmaps +hashbrown = { version = "0.11", features = ["serde", "ahash-compile-time-rng"], default-features=false } # A faster hashmap, nostd compatible +nix = "0.23.0" +goblin = "0.4.2" +either = "1.6.1" +num-traits = "0.2" +petgraph = { version="0.6.0", features = ["serde-1"] } \ No newline at end of file diff --git a/fuzzers/wcet_qemu_sys/Makefile b/fuzzers/wcet_qemu_sys/Makefile new file mode 100644 index 0000000000..c318f66582 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/Makefile @@ -0,0 +1,113 @@ +BENCHDIR = target/bench +TNAME = tmrint +TARGET = $(TNAME).axf +TARGET_TRACE = $(BENCHDIR)/traces/$(TNAME)_worst.ron +TARGET_EDGES = $(BENCHDIR)/edges/$(TNAME)_worst.ron +RUNTIME = 3600 +INT_FLAG = ,fuzz_interrupt +COMMON_FLAGS = benchmark,dump_infos#,$(INT_FLAG) +NUM_JOB = 3 +NUM_ITERATIONS = 10 +LOCKFILE = /tmp/bench_sem +LOCK = ./bash_sem.sh $(LOCKFILE) lock +RELEASE = ./bash_sem.sh $(LOCKFILE) release + +$(BENCHDIR)/bin: + mkdir -p $@ + +$(BENCHDIR)/target_random: + cargo build --target-dir $@ --features $(COMMON_FLAGS),sched_queue,fuzz_random + +$(BENCHDIR)/target_known_edges: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_known_edges,sched_queue + +$(BENCHDIR)/target_afl_queue: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_afl,sched_queue + +$(BENCHDIR)/target_afl_mapmax: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_afl,sched_mapmax + +$(BENCHDIR)/target_state: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_state,sched_state + +$(BENCHDIR)/target_state_afl: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_state,feed_afl,sched_state + +$(BENCHDIR)/target_state_afl_int: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_state,sched_state,muta_interrupt + +$(BENCHDIR)/target_graph: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph + +$(BENCHDIR)/target_graph_muta: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,muta_snip,muta_input,muta_suffix + +$(BENCHDIR)/target_graph_afl: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,feed_afl + +$(BENCHDIR)/target_graph_muta_afl: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,feed_afl,muta_snip,muta_input,muta_suffix + +$(BENCHDIR)/target_graph_muta_afl_int: + cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,feed_afl,muta_snip,muta_input,muta_suffix,muta_interrupt + +binaries: $(BENCHDIR)/target_random $(BENCHDIR)/target_known_edges $(BENCHDIR)/target_afl_queue $(BENCHDIR)/target_afl_mapmax $(BENCHDIR)/target_state $(BENCHDIR)/target_state_afl $(BENCHDIR)/target_state_afl_int \ + $(BENCHDIR)/target_graph $(BENCHDIR)/target_graph_muta $(BENCHDIR)/target_graph_afl $(BENCHDIR)/target_graph_muta_afl $(BENCHDIR)/target_graph_muta_afl_int + +# variants: known_edges, afl_queue, afl_mapmax, state, state_afl, graph, graph_muta, graph_afl, graph_all +$(BENCHDIR)/bench_%.log: $(BENCHDIR)/target_% $(TARGET_TRACE) + mkdir -p $(BENCHDIR)/execs + for i in {1..$(NUM_ITERATIONS)}; do \ + CASE=$$(basename -s.log $@ | cut -d'_' -f 2- ); \ + [ -f $(BENCHDIR)/execs/$$CASE\_$$i.exec -a -f $@_$$i ] && continue; \ + $(LOCK); \ + echo $$CASE iteration $$i; \ + mkdir -p $(BENCHDIR)/infos/$$CASE ; \ + ./bench_fuzzer.sh $ $@_$$i && \ + sed -i "1 i\\$$CASE " $(BENCHDIR)/execs/$$CASE\_$$i.exec && \ + $(RELEASE) & \ + done + wait + for i in $@_*; do grep Stats $$i | tail -n 1 >> $@; done + +benchmarks_noint: target/bench/bench_known_edges.log target/bench/bench_afl_queue.log target/bench/bench_afl_mapmax.log target/bench/bench_state.log target/bench/bench_state_afl.log \ + target/bench/bench_graph.log target/bench/bench_graph_muta.log target/bench/bench_graph_afl.log target/bench/bench_graph_muta_afl.log +# target/bench/bench_graph_all.log target/bench/bench_state_afl_int.log + +benchmarks_int: target/bench/bench_graph_muta_afl.log target/bench/bench_graph_muta_afl_int.log target/bench/bench_state_afl.log target/bench/bench_state_afl_int.log target/bench/bench_afl_mapmax.log target/bench/bench_afl_queue.log + +benchmark_random: target/bench/bench_random.log + +all: binaries benchmarks_noint + +clean_bench: + rm -rf $(BENCHDIR)/bench_* + +clean_aggregate: + rm -rf $(BENCHDIR)/bench_*.log + +clean: + rm -rf target/bench + echo $(NUM_JOB) > $(LOCKFILE) + rm -rf $(LOCKFILE)_lockdir + +reset_sem: + echo $(NUM_JOB) > $(LOCKFILE) + rm -rf $(LOCKFILE)_lockdir + +%.case: %_inputs $(BENCHDIR)/target_random + mkdir -p $(BENCHDIR)/traces $(BENCHDIR)/edges + for i in $ /tmp/test + +set -e +if [[ ! -f $1 ]]; then echo "Parameter 1: File Does not exist"; exit; fi +if [[ $2 != "lock" ]] && [[ $2 != "release" ]]; then echo "Parameter 2: must be lock or release"; exit; fi +if [[ $2 = "lock" ]]; then + SEM='' + while [[ -z $SEM ]]; do + if (( $(cat $1 ) == 0 )); then sleep 1; wait; continue; fi + if mkdir $1_lockdir > /dev/null 2>&1 ; then + VAL=$(cat $1) + if (( $VAL > 0 )) + then + SEM=$(sed -i "s@$VAL@$(( $VAL - 1))@w /dev/stdout" $1) + echo "Take $VAL -> $SEM" + rmdir $1_lockdir + else + rmdir $1_lockdir + sleep 1; wait + fi + else + sleep 0.5; + fi + done +else + echo "Attempt unlock" + SEM='' + while [[ -z $SEM ]]; do + if mkdir $1_lockdir > /dev/null 2>&1 ; then + VAL=$(cat $1) + SEM=$(sed -i "s@$VAL@$(( $VAL + 1))@w /dev/stdout" $1) + echo "Give $VAL -> $(( $VAL + 1 ))" + else + sleep 0.1; + fi + rmdir $1_lockdir + done +fi + +#SEM=''; while [[ -z SEM ]]; do VAL=$(cat /tmp/test); if (( $VAL > 0 )); then SEM=$(sed -i "s@$VAL@$(( $VAL - 1))@w /dev/stdout" /tmp/test); else sleep 1; wait; fi; done diff --git a/fuzzers/wcet_qemu_sys/bench_fuzzer.sh b/fuzzers/wcet_qemu_sys/bench_fuzzer.sh new file mode 100755 index 0000000000..78135e5f48 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/bench_fuzzer.sh @@ -0,0 +1,5 @@ +mkdir -p tmp/test_in tmp/test_out +[ ! -f tmp/test_in/test ] && echo " !test" > tmp/test_in/test +[ ! -f tmp/dummy.qcow2 ] && qemu-img create -f qcow2 tmp/dummy.qcow2 32M +export LD_LIBRARY_PATH=target/debug +$1 --libafl-snapshot tmp/dummy.qcow2 --libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel ${@:2} diff --git a/fuzzers/wcet_qemu_sys/fuzzer.sh b/fuzzers/wcet_qemu_sys/fuzzer.sh new file mode 100755 index 0000000000..ee53330472 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/fuzzer.sh @@ -0,0 +1,4 @@ +mkdir -p tmp/test_in tmp/test_out +[ ! -f tmp/test_in/test ] && echo " !test" > tmp/test_in/test +[ ! -f tmp/dummy.qcow2 ] && qemu-img create -f qcow2 tmp/dummy.qcow2 32M +LD_LIBRARY_PATH=target/debug target/debug/fuzzer --libafl-logfile tmp/libafl.log --libafl-snapshot tmp/dummy.qcow2 --libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel $@ diff --git a/fuzzers/wcet_qemu_sys/showmap.sh b/fuzzers/wcet_qemu_sys/showmap.sh new file mode 100755 index 0000000000..9cba846cdb --- /dev/null +++ b/fuzzers/wcet_qemu_sys/showmap.sh @@ -0,0 +1,4 @@ +mkdir -p tmp/test_in tmp/test_out +[ ! -f tmp/test_in/test ] && echo " !test" > tmp/test_in/test +[ ! -f tmp/dummy.qcow2 ] && qemu-img create -f qcow2 tmp/dummy.qcow2 32M +LD_LIBRARY_PATH=target/debug target/debug/showmap --libafl-snapshot tmp/dummy.qcow2 --libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel $@ diff --git a/fuzzers/wcet_qemu_sys/src/bin/fuzzer.rs b/fuzzers/wcet_qemu_sys/src/bin/fuzzer.rs new file mode 100644 index 0000000000..255000666f --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/bin/fuzzer.rs @@ -0,0 +1,769 @@ +//! A singlethreaded QEMU fuzzer that can auto-restart. +use libafl::Evaluator; +use libafl::generators::Generator; +use core::cmp::min; +use wcet_qemu_sys::sysstate::mutators::InterruptShifterMutator; +use wcet_qemu_sys::sysstate::IRQ_INPUT_OFFSET; +use std::str::FromStr; +use wcet_qemu_sys::worst::DumpEdgesMapMetadata; +use wcet_qemu_sys::worst::DumpMapFeedback; +use libafl::inputs::HasBytesVec; +use wcet_qemu_sys::sysstate::FreeRTOSSystemStateMetadata; +use wcet_qemu_sys::sysstate::feedbacks::DumpSystraceFeedback; +use libafl::state::HasSolutions; +use wcet_qemu_sys::worst::TimeMaximizerCorpusScheduler; +use libafl::corpus::InMemoryCorpus; +use wcet_qemu_sys::sysstate::graph::RandGraphSuffixMutator; +use wcet_qemu_sys::sysstate::graph::RandInputSnippetMutator; +use wcet_qemu_sys::worst::DummyFeedback; +use wcet_qemu_sys::worst::ExecTimeCollectorFeedback; +use wcet_qemu_sys::worst::EXEC_TIME_COLLECTION; +use wcet_qemu_sys::worst::ExecTimeReachedFeedback; +use libafl::inputs::Input; +use libafl::feedbacks::Feedback; +use libafl::HasFeedback; +use libafl::bolts::tuples::MatchName; +use libafl::state::HasFeedbackStates; +use wcet_qemu_sys::sysstate::graph::SysGraphMetadata; +use wcet_qemu_sys::sysstate::helpers::INTR_OFFSET; +use wcet_qemu_sys::sysstate::graph::RandGraphSnippetMutator; +use wcet_qemu_sys::sysstate::graph::GraphMaximizerCorpusScheduler; +use wcet_qemu_sys::sysstate::graph::SysMapFeedback; +use wcet_qemu_sys::sysstate::graph::SysGraphFeedbackState; +use libafl::stats::SimpleStats; +use wcet_qemu_sys::sysstate::feedbacks::HitSysStateFeedback; +use wcet_qemu_sys::sysstate::RefinedFreeRTOSSystemState; +use libafl::corpus::QueueCorpusScheduler; +use libafl_qemu::QemuInstrumentationFilter; +use wcet_qemu_sys::sysstate::helpers::QemuSystemStateHelper; +use wcet_qemu_sys::sysstate::observers::QemuSysStateObserver; +use wcet_qemu_sys::sysstate::feedbacks::SysStateFeedbackState; +use wcet_qemu_sys::sysstate::feedbacks::NovelSysStateFeedback; +use wcet_qemu_sys::sysstate::IRQ_INPUT_BYTES_NUMBER; +use wcet_qemu_sys::worst::QemuHashMapObserver; +use wcet_qemu_sys::minimizer::QemuCaseMinimizerStage; +use hashbrown::HashMap; +use clap::{App, Arg}; +use core::{cell::RefCell, time::Duration}; +use std::time::SystemTime; +#[cfg(unix)] +use nix::{self, unistd::dup}; +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::{ + env, + fs::{self, File, OpenOptions}, + io::{self, Write}, + path::PathBuf, + process, +}; +use petgraph::prelude::DiGraph; +use petgraph::dot::{Dot, Config}; + +use libafl::{ + bolts::{ + current_nanos, current_time, + os::{dup2,Cores}, + rands::StdRand, + tuples::{tuple_list, Merge}, + shmem::{ShMemProvider,StdShMemProvider}, + launcher::Launcher, + }, + corpus::{ + Corpus, OnDiskCorpus, PowerQueueCorpusScheduler, + }, + executors::{ExitKind, ShadowExecutor, TimeoutExecutor}, + feedback_or, + feedbacks::{MapFeedbackState, MaxMapFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::{MultiMonitor,SimpleMonitor}, + mutators::{ + scheduled::havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, + StdMOptMutator, StdScheduledMutator, Tokens, + }, + observers::{VariableMapObserver}, + stages::{ + calibrate::CalibrationStage, + power::{PowerMutationalStage, PowerSchedule}, + ShadowTracingStage, StdMutationalStage, + }, + state::{HasCorpus, HasMetadata, StdState}, + events::{SimpleEventManager,EventConfig}, + generators::RandBytesGenerator, + Error, +}; +use libafl_qemu::{ + //asan::QemuAsanHelper, + cmplog, + cmplog::{CmpLogObserver, QemuCmpLogHelper}, + edges, + edges::QemuEdgeCoverageHelper, + elf::EasyElf, + emu::Emulator, filter_qemu_args, libafl_int_offset, libafl_exec_block_hook, + snapshot_sys::QemuSysSnapshotHelper, + QemuExecutor, + clock, + QemuClockObserver, + clock::ClockFeedback, + clock::QemuClockIncreaseFeedback +}; +use wcet_qemu_sys::worst::{SortedFeedback,HitFeedback,HitImprovingFeedback,TimeStateMaximizerCorpusScheduler,LenTimeMaximizerCorpusScheduler}; + + +/// The fuzzer main +pub fn main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let res = match App::new("wcet_qemu_fuzzer") + .version("0.4.0") + .author("Alwin Berger") + .about("LibAFL-based fuzzer for WCET in System Kernels.") + .arg( + Arg::new("k") + .long("libafl-kernel") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("out") + .help("The directory to place finds in ('corpus')") + .long("libafl-out") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("in") + .help("The directory to read initial inputs from ('seeds')") + .long("libafl-in") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("tokens") + .long("libafl-tokens") + .help("A file to read tokens from, to be used during fuzzing") + .takes_value(true), + ) + .arg( + Arg::new("logfile") + .long("libafl-logfile") + .help("Duplicates all output to this file") + .default_value("libafl.log"), + ) + .arg( + Arg::new("timeout") + .long("libafl-timeout") + .help("Timeout for each individual execution, in milliseconds") + .default_value("1000"), + ) + .arg( + Arg::new("edges") + .long("libafl-edges") + .takes_value(true), + ) + .arg( + Arg::new("traces") + .long("libafl-traces") + .takes_value(true), + ) + .arg( + Arg::new("snapshot") + .help("The qcow2 file used for snapshots") + .long("libafl-snapshot") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("exectimes") + .long("libafl-exectimes") + .takes_value(true), + ) + .arg( + Arg::new("targettime") + .long("libafl-targettime") + .takes_value(true), + ) + .arg( + Arg::new("dump") + .long("libafl-dump") + .takes_value(true), + ) + .arg( + Arg::new("fuzztime") + .long("libafl-fuzztime") + .takes_value(true), + ) + .arg( + Arg::new("graphdump") + .long("libafl-graphdump") + .takes_value(true), + ) + .try_get_matches_from(filter_qemu_args()) + { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, --libafl-in --libafl-out \n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err.info, + ); + return; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir. + let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string()); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return; + } + } + let mut crashes = out_dir.clone(); + crashes.push("wcets"); + out_dir.push("queue"); + + let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string()); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return; + } + + let tokens = res.value_of("tokens").map(PathBuf::from); + + let logfile = PathBuf::from(res.value_of("logfile").unwrap().to_string()); + + let timeout = Duration::from_millis( + res.value_of("timeout") + .unwrap() + .to_string() + .parse() + .expect("Could not parse timeout in milliseconds"), + ); + + let kernel = PathBuf::from(res.value_of("k").unwrap().to_string()); + let edges = match res.value_of("edges") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + let traces = match res.value_of("traces") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + let exectimes = match res.value_of("exectimes") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + let targettime = match res.value_of("targettime") { + Some(st) => Some(st.to_string().parse::().expect("targettime not a number")), + None => None + }; + + let snapshot = PathBuf::from(res.value_of("snapshot").unwrap().to_string()); + + let dump_path = match res.value_of("dump") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + let fuzztime = match res.value_of("fuzztime") { + Some(st) => Some(u64::from_str(st).expect("Failed parsing fuzztime")), + None => None + }; + + let graph_dump = match res.value_of("graphdump") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + fuzz(out_dir, crashes, in_dir, tokens, logfile, timeout, kernel, edges, traces, exectimes, targettime, snapshot, dump_path, fuzztime, graph_dump) + .expect("An error occurred while fuzzing"); +} + +fn virt2phys(vaddr : u64, tab : &goblin::elf::Elf) -> u64 { + let ret; + for i in &tab.program_headers { + if i.vm_range().contains(&vaddr.try_into().expect("Can not cast u64 to usize")) { + ret = vaddr-i.p_vaddr+i.p_paddr; + return ret - (ret % 2); + } + } + ret = vaddr; + // unlike the arm-toolcahin goblin produces some off-by one errors when parsing arm + return ret - (ret % 2); +} + +/// The actual fuzzer +fn fuzz( + corpus_dir: PathBuf, + objective_dir: PathBuf, + seed_dir: PathBuf, + tokenfile: Option, + logfile: PathBuf, + _timeout: Duration, + kernel: PathBuf, + dump_edges: Option, + dump_traces: Option, + dump_exectimes: Option, + targettime: Option, + snapshot: PathBuf, + dump_path: Option, + fuzztime: Option, + graph_dump: Option, +) -> Result<(), Error> { + env::remove_var("LD_LIBRARY_PATH"); + + //=========== Analyze the binary to find the target function address + let mut elf_buffer = Vec::new(); + let elf = EasyElf::from_file(&kernel, &mut elf_buffer)?; + + let input_addr = elf + .resolve_symbol("FUZZ_INPUT", 0) + .expect("Symbol FUZZ_INPUT not found"); + let input_addr = virt2phys(input_addr,&elf.goblin()); + println!("FUZZ_INPUT @ {:#x}", input_addr); + let test_length_ptr = elf + .resolve_symbol("FUZZ_LENGTH", 0) + .expect("Symbol FUZZ_LENGTH not found"); + let test_length_ptr = virt2phys(test_length_ptr,&elf.goblin()); + println!("FUZZ_LENGTH @ {:#x}", test_length_ptr); + let input_counter = elf + .resolve_symbol("FUZZ_POINTER", 0) + .expect("Symbol FUZZ_POINTER not found"); + // let input_counter = virt2phys(input_counter,&elf.goblin()); + println!("FUZZ_LENGTH @ {:#x}", test_length_ptr); + let check_breakpoint = elf + .resolve_symbol("trigger_Qemu_break", 0) + .expect("Symbol trigger_Qemu_break not found"); + let check_breakpoint = virt2phys(check_breakpoint,&elf.goblin()); + println!("Breakpoint at {:#x}", check_breakpoint); + + 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.goblin()); + 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.goblin()); + + //=========== Prepare Emulator Args + let args: Vec = vec![ + "qemu-system-arm", + "-machine","mps2-an385", + "-monitor", "null", + "-semihosting", + "--semihosting-config", "enable=on,target=native", + "-kernel", kernel.to_str().unwrap(), + "-serial", "stdio", "-nographic", + "-snapshot", "-drive", format!("if=none,format=qcow2,file={}",snapshot.to_string_lossy()).as_str(), + "-icount", "shift=3,align=off,sleep=off", + "-S" + ].iter().map(|x| x.to_string()).collect(); + let env: Vec<(String, String)> = env::vars().collect(); + + + let log = RefCell::new( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + let monitor = MultiMonitor::new(|s| { + println!("{}",s); + writeln!(log.borrow_mut(), "{}",s).unwrap(); + }); + + //====== Child Function + let mut run_client = |state: Option>, mut mgr, _core_id| -> std::result::Result<(), libafl::Error> { + + //====== Set up Emu and termination-point + let emu = Emulator::new(&args, &env); + emu.set_breakpoint(check_breakpoint); // trigger_Qemu_break + // Create an observation channel using the coverage map + let edges = unsafe { &mut edges::EDGES_MAP }; + let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM }; + let edges_observer = + QemuHashMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter)); + + // Create an observation channel to keep track of the execution time + // let time_observer = TimeObserver::new("time"); + let clock_observer = QemuClockObserver::default(); + + // Create an observation channel using cmplog map + // let cmplog_observer = CmpLogObserver::new("cmplog", unsafe { &mut cmplog::CMPLOG_MAP }, true); + + // The state of the edges feedback. + let feedback_state = MapFeedbackState::with_observer(&edges_observer); + + let sysstate_observer = QemuSysStateObserver::new(); + #[cfg(feature = "sched_state")] + let sysstate_feedback_state = SysStateFeedbackState::default(); + #[cfg(not(feature = "sched_state"))] + let sysstate_feedback_state = SysGraphFeedbackState::new(); + + let target_map : HashMap<(u64,u64),u16> = match dump_edges { + None => HashMap::new(), + Some(ref s) => { + let raw = fs::read(s).expect("Can not read dumped edges"); + let hmap : HashMap<(u64,u64),u16> = ron::from_str(&String::from_utf8_lossy(&raw)).expect("Can not parse HashMap"); + hmap + }, + }; + let target_trace : Option> = match dump_traces { + None => None, + Some(ref s) => { + let raw = fs::read(s).expect("Can not read dumped traces"); + let trace : Vec = ron::from_str(&String::from_utf8_lossy(&raw)).expect("Can not parse traces"); + Some(trace) + }, + }; + // Feedback to rate the interestingness of an input + let feedback = ClockFeedback::new_with_observer(&clock_observer); + #[cfg(all(not(feature = "feed_state"), feature = "dump_infos"))] // for diagnostic purposes it's necessary to collect the state in any case + let feedback = feedback_or!(feedback, DumpSystraceFeedback::metadata_only()); + #[cfg(all(feature = "obj_collect", feature = "fuzz_random"))] // random fuzzing does not trigger objective feedbacks + let feedback = feedback_or!(feedback, ExecTimeCollectorFeedback::new()); + #[cfg(feature = "dump_infos")] // for diagnostic purposes it's necessary to collect the state in any case + let feedback = feedback_or!(feedback, DumpMapFeedback::metadata_only(&edges_observer)); + #[cfg(feature = "feed_known_edges")] + let feedback = feedback_or!(feedback, HitImprovingFeedback::new(target_map.clone(), &edges_observer)); + #[cfg(feature = "feed_afl")] + let feedback = feedback_or!(feedback, MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false)); + #[cfg(feature = "feed_clock")] + let feedback = feedback_or!(feedback, QemuClockIncreaseFeedback::default()); + #[cfg(feature = "feed_state")] + let feedback = feedback_or!(feedback, NovelSysStateFeedback::default()); + #[cfg(feature = "feed_graph")] + let feedback = feedback_or!(feedback, SysMapFeedback::new()); + + // A feedback to choose if an input is a solution or not + let objective = DummyFeedback::new(false); + #[cfg(all(feature = "obj_collect",not(feature = "fuzz_random")))] + let objective = ExecTimeCollectorFeedback::new(); + #[cfg(feature = "obj_trace")] + let objective = feedback_or!(HitSysStateFeedback::new(target_trace)); + #[cfg(feature = "obj_edges")] + let objective = feedback_or!(HitFeedback::new(target_map,0.0,&edges_observer), objective); + #[cfg(feature = "obj_ticks")] + let objective = feedback_or!(ExecTimeReachedFeedback::new(targettime)); + + // create a State from scratch + #[cfg(feature = "benchmark")] + let mut state = state.unwrap_or_else(||{ + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + InMemoryCorpus::new(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state,clock::MaxIcountMetadata::default(),sysstate_feedback_state), + ) + }); + #[cfg(not(feature = "benchmark"))] + let mut state = state.unwrap_or_else(||{ + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved + OnDiskCorpus::new(&corpus_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(&objective_dir).unwrap(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state,clock::MaxIcountMetadata::default(),sysstate_feedback_state), + ) + }); + + // let calibration = CalibrationStage::new(&mut state, &edges_observer); + + // Setup a randomic Input2State stage + // let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); + + let mutator_list = havoc_mutations(); + #[cfg(feature = "muta_input")] + let mutator_list = mutator_list.merge(tuple_list!(RandInputSnippetMutator::new())); + #[cfg(feature = "muta_suffix")] + let mutator_list = mutator_list.merge(tuple_list!(RandGraphSuffixMutator::new())); + #[cfg(feature = "muta_snip")] + let mutator_list = mutator_list.merge(tuple_list!(RandGraphSnippetMutator::new())); + #[cfg(all(feature = "muta_interrupt",feature = "fuzz_interrupt"))] + let mutator_list = mutator_list.merge(tuple_list!(InterruptShifterMutator::new())); + // Setup a MOPT mutator + let mutator = StdMOptMutator::new(&mut state, mutator_list,5)?; + + // let power = PowerMutationalStage::new(mutator, PowerSchedule::FAST, &edges_observer); + let mutation = StdMutationalStage::new(mutator); + + // A minimization+queue policy to get testcasess from the corpus + #[cfg(feature = "sched_queue")] + let scheduler = QueueCorpusScheduler::new(); + #[cfg(feature = "sched_mapmax")] + let scheduler = TimeMaximizerCorpusScheduler::new(QueueCorpusScheduler::new()); + #[cfg(feature = "sched_state")] + let scheduler = TimeStateMaximizerCorpusScheduler::new(QueueCorpusScheduler::new()); + #[cfg(feature = "sched_graph")] + let scheduler = GraphMaximizerCorpusScheduler::new(QueueCorpusScheduler::new()); + + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let mut len = buf.len(); + let mut int_tick : Option = None; + if len > IRQ_INPUT_BYTES_NUMBER as usize { + let mut t : [u8; 4] = [0,0,0,0]; // 4 extra bytes determine the tick to execute an interrupt + for i in 0..min(4,IRQ_INPUT_BYTES_NUMBER) { + t[i as usize]=buf[i as usize]; + } + int_tick = Some(u32::from_le_bytes(t)); + buf = &buf[IRQ_INPUT_BYTES_NUMBER as usize..]; + len = buf.len(); + } + if len >= 32 { + buf = &buf[0..32]; + len = 32; + } + + unsafe { + if IRQ_INPUT_BYTES_NUMBER!= 0 { + libafl_int_offset = IRQ_INPUT_OFFSET+int_tick.unwrap_or(0); + } + // INTR_OFFSET = int_tick; + emu.write_mem(test_length_ptr,&(len as u32).to_le_bytes()); + emu.write_mem(input_addr,buf); + + emu.run(); + // since the breakpoint interrupted the last task the last state needs to be recorded + libafl_exec_block_hook(check_breakpoint); + } + + ExitKind::Ok + }; + + let system_state_filter = QemuInstrumentationFilter::AllowList(vec![svh..svh+1,check_breakpoint..check_breakpoint+1]); + let mut executor = QemuExecutor::new( + &mut harness, + &emu, + tuple_list!( + QemuEdgeCoverageHelper::new(), + QemuCmpLogHelper::new(), + //QemuAsanHelper::new(), + QemuSysSnapshotHelper::new(), + QemuSystemStateHelper::with_instrumentation_filter( + system_state_filter,curr_tcb_pointer.try_into().unwrap(), + task_queue_addr.try_into().unwrap(), + input_counter.try_into().unwrap() + ) + ), + tuple_list!(edges_observer, clock_observer,sysstate_observer), + &mut fuzzer, + &mut state, + &mut mgr, + )?; + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + // let executor = TimeoutExecutor::new(executor, timeout); + // Show the cmplog observer + // let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer)); + + // Read tokens + if let Some(tokenfile) = &tokenfile { + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::from_tokens_file(&tokenfile)?); + } + } + + #[cfg(not(feature = "fuzz_random"))] + if state.corpus().count() < 1 { + if _core_id == 0 && state.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]).is_ok() { + // println!("We imported {} inputs from disk.", state.corpus().count()); + } else { + let mut generator = RandBytesGenerator::new(32); + state + .generate_initial_inputs( + &mut fuzzer, + &mut executor, + &mut generator, + &mut mgr, + 8, + ) + .expect("Failed to generate the initial corpus"); + } + } + + // let tracing = ShadowTracingStage::new(&mut executor); + + // The order of the stages matter! + let mut stages = tuple_list!(mutation,QemuCaseMinimizerStage::new(16)); + + // Remove target ouput (logs still survive) + #[cfg(unix)] + let file_null = File::open("/dev/null")?; + #[cfg(unix)] + #[cfg(feature = "multicore")] + { + let null_fd = file_null.as_raw_fd(); + dup2(null_fd, io::stdout().as_raw_fd())?; + dup2(null_fd, io::stderr().as_raw_fd())?; + } + + #[cfg(not(feature = "fuzz_random"))] + { + match fuzztime { + Some(t) => fuzzer + .fuzz_for_solution_or_n(&mut stages, &mut executor, &mut state, &mut mgr, t) + .expect("Error in the fuzzing loop"), + None => fuzzer + .fuzz_for_solution(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in the fuzzing loop"), + }; + } + #[cfg(feature = "fuzz_random")] + { + let mut generator = RandBytesGenerator::new(32); + let target_duration = Duration::from_secs(fuzztime.expect("Random fuzzing needs target time")); + let start_time = SystemTime::now(); + while start_time.elapsed().unwrap() < target_duration { + let inp = generator.generate(&mut state).unwrap(); + fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp); + } + match fuzzer.fuzz_for_solution_or_n(&mut stages, &mut executor, &mut state, &mut mgr, 0) { + _ => (), + } // Just here for the typecheker + } + + + #[cfg(not(feature = "benchmark"))] + #[cfg(feature = "feed_graph")] + { + let feedbackstate = state + .feedback_states() + .match_name::("SysMap") + .unwrap(); + let newgraph = feedbackstate.graph.map( + |_, n| n.get_taskname(), + // |_, n| format!("{} {:?}",n.get_taskname(),n.get_input_counts().iter().min().unwrap_or(&0)), + |_, e| e, + ); + let tempg = format!("{:?}",Dot::with_config(&newgraph, &[Config::EdgeNoLabel])); + match graph_dump { + Some(gd) => fs::write(gd,tempg).expect("Graph can not be written"), + None => (), + } + } + + // Wite out the collected exec times + #[cfg(feature = "obj_collect")] + match dump_exectimes { + Some(et) => unsafe { + let mut stringforms : Vec = Vec::new(); + for i in EXEC_TIME_COLLECTION.drain(..) { + stringforms.push(format!("{}\n",i)); + } + fs::write(et,stringforms.concat()).expect("Exec times can not be written"); + } + None => (), + } + + #[cfg(feature = "dump_infos")] + { + let c = if state.solutions().count()>0 { state.solutions() } else { state.corpus() }; + let mut worst = Duration::new(0,0); + let mut worst_input : Option> = Some(vec![]); + let mut worst_trace : Option = None; + let mut worst_map : Option = None; + for i in 0..c.count() { + let tc = c.get(i).expect("Could not get element from corpus").borrow(); + if worst < tc.exec_time().expect("Testcase missing duration") { + worst = tc.exec_time().expect("Testcase missing duration"); + let metadata = tc.metadata(); + worst_trace = metadata.get::().cloned(); + worst_map = metadata.get::().cloned(); + worst_input = Some(tc.input().as_ref().unwrap().bytes().to_owned()); + } + } + let mut dump_path = dump_path; + match &mut dump_path { + Some(dp) => { + dp.set_extension("trace"); + println!("Path found: {:?}",dp); + fs::write(&dp,ron::to_string(&worst_trace).expect("Error serializing Trace")).expect("Exec times can not be written"); + dp.set_extension("map"); + println!("Path found: {:?}",dp); + fs::write(&dp,ron::to_string(&worst_map).expect("Error serializing hashmap")).expect("Exec times can not be written"); + dp.set_extension("input"); + println!("Path found: {:?}",dp); + fs::write(&dp,worst_input.unwrap()); + dp.set_extension("time"); + println!("Path found: {:?}",dp); + fs::write(&dp,format!("{}",worst.as_nanos()>>3)); + }, + None => (), + } + } + + // Never reached + Ok(()) + + }; + + // Multicore Variant + #[cfg(feature = "multicore")] + { + match Launcher::builder() + .shmem_provider(shmem_provider) + .configuration(EventConfig::AlwaysUnique) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&Cores::from_cmdline("all").unwrap()) + // .broker_port(1337) + // .remote_broker_addr(remote_broker_addr) + //.stdout_file(Some("/dev/null")) + .build() + .launch() + { + Ok(_) | Err(Error::ShuttingDown) => (), + Err(e) => panic!("{:?}", e), + }; + Ok(()) + } + + // Simple Variant + #[cfg(not(feature = "multicore"))] + { + let stats = SimpleStats::new(|s| println!("{}", s)); + let mgr = SimpleEventManager::new(stats); + run_client(None, mgr, 0) + } +} diff --git a/fuzzers/wcet_qemu_sys/src/bin/showmap.rs b/fuzzers/wcet_qemu_sys/src/bin/showmap.rs new file mode 100644 index 0000000000..77e9d02712 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/bin/showmap.rs @@ -0,0 +1,432 @@ +//! A singlethreaded QEMU fuzzer that can auto-restart. + +use core::cmp::min; +use wcet_qemu_sys::sysstate::IRQ_INPUT_OFFSET; +use wcet_qemu_sys::sysstate::IRQ_INPUT_BYTES_NUMBER; +use wcet_qemu_sys::sysstate::helpers::INTR_OFFSET; +use std::io::Read; +use wcet_qemu_sys::sysstate::observers::QemuSysStateObserver; +use wcet_qemu_sys::sysstate::feedbacks::DumpSystraceFeedback; +use wcet_qemu_sys::worst::QemuHashMapObserver; +use wcet_qemu_sys::{ + worst::{DumpMapFeedback,DummyFeedback}, + sysstate::helpers::QemuSystemStateHelper, +}; +use clap::{App, Arg}; +use std::{ + env, + fs::{self}, + path::PathBuf, +}; + +use libafl::{ + bolts::{ + current_nanos, + rands::StdRand, + tuples::{tuple_list}, + }, + corpus::{Corpus,InMemoryCorpus,QueueCorpusScheduler}, + executors::{ExitKind}, + fuzzer::{StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + observers::{VariableMapObserver}, + state::{HasCorpus,StdState}, + Error, + Evaluator, + stats::SimpleStats, + events::SimpleEventManager, + stages::StdMutationalStage, + mutators::BitFlipMutator, + Fuzzer, + feedback_or, +}; +use libafl_qemu::{ + edges, + edges::QemuEdgeCoverageHelper, + emu::Emulator, filter_qemu_args, libafl_int_offset, libafl_exec_block_hook, + elf::EasyElf, + snapshot_sys::QemuSysSnapshotHelper, + clock::{QemuClockObserver}, + QemuInstrumentationFilter, + QemuExecutor, +}; + +use either::{Either,Left,Right}; + +/// The fuzzer main +pub fn main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let res = match App::new("wcet_qemu_fuzzer") + .version("0.4.0") + .author("Alwin Berger") + .about("LibAFL-based fuzzer for WCET in System Kernels.") + .arg( + Arg::new("k") + .long("libafl-kernel") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("out") + .long("libafl-out") + .required(false) + .takes_value(true), + ) + .arg( + Arg::new("in") + .long("libafl-in") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("tokens") + .long("libafl-tokens") + .takes_value(true), + ) + .arg( + Arg::new("logfile") + .long("libafl-logfile") + .default_value("libafl.log"), + ) + .arg( + Arg::new("timeout") + .long("libafl-timeout") + .default_value("1000"), + ) + .arg( + Arg::new("edges") + .long("libafl-edges") + .takes_value(true), + ) + .arg( + Arg::new("traces") + .long("libafl-traces") + .takes_value(true), + ) + .arg( + Arg::new("snapshot") + .help("The qcow2 file used for snapshots") + .long("libafl-snapshot") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("single") + .long("libafl-single") + .takes_value(true) + ) + .try_get_matches_from(filter_qemu_args()) + { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, --libafl-in \n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err.info, + ); + return; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir. + let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string()); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return; + } + } + let mut worstcases = out_dir.clone(); + worstcases.push("worstcase"); + out_dir.push("queue"); + + let seed = match res.value_of("single") { + Some(s) => if s=="-" { + let mut buf = Vec::::new(); + std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin"); + Left(buf) + } else { + Left(fs::read(s).expect("Input file for --libafl-single can not be read")) + }, + None => { + let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string()); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return; + } + Right(in_dir) + }, + }; + println!("{:?}",seed); + + let kernel = PathBuf::from(res.value_of("k").unwrap().to_string()); + let edges = match res.value_of("edges") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + let traces = match res.value_of("traces") { + Some(st) => Some(PathBuf::from(st.to_string())), + None => None + }; + + let snapshot = PathBuf::from(res.value_of("snapshot").unwrap().to_string()); + + fuzz(seed, kernel, edges, traces, snapshot) + .expect("An error occurred while fuzzing"); +} + +fn virt2phys(vaddr : u64, tab : &goblin::elf::Elf) -> u64 { + let ret; + for i in &tab.program_headers { + if i.vm_range().contains(&vaddr.try_into().expect("Can not cast u64 to usize")) { + ret = vaddr-i.p_vaddr+i.p_paddr; + return ret - (ret % 2); + } + } + ret = vaddr; + // unlike the arm-toolcahin goblin produces some off-by one errors when parsing arm + return ret - (ret % 2); +} + +/// The actual fuzzer +fn fuzz( + seed: Either,PathBuf>, + kernel: PathBuf, + dump_edges: Option, + dump_traces: Option, + snapshot: PathBuf, +) -> Result<(), Error> { + //=========== Setup emulator + let mut env: Vec<(String, String)> = env::vars().collect(); + let mut args: Vec = vec![ + "qemu-system-arm", + "-machine","mps2-an385", + "-monitor", "null", + "-semihosting", + "--semihosting-config", "enable=on,target=native", + "-kernel", kernel.to_str().unwrap(), + "-serial", "stdio", "-nographic", + "-snapshot", "-drive", format!("if=none,format=qcow2,file={}",snapshot.to_string_lossy()).as_str(), + "-icount", "shift=3,align=off,sleep=off", + "-S" + ].iter().map(|x| x.to_string()).collect(); + let emu = Emulator::new(&mut args, &mut env); + //=========== Analyze the binary to find the target function address + let mut elf_buffer = Vec::new(); + let bin_path=kernel; + let elf = EasyElf::from_file(bin_path, &mut elf_buffer)?; + + let test_one_input_ptr = elf + .resolve_symbol("FUZZ_INPUT", 0) + .expect("Symbol FUZZ_INPUT not found"); + let test_one_input_ptr = virt2phys(test_one_input_ptr,&elf.goblin()); + println!("FUZZ_INPUT @ {:#x}", test_one_input_ptr); + let test_length_ptr = elf + .resolve_symbol("FUZZ_LENGTH", 0) + .expect("Symbol FUZZ_LENGTH not found"); + let test_length_ptr = virt2phys(test_length_ptr,&elf.goblin()); + println!("FUZZ_LENGTH @ {:#x}", test_length_ptr); + let input_counter = elf + .resolve_symbol("FUZZ_POINTER", 0) + .expect("Symbol FUZZ_POINTER not found"); + // let input_counter = virt2phys(input_counter,&elf.goblin()); + println!("FUZZ_LENGTH @ {:#x}", test_length_ptr); + let check_breakpoint = elf + .resolve_symbol("trigger_Qemu_break", 0) + .expect("Symbol trigger_Qemu_break not found"); + let check_breakpoint = virt2phys(check_breakpoint,&elf.goblin()); + println!("Breakpoint at {:#x}", check_breakpoint); + 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.goblin()); + 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 systick_handler = elf + .resolve_symbol("xPortSysTickHandler", 0) + .expect("Symbol xPortSysTickHandler not found"); + let systick_handler = virt2phys(systick_handler,&elf.goblin()); + println!("SysTick at {:#x}", systick_handler); + let svc_handle = elf + .resolve_symbol("vPortSVCHandler", 0) + .expect("Symbol vPortSVCHandler not found"); + let svc_handle = virt2phys(svc_handle,&elf.goblin()); + println!("SVChandle at {:#x}", svc_handle); + let svh = elf + .resolve_symbol("xPortPendSVHandler", 0) + .expect("Symbol xPortPendSVHandler not found"); + let svh = virt2phys(svh,&elf.goblin()); + println!("PendHandle at {:#x}", svh); + let app_code_start = elf + .resolve_symbol("__APP_CODE_START__", 0) + .expect("Symbol __APP_CODE_START__ not found"); + let app_code_start = virt2phys(app_code_start,&elf.goblin()); + let app_code_end = elf + .resolve_symbol("__APP_CODE_END__", 0) + .expect("Symbol __APP_CODE_END__ not found"); + let app_code_end = virt2phys(app_code_end,&elf.goblin()); + let app_range = app_code_start..app_code_end; + println!("App Code {:x}-{:x}",app_code_start,app_code_end); + + + + //====== Create the input field + let input_addr = test_one_input_ptr; + println!("Placing input at {:#x}", input_addr); + emu.set_breakpoint(check_breakpoint); + + //====== Create the most simple status display and managers. + + let stats = SimpleStats::new(|s| println!("{}", s)); + + let mut mgr = SimpleEventManager::new(stats); + + + //========== EDGES_MAP is static field which somehow(?) gets handed over to the Qemu instrumentation + //========== Otherwise setup generic observers + // Create an observation channel using the coverage map + let edges = unsafe { &mut edges::EDGES_MAP }; + let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM }; + let edges_observer = + QemuHashMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter)); + + //========== Observe Execution Cycles + let clock_observer = QemuClockObserver::default(); + + //========= Feedback-Function evaluate the Maps. Need to dump it for debugging and check if it reaches targets. + let feedback = feedback_or!(DumpMapFeedback::with_dump(dump_edges, &edges_observer),DumpSystraceFeedback::with_dump(dump_traces)); + + // A feedback to choose if an input is a solution or not + let objective = DummyFeedback::new(false); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + InMemoryCorpus::new(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + (), + ); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = QueueCorpusScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + //======== The harness has to start the execution of the emulator + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let mut len = buf.len(); + let mut int_tick : Option = None; + if len > IRQ_INPUT_BYTES_NUMBER as usize && IRQ_INPUT_BYTES_NUMBER!=0{ + let mut t : [u8; 4] = [0,0,0,0]; // 4 extra bytes determine the tick to execute an interrupt + for i in 0..min(4,IRQ_INPUT_BYTES_NUMBER) { + t[i as usize]=buf[i as usize]; + } + int_tick = Some(u32::from_le_bytes(t)); + buf = &buf[IRQ_INPUT_BYTES_NUMBER as usize..]; + len = buf.len(); + } + if len >= 32 { + buf = &buf[0..32]; + len = 32; + } + + unsafe { + if IRQ_INPUT_BYTES_NUMBER!= 0 { + libafl_int_offset = IRQ_INPUT_OFFSET+int_tick.unwrap_or(0); + } + // INTR_OFFSET = int_tick; + emu.write_mem(test_length_ptr,&(len as u32).to_le_bytes()); + emu.write_mem(input_addr,buf); + + emu.run(); + // since the breakpoint interrupted the last task the last state needs to be recorded + libafl_exec_block_hook(check_breakpoint); + println!("Qemu Ticks: {}",emu.get_ticks()); + } + + ExitKind::Ok + }; + //======= Set System-State watchpoints + let system_state_filter = QemuInstrumentationFilter::AllowList(vec![svh..svh+1,check_breakpoint..check_breakpoint+1]); + + //======= Construct the executor, including the Helpers. The edges_observer still contains the ref to EDGES_MAP + let mut executor = QemuExecutor::new( + &mut harness, + &emu, + tuple_list!( + QemuEdgeCoverageHelper::with_app_range(app_range), + // QemuCmpLogHelper::new(), + // QemuAsanHelper::new(), + QemuSysSnapshotHelper::new(), + QemuSystemStateHelper::with_instrumentation_filter( + system_state_filter,curr_tcb_pointer.try_into().unwrap(), + task_queue_addr.try_into().unwrap(), + input_counter.try_into().unwrap() + ) + ), + tuple_list!(edges_observer,clock_observer,QemuSysStateObserver::new()), + &mut fuzzer, + &mut state, + &mut mgr, + )?; + match seed { + Right(pb) => { + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[pb.clone()]) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &pb); + return; + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + // fuzzer + // .fuzz_one(&mut tuple_list!(StdMutationalStage::new(BitFlipMutator::new())), &mut executor, &mut state, &mut mgr) + // .expect("Error in the fuzzing loop"); + }, + Left(s) => { + fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(s)).expect("Evaluation failed"); + } + } + // let firstinput = match seed.clone().is_dir() { + // true => seed.clone().read_dir().expect("Directory not a directory?").next().expect("Directory empty?").expect("File not in directory?").path(), + // false => seed.clone() + // }; + // let secondinput = match seed_dir.clone().is_dir() { + // true => { + // let mut a = seed_dir.clone().read_dir().expect("Directory not a directory?"); + // a.advance_by(1); + // a.next().unwrap().expect("File not in directory?").path() + // }, + // false => seed_dir.clone() + // }; + // fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, Input::from_file(&secondinput).expect("Could not load file")).expect("Evaluation failed"); + // println!("Nach Eval"); + return Ok(()); +} diff --git a/fuzzers/wcet_qemu_sys/src/lib.rs b/fuzzers/wcet_qemu_sys/src/lib.rs new file mode 100644 index 0000000000..a9c17174e1 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/lib.rs @@ -0,0 +1,7 @@ +#![feature(iter_advance_by)] +#![feature(is_sorted)] +#[cfg(target_os = "linux")] +pub mod sysstate; + +pub mod worst; +pub mod minimizer; \ No newline at end of file diff --git a/fuzzers/wcet_qemu_sys/src/minimizer.rs b/fuzzers/wcet_qemu_sys/src/minimizer.rs new file mode 100644 index 0000000000..8822bca5d0 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/minimizer.rs @@ -0,0 +1,70 @@ + +use libafl::inputs::HasBytesVec; +use libafl::corpus::Corpus; +use libafl::bolts::rands::Rand; +use libafl::inputs::BytesInput; +use libafl::stages::Stage; +use libafl::stages::MutationalStage; +use libafl::stages::mutational::DEFAULT_MUTATIONAL_MAX_ITERATIONS; +use core::marker::PhantomData; +use libafl::Evaluator; +use libafl::state::HasRand; +use libafl::state::HasCorpus; +use libafl::Error; +use libafl::state::HasClientPerfMonitor; +use libafl::mutators::Mutator; +use libafl::inputs::Input; + +#[derive(Clone, Debug)] +pub struct QemuCaseMinimizerStage +where + S: HasClientPerfMonitor + HasCorpus + HasRand, + Z: Evaluator, +{ + max_input_length: usize, + #[allow(clippy::type_complexity)] + phantom: PhantomData<(E, EM, S, Z)>, +} + +impl Stage for QemuCaseMinimizerStage +where + S: HasClientPerfMonitor + HasCorpus + HasRand, + Z: Evaluator, +{ + #[inline] + #[allow(clippy::let_and_return)] + fn perform( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + corpus_idx: usize, + ) -> Result<(), Error> { + let mut corpus = state.corpus_mut(); + let mut case = corpus.get(corpus_idx).unwrap().borrow_mut(); + let mut input = case.input_mut().as_mut().unwrap(); + let mut bytes = input.bytes_mut(); + if bytes.len() > self.max_input_length { + bytes.drain(self.max_input_length..); + } + + #[cfg(feature = "introspection")] + state.introspection_monitor_mut().finish_stage(); + Ok(()) + } +} + +impl QemuCaseMinimizerStage +where + S: HasClientPerfMonitor + HasCorpus + HasRand, + Z: Evaluator, +{ + /// Creates a new default mutational stage + pub fn new(max: usize) -> Self { + Self { + max_input_length: max, + phantom: PhantomData, + } + } +} \ No newline at end of file diff --git a/fuzzers/wcet_qemu_sys/src/sysstate/feedbacks.rs b/fuzzers/wcet_qemu_sys/src/sysstate/feedbacks.rs new file mode 100644 index 0000000000..ca6d9e4634 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/feedbacks.rs @@ -0,0 +1,288 @@ +use libafl::bolts::ownedref::OwnedSlice; +use libafl::inputs::BytesInput; +use libafl::prelude::UsesInput; +use std::path::PathBuf; +use libafl_qemu::clock::QemuClockObserver; +// use libafl::feedbacks::FeedbackState; +use libafl::corpus::Testcase; +// use libafl::state::HasFeedbackStates; +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::QemuSysStateObserver; +use petgraph::prelude::DiGraph; +use petgraph::graph::NodeIndex; +use petgraph::Direction; +use std::cmp::Ordering; + +//============================= Feedback + +/// Shared Metadata for a SysStateFeedback +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct SysStateFeedbackState +{ + known_traces: HashMap, // encounters,ticks,length + longest: Vec, +} +impl Named for SysStateFeedbackState +{ + #[inline] + fn name(&self) -> &str { + "sysstate" + } +} +// impl FeedbackState for SysStateFeedbackState +// { +// fn reset(&mut self) -> Result<(), Error> { +// self.longest.clear(); +// self.known_traces.clear(); +// Ok(()) +// } +// } + +/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSysStateObserver`] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct NovelSysStateFeedback +{ + last_trace: Option>, + // known_traces: HashMap, +} + +impl Feedback for NovelSysStateFeedback +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::("sysstate") + .expect("QemuSysStateObserver not found"); + let clock_observer = observers.match_name::("clock") //TODO not fixed + .expect("QemuSysStateObserver not found"); + let feedbackstate = state + .feedback_states_mut() + .match_name_mut::("sysstate") + .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 NovelSysStateFeedback +{ + #[inline] + fn name(&self) -> &str { + "sysstate" + } +} + +//============================= + +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 [`QemuSysStateObserver`] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct HitSysStateFeedback +{ + target: Option>, +} + +impl Feedback for HitSysStateFeedback +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::("sysstate") + .expect("QemuSysStateObserver not found"); + // Do Stuff + match &self.target { + Some(s) => { + // #[cfg(debug_assertions)] eprintln!("Hit SysState Feedback trigger"); + Ok(match_traces_name(s, &observer.last_run)) + }, + None => Ok(false), + } + } +} + +impl Named for HitSysStateFeedback +{ + #[inline] + fn name(&self) -> &str { + "hit_sysstate" + } +} + +impl HitSysStateFeedback { + 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 [`QemuSysStateObserver`] +#[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::("sysstate") + .expect("QemuSysStateObserver 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 { + "DumpSysState" + } +} + +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/wcet_qemu_sys/src/sysstate/freertos.rs b/fuzzers/wcet_qemu_sys/src/sysstate/freertos.rs new file mode 100644 index 0000000000..31bdbcbeec --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/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::sysstate::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/wcet_qemu_sys/src/sysstate/graph.rs b/fuzzers/wcet_qemu_sys/src/sysstate/graph.rs new file mode 100644 index 0000000000..65db4f9b73 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/graph.rs @@ -0,0 +1,592 @@ + +/// 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 libafl_qemu::clock::QemuClockObserver; +// use libafl::feedbacks::FeedbackState; +use libafl::corpus::Testcase; +// use libafl::state::HasFeedbackStates; +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::QemuSysStateObserver; +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 [`QemuSysStateObserver`] +#[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 + HasMetadata, +{ + 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::("sysstate") + .expect("QemuSysStateObserver 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/wcet_qemu_sys/src/sysstate/helpers.rs b/fuzzers/wcet_qemu_sys/src/sysstate/helpers.rs new file mode 100644 index 0000000000..2a3b03e248 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/helpers.rs @@ -0,0 +1,163 @@ +use libafl::prelude::UsesInput; +use libafl_qemu::edges::QemuEdgesMapMetadata; +use crate::sysstate::RawFreeRTOSSystemState; +use crate::sysstate::CURRENT_SYSSTATE_VEC; +use crate::sysstate::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::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; + +use libafl_qemu::{ + emu::Emulator, + executor::QemuExecutor, + helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + edges::SAVED_JUMP, +}; + +//============================= Struct definitions + +pub static mut INTR_OFFSET : Option = None; +pub static mut INTR_DONE : bool = true; + +//============================= Qemu Helper + +/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur +#[derive(Debug)] +pub struct QemuSystemStateHelper { + filter: QemuInstrumentationFilter, + tcb_addr: u32, + ready_queues: u32, + input_counter: u32, +} + +impl QemuSystemStateHelper { + #[must_use] + pub fn new( + tcb_addr: u32, + ready_queues: u32, + input_counter: u32 + ) -> Self { + Self { + filter: QemuInstrumentationFilter::None, + tcb_addr: tcb_addr, + ready_queues: ready_queues, + input_counter: input_counter, + } + } + + #[must_use] + pub fn with_instrumentation_filter( + filter: QemuInstrumentationFilter, + tcb_addr: u32, + ready_queues: u32, + input_counter: u32 + ) -> Self { + Self { filter, tcb_addr, ready_queues, input_counter} + } + + #[must_use] + pub fn must_instrument(&self, addr: u64) -> bool { + self.filter.allowed(addr) + } +} + +impl QemuHelper for QemuSystemStateHelper +where + S: UsesInput, +{ + fn init_hooks(&self, executor: &QemuExecutor) + where + QT: QemuHelperTuple, + { + // emu::Emulator{_private: ()}.set_gen_block_hook(test_gen_hook); + executor.hook_block_generation(gen_not_exec_block_hook::); + executor.hook_block_execution(exec_syscall_hook::); + } +} + +pub fn exec_syscall_hook( + emulator: &Emulator, + helpers: &mut QT, + state: &mut S, + pc: u64, +) +where + S: HasMetadata, + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers.match_first_type::().expect("QemuSystemHelper not found in helper tupel"); + if !h.must_instrument(pc) { + return; + } + let listbytes : u32 = u32::try_from(std::mem::size_of::()).unwrap(); + let mut sysstate = RawFreeRTOSSystemState::default(); + sysstate.qemu_tick = emulator.get_ticks(); + let mut buf : [u8; 4] = [0,0,0,0]; + unsafe { emulator.read_mem(h.input_counter.into(), &mut buf) }; + sysstate.input_counter = u32::from_le_bytes(buf); + + let curr_tcb_addr : freertos::void_ptr = freertos::emu_lookup::lookup(emulator, h.tcb_addr); + sysstate.current_tcb = freertos::emu_lookup::lookup(emulator,curr_tcb_addr); + + unsafe { + match SAVED_JUMP.take() { + Some(s) => { + sysstate.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; + sysstate.prio_ready_lists[i] = freertos::emu_lookup::lookup(emulator, target); + // println!("List at {}: {:?}",target, sysstate.prio_ready_lists[i]); + let mut next_index = sysstate.prio_ready_lists[i].pxIndex; + for _j in 0..sysstate.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; + sysstate.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); + sysstate.dumping_ground.insert(next_item.pvOwner,TCB_struct(next_tcb.clone())); + sysstate.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); + sysstate.dumping_ground.insert(next_index,List_MiniItem_struct(next_item)); + } + } + + unsafe { CURRENT_SYSSTATE_VEC.push(sysstate); } +} + +pub fn gen_not_exec_block_hook( + _emulator: &Emulator, + helpers: &mut QT, + _state: &mut S, + pc: u64, +) +-> Option where + S: HasMetadata, + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers.match_first_type::().expect("QemuSystemHelper not found in helper tupel"); + if !h.must_instrument(pc) { + None + } else {Some(1)} +} diff --git a/fuzzers/wcet_qemu_sys/src/sysstate/mod.rs b/fuzzers/wcet_qemu_sys/src/sysstate/mod.rs new file mode 100644 index 0000000000..c5eabb82d0 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/mod.rs @@ -0,0 +1,163 @@ +//! Sysstate 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_SYSSTATE_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() + } +} + +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/wcet_qemu_sys/src/sysstate/mutators.rs b/fuzzers/wcet_qemu_sys/src/sysstate/mutators.rs new file mode 100644 index 0000000000..29082a572c --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/mutators.rs @@ -0,0 +1,123 @@ +use crate::sysstate::graph::SysGraphMetadata; +use crate::sysstate::graph::SysGraphNode; +use libafl::state::HasFeedbackStates; +use crate::sysstate::IRQ_INPUT_OFFSET; +use crate::sysstate::IRQ_INPUT_BYTES_NUMBER; +use crate::sysstate::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 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 + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + phantom: PhantomData<(I, S)>, +} +impl InterruptShifterMutator +where + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + pub fn new() -> Self { + InterruptShifterMutator{phantom: PhantomData} + } +} +impl Mutator for InterruptShifterMutator +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 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 + I: Input + HasBytesVec, + S: HasRand + HasMetadata + HasCorpus + HasSolutions + HasFeedbackStates, +{ + fn name(&self) -> &str { + "InterruptShifterMutator" + } +} \ No newline at end of file diff --git a/fuzzers/wcet_qemu_sys/src/sysstate/observers.rs b/fuzzers/wcet_qemu_sys/src/sysstate/observers.rs new file mode 100644 index 0000000000..91b5480a51 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/sysstate/observers.rs @@ -0,0 +1,139 @@ +use crate::sysstate::IRQ_INPUT_BYTES_NUMBER; +use libafl::inputs::HasTargetBytes; +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_SYSSTATE_VEC, + RawFreeRTOSSystemState, + RefinedTCB, + RefinedFreeRTOSSystemState, + freertos::{List_t, TCB_t, rtos_struct, rtos_struct::*}, +}; + +//============================= Observer + +/// The QemuSysState Observer retrieves the SysState +/// that will get updated by the target. +#[derive(Serialize, Deserialize, Debug, Default)] +#[allow(clippy::unsafe_derive_deserialize)] +pub struct QemuSysStateObserver +{ + pub last_run: Vec, + pub last_input: Vec, + name: String, +} + +impl Observer for QemuSysStateObserver +where + I: HasTargetBytes +{ + #[inline] + fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { + unsafe {CURRENT_SYSSTATE_VEC.clear(); } + Ok(()) + } + + #[inline] + fn post_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> { + unsafe {self.last_run = refine_system_states(&mut CURRENT_SYSSTATE_VEC);} + self.last_input=_input.target_bytes().as_slice().to_owned(); + // let mut hasher = DefaultHasher::new(); + // let mut a = self.parse_last(); + // a[0].start_tick=21355; + // a[0].end_tick=2131; + // a.hash(&mut hasher); + // let somehash = hasher.finish(); + // println!("HashValue: {}",somehash); + // println!("{:#?}",self.parse_last()); + Ok(()) + } +} + +impl Named for QemuSysStateObserver +{ + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl HasLen for QemuSysStateObserver +{ + #[inline] + fn len(&self) -> usize { + self.last_run.len() + } +} + +impl QemuSysStateObserver { + pub fn new() -> Self { + Self{last_run: vec![], last_input: vec![], name: "sysstate".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 diff --git a/fuzzers/wcet_qemu_sys/src/worst.rs b/fuzzers/wcet_qemu_sys/src/worst.rs new file mode 100644 index 0000000000..dba07641a8 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/worst.rs @@ -0,0 +1,763 @@ +use libafl::feedbacks::FeedbackState; +use libafl_qemu::QemuClockObserver; +use crate::sysstate::FreeRTOSSystemStateMetadata; +use num_traits::PrimInt; +use core::fmt::Debug; +use core::cmp::Ordering::{Greater,Less,Equal}; +use libafl::inputs::BytesInput; +use libafl::inputs::HasTargetBytes; +use libafl::feedbacks::MapIndexesMetadata; +use libafl::corpus::Testcase; +use libafl::corpus::FavFactor; +use core::marker::PhantomData; +use libafl::corpus::MinimizerCorpusScheduler; +use std::path::PathBuf; +use std::fs; +use hashbrown::{HashMap}; +use libafl::observers::ObserversTuple; +use libafl::executors::ExitKind; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::inputs::Input; +use libafl::feedbacks::Feedback; +use libafl::state::HasMetadata; +use libafl_qemu::edges::QemuEdgesMapMetadata; +use libafl::observers::MapObserver; +use serde::{Deserialize, Serialize}; +use std::cmp; + +use libafl::{ + bolts::{ + tuples::Named, + HasLen, + }, + observers::Observer, + Error, +}; +//=================================================================== +/// A wrapper around some other [`MapObserver`], using [`QemuEdgesMapMetadata`] to offer a convinient Hashmap. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "M: serde::de::DeserializeOwned")] +pub struct QemuHashMapObserver +where + M: serde::Serialize + serde::de::DeserializeOwned, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, +{ + base: M, + pub edgemap: HashMap<(u64,u64),T>, +} + +impl Observer for QemuHashMapObserver +where + M: MapObserver + Observer, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + S: HasMetadata, // Need to grab the HashMap from a Helper +{ + #[inline] + fn pre_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> { + self.edgemap = HashMap::new(); + self.base.pre_exec(state, input) + } + + #[inline] + fn post_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> { + let original_hashmap=&state.metadata().get::().expect("QemuEdgesMapMetadata not found").map; + for (key, val) in original_hashmap.iter() { + self.edgemap.insert(*key,*self.base.get(*val as usize)); + } + self.base.post_exec(state, input) + } +} + +impl Named for QemuHashMapObserver +where + M: Named + serde::Serialize + serde::de::DeserializeOwned, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, +{ + #[inline] + fn name(&self) -> &str { + self.base.name() + } +} + +impl HasLen for QemuHashMapObserver +where + M: MapObserver, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, +{ + #[inline] + fn len(&self) -> usize { + self.base.len() + } +} + +impl MapObserver for QemuHashMapObserver +where + M: MapObserver, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, +{ + #[inline] + fn map(&self) -> Option<&[T]> { + self.base.map() + } + + #[inline] + fn map_mut(&mut self) -> Option<&mut [T]> { + self.base.map_mut() + } + + #[inline] + fn usable_count(&self) -> usize { + self.base.usable_count() + } + + #[inline] + fn initial(&self) -> T { + self.base.initial() + } + + #[inline] + fn initial_mut(&mut self) -> &mut T { + self.base.initial_mut() + } + + #[inline] + fn set_initial(&mut self, initial: T) { + self.base.set_initial(initial); + } +} + +impl QemuHashMapObserver +where + M: serde::Serialize + serde::de::DeserializeOwned, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, +{ + /// Creates a new [`MapObserver`] + pub fn new(base: M) -> Self { + Self { edgemap:HashMap::new(), base } + } +} + +//=================================================================== + + +/// A [`HitFeedback`] reports as interesting when all predicted worst case edges have been matched. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "T: serde::de::DeserializeOwned")] +pub struct HitFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + target_map: HashMap<(u64,u64),T>, + target_msd: f64, + phantom: PhantomData<(O, T)>, +} + +impl Feedback for HitFeedback +where + I: Input, + S: HasClientPerfMonitor, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + 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::>("edges") + .expect("QemuHashMapObserver not found"); + if self.target_map.len() == 0 { return Ok(false) }; + + let mut sum_of_square_difference : u64 = 0; // does not include found edges not in target + for (edg, val) in &self.target_map { + match observer.edgemap.get(&edg) { + Some(x) => { + sum_of_square_difference+=((cmp::max(*x,*val)-cmp::min(*x,*val)).to_u64().unwrap()).pow(2); + }, + None => sum_of_square_difference+=((*val).to_u64().unwrap()).pow(2), + } + } + let mean_sum_of_squares = (sum_of_square_difference as f64) / (self.target_map.len() as f64); + let hit_target = mean_sum_of_squares <= self.target_msd; + if hit_target { + // #[cfg(debug_assertions)] eprintln!("Hit Feedback trigger"); + Ok(true) + } else { + Ok(false) + } + } +} + +impl Named for HitFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + #[inline] + fn name(&self) -> &str { + "HitFeedback" + } +} + +impl HitFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new(target_map: HashMap<(u64,u64),T>, target_msd: f64, _map_observer: &QemuHashMapObserver) -> Self { + Self {target_map: target_map, target_msd: target_msd, phantom: PhantomData} + } +} + +//=================================================================== + + +// /// A [`MapHitIncreaseFeedback`] reports as interesting when the total number of used edges increases. +// #[derive(Serialize, Deserialize, Clone, Debug)] +// pub struct MapHitIncreaseFeedback { +// record_high : u64, +// } + +// impl Feedback for MapHitIncreaseFeedback +// where +// I: Input, +// S: HasClientPerfMonitor, +// T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, +// O: MapObserver, +// { +// 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::>("edges") +// .expect("QemuHashMapObserver not found"); +// let cur = observer.edgemap.values().fold(0,|a,b| a+(*b as u64)); +// if cur > self.record_high { +// self.record_high = cur; +// return Ok(true); +// } +// return Ok(false); +// } +// } + +// impl Named for MapHitIncreaseFeedback { +// #[inline] +// fn name(&self) -> &str { +// "HitFeedback" +// } +// } + +// impl MapHitIncreaseFeedback { +// /// Creates a new [`HitFeedback`] +// #[must_use] +// pub fn new() -> Self { +// Self {record_high: 0} +// } +// } + +// impl Default for MapHitIncreaseFeedback { +// fn default() -> Self { +// Self::new() +// } +// } + +//=================================================================== + + +/// A [`HitFeedback`] reports as interesting when all predicted worst case edges have been matched. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "T: serde::de::DeserializeOwned")] +pub struct HitImprovingFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + target_map: HashMap<(u64,u64),T>, + best_msd: f64, + phantom: PhantomData<(O, T)>, +} + +impl Feedback for HitImprovingFeedback +where + I: Input, + S: HasClientPerfMonitor, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + 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::>("edges") + .expect("QemuHashMapObserver not found"); + if self.target_map.len() == 0 { return Ok(false) }; + + let mut sum_of_square_difference : u64 = 0; // does not include found edges not in target + for (edg, val) in &self.target_map { + match observer.edgemap.get(&edg) { + Some(x) => { + sum_of_square_difference+=((cmp::max(*x,*val)-cmp::min(*x,*val)).to_u64().unwrap()).pow(2); + }, + None => sum_of_square_difference+=((*val).to_u64().unwrap()).pow(2), + } + } + let mean_sum_of_squares = (sum_of_square_difference as f64) / (self.target_map.len() as f64); + let hit_target = mean_sum_of_squares < self.best_msd; + if hit_target { + // #[cfg(debug_assertions)] eprintln!("Hit Improving: {}",mean_sum_of_squares); + self.best_msd = mean_sum_of_squares; + Ok(true) + } else { + Ok(false) + } + } +} + +impl Named for HitImprovingFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + #[inline] + fn name(&self) -> &str { + "HitImprovingFeedback" + } +} + +impl HitImprovingFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + /// Creates a new [`HitImprovingFeedback`] + #[must_use] + pub fn new(target_map: HashMap<(u64,u64),T>, _map_observer: &QemuHashMapObserver) -> Self { + Self {target_map: target_map, best_msd: f64::MAX, phantom: PhantomData} + } +} + +//=========================== Debugging Feedback +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct DumpEdgesMapMetadata +{ + pub map: HashMap<(u64, u64), usize>, +} + +impl DumpEdgesMapMetadata +{ + #[must_use] + pub fn new(input: HashMap<(u64,u64),usize>) -> Self { + Self { + map: input, + } + } +} + +libafl::impl_serdeany!(DumpEdgesMapMetadata); +/// A [`Feedback`] meant to dump the edgemap for debugging. +#[derive(Debug)] +pub struct DumpMapFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + dumpfile: Option, + phantom: PhantomData<(O, T)>, + dump_metadata: bool, + last_map: Option>, +} + +impl Feedback for DumpMapFeedback +where + I: Input, + S: HasClientPerfMonitor, + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + 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::>("edges") + .expect("QemuHashMapObserver not found"); + match &self.dumpfile { + Some(s) => { + fs::write(s,ron::to_string(&observer.edgemap).expect("Error serializing hashmap")).expect("Can not dump to file"); + self.dumpfile = None + }, + None => if !self.dump_metadata {println!("{:?}",observer.edgemap);} + }; + if self.dump_metadata {self.last_map=Some(observer.edgemap.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_map.take(); + match a { + Some(s) => { + let mut tmp : HashMap<(u64,u64),usize> = HashMap::new(); + for (k, v) in s.iter() { + tmp.insert(*k, T::to_usize(v).unwrap()); + } + testcase.metadata_mut().insert(DumpEdgesMapMetadata::new(tmp)); + } + 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_map = None; + Ok(()) + } +} + +impl Named for DumpMapFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + #[inline] + fn name(&self) -> &str { + "HitFeedback" + } +} + +impl DumpMapFeedback +where + T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug, + O: MapObserver, +{ + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new(_map_observer: &QemuHashMapObserver) -> Self { + Self {dumpfile: None, phantom: PhantomData, dump_metadata: false, last_map: None} + } + pub fn with_dump(dumpfile: Option,_map_observer: &QemuHashMapObserver) -> Self { + Self {dumpfile: dumpfile, phantom: PhantomData, dump_metadata: false, last_map: None} + } + pub fn metadata_only(_map_observer: &QemuHashMapObserver) -> Self { + Self {dumpfile: None, phantom: PhantomData, dump_metadata: true, last_map: None} + } + +} + +//=========================== Debugging Feedback +/// A NoOp [`Feedback`] with fixed response. +#[derive(Debug)] +pub struct DummyFeedback { + response: bool +} + +impl Feedback for DummyFeedback +where + I: Input, + S: HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &I, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + // eprintln!("Input was: {:?}",_input); + Ok(self.response) + } +} + +impl Named for DummyFeedback { + #[inline] + fn name(&self) -> &str { + "DummyFeedback" + } +} + +impl DummyFeedback { + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new(response: bool) -> Self { + Self {response: response} + } +} + +impl Default for DummyFeedback { + fn default() -> Self { + Self::new(true) + } +} + +//=========================== Scheduler + +pub type TimeMaximizerCorpusScheduler = + MinimizerCorpusScheduler, I, MapIndexesMetadata, S>; + +/// Multiply the testcase size with the execution time. +/// This favors small and quick testcases. +#[derive(Debug, Clone)] +pub struct MaxTimeFavFactor +where + I: Input + HasLen, +{ + phantom: PhantomData, +} + +impl FavFactor for MaxTimeFavFactor +where + I: Input + HasLen, +{ + fn compute(entry: &mut Testcase) -> Result { + // TODO maybe enforce entry.exec_time().is_some() + let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64()) as u64; + Ok(execs_per_hour) + } +} + +pub type LenTimeMaximizerCorpusScheduler = + MinimizerCorpusScheduler, I, MapIndexesMetadata, S>; + +pub type TimeStateMaximizerCorpusScheduler = + MinimizerCorpusScheduler, I, FreeRTOSSystemStateMetadata, S>; + +/// Multiply the testcase size with the execution time. +/// This favors small and quick testcases. +#[derive(Debug, Clone)] +pub struct MaxExecsLenFavFactor +where + I: Input + HasLen, +{ + phantom: PhantomData, +} + +impl FavFactor for MaxExecsLenFavFactor +where + I: Input + HasLen, +{ + fn compute(entry: &mut Testcase) -> Result { + let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64()) as u64; + let execs_times_length_per_hour = execs_per_hour*entry.cached_len()? as u64; + Ok(execs_times_length_per_hour) + } +} + +//=================================================================== + +/// A Feedback reporting if the Input consists of strictly decreasing bytes. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SortedFeedback { +} + +impl Feedback for SortedFeedback +where + S: HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &BytesInput, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let t = _input.target_bytes(); + let tmp = t.as_slice(); + if tmp.len()<32 {return Ok(false);} + let tmp = Vec::::from(&tmp[0..32]); + // tmp.reverse(); + if tmp.is_sorted_by(|a,b| match a.partial_cmp(b).unwrap_or(Less) { + Less => Some(Greater), + Equal => Some(Greater), + Greater => Some(Less), + }) {return Ok(true)}; + return Ok(false); + } +} + +impl Named for SortedFeedback { + #[inline] + fn name(&self) -> &str { + "Sorted" + } +} + +impl SortedFeedback { + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new() -> Self { + Self {} + } +} + +impl Default for SortedFeedback { + fn default() -> Self { + Self::new() + } +} + +//=================================================================== +/// A Feedback which expects a certain minimum execution time +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ExecTimeReachedFeedback +{ + target_time: u64, +} + +impl Feedback for ExecTimeReachedFeedback +where + I: Input, + S: HasClientPerfMonitor, +{ + 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::("clock") + .expect("QemuClockObserver not found"); + Ok(observer.last_runtime() >= self.target_time) + } +} + +impl Named for ExecTimeReachedFeedback +{ + #[inline] + fn name(&self) -> &str { + "ExecTimeReachedFeedback" + } +} + +impl ExecTimeReachedFeedback +where +{ + /// Creates a new [`ExecTimeReachedFeedback`] + #[must_use] + pub fn new(target_time : u64) -> Self { + Self {target_time: target_time} + } +} + +pub static mut EXEC_TIME_COLLECTION : Vec = Vec::new(); + +/// A Noop Feedback which records a list of all execution times +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ExecTimeCollectorFeedback +{ +} + +impl Feedback for ExecTimeCollectorFeedback +where + I: Input, + S: HasClientPerfMonitor, +{ + 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::("clock") + .expect("QemuClockObserver not found"); + unsafe { EXEC_TIME_COLLECTION.push(observer.last_runtime().try_into().unwrap()); } + Ok(false) + } +} + +impl Named for ExecTimeCollectorFeedback +{ + #[inline] + fn name(&self) -> &str { + "ExecTimeCollectorFeedback" + } +} + +impl ExecTimeCollectorFeedback +where +{ + /// Creates a new [`ExecTimeCollectorFeedback`] + #[must_use] + pub fn new() -> Self { + Self {} + } +} + +/// Shared Metadata for a SysStateFeedback +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct ExecTimeCollectorFeedbackState +{ + collection: Vec, +} +impl Named for ExecTimeCollectorFeedbackState +{ + #[inline] + fn name(&self) -> &str { + "ExecTimeCollectorFeedbackState" + } +} +impl FeedbackState for ExecTimeCollectorFeedbackState +{ + fn reset(&mut self) -> Result<(), Error> { + self.collection.clear(); + Ok(()) + } +} \ No newline at end of file diff --git a/fuzzers/wcet_qemu_sys/tmr_inputs/.gitignore b/fuzzers/wcet_qemu_sys/tmr_inputs/.gitignore new file mode 100644 index 0000000000..f9f3041727 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/tmr_inputs/.gitignore @@ -0,0 +1 @@ +*.case diff --git a/fuzzers/wcet_qemu_sys/tmr_inputs/inputs.sh b/fuzzers/wcet_qemu_sys/tmr_inputs/inputs.sh new file mode 100644 index 0000000000..da2decba55 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/tmr_inputs/inputs.sh @@ -0,0 +1,4 @@ +echo "" > best.case +echo "\x00\x00" > best_success.case +echo "\x05\x29\x07\x1f\x0b\x17\x00\x17" > worst_trace.case +echo "\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst.case diff --git a/fuzzers/wcet_qemu_sys/tmrint_inputs/.gitignore b/fuzzers/wcet_qemu_sys/tmrint_inputs/.gitignore new file mode 100644 index 0000000000..f9f3041727 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/tmrint_inputs/.gitignore @@ -0,0 +1 @@ +*.case diff --git a/fuzzers/wcet_qemu_sys/tmrint_inputs/inputs.sh b/fuzzers/wcet_qemu_sys/tmrint_inputs/inputs.sh new file mode 100644 index 0000000000..6d1a06f8a2 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/tmrint_inputs/inputs.sh @@ -0,0 +1,6 @@ +echo "\xff\xff" > best.case +echo "\xff\xff\x00\x00" > best_success.case +echo "\xff\x05\x05\x29\x07\x1f\x0b\x17\x00\x17" > worst_trace.case +echo "\xff\x05\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst.case +echo "\x3F\x05\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst_preempt.case +echo "\x93\x1F\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst_preempt_adv.case diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index b1c25b2d75..946560c753 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -2273,11 +2273,12 @@ where loop { match listener.accept() { ListenerStream::Tcp(mut stream, addr) => { - eprintln!( - "New connection: {:?}/{:?}", - addr, - stream.peer_addr().unwrap() - ); + // For some reason stderr is not avalible in child + // eprintln!( + // "New connection: {:?}/{:?}", + // addr, + // stream.peer_addr().unwrap() + // ); // Send initial information, without anyone asking. // This makes it a tiny bit easier to map the broker map for new Clients. diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index 2640d62564..263b00a6c1 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -190,6 +190,44 @@ where } } + // /// Fuzz until the first solution. + // fn fuzz_for_solution( + // &mut self, + // stages: &mut ST, + // executor: &mut E, + // state: &mut EM::State, + // manager: &mut EM, + // ) -> Result { + // let mut last = current_time(); + // let monitor_timeout = STATS_TIMEOUT_DEFAULT; + // let mut done = false; + // while !done { + // self.fuzz_one(stages, executor, state, manager)?; + // last = manager.maybe_report_progress(state, last, monitor_timeout)?; + // done = state.solutions().count() > 0; + // } + // return Ok(0); + // } + + // /// Fuzz until solution with limit + // fn fuzz_for_solution_or_n( + // &mut self, + // stages: &mut ST, + // executor: &mut E, + // state: &mut EM::State, + // manager: &mut EM, + // iters: u64, + // ) -> Result { + // let mut last = current_time(); + // let monitor_timeout = STATS_TIMEOUT_DEFAULT; + // for _ in 0..iters { + // self.fuzz_one(stages, executor, state, manager)?; + // last = manager.maybe_report_progress(state, last, monitor_timeout)?; + // if state.solutions().count() > 0 {break;} + // } + // return Ok(0); + // } + /// Fuzz for n iterations. /// Returns the index of the last fuzzed corpus item. /// (Note: An iteration represents a complete run of every stage. diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index e94610080d..bcd5fe608e 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -30,6 +30,8 @@ slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslir clippy = [] # special feature for clippy, don't use in normal projects§ +jmp_as_edge = [] # Add all jumps in app code to edges, circumvents bugs in the original instrumentation + [dependencies] libafl = { path = "../libafl", version = "0.8.2", default-features = false, features = ["std", "derive", "llmp_compression"] } libafl_targets = { path = "../libafl_targets", version = "0.8.2" } diff --git a/libafl_qemu/src/clock.rs b/libafl_qemu/src/clock.rs new file mode 100644 index 0000000000..c08026fc55 --- /dev/null +++ b/libafl_qemu/src/clock.rs @@ -0,0 +1,290 @@ +use hashbrown::{hash_map::Entry, HashMap}; +use libafl::{ + bolts::{ + current_nanos, + rands::StdRand, + tuples::{tuple_list}, + }, + executors::{ExitKind}, + fuzzer::{StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + observers::{Observer,VariableMapObserver}, + state::{StdState, HasNamedMetadata}, + Error, + observers::ObserversTuple, prelude::UsesInput, +}; +use serde::{Deserialize, Serialize}; +use std::{cell::UnsafeCell, cmp::max}; +use libafl::bolts::tuples::Named; + +use crate::{ + emu, + emu::Emulator, + executor::QemuExecutor, + helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, +}; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::inputs::Input; +use libafl::feedbacks::Feedback; +use libafl::SerdeAny; +use libafl::state::HasMetadata; +use libafl::corpus::testcase::Testcase; +use core::{fmt::Debug, time::Duration}; +// use libafl::feedbacks::FeedbackState; +// use libafl::state::HasFeedbackStates; +use libafl::bolts::tuples::MatchName; + +//========== Metadata +#[derive(Debug, Serialize, Deserialize, SerdeAny)] +pub struct QemuIcountMetadata { + runtime: u64, +} +// libafl::impl_serdeany!(QemuIcountMetadata); + +/// Metadata for [`QemuClockIncreaseFeedback`] +#[derive(Debug, Serialize, Deserialize, SerdeAny)] +pub struct MaxIcountMetadata { + pub max_icount_seen: u64, + pub name: String, +} + +// impl FeedbackState for MaxIcountMetadata +// { +// fn reset(&mut self) -> Result<(), Error> { +// self.max_icount_seen = 0; +// Ok(()) +// } +// } + +impl Named for MaxIcountMetadata +{ + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl MaxIcountMetadata +{ + /// Create new `MaxIcountMetadata` + #[must_use] + pub fn new(name: &'static str) -> Self { + Self { + max_icount_seen: 0, + name: name.to_string(), + } + } +} + +impl Default for MaxIcountMetadata { + fn default() -> Self { + Self::new("MaxClock") + } +} + +//========== Observer + +/// A simple observer, just overlooking the runtime of the target. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct QemuClockObserver { + name: String, + last_runtime: u64, +} + +impl QemuClockObserver { + /// Creates a new [`QemuClockObserver`] with the given name. + #[must_use] + pub fn new(name: &'static str) -> Self { + Self { + name: name.to_string(), + last_runtime: 0, + } + } + + /// Gets the runtime for the last execution of this target. + #[must_use] + pub fn last_runtime(&self) -> u64 { + self.last_runtime + } +} + +impl Observer for QemuClockObserver +where + S: UsesInput, +{ + fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { + self.last_runtime=0; + Ok(()) + } + + fn post_exec(&mut self, _state: &mut S, _input: &S::Input, _exit_kind: &ExitKind) -> Result<(), Error> { + unsafe { self.last_runtime = emu::icount_get_raw() }; + Ok(()) + } +} + +impl Named for QemuClockObserver { + #[inline] + fn name(&self) -> &str { + &self.name + } +} + +impl Default for QemuClockObserver { + fn default() -> Self { + Self { + name: String::from("clock"), + last_runtime: 0, + } + } +} + +//========== Feedback +/// Nop feedback that annotates execution time in the new testcase, if any +/// for this Feedback, the testcase is never interesting (use with an OR) +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ClockFeedback { + exec_time: Option, + name: String, +} + +impl Feedback for ClockFeedback +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, + { + // TODO Replace with match_name_type when stable + let observer = observers.match_name::(self.name()).unwrap(); + self.exec_time = Some(observer.last_runtime()); + Ok(false) + } + + /// 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> { + *testcase.exec_time_mut() = match self.exec_time { + Some(s) => Some(Duration::from_nanos(s << 3)), //emulated time is << 3, real time more like * 360 + None => None, + }; + self.exec_time = 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.exec_time = None; + Ok(()) + } +} + +impl Named for ClockFeedback { + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl ClockFeedback { + /// Creates a new [`ClockFeedback`], deciding if the value of a [`TimeObserver`] with the given `name` of a run is interesting. + #[must_use] + pub fn new(name: &'static str) -> Self { + Self { + exec_time: None, + name: name.to_string(), + } + } + + /// Creates a new [`ClockFeedback`], deciding if the given [`TimeObserver`] value of a run is interesting. + #[must_use] + pub fn new_with_observer(observer: &QemuClockObserver) -> Self { + Self { + exec_time: None, + name: observer.name().to_string(), + } + } +} + +/// A [`Feedback`] rewarding increasing the execution cycles on Qemu. +#[derive(Debug)] +pub struct QemuClockIncreaseFeedback { + name: String, +} + +impl Feedback for QemuClockIncreaseFeedback +where + S: UsesInput + HasNamedMetadata + HasClientPerfMonitor + Debug, +{ + 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::("clock") + .expect("QemuClockObserver not found"); + let clock_state = state + .named_metadata_mut() + .get_mut::(&self.name) + .unwrap(); + if observer.last_runtime() > clock_state.max_icount_seen { + // println!("Clock improving {}",observer.last_runtime()); + clock_state.max_icount_seen = observer.last_runtime(); + return Ok(true); + } + Ok(false) + } + + /// 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> { + // testcase.metadata_mut().insert(QemuIcountMetadata{runtime: self.last_runtime}); + 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> { + Ok(()) + } + +} + +impl Named for QemuClockIncreaseFeedback { + #[inline] + fn name(&self) -> &str { + &self.name + } +} + +impl QemuClockIncreaseFeedback { + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new(name: &'static str) -> Self { + Self {name: String::from(name)} + } +} + +impl Default for QemuClockIncreaseFeedback { + fn default() -> Self { + Self::new("MaxClock") + } +} \ No newline at end of file diff --git a/libafl_qemu/src/edges.rs b/libafl_qemu/src/edges.rs index ff6992df24..812d95bf22 100644 --- a/libafl_qemu/src/edges.rs +++ b/libafl_qemu/src/edges.rs @@ -1,7 +1,7 @@ -use std::{cell::UnsafeCell, cmp::max}; +use std::{cell::UnsafeCell, cmp::max, ops::Range}; use hashbrown::{hash_map::Entry, HashMap}; -use libafl::{inputs::UsesInput, state::HasMetadata}; +use libafl::{inputs::UsesInput, state::HasMetadata, prelude::Input}; pub use libafl_targets::{ edges_max_num, EDGES_MAP, EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE, EDGES_MAP_SIZE, MAX_EDGES_NUM, }; @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use crate::{ emu::GuestAddr, + Emulator, helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, - hooks::QemuHooks, + hooks::QemuHooks, executor, }; #[derive(Debug, Default, Serialize, Deserialize)] @@ -35,6 +36,7 @@ libafl::impl_serdeany!(QemuEdgesMapMetadata); pub struct QemuEdgeCoverageHelper { filter: QemuInstrumentationFilter, use_hitcounts: bool, + app_range: Option>, } impl QemuEdgeCoverageHelper { @@ -43,6 +45,7 @@ impl QemuEdgeCoverageHelper { Self { filter, use_hitcounts: true, + app_range: None, } } @@ -51,6 +54,7 @@ impl QemuEdgeCoverageHelper { Self { filter, use_hitcounts: false, + app_range: None, } } @@ -58,6 +62,21 @@ impl QemuEdgeCoverageHelper { pub fn must_instrument(&self, addr: u64) -> bool { self.filter.allowed(addr) } + + #[must_use] + pub fn must_save(&self, src: u64, dst: u64) -> bool { + match &self.app_range { + None => false, + Some(s) => { + // if src != 0 { + // println!("must_save {} {:x} {:x}",s.contains(&src) && !s.contains(&dst),src,dst); + // } + s.contains(&src) || s.contains(&dst) + // println!("must_save {} {:x} {:x}",src==0&&dst!=0x9cc,src,dst); + // src==0&&dst!=0x9cc + }, + } + } } impl Default for QemuEdgeCoverageHelper { @@ -144,11 +163,48 @@ where Some(trace_edge_single_ptr), ); } + // if self.app_range.is_some() { + // executor.hook_jmp_generation(gen_jmp_instrument::); + // executor.emulator().set_exec_jmp_hook(trace_jmp) + // } } + + // fn pre_exec(&mut self, _emulator: &Emulator, _input: &I) { + // unsafe { SAVED_JUMP=None; } + // } } thread_local!(static PREV_LOC : UnsafeCell = UnsafeCell::new(0)); +/// Save the source and destination of last interesting jump +pub static mut SAVED_JUMP: Option<(u64, u64)> = None; + +// pub fn gen_jmp_instrument( +// _emulator: &Emulator, +// helpers: &mut QT, +// state: &mut S, +// src: u64, +// dest: u64, +// ) -> Option +// where +// S: HasMetadata, +// S: UsesInput, +// QT: QemuHelperTuple, +// { +// if let Some(h) = helpers.match_first_type::() { +// if !h.must_instrument(src) && !h.must_instrument(dest) { +// return None; +// } +// if !h.must_save(src, dest) { +// return None; +// } +// } +// // Temporary fux for missing edges +// #[cfg(feature = "jmp_as_edge")] +// return gen_unique_edge_ids(_emulator,helpers,state,src,dest); +// #[cfg(not(feature = "jmp_as_edge"))] +// return Some(1); +// } pub fn gen_unique_edge_ids( hooks: &mut QemuHooks<'_, QT, S>, state: Option<&mut S>, @@ -197,6 +253,15 @@ where } } +// pub extern "C" fn trace_jmp(src: u64, des: u64, id: u64) { +// unsafe { +// SAVED_JUMP=Some((src, des)); +// } +// // temporary hack to catch app code blocks +// #[cfg(feature = "jmp_as_edge")] +// unsafe { trace_edge_hitcount(id); } +// } + pub extern "C" fn trace_edge_hitcount(id: u64, _data: u64) { unsafe { EDGES_MAP[id as usize] = EDGES_MAP[id as usize].wrapping_add(1); diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index e1471a6dbc..8c732e0a49 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -1,5 +1,6 @@ //! Expose QEMU user `LibAFL` C api to Rust +use libc::c_char; use core::{ convert::Into, ffi::c_void, @@ -245,7 +246,10 @@ impl MapInfo { #[cfg(emulation_mode = "usermode")] extern "C" { + #[cfg(not(feature = "systemmode"))] fn qemu_user_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; + #[cfg(feature = "systemmode")] + fn libafl_qemu_sys_init(argc: i32, argv: *const *const u8, envp: *const *const u8) -> i32; fn libafl_qemu_run() -> i32; @@ -258,14 +262,27 @@ extern "C" { fn libafl_maps_next(map_info: *const c_void, ret: *mut MapInfo) -> *const c_void; + #[cfg(feature = "systemmode")] + #[cfg(feature = "arm")] + pub fn libafl_send_irq(irqn: u32); + #[cfg(feature = "systemmode")] + #[cfg(feature = "arm")] + pub static mut libafl_int_offset: u32; + static exec_path: *const u8; static guest_base: usize; static mut mmap_next_start: GuestAddr; static mut libafl_on_thread_hook: unsafe extern "C" fn(u32); + #[cfg(feature = "systemmode")] + static mut libafl_exec_jmp_hook: unsafe extern "C" fn(u64, u64, u64); + #[cfg(feature = "systemmode")] + static mut libafl_gen_jmp_hook: unsafe extern "C" fn(u64, u64) -> u64; + #[cfg(not(feature = "systemmode"))] static mut libafl_pre_syscall_hook: unsafe extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult; + #[cfg(not(feature = "systemmode"))] static mut libafl_post_syscall_hook: unsafe extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64; } @@ -281,6 +298,7 @@ extern "C" { fn libafl_save_qemu_snapshot(name: *const u8, sync: bool); #[allow(unused)] fn libafl_load_qemu_snapshot(name: *const u8, sync: bool); + pub fn icount_get_raw() -> u64; } #[cfg(emulation_mode = "systemmode")] @@ -1054,6 +1072,25 @@ impl Emulator { let s = CString::new(name).expect("Invalid snapshot name"); unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) }; } + // #[cfg(feature = "systemmode")] + // pub fn set_exec_jmp_hook(&self, hook: extern "C" fn(src: u64, dest: u64, id: u64)) { + // unsafe { + // libafl_exec_jmp_hook = hook; + // } + // } + + // #[cfg(feature = "systemmode")] + // pub fn set_gen_jmp_hook(&self, hook: extern "C" fn(src: u64, dest: u64) -> u64) { + // unsafe { + // libafl_gen_jmp_hook = hook; + // } + // } + + // pub fn set_exec_block_hook(&self, hook: extern "C" fn(pc: u64)) { + // unsafe { + // libafl_exec_block_hook = hook; + // } + // } #[cfg(emulation_mode = "systemmode")] pub fn load_snapshot(&self, name: &str, sync: bool) { diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 5583f89a1f..21f30888f6 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -1,5 +1,10 @@ //! A `QEMU`-based executor for binary-only instrumentation in `LibAFL` use core::fmt::{self, Debug, Formatter}; +use core::mem::transmute; +use libafl::prelude::inprocess::inprocess_get_state; +use libafl::prelude::Input; +use core::ffi::c_void; +use crate::SKIP_EXEC_HOOK; #[cfg(feature = "fork")] use libafl::{ @@ -19,6 +24,40 @@ use libafl::{ use crate::{emu::Emulator, helper::QemuHelperTuple, hooks::QemuHooks}; +// static mut QEMU_HELPERS_PTR: *const c_void = ptr::null(); + +// static mut GEN_JMP_HOOK_PTR: *const c_void = ptr::null(); +// extern "C" fn gen_jmp_hook_wrapper(src: u64, dst: u64) -> u64 +// where +// S: UsesInput, +// QT: QemuHelperTuple, +// { +// unsafe { +// let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); +// let state = inprocess_get_state::().unwrap(); +// let emulator = Emulator::new_empty(); +// let func: fn(&Emulator, &mut QT, &mut S, u64, u64) -> Option = +// transmute(GEN_JMP_HOOK_PTR); +// (func)(&emulator, helpers, state, src, dst).map_or(SKIP_EXEC_HOOK, |id| id) +// } +// } + +// static mut JMP_HOOKS: Vec<*const c_void> = vec![]; +// extern "C" fn jmp_hooks_wrapper(src: u64, dst: u64, id: u64) +// where +// S: UsesInput, +// QT: QemuHelperTuple, +// { +// let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; +// let state = inprocess_get_state::().unwrap(); +// let emulator = Emulator::new_empty(); +// for hook in unsafe { &JMP_HOOKS } { +// let func: fn(&Emulator, &mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; +// (func)(&emulator, helpers, state, src, dst); +// } +// } + +// static mut GEN_EDGE_HOOK_PTR: *const c_void = ptr::null(); pub struct QemuExecutor<'a, H, OT, QT, S> where H: FnMut(&S::Input) -> ExitKind, @@ -93,6 +132,29 @@ where pub fn emulator(&self) -> &Emulator { self.hooks.emulator() } + // #[cfg(feature = "systemmode")] + // #[allow(clippy::unused_self)] + // pub fn hook_jmp_generation( + // &self, + // hook: fn(&Emulator, &mut QT, &mut S, src: u64, dest: u64) -> Option, + // ) { + // unsafe { + // GEN_JMP_HOOK_PTR = hook as *const _; + // } + // self.emulator + // .set_gen_jmp_hook(gen_jmp_hook_wrapper::); + // } + + // #[cfg(feature = "systemmode")] + // #[allow(clippy::unused_self)] + // pub fn hook_jmp_execution(&self, hook: fn(&Emulator, &mut QT, &mut S, src: u64, dest: u64, id: u64)) { + // unsafe { + // JMP_HOOKS.push(hook as *const _); + // } + // self.emulator + // .set_exec_jmp_hook(jmp_hooks_wrapper::); + // } + } impl<'a, EM, H, OT, QT, S, Z> Executor for QemuExecutor<'a, H, OT, QT, S> diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 1cc81e169e..a1d5e24e34 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -856,6 +856,33 @@ where } } + // pub fn jmp_raw( + // &self, + // generation_hook: Option< + // fn(&mut Self, Option<&mut S>, src: GuestAddr, dest: GuestAddr) -> Option, + // >, + // execution_hook: Option, + // ) { + // unsafe { + // let index = EDGE_HOOKS.len(); + // self.emulator.add_jmp_hooks( + // if generation_hook.is_none() { + // None + // } else { + // Some(gen_jmp_hook_wrapper::) + // }, + // execution_hook, + // index as u64, + // ); + // EDGE_HOOKS.push(( + // generation_hook.map_or(Hook::Empty, |hook| { + // Hook::Function(hook as *const libc::c_void) + // }), + // Hook::Empty, + // )); + // } + // } + pub fn blocks( &self, generation_hook: Option, pc: GuestAddr) -> Option>, diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index aa0dbfc254..778cc23ebc 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -64,6 +64,8 @@ pub use snapshot::QemuSnapshotHelper; pub mod asan; #[cfg(emulation_mode = "usermode")] pub use asan::{init_with_asan, QemuAsanHelper}; +#[cfg(not(emulation_mode = "usermode"))] +pub mod clock; pub mod blocks; pub mod calls; diff --git a/libafl_qemu/src/snapshot_sys.rs b/libafl_qemu/src/snapshot_sys.rs new file mode 100644 index 0000000000..0441c1b037 --- /dev/null +++ b/libafl_qemu/src/snapshot_sys.rs @@ -0,0 +1,63 @@ +use crate::Emulator; +use crate::QemuExecutor; +use crate::QemuHelper; +use crate::QemuHelperTuple; +use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; + +use crate::{ + emu, +}; +// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html +#[derive(Debug)] +pub struct QemuSysSnapshotHelper { + pub empty: bool, +} + +impl QemuSysSnapshotHelper { + #[must_use] + pub fn new() -> Self { + Self { + empty: true, + } + } + + pub fn snapshot(&mut self, emulator: &Emulator) { + if self.empty { + let ret = emulator.snapshot_save("Start"); + if !ret { panic!("QemuSysSnapshotHelper failed to take a snapshot") }; + self.empty = false; + } + } + pub fn reset(&mut self, emulator: &Emulator) { + let ret = emulator.snapshot_load("Start"); + if !ret { panic!("QemuSysSnapshotHelper failed to load a snapshot") }; + } +} + +impl Default for QemuSysSnapshotHelper { + fn default() -> Self { + Self::new() + } +} + +impl QemuHelper for QemuSysSnapshotHelper +where + I: Input, + S: HasMetadata, +{ + fn init<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + } + + fn pre_exec(&mut self, emulator: &Emulator, _input: &I) { + if self.empty { + self.snapshot(emulator); + } else { + self.reset(emulator); + } + } +} \ No newline at end of file diff --git a/libafl_targets/src/coverage.rs b/libafl_targets/src/coverage.rs index 9b69115286..ebe52f945a 100644 --- a/libafl_targets/src/coverage.rs +++ b/libafl_targets/src/coverage.rs @@ -7,7 +7,7 @@ use crate::{ACCOUNTING_MAP_SIZE, EDGES_MAP_SIZE}; /// The map for edges. #[no_mangle] -pub static mut __afl_area_ptr_local: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE]; +pub static mut __afl_area_ptr_local: [u16; EDGES_MAP_SIZE] = [0u16; EDGES_MAP_SIZE]; pub use __afl_area_ptr_local as EDGES_MAP; /// The map for accounting mem writes. @@ -20,7 +20,7 @@ pub static mut MAX_EDGES_NUM: usize = 0; extern "C" { /// The area pointer points to the edges map. - pub static mut __afl_area_ptr: *mut u8; + pub static mut __afl_area_ptr: *mut u16; /// The area pointer points to the accounting mem operations map. pub static mut __afl_acc_memop_ptr: *mut u32; @@ -62,7 +62,7 @@ use libafl::bolts::ownedref::OwnedSliceMut; /// This function will crash if `EDGES_MAP_PTR` is not a valid pointer. /// The `EDGES_MAP_PTR_SIZE` needs to be smaller than, or equal to the size of the map. #[must_use] -pub unsafe fn edges_map_from_ptr<'a>() -> OwnedSliceMut<'a, u8> { +pub unsafe fn edges_map_from_ptr<'a>() -> OwnedSliceMut<'a, u16> { debug_assert!(!EDGES_MAP_PTR.is_null()); OwnedSliceMut::from_raw_parts_mut(EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE) }