diff --git a/fuzzers/FRET/Cargo.toml b/fuzzers/FRET/Cargo.toml index 8767916a1f..befa1e593d 100644 --- a/fuzzers/FRET/Cargo.toml +++ b/fuzzers/FRET/Cargo.toml @@ -53,7 +53,7 @@ codegen-units = 1 debug = true [dependencies] -libafl = { path = "../../libafl/" } +libafl = { path = "../../libafl/", features = ["multipart_inputs"] } libafl_bolts = { path = "../../libafl_bolts/" } libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib diff --git a/fuzzers/FRET/src/cli.rs b/fuzzers/FRET/src/cli.rs new file mode 100644 index 0000000000..8f640c0df1 --- /dev/null +++ b/fuzzers/FRET/src/cli.rs @@ -0,0 +1,88 @@ +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +// Argument parsing ================================================================================ + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// Kernel Image + #[arg(short, long, value_name = "FILE")] + pub kernel: PathBuf, + + /// Sets a custom config file + #[arg(short, long, value_name = "FILE")] + pub config: PathBuf, + + /// Sets the prefix of dumed files + #[arg(short='n', long, value_name = "FILENAME")] + pub dump_name: Option, + + /// do time dumps + #[arg(short='t', long)] + pub dump_times: bool, + + /// do worst-case dumps + #[arg(short='a', long)] + pub dump_cases: bool, + + /// do trace dumps (if supported) + #[arg(short='r', long)] + pub dump_traces: bool, + + /// do graph dumps (if supported) + #[arg(short='g', long)] + pub dump_graph: bool, + + #[command(subcommand)] + pub command: Commands, +} +#[derive(Subcommand,Clone)] +pub enum Commands { + /// run a single input + Showmap { + /// take this input + #[arg(short, long)] + input: PathBuf, + }, + /// start fuzzing campaign + Fuzz { + /// disable heuristic + #[arg(short, long)] + random: bool, + /// seed for randomness + #[arg(short, long)] + seed: Option, + /// runtime in seconds + #[arg(short, long)] + time: Option, + } +} + +pub fn set_env_from_config(kernel : &PathBuf, path : &PathBuf) { + let is_csv = path.as_path().extension().map_or(false, |x| x=="csv"); + if !is_csv { + let lines = std::fs::read_to_string(path).expect("Config file not found"); + let lines = lines.lines().filter( + |x| x.len()>0 + ); + for l in lines { + let pair = l.split_once('=').expect("Non VAR=VAL line in config"); + std::env::set_var(pair.0, pair.1); + } + } else { + let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed"); + let p = kernel.as_path(); + let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap(); + for r in reader.records() { + let rec = r.expect("CSV entry error"); + if stem == &rec[0] { + std::env::set_var("FUZZ_MAIN", &rec[1]); + std::env::set_var("FUZZ_INPUT", &rec[2]); + std::env::set_var("FUZZ_INPUT_LEN", &rec[3]); + std::env::set_var("BREAKPOINT", &rec[4]); + break; + } + } + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 6f6ce09843..49bc3e59a9 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -7,17 +7,16 @@ use libafl_bolts::{ core_affinity::Cores, current_nanos, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, AsSlice }; use libafl::{ -common::{HasMetadata, HasNamedMetadata}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::{launcher::Launcher, EventConfig}, executors::ExitKind, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes}, monitors::MultiMonitor, observers::{CanTrack, VariableMapObserver}, prelude::{havoc_mutations, minimizer::TopRatedsMetadata, CorpusId, Generator, HitcountsMapObserver, RandBytesGenerator, SimpleEventManager, SimpleMonitor, SimpleRestartingEventManager, StdScheduledMutator}, schedulers::QueueScheduler, stages::StdMutationalStage, state::{HasCorpus, StdState}, Error, Evaluator +common::{HasMetadata, HasNamedMetadata}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::{launcher::Launcher, EventConfig}, executors::ExitKind, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{multi::MultipartInput, BytesInput, HasTargetBytes, Input}, monitors::MultiMonitor, observers::{CanTrack, VariableMapObserver}, prelude::{havoc_mutations, minimizer::TopRatedsMetadata, CorpusId, Generator, HitcountsMapObserver, RandBytesGenerator, SimpleEventManager, SimpleMonitor, SimpleRestartingEventManager, StdScheduledMutator}, schedulers::QueueScheduler, stages::StdMutationalStage, state::{HasCorpus, StdState}, Error, Evaluator }; use libafl_qemu::{ -edges::{self, edges_map_mut_ptr, QemuEdgeCoverageHelper, MAX_EDGES_FOUND}, elf::EasyElf, emu::Emulator, GuestAddr, GuestPhysAddr, QemuExecutor, QemuFilterList, QemuHooks, Regs, StdInstrumentationFilter +edges::{self, edges_map_mut_ptr, QemuEdgeCoverageHelper, MAX_EDGES_FOUND}, elf::EasyElf, emu::Emulator, GuestAddr, GuestPhysAddr, QemuExecutor, QemuExitReason, QemuFilterList, QemuHooks, Regs, StdInstrumentationFilter }; use rand::{SeedableRng, StdRng, Rng}; use crate::{ -clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback, SystraceErrorFeedback}, helpers::{QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler} +clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback, SystraceErrorFeedback}, helpers::{get_function_range, load_symbol, try_load_symbol, QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler} }; use std::time::{SystemTime, UNIX_EPOCH}; -use clap::{Parser, Subcommand}; use csv::Reader; use petgraph::{dot::{Config, Dot}, Graph}; use petgraph::graph::EdgeIndex; @@ -27,6 +26,10 @@ use crate::systemstate::stg::STGFeedbackState; use crate::clock::QEMU_ICOUNT_SHIFT; use libafl::inputs::HasMutatorBytes; use libafl_qemu::Qemu; +use crate::cli::Cli; +use crate::cli::Commands; +use crate::cli::set_env_from_config; +use clap::Parser; // Constants ================================================================================ @@ -38,61 +41,6 @@ pub const FIRST_INT : u32 = 500000; pub const MAX_NUM_INTERRUPT: usize = 128; pub const DO_NUM_INTERRUPT: usize = 128; pub static mut MAX_INPUT_SIZE: usize = 32; -/// Read ELF program headers to resolve physical load addresses. -fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { -let ret; -for i in &tab.goblin().program_headers { - if i.vm_range().contains(&vaddr.try_into().unwrap()) { - ret = vaddr - TryInto::::try_into(i.p_vaddr).unwrap() - + TryInto::::try_into(i.p_paddr).unwrap(); - return ret - (ret % 2); - } -} -return vaddr; -} - -pub fn load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> GuestAddr { -try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol)) -} - -pub fn try_load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> Option { -let ret = elf - .resolve_symbol(symbol, 0); -if do_translation { - Option::map_or(ret, None, |x| Some(virt2phys(x as GuestPhysAddr,&elf) as GuestAddr)) -} else {ret} -} - -pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option> { -let gob = elf.goblin(); - -let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect(); -funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value)); - -for sym in &gob.syms { - if let Some(sym_name) = gob.strtab.get_at(sym.st_name) { - if sym_name == symbol { - if sym.st_value == 0 { - return None; - } else { - //#[cfg(cpu_target = "arm")] - // Required because of arm interworking addresses aka bit(0) for thumb mode - let addr = (sym.st_value as GuestAddr) & !(0x1 as GuestAddr); - //#[cfg(not(cpu_target = "arm"))] - //let addr = sym.st_value as GuestAddr; - // look for first function after addr - let sym_end = funcs.iter().find(|x| x.st_value > sym.st_value); - if let Some(sym_end) = sym_end { - // println!("{} {:#x}..{} {:#x}", gob.strtab.get_at(sym.st_name).unwrap_or(""),addr, gob.strtab.get_at(sym_end.st_name).unwrap_or(""),sym_end.st_value & !0x1); - return Some(addr..((sym_end.st_value & !0x1) as GuestAddr)); - } - return None; - }; - } - } -} -return None; -} pub fn get_all_fn_symbol_ranges(elf: &EasyElf, api_range: std::ops::Range) -> HashMap> { let mut api_addreses : HashMap> = HashMap::new(); @@ -123,63 +71,6 @@ static mut libafl_interrupt_offsets : [u32; MAX_NUM_INTERRUPT]; static mut libafl_num_interrupts : usize; } -// Argument parsing ================================================================================ - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { -/// Kernel Image -#[arg(short, long, value_name = "FILE")] -kernel: PathBuf, - -/// Sets a custom config file -#[arg(short, long, value_name = "FILE")] -config: PathBuf, - -/// Sets the prefix of dumed files -#[arg(short='n', long, value_name = "FILENAME")] -dump_name: Option, - -/// do time dumps -#[arg(short='t', long)] -dump_times: bool, - -/// do worst-case dumps -#[arg(short='a', long)] -dump_cases: bool, - -/// do trace dumps (if supported) -#[arg(short='r', long)] -dump_traces: bool, - -/// do graph dumps (if supported) -#[arg(short='g', long)] -dump_graph: bool, - -#[command(subcommand)] -command: Commands, -} -#[derive(Subcommand,Clone)] -enum Commands { -/// run a single input -Showmap { - /// take this input - #[arg(short, long)] - input: PathBuf, -}, -/// start fuzzing campaign -Fuzz { - /// disable heuristic - #[arg(short, long)] - random: bool, - /// seed for randomness - #[arg(short, long)] - seed: Option, - /// runtime in seconds - #[arg(short, long)] - time: Option, -} -} /// Takes a state, cli and a suffix, writes out the current worst case macro_rules! do_dump_case { @@ -193,12 +84,12 @@ macro_rules! do_dump_case { for i in 0..corpus.count() { let tc = corpus.get(corpus.nth(i.into())).expect("Could not get element from corpus").borrow(); if worst < tc.exec_time().expect("Testcase missing duration") { - worst_input = Some(tc.input().as_ref().unwrap().bytes().to_owned()); + worst_input = Some(tc.input().as_ref().unwrap().clone()); worst = tc.exec_time().expect("Testcase missing duration"); } } if let Some(wi) = worst_input { - fs::write(dump_path,wi).expect("Failed to write worst corpus element"); + wi.to_file(dump_path); } } } @@ -260,39 +151,12 @@ macro_rules! do_dump_toprated { }; } -fn env_from_config(kernel : &PathBuf, path : &PathBuf) { -let is_csv = path.as_path().extension().map_or(false, |x| x=="csv"); -if !is_csv { - let lines = std::fs::read_to_string(path).expect("Config file not found"); - let lines = lines.lines().filter( - |x| x.len()>0 - ); - for l in lines { - let pair = l.split_once('=').expect("Non VAR=VAL line in config"); - std::env::set_var(pair.0, pair.1); - } -} else { - let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed"); - let p = kernel.as_path(); - let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap(); - for r in reader.records() { - let rec = r.expect("CSV entry error"); - if stem == &rec[0] { - std::env::set_var("FUZZ_MAIN", &rec[1]); - std::env::set_var("FUZZ_INPUT", &rec[2]); - std::env::set_var("FUZZ_INPUT_LEN", &rec[3]); - std::env::set_var("BREAKPOINT", &rec[4]); - break; - } - } -} -} // Fuzzer setup ================================================================================ pub fn fuzz() { let cli = Cli::parse(); -env_from_config(&cli.kernel, &cli.config); +set_env_from_config(&cli.kernel, &cli.config); unsafe {FUZZ_START_TIMESTAMP = SystemTime::now();} if cli.dump_name.is_none() && (cli.dump_times || cli.dump_cases || cli.dump_traces || cli.dump_graph) { panic!("Dump name not give but dump is requested"); @@ -431,68 +295,69 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| { "if=none,format=qcow2,file=dummy.qcow2", ].into_iter().map(String::from).collect(); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Qemu::init(&args, &env).expect("Emulator creation failed"); + let qemu = Qemu::init(&args, &env).expect("Emulator creation failed"); if let Some(main_addr) = main_addr { + qemu.set_breakpoint(main_addr); unsafe { - libafl_qemu::sys::libafl_qemu_set_native_breakpoint(main_addr as u64); - emu.run(); - libafl_qemu::sys::libafl_qemu_remove_native_breakpoint(main_addr as u64); + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + _ => panic!("Unexpected QEMU exit."), + } } + qemu.remove_breakpoint(main_addr); } + + qemu.set_breakpoint(breakpoint); // BREAKPOINT + + let devices = qemu.list_devices(); + println!("Devices = {devices:?}"); + #[cfg(feature = "snapshot_fast")] - let initial_snap = Some(emu.create_fast_snapshot(true)); + let initial_snap = Some(qemu.create_fast_snapshot(true)); #[cfg(not(feature = "snapshot_fast"))] let initial_snap = None; - unsafe { emu.set_breakpoint(breakpoint); }// BREAKPOINT - // 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 harness = |input: &MultipartInput| { unsafe { #[cfg(feature = "fuzz_int")] { - let t = input_bytes_to_interrupt_times(buf); + let time_bytes = input.parts_by_name("interrupts").next().unwrap().1.bytes(); + let t = input_bytes_to_interrupt_times(time_bytes); for i in 0..t.len() {libafl_interrupt_offsets[i]=t[i];} libafl_num_interrupts=t.len(); - if buf.len() > libafl_num_interrupts*4 { - buf = &buf[libafl_num_interrupts*4..]; - len = buf.len(); - } - // for i in 0 .. libafl_num_interrupts { - // libafl_interrupt_offsets[i] = FIRST_INT+TryInto::::try_into(i).unwrap()*MINIMUM_INTER_ARRIVAL_TIME; - // } // println!("Load: {:?}", libafl_interrupt_offsets[0..libafl_num_interrupts].to_vec()); - } - if len > MAX_INPUT_SIZE { - buf = &buf[0..MAX_INPUT_SIZE]; - len = MAX_INPUT_SIZE; - } - - // Note: I could not find a difference between write_mem and write_phys_mem for my usecase - emu.write_mem(input_addr, buf); - if let Some(s) = input_length_ptr { - emu.write_mem(s, &len.to_le_bytes()) - } - - emu.run(); - - // If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash - let mut pcs = (0..emu.num_cpus()) - .map(|i| emu.cpu_from_index(i)) - .map(|cpu| -> Result { cpu.read_reg(Regs::Pc) }); - match pcs - .find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0))) - { - Some(_) => ExitKind::Ok, - None => ExitKind::Crash, - } } - }; + + let mut bytes = input.parts_by_name("bytes").next().unwrap().1.bytes(); + let mut len = bytes.len(); + if len > MAX_INPUT_SIZE { + bytes = &bytes[0..MAX_INPUT_SIZE]; + len = MAX_INPUT_SIZE; + } + + // Note: I could not find a difference between write_mem and write_phys_mem for my usecase + qemu.write_mem(input_addr, bytes); + if let Some(s) = input_length_ptr { + qemu.write_mem(s, &len.to_le_bytes()) + } + + qemu.run(); + + // If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash + let mut pcs = (0..qemu.num_cpus()) + .map(|i| qemu.cpu_from_index(i)) + .map(|cpu| -> Result { cpu.read_reg(Regs::Pc) }); + match pcs + .find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0))) + { + Some(_) => ExitKind::Ok, + None => ExitKind::Crash, + } + } + }; // Create an observation channel to keep track of the execution time let clock_time_observer = QemuClockObserver::new("clocktime", if cli.dump_times {cli.dump_name.clone().map(|x| x.with_extension("time"))} else {None} ); @@ -599,7 +464,7 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| { let qhelpers = (QemuEdgeCoverageHelper::new(denylist, QemuFilterList::None), qhelpers); let qhelpers = (QemuStateRestoreHelper::with_fast(initial_snap), qhelpers); - let mut hooks = QemuHooks::new(emu.clone(),qhelpers); + let mut hooks = QemuHooks::new(qemu.clone(),qhelpers); let observer_list = tuple_list!(); #[cfg(feature = "observe_systemstate")] @@ -633,16 +498,17 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| { if let Commands::Showmap { input } = cli.command.clone() { let s = input.as_os_str(); - let show_input = if s=="-" { - let mut buf = Vec::::new(); - std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin"); - buf - } else if s=="$" { - env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned() - } else { - fs::read(s).expect("Input file for DO_SHOWMAP can not be read") - }; - fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input)) + // let show_input = BytesInput::new(if s=="-" { + // let mut buf = Vec::::new(); + // std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin"); + // buf + // } else if s=="$" { + // env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned() + // } else { + // // fs::read(s).expect("Input file for DO_SHOWMAP can not be read") + // }); + let show_input = MultipartInput::from_file(input.as_os_str()).expect("Error reading input file"); + fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, show_input) .unwrap(); do_dump_times!(state, &cli, ""); do_dump_stg!(state, &cli, ""); @@ -651,7 +517,9 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| { unsafe { let mut rng = StdRng::seed_from_u64(se); for i in 0..10 { - let inp = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); + let inp1 = BytesInput::new(vec![rng.gen::(); MAX_NUM_INTERRUPT*4]); + let inp2 = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); + let inp = MultipartInput::from([("interrupts",inp1),("bytes",inp2)]); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); } } @@ -692,7 +560,9 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| { while start_time.elapsed() < target_duration { // let inp = generator.generate(&mut state).unwrap(); // libafl's generator is too slow - let inp = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); + let inp1 = BytesInput::new(vec![rng.gen::(); MAX_NUM_INTERRUPT*4]); + let inp2 = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); + let inp = MultipartInput::from([("interrupts",inp1),("bytes",inp2)]); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); } }} else { @@ -752,7 +622,7 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| { let emu = Qemu::init(&args, &env).expect("Emu creation failed"); if let Some(main_addr) = main_addr { - unsafe { emu.set_breakpoint(main_addr); }// BREAKPOINT + emu.set_breakpoint(main_addr); // BREAKPOINT } unsafe { emu.run(); diff --git a/fuzzers/FRET/src/lib.rs b/fuzzers/FRET/src/lib.rs index 62aea9374f..0b9c5dec10 100644 --- a/fuzzers/FRET/src/lib.rs +++ b/fuzzers/FRET/src/lib.rs @@ -9,4 +9,6 @@ pub mod systemstate; #[cfg(target_os = "linux")] mod mutational; #[cfg(target_os = "linux")] -mod worst; \ No newline at end of file +mod worst; +#[cfg(target_os = "linux")] +mod cli; \ No newline at end of file diff --git a/fuzzers/FRET/src/main.rs b/fuzzers/FRET/src/main.rs index 7575ece678..84c82aadd9 100644 --- a/fuzzers/FRET/src/main.rs +++ b/fuzzers/FRET/src/main.rs @@ -11,6 +11,8 @@ mod systemstate; mod worst; #[cfg(target_os = "linux")] mod mutational; +#[cfg(target_os = "linux")] +mod cli; #[cfg(target_os = "linux")] pub fn main() { diff --git a/fuzzers/FRET/src/mutational.rs b/fuzzers/FRET/src/mutational.rs index 4fd132c090..e36d91d121 100644 --- a/fuzzers/FRET/src/mutational.rs +++ b/fuzzers/FRET/src/mutational.rs @@ -10,7 +10,7 @@ use libafl_bolts::rands::{ Rand }; use libafl::{ - common::{HasMetadata, HasNamedMetadata}, corpus::{self, Corpus}, fuzzer::Evaluator, inputs::HasMutatorBytes, mark_feature_time, prelude::{new_hash_feedback, CorpusId, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error + common::{HasMetadata, HasNamedMetadata}, corpus::{self, Corpus}, fuzzer::Evaluator, inputs::{HasMutatorBytes, HasTargetBytes, Input, MultipartInput}, mark_feature_time, prelude::{new_hash_feedback, CorpusId, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error }; use libafl::prelude::State; use crate::{clock::{IcHist, QEMU_ISNS_PER_USEC}, fuzzer::{DO_NUM_INTERRUPT, FIRST_INT, MAX_NUM_INTERRUPT}, systemstate::{stg::{STGFeedbackState, STGNodeMetadata}, CaptureEvent, ExecInterval, FreeRTOSSystemStateMetadata, ReducedFreeRTOSSystemState}}; @@ -72,13 +72,15 @@ where } } -impl Stage for InterruptShiftStage +impl Stage for InterruptShiftStage where E: UsesState, EM: UsesState, Z: Evaluator, Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand + HasMetadata + HasNamedMetadata, - ::Input: HasMutatorBytes + ::Input: Input, + Z::State: UsesInput>, + I: HasMutatorBytes + Default { fn perform( &mut self, @@ -86,35 +88,34 @@ where executor: &mut E, state: &mut Self::State, manager: &mut EM - // corpus_idx: CorpusId, ) -> Result<(), Error> { let mut myrand = StdRand::new(); myrand.set_seed(state.rand_mut().next()); - let mut _input = state.current_testcase()?.clone(); - let mut newinput = _input.input().as_ref().unwrap().clone(); + let current_case = state.current_testcase()?; + let old_input = current_case.input().as_ref().unwrap(); + let old_interrupt_times = old_input.parts_by_name("interrupts").next(); + let mut new_input = old_input.clone(); + + let mut new_interrupt_times : &mut I = if new_input.parts_by_name("interrupts").next().is_some() { + new_input.parts_by_name_mut("interrupts").next().unwrap() + } else { + new_input.add_part(String::from("interrupts"), I::default()); new_input.parts_by_name_mut("interrupts").next().unwrap() + }.1; let mut do_rerun = false; // if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time { - // need our own random generator, because borrowing rules - let mut target_bytes : Vec = vec![]; - { - let input = _input.input().as_ref().unwrap(); - target_bytes = input.bytes().to_vec(); - } // produce a slice of absolute interrupt times let mut interrupt_offsets : [u32; MAX_NUM_INTERRUPT] = [u32::MAX; MAX_NUM_INTERRUPT]; let mut num_interrupts : usize = 0; { - let t = input_bytes_to_interrupt_times(&target_bytes); + let t = input_bytes_to_interrupt_times(new_interrupt_times.bytes()); for i in 0..t.len() {interrupt_offsets[i]=t[i];} num_interrupts=t.len(); } interrupt_offsets.sort_unstable(); // println!("Vor Mutator: {:?}", interrupt_offsets[0..num_interrupts].to_vec()); - // let num_i = min(target_bytes.len() / 4, DO_NUM_INTERRUPT); - let mut suffix = target_bytes.split_off(4 * num_interrupts); let mut prefix : Vec<[u8; 4]> = vec![]; // let mut suffix : Vec = vec![]; #[cfg(feature = "mutate_stg")] @@ -201,7 +202,7 @@ where // } // else { // old version of the alternative search { - let tmp = _input.metadata_map().get::(); + let tmp = current_case.metadata_map().get::(); if tmp.is_some() { let trace = tmp.expect("STGNodeMetadata not found"); @@ -318,15 +319,13 @@ where } } } - - let mut n : Vec = vec![]; - n = [prefix.concat(), suffix].concat(); - newinput.drain(..); - newinput.extend(&n); + new_interrupt_times.drain(..); + new_interrupt_times.extend(&prefix.concat()); } + drop(current_case); // InterruptShifterMutator::mutate(&mut mymut, state, &mut input, 0)?; if do_rerun { - let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, newinput)?; + let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, new_input)?; } Ok(()) } diff --git a/fuzzers/FRET/src/systemstate/feedbacks.rs b/fuzzers/FRET/src/systemstate/feedbacks.rs index 8c466d3e13..39a8ae45ba 100644 --- a/fuzzers/FRET/src/systemstate/feedbacks.rs +++ b/fuzzers/FRET/src/systemstate/feedbacks.rs @@ -73,7 +73,7 @@ where EM: EventFirer, OT: ObserversTuple { - let observer = observers.match_name::("systemstate") + let observer = observers.match_name::>("systemstate") .expect("QemuSystemStateObserver not found"); let clock_observer = observers.match_name::("clocktime") //TODO not fixed .expect("QemuClockObserver not found"); @@ -181,7 +181,7 @@ where OT: ObserversTuple { if self.dumpfile.is_none() {return Ok(false)}; - let observer = observers.match_name::("systemstate") + let observer = observers.match_name::>("systemstate") .expect("QemuSystemStateObserver not found"); let names : Vec = observer.last_run.iter().map(|x| x.current_task.task_name.clone()).collect(); match &self.dumpfile { @@ -263,7 +263,7 @@ where EM: EventFirer, OT: ObserversTuple { - let observer = observers.match_name::("systemstate") + let observer = observers.match_name::>("systemstate") .expect("QemuSystemStateObserver not found"); Ok(self.dump_case&&!observer.success) } diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs index ce121a72ca..13fe53eedb 100644 --- a/fuzzers/FRET/src/systemstate/helpers.rs +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -2,8 +2,10 @@ use std::ops::Range; use hashbrown::HashMap; use libafl::prelude::ExitKind; use libafl::prelude::UsesInput; +use libafl_qemu::elf::EasyElf; use libafl_qemu::read_user_reg_unchecked; use libafl_qemu::GuestAddr; +use libafl_qemu::GuestPhysAddr; use libafl_qemu::QemuHooks; use libafl_qemu::Hook; use libafl_qemu::helpers::{QemuHelper, QemuHelperTuple}; @@ -24,9 +26,67 @@ use super::CaptureEvent; pub const ISR_SYMBOLS : &'static [&'static str] = &[ // ISRs -"Reset_Handler","Default_Handler","Default_Handler2","Default_Handler3","Default_Handler4","Default_Handler5","Default_Handler6","vPortSVCHandler","xPortPendSVHandler","xPortSysTickHandler","isr_starter"//,"vTaskGenericNotifyGiveFromISR" +"Reset_Handler","Default_Handler","Default_Handler2","Default_Handler3","Default_Handler4","Default_Handler5","Default_Handler6","vPortSVCHandler","xPortPendSVHandler","xPortSysTickHandler","isr_starter" ]; +/// Read ELF program headers to resolve physical load addresses. +fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { + let ret; + for i in &tab.goblin().program_headers { + if i.vm_range().contains(&vaddr.try_into().unwrap()) { + ret = vaddr - TryInto::::try_into(i.p_vaddr).unwrap() + + TryInto::::try_into(i.p_paddr).unwrap(); + return ret - (ret % 2); + } + } + return vaddr; +} + +/// Lookup a symbol in the ELF file, optionally resolve segment offsets +pub fn load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> GuestAddr { + try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol)) +} + +pub fn try_load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> Option { + let ret = elf + .resolve_symbol(symbol, 0); + if do_translation { + Option::map_or(ret, None, |x| Some(virt2phys(x as GuestPhysAddr,&elf) as GuestAddr)) + } else {ret} +} + +/// Try looking up the address range of a function in the ELF file +pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option> { + let gob = elf.goblin(); + + let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect(); + funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value)); + + for sym in &gob.syms { + if let Some(sym_name) = gob.strtab.get_at(sym.st_name) { + if sym_name == symbol { + if sym.st_value == 0 { + return None; + } else { + //#[cfg(cpu_target = "arm")] + // Required because of arm interworking addresses aka bit(0) for thumb mode + let addr = (sym.st_value as GuestAddr) & !(0x1 as GuestAddr); + //#[cfg(not(cpu_target = "arm"))] + //let addr = sym.st_value as GuestAddr; + // look for first function after addr + let sym_end = funcs.iter().find(|x| x.st_value > sym.st_value); + if let Some(sym_end) = sym_end { + // println!("{} {:#x}..{} {:#x}", gob.strtab.get_at(sym.st_name).unwrap_or(""),addr, gob.strtab.get_at(sym_end.st_name).unwrap_or(""),sym_end.st_value & !0x1); + return Some(addr..((sym_end.st_value & !0x1) as GuestAddr)); + } + return None; + }; + } + } + } + return None; +} + //============================= Qemu Helper /// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs diff --git a/fuzzers/FRET/src/systemstate/observers.rs b/fuzzers/FRET/src/systemstate/observers.rs index 693a4dd732..17eecceb49 100644 --- a/fuzzers/FRET/src/systemstate/observers.rs +++ b/fuzzers/FRET/src/systemstate/observers.rs @@ -28,20 +28,19 @@ use super::{ /// that will get updated by the target. #[derive(Serialize, Deserialize, Debug)] #[allow(clippy::unsafe_derive_deserialize)] -pub struct QemuSystemStateObserver +pub struct QemuSystemStateObserver { pub last_run: Vec, pub last_states: HashMap, pub last_trace: Vec, - pub last_input: Vec, + pub last_input: I, pub success: bool, name: Cow<'static, str>, } -impl Observer for QemuSystemStateObserver +impl Observer for QemuSystemStateObserver where S: UsesInput, - S::Input : HasTargetBytes, { #[inline] fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { @@ -67,19 +66,19 @@ where // println!("{:?}",abbs); // let abbs = trace_to_state_abb(&self.last_run); // println!("{:?}",abbs); - self.last_input=_input.target_bytes().as_slice().to_owned(); + self.last_input=_input.clone(); Ok(()) } } -impl Named for QemuSystemStateObserver +impl Named for QemuSystemStateObserver { fn name(&self) -> &Cow<'static, str> { &self.name } } -impl HasLen for QemuSystemStateObserver +impl HasLen for QemuSystemStateObserver { #[inline] fn len(&self) -> usize { @@ -87,12 +86,14 @@ impl HasLen for QemuSystemStateObserver } } -impl QemuSystemStateObserver { +impl QemuSystemStateObserver +where I: Default { pub fn new() -> Self { - Self{last_run: vec![], last_trace: vec![], last_input: vec![], name: Cow::from("systemstate".to_string()), last_states: HashMap::new(), success: false } + Self{last_run: vec![], last_trace: vec![], last_input: I::default(), name: Cow::from("systemstate".to_string()), last_states: HashMap::new(), success: false } } } -impl Default for QemuSystemStateObserver { +impl Default for QemuSystemStateObserver +where I: Default { fn default() -> Self { Self::new() } diff --git a/fuzzers/FRET/src/systemstate/stg.rs b/fuzzers/FRET/src/systemstate/stg.rs index f25d11cbe0..886ba71cd3 100644 --- a/fuzzers/FRET/src/systemstate/stg.rs +++ b/fuzzers/FRET/src/systemstate/stg.rs @@ -245,17 +245,6 @@ impl STGNodeMetadata { } } -// impl AsSlice for STGNodeMetadata { -// /// Convert the slice of system-states to a slice of hashes over enumerated states -// fn as_slice(&self) -> &[usize] { -// self.indices.as_slice() -// } - -// type Entry = usize; - -// type SliceRef = &[usize]; -// } - impl Deref for STGNodeMetadata { type Target = [usize]; /// Convert to a slice @@ -467,7 +456,6 @@ impl StgFeedback { impl Feedback for StgFeedback where S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata, - S::Input: HasTargetBytes, { #[allow(clippy::wrong_self_convention)] fn is_interesting( @@ -482,7 +470,7 @@ where EM: EventFirer, OT: ObserversTuple, { - let observer = observers.match_name::("systemstate") + let observer = observers.match_name::>("systemstate") .expect("QemuSystemStateObserver not found"); let clock_observer = observers.match_name::("clocktime") .expect("QemuClockObserver not found"); diff --git a/fuzzers/FRET/src/worst.rs b/fuzzers/FRET/src/worst.rs index 77783d0120..9d1213daa8 100644 --- a/fuzzers/FRET/src/worst.rs +++ b/fuzzers/FRET/src/worst.rs @@ -48,7 +48,6 @@ pub type TimeMaximizerCorpusScheduler = pub struct MaxTimeFavFactor where S: HasCorpus + HasMetadata, - S::Input: HasLen, { phantom: PhantomData, } @@ -56,7 +55,6 @@ where impl TestcaseScore for MaxTimeFavFactor where S: HasCorpus + HasMetadata, - S::Input: HasLen, { fn compute(state: &S, entry: &mut Testcase<::Input>) -> Result { // TODO maybe enforce entry.exec_time().is_some() @@ -420,7 +418,6 @@ pub type TimeProbMassScheduler = pub struct TimeProbFactor where S: HasCorpus + HasMetadata, - S::Input: HasLen, { phantom: PhantomData, } @@ -428,7 +425,6 @@ where impl TestcaseScore for TimeProbFactor where S: HasCorpus + HasMetadata, - S::Input: HasLen, { fn compute(_state: &S, entry: &mut Testcase<::Input>) -> Result { // TODO maybe enforce entry.exec_time().is_some()