From ac181eb99d13da2a09fbf29fe8e9e20e31806abb Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Sun, 16 Jan 2022 00:21:06 +0100 Subject: [PATCH] add some basic fuzzer --- fuzzers/wcet_qemu_sys/Cargo.toml | 1 + fuzzers/wcet_qemu_sys/src/fuzzer.rs | 417 ++++++++++++++++++++++++++++ fuzzers/wcet_qemu_sys/src/main.rs | 27 +- fuzzers/wcet_qemu_sys/src/worst.rs | 58 ++++ 4 files changed, 481 insertions(+), 22 deletions(-) create mode 100644 fuzzers/wcet_qemu_sys/src/fuzzer.rs diff --git a/fuzzers/wcet_qemu_sys/Cargo.toml b/fuzzers/wcet_qemu_sys/Cargo.toml index f415c52cfb..306d737b3f 100644 --- a/fuzzers/wcet_qemu_sys/Cargo.toml +++ b/fuzzers/wcet_qemu_sys/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [features] default = ["std"] std = [] +showmap = [] [profile.release] debug = true diff --git a/fuzzers/wcet_qemu_sys/src/fuzzer.rs b/fuzzers/wcet_qemu_sys/src/fuzzer.rs new file mode 100644 index 0000000000..b5d0d59698 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/fuzzer.rs @@ -0,0 +1,417 @@ +//! A singlethreaded QEMU fuzzer that can auto-restart. + +use libafl::stats::SimpleStats; +use libafl::events::SimpleEventManager; +use clap::{App, Arg}; +use core::{cell::RefCell, time::Duration}; +#[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 libafl::{ + bolts::{ + current_nanos, current_time, + os::dup2, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + }, + corpus::{ + Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, PowerQueueCorpusScheduler, + }, + events::SimpleRestartingEventManager, + executors::{ExitKind, ShadowExecutor, TimeoutExecutor}, + feedback_or, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + mutators::{ + scheduled::havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, + StdMOptMutator, StdScheduledMutator, Tokens, + }, + observers::{TimeObserver, VariableMapObserver}, + stages::{ + calibrate::CalibrationStage, + power::{PowerMutationalStage, PowerSchedule}, + ShadowTracingStage, StdMutationalStage, + }, + state::{HasCorpus, HasMetadata, StdState}, + Error, +}; +use libafl_qemu::{ + //asan::QemuAsanHelper, + cmplog, + cmplog::{CmpLogObserver, QemuCmpLogHelper}, + edges, + edges::QemuEdgeCoverageHelper, + elf::EasyElf, + emu::Emulator, + filter_qemu_args, + snapshot_sys::QemuSysSnapshotHelper, + MmapPerms, + QemuExecutor, + Regs, +}; +use crate::worst::HitcountsMapObserver; +use crate::worst::MapHitIncreaseFeedback; + + +/// 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"), + ) + .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("crashes"); + 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()); + + fuzz(out_dir, crashes, in_dir, tokens, logfile, timeout, kernel) + .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, +) -> Result<(), Error> { + env::remove_var("LD_LIBRARY_PATH"); + + //=========== Initialize the Emulator + 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", "if=none,format=qcow2,file=dummy.qcow2", + "-S" + ].iter().map(|x| x.to_string()).collect(); + let env: Vec<(String, String)> = env::vars().collect(); + let emu = Emulator::new(&args, &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 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); + + + //====== Note the input field + let input_addr = test_one_input_ptr; + println!("Placing input at {:#x}", input_addr); + emu.set_breakpoint(check_breakpoint); // trigger_Qemu_break + + //====== Setup log and stdout + let log = RefCell::new( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + #[cfg(unix)] + let mut stdout_cpy = unsafe { + let new_fd = dup(io::stdout().as_raw_fd())?; + File::from_raw_fd(new_fd) + }; + #[cfg(unix)] + let file_null = File::open("/dev/null")?; + + //====== Create the most simple status display and managers. + + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let monitor = SimpleMonitor::new(|s| { + #[cfg(unix)] + writeln!(&mut stdout_cpy, "{}", s).unwrap(); + #[cfg(windows)] + println!("{}", s); + writeln!(log.borrow_mut(), "{:?} {}", current_time(), s).unwrap(); + }); + + let mut shmem_provider = StdShMemProvider::new()?; + + //====== Create the most simple status display and managers. + let mut mgr = SimpleEventManager::new(monitor); + + // 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 = + HitcountsMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter)); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // 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); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), + MapHitIncreaseFeedback::new(), + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let objective = CrashFeedback::new(); + + // 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 + 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), + ) + }; + + let calibration = CalibrationStage::new(&mut state, &edges_observer); + + // Setup a randomic Input2State stage + let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); + + // Setup a MOPT mutator + let mutator = StdMOptMutator::new(&mut state, havoc_mutations().merge(tokens_mutations()), 5)?; + + let power = PowerMutationalStage::new(mutator, PowerSchedule::FAST, &edges_observer); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(PowerQueueCorpusScheduler::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(); + if len > 32 { + buf = &buf[0..32]; + len = 32; + } + + unsafe { + emu.write_mem(test_length_ptr,&(len as u32).to_le_bytes()); + emu.write_mem(input_addr,buf); + // println!("{:#?}",edges_copy); + + emu.run(); + // println!("{:#?}",edges_copy); + } + + ExitKind::Ok + }; + + let executor = QemuExecutor::new( + &mut harness, + &emu, + tuple_list!( + QemuEdgeCoverageHelper::new(), + QemuCmpLogHelper::new(), + //QemuAsanHelper::new(), + QemuSysSnapshotHelper::new() + ), + tuple_list!(edges_observer, time_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)?); + } + } + + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &seed_dir); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + let tracing = ShadowTracingStage::new(&mut executor); + + // The order of the stages matter! + let mut stages = tuple_list!(calibration, tracing, i2s, power); + + // Remove target ouput (logs still survive) + #[cfg(unix)] + { + let null_fd = file_null.as_raw_fd(); + dup2(null_fd, io::stdout().as_raw_fd())?; + dup2(null_fd, io::stderr().as_raw_fd())?; + } + // reopen file to make sure we're at the end + log.replace( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in the fuzzing loop"); + + // Never reached + Ok(()) +} diff --git a/fuzzers/wcet_qemu_sys/src/main.rs b/fuzzers/wcet_qemu_sys/src/main.rs index 5d7a3b5017..fcdf8ce275 100644 --- a/fuzzers/wcet_qemu_sys/src/main.rs +++ b/fuzzers/wcet_qemu_sys/src/main.rs @@ -1,5 +1,5 @@ #[cfg(target_os = "linux")] -// pub mod fuzzer; +pub mod fuzzer; pub mod showmap; pub mod worst; use libafl_qemu::{ @@ -9,25 +9,8 @@ use libafl_qemu::{ }; fn main() { - // let mut args2: Vec = vec![ - // "qemu-system-arm", - // "-machine","mps2-an385", - // "-monitor", "null", - // "-semihosting", - // "--semihosting-config", "enable=on,target=native", - // "-kernel", "RTOSDemo.axf", - // "-serial", "stdio", "-nographic", - // "-snapshot", "-drive", "if=none,format=qcow2,file=dummy.qcow2", - // "-S" - // ].iter().map(|x| x.to_string()).collect(); - // init(&mut args2, &Vec::new()); - // let succ = emu::snapshot_save("Start"); - // println!("Snaphsot: {}",succ); - // emu::set_breakpoint(0x00004f5c); - // emu::run(); - // let succ = emu::snapshot_load("Start"); - // emu::run(); - #[cfg(target_os = "linux")] - // fuzzer::main() - showmap::main() + #[cfg(all(target_os = "linux", feature = "showmap"))] + showmap::main(); + #[cfg(all(target_os = "linux", not(feature = "showmap")))] + fuzzer::main(); } diff --git a/fuzzers/wcet_qemu_sys/src/worst.rs b/fuzzers/wcet_qemu_sys/src/worst.rs index 9b207b99ad..32e21026c4 100644 --- a/fuzzers/wcet_qemu_sys/src/worst.rs +++ b/fuzzers/wcet_qemu_sys/src/worst.rs @@ -216,3 +216,61 @@ impl Default for HitFeedback { Self::new() } } + +//=================================================================== + + +/// 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, +{ + 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("HitcountsMapObserver 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() + } +} \ No newline at end of file