diff --git a/Cargo.toml b/Cargo.toml index 991821b677..8464e0c1c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "libafl_cc", "libafl_targets", "libafl_frida", + "libafl_qemu", "libafl_tests", ] default-members = [ diff --git a/fuzzers/fuzzbench/src/lib.rs b/fuzzers/fuzzbench/src/lib.rs index adbe850330..04275644e3 100644 --- a/fuzzers/fuzzbench/src/lib.rs +++ b/fuzzers/fuzzbench/src/lib.rs @@ -34,7 +34,7 @@ use libafl::{ token_mutations::I2SRandReplace, tokens_mutations, Tokens, }, - observers::{StdMapObserver, TimeObserver}, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::{StdMutationalStage, TracingStage}, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, diff --git a/fuzzers/fuzzbench_qemu/.gitignore b/fuzzers/fuzzbench_qemu/.gitignore new file mode 100644 index 0000000000..47166ca405 --- /dev/null +++ b/fuzzers/fuzzbench_qemu/.gitignore @@ -0,0 +1,2 @@ +build/ +qemu-libafl-bridge/ diff --git a/fuzzers/fuzzbench_qemu/Cargo.toml b/fuzzers/fuzzbench_qemu/Cargo.toml new file mode 100644 index 0000000000..6b644e1207 --- /dev/null +++ b/fuzzers/fuzzbench_qemu/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "fuzzbench_qemu" +version = "0.5.0" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2018" + +[features] +default = ["std"] +std = [] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +which = { version = "4.0.2" } +num_cpus = "1.0" + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_qemu = { path = "../../libafl_qemu/" } +clap = { version = "3.0.0-beta.2", features = ["default"] } +nix = "0.20.0" + +[lib] +name = "fuzzbench_qemu" +crate-type = ["staticlib"] diff --git a/fuzzers/fuzzbench_qemu/Makefile b/fuzzers/fuzzbench_qemu/Makefile new file mode 100644 index 0000000000..edbae46d36 --- /dev/null +++ b/fuzzers/fuzzbench_qemu/Makefile @@ -0,0 +1,61 @@ +FUZZER_NAME=fuzzbench_qemu +PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +ifeq ($(strip $(CPU_TARGET)),) + CPU_TARGET=$(shell uname -m) + ifeq ($(strip $(CPU_TARGET)),i686) + CPU_TARGET=i386 + else ifeq ($(strip $(CPU_TARGET)),arm64v8) + CPU_TARGET=aarch64 + endif +endif + +ifeq ($(strip $(BUILD_TARGET)),) + BUILD_TARGET=release +endif + +ifeq ($(strip $(DEBUG)),1) + BUILD_TARGET=debug +endif + +ifeq ($(strip $(BUILD_TARGET)),release) + CARGO_ARGS+= --release +endif + +UNAME := $(shell uname) + +.PHONY: clean short_test all + +all: build/qemu-$(CPU_TARGET) + +target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a: src/* + cargo build $(CARGO_ARGS) + +qemu-libafl-bridge: + git clone git@github.com:AFLplusplus/qemu-libafl-bridge.git + +build/config.status: qemu-libafl-bridge qemu-libafl-bridge/configure + mkdir -p build + cd build && ../qemu-libafl-bridge/configure --target-list=$(CPU_TARGET)-linux-user --with-libafl-bridge="$(PROJECT_DIR)/target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a" + +build/qemu-$(CPU_TARGET): target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a build/config.status + $(MAKE) -C build + +pull: qemu-libafl-bridge + cd qemu-libafl-bridge && git pull + +clean: + rm -rf build + cargo clean + +ifeq ($(UNAME), Linux) + +short_test: target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a + @echo "Skipping short test" + +else + +short_test: + @echo "Skipping build and short test" + +endif diff --git a/fuzzers/fuzzbench_qemu/README.md b/fuzzers/fuzzbench_qemu/README.md new file mode 100644 index 0000000000..df34f5e090 --- /dev/null +++ b/fuzzers/fuzzbench_qemu/README.md @@ -0,0 +1,17 @@ +# Fuzzbench Harness + +This folder contains an example fuzzer tailored for fuzzbench. +It uses the best possible setting, with the exception of a SimpleRestartingEventManager instead of an LlmpEventManager - since fuzzbench is single threaded. +Real fuzz campaigns should consider using multithreaded LlmpEventManager, see the other examples. + +## Build + +To build this example, run `cargo build --release`. +This will build the fuzzer compilers (`libafl_cc` and `libafl_cpp`) with `src/lib.rs` as fuzzer. +The fuzzer uses the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. + +These can then be used to build libfuzzer harnesses in the software project of your choice. +Finally, just run the resulting binary with `out_dir`, `in_dir`. + +In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet). + diff --git a/fuzzers/fuzzbench_qemu/fuzz.c b/fuzzers/fuzzbench_qemu/fuzz.c new file mode 100644 index 0000000000..5f2460d84e --- /dev/null +++ b/fuzzers/fuzzbench_qemu/fuzz.c @@ -0,0 +1,16 @@ +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + // printf("Got %ld bytes.\n", Size); + if (Size >= 4 && *(uint16_t*)Data == 0xaabb && *(uint16_t*)&Data[2] == 0xccab) + abort(); +} + +int main() { + + char buf [10] = {0}; + LLVMFuzzerTestOneInput(buf, 10); + +} diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs new file mode 100644 index 0000000000..53b66f4cab --- /dev/null +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -0,0 +1,342 @@ +//! A singlethreaded QEMU fuzzer that can auto-restart. + +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, QueueCorpusScheduler}, + events::SimpleRestartingEventManager, + executors::{ExitKind, TimeoutExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + mutators::{ + scheduled::{havoc_mutations, StdScheduledMutator}, + tokens_mutations, Tokens, + }, + observers::{HitcountsMapObserver, TimeObserver, VariableMapObserver}, + stages::StdMutationalStage, + state::{HasCorpus, HasMetadata, StdState}, + stats::SimpleStats, + Error, +}; +use libafl_qemu::{ + amd64::Amd64Regs, elf::EasyElf, emu, filter_qemu_args, hooks, MmapPerms, QemuExecutor, +}; + +/// The fuzzer main (as `no_mangle` C function) +#[no_mangle] +pub fn libafl_qemu_main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let res = match App::new("libafl_qemu_fuzzbench") + .version("0.4.0") + .author("AFLplusplus team") + .about("LibAFL-based fuzzer with QEMU for Fuzzbench") + .arg( + Arg::new("out") + .about("The directory to place finds in ('corpus')") + .long("libafl-out") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("in") + .about("The directory to read initial inputs from ('seeds')") + .long("libafl-in") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("tokens") + .long("libafl-tokens") + .about("A file to read tokens from, to be used during fuzzing") + .takes_value(true), + ) + .arg( + Arg::new("logfile") + .long("libafl-logfile") + .about("Duplicates all output to this file") + .default_value("libafl.log"), + ) + .arg( + Arg::new("timeout") + .long("libafl-timeout") + .about("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"), + ); + + fuzz(out_dir, crashes, in_dir, tokens, logfile, timeout) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz( + corpus_dir: PathBuf, + objective_dir: PathBuf, + seed_dir: PathBuf, + tokenfile: Option, + logfile: PathBuf, + timeout: Duration, +) -> Result<(), Error> { + let mut elf_buffer = Vec::new(); + let elf = EasyElf::from_file(emu::binary_path(), &mut elf_buffer)?; + + let test_one_input_ptr = elf.resolve_symbol("LLVMFuzzerTestOneInput").unwrap(); + println!("LLVMFuzzerTestOneInput @ {:#x}", test_one_input_ptr); + + emu::set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + emu::run(); + + println!( + "Break at {:#x}", + emu::read_reg::<_, u64>(Amd64Regs::Rip).unwrap() + ); + + let stack_ptr: u64 = emu::read_reg(Amd64Regs::Rsp).unwrap(); + let mut ret_addr = [0u64]; + emu::read_mem(stack_ptr, &mut ret_addr); + let ret_addr = ret_addr[0]; + + println!("Stack pointer = {:#x}", stack_ptr); + println!("Return address = {:#x}", ret_addr); + + emu::remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + emu::set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr + + let input_addr = emu::map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); + println!("Placing input at {:#x}", input_addr); + + 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")?; + + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let stats = SimpleStats::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()?; + + let (state, mut mgr) = match SimpleRestartingEventManager::launch(stats, &mut shmem_provider) { + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; + + // Create an observation channel using the coverage map + let edges = unsafe { &mut hooks::EDGES_MAP }; + let edges_counter = unsafe { &mut hooks::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"); + + // 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), + // 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 = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // create a State from scratch + let mut state = state.unwrap_or_else(|| { + 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), + ) + }); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::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(); + if buf.len() > 32 { + buf = &buf[0..32]; + } + + emu::write_mem(input_addr, buf); + + emu::write_reg(Amd64Regs::Rdi, input_addr).unwrap(); + emu::write_reg(Amd64Regs::Rsi, buf.len()).unwrap(); + emu::write_reg(Amd64Regs::Rip, test_one_input_ptr).unwrap(); + emu::write_reg(Amd64Regs::Rsp, stack_ptr).unwrap(); + + emu::run(); + + ExitKind::Ok + }; + + let executor = QemuExecutor::new( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + )?; + + executor.hook_edge_generation(hooks::gen_unique_edges_id); + executor.hook_edge_execution(hooks::exec_log_hitcount); + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + let mut executor = TimeoutExecutor::new(executor, timeout); + + // 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()); + } + + // Setup a mutational stage with a basic bytes mutator + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + // 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/fuzzbench_qemu/src/lib.rs b/fuzzers/fuzzbench_qemu/src/lib.rs new file mode 100644 index 0000000000..101c91748e --- /dev/null +++ b/fuzzers/fuzzbench_qemu/src/lib.rs @@ -0,0 +1,2 @@ +#[cfg(target_os = "linux")] +pub mod fuzzer; diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 443ebd49c7..6a6dc6cca5 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -15,11 +15,6 @@ use crate::bolts::os::unix_signals::setup_signal_handler; #[cfg(all(windows, feature = "std"))] use crate::bolts::os::windows_exceptions::setup_exception_handler; -#[cfg(unix)] -pub use unix_signal_handler::{nop_handler, HandlerFuncPtr}; -#[cfg(all(windows, feature = "std"))] -pub use windows_exception_handler::{nop_handler, HandlerFuncPtr}; - use crate::{ corpus::Corpus, events::{EventFirer, EventRestarter}, @@ -44,11 +39,9 @@ where /// The observers, observing each run observers: OT, /// On crash C function pointer - #[cfg(any(unix, all(windows, feature = "std")))] - crash_handler: HandlerFuncPtr, + crash_handler: *const c_void, /// On timeout C function pointer - #[cfg(unix)] - timeout_handler: HandlerFuncPtr, + timeout_handler: *const c_void, phantom: PhantomData<(I, S)>, } @@ -68,7 +61,7 @@ where ) -> Result { #[cfg(unix)] unsafe { - let data = &mut unix_signal_handler::GLOBAL_STATE; + let data = &mut GLOBAL_STATE; write_volatile( &mut data.current_input_ptr, input as *const _ as *const c_void, @@ -88,7 +81,7 @@ where } #[cfg(all(windows, feature = "std"))] unsafe { - let data = &mut windows_exception_handler::GLOBAL_STATE; + let data = &mut GLOBAL_STATE; write_volatile( &mut data.current_input_ptr, input as *const _ as *const c_void, @@ -111,18 +104,12 @@ where #[cfg(unix)] unsafe { - write_volatile( - &mut unix_signal_handler::GLOBAL_STATE.current_input_ptr, - ptr::null(), - ); + write_volatile(&mut GLOBAL_STATE.current_input_ptr, ptr::null()); compiler_fence(Ordering::SeqCst); } #[cfg(all(windows, feature = "std"))] unsafe { - write_volatile( - &mut windows_exception_handler::GLOBAL_STATE.current_input_ptr, - ptr::null(), - ); + write_volatile(&mut GLOBAL_STATE.current_input_ptr, ptr::null()); compiler_fence(Ordering::SeqCst); } @@ -175,14 +162,15 @@ where { #[cfg(unix)] unsafe { - let data = &mut unix_signal_handler::GLOBAL_STATE; + let data = &mut GLOBAL_STATE; setup_signal_handler(data)?; compiler_fence(Ordering::SeqCst); Ok(Self { harness_fn, observers, - crash_handler: unix_signal_handler::inproc_crash_handler::, + crash_handler: unix_signal_handler::inproc_crash_handler:: + as *const _, timeout_handler: unix_signal_handler::inproc_timeout_handler::< EM, I, @@ -191,13 +179,13 @@ where OT, S, Z, - >, + > as *const _, phantom: PhantomData, }) } #[cfg(all(windows, feature = "std"))] unsafe { - let data = &mut windows_exception_handler::GLOBAL_STATE; + let data = &mut GLOBAL_STATE; setup_exception_handler(data)?; compiler_fence(Ordering::SeqCst); @@ -212,8 +200,9 @@ where OT, S, Z, - >, - // timeout_handler: windows_exception_handler::inproc_timeout_handler::, + > as *const _, + // timeout_handler: windows_exception_handler::inproc_timeout_handler:: as *const _, + timeout_handler: ptr::null(), phantom: PhantomData, }) } @@ -238,11 +227,42 @@ where } } +pub struct InProcessExecutorHandlerData { + pub state_ptr: *mut c_void, + pub event_mgr_ptr: *mut c_void, + pub fuzzer_ptr: *mut c_void, + pub observers_ptr: *const c_void, + pub current_input_ptr: *const c_void, + pub crash_handler: *const c_void, + pub timeout_handler: *const c_void, +} + +unsafe impl Send for InProcessExecutorHandlerData {} +unsafe impl Sync for InProcessExecutorHandlerData {} + +/// Exception handling needs some nasty unsafe. +pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { + /// The state ptr for signal handling + state_ptr: ptr::null_mut(), + /// The event manager ptr for signal handling + event_mgr_ptr: ptr::null_mut(), + /// The fuzzer ptr for signal handling + fuzzer_ptr: ptr::null_mut(), + /// The observers ptr for signal handling + observers_ptr: ptr::null(), + /// The current input for signal handling + current_input_ptr: ptr::null(), + /// The crash handler fn + crash_handler: ptr::null(), + /// The timeout handler fn + timeout_handler: ptr::null(), +}; + #[cfg(unix)] mod unix_signal_handler { use alloc::vec::Vec; - use core::ptr; - use libc::{c_void, siginfo_t, ucontext_t}; + use core::{mem::transmute, ptr}; + use libc::{siginfo_t, ucontext_t}; #[cfg(feature = "std")] use std::io::{stdout, Write}; @@ -250,7 +270,10 @@ mod unix_signal_handler { bolts::os::unix_signals::{Handler, Signal}, corpus::{Corpus, Testcase}, events::{Event, EventFirer, EventRestarter}, - executors::ExitKind, + executors::{ + inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE}, + ExitKind, + }, feedbacks::Feedback, fuzzer::HasObjective, inputs::Input, @@ -261,47 +284,14 @@ mod unix_signal_handler { pub type HandlerFuncPtr = unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut InProcessExecutorHandlerData); - // TODO merge GLOBAL_STATE with the Windows one - - /// Signal handling on unix systems needs some nasty unsafe. - pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { - /// The state ptr for signal handling - state_ptr: ptr::null_mut(), - /// The event manager ptr for signal handling - event_mgr_ptr: ptr::null_mut(), - /// The fuzzer ptr for signal handling - fuzzer_ptr: ptr::null_mut(), - /// The observers ptr for signal handling - observers_ptr: ptr::null(), - /// The current input for signal handling - current_input_ptr: ptr::null(), - /// The crash handler fn - crash_handler: nop_handler, - /// The timeout handler fn - timeout_handler: nop_handler, - }; - - pub struct InProcessExecutorHandlerData { - pub state_ptr: *mut c_void, - pub event_mgr_ptr: *mut c_void, - pub fuzzer_ptr: *mut c_void, - pub observers_ptr: *const c_void, - pub current_input_ptr: *const c_void, - pub crash_handler: HandlerFuncPtr, - pub timeout_handler: HandlerFuncPtr, - } - - unsafe impl Send for InProcessExecutorHandlerData {} - unsafe impl Sync for InProcessExecutorHandlerData {} - /// A handler that does nothing. - pub fn nop_handler( + /*pub fn nop_handler( _signal: Signal, _info: siginfo_t, _context: &mut ucontext_t, _data: &mut InProcessExecutorHandlerData, ) { - } + }*/ #[cfg(unix)] impl Handler for InProcessExecutorHandlerData { @@ -310,9 +300,17 @@ mod unix_signal_handler { let data = &mut GLOBAL_STATE; match signal { Signal::SigUser2 | Signal::SigAlarm => { - (data.timeout_handler)(signal, info, context, data); + if !data.timeout_handler.is_null() { + let func: HandlerFuncPtr = transmute(data.timeout_handler); + (func)(signal, info, context, data); + } + } + _ => { + if !data.crash_handler.is_null() { + let func: HandlerFuncPtr = transmute(data.crash_handler); + (func)(signal, info, context, data); + } } - _ => (data.crash_handler)(signal, info, context, data), } } } @@ -544,7 +542,7 @@ mod unix_signal_handler { #[cfg(all(windows, feature = "std"))] mod windows_exception_handler { use alloc::vec::Vec; - use core::{ffi::c_void, ptr}; + use core::{mem::transmute, ptr}; #[cfg(feature = "std")] use std::io::{stdout, Write}; @@ -557,7 +555,10 @@ mod windows_exception_handler { }, corpus::{Corpus, Testcase}, events::{Event, EventFirer, EventRestarter}, - executors::ExitKind, + executors::{ + inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE}, + ExitKind, + }, feedbacks::Feedback, fuzzer::HasObjective, inputs::Input, @@ -568,49 +569,22 @@ mod windows_exception_handler { pub type HandlerFuncPtr = unsafe fn(ExceptionCode, *mut EXCEPTION_POINTERS, &mut InProcessExecutorHandlerData); - /// Signal handling on unix systems needs some nasty unsafe. - pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { - /// The state ptr for signal handling - state_ptr: ptr::null_mut(), - /// The event manager ptr for signal handling - event_mgr_ptr: ptr::null_mut(), - /// The fuzzer ptr for signal handling - fuzzer_ptr: ptr::null_mut(), - /// The observers ptr for signal handling - observers_ptr: ptr::null(), - /// The current input for signal handling - current_input_ptr: ptr::null(), - /// The crash handler fn - crash_handler: nop_handler, - // The timeout handler fn - //timeout_handler: nop_handler, - }; - - pub struct InProcessExecutorHandlerData { - pub state_ptr: *mut c_void, - pub event_mgr_ptr: *mut c_void, - pub fuzzer_ptr: *mut c_void, - pub observers_ptr: *const c_void, - pub current_input_ptr: *const c_void, - pub crash_handler: HandlerFuncPtr, - //pub timeout_handler: HandlerFuncPtr, - } - - unsafe impl Send for InProcessExecutorHandlerData {} - unsafe impl Sync for InProcessExecutorHandlerData {} - - pub unsafe fn nop_handler( + /*pub unsafe fn nop_handler( _code: ExceptionCode, _exception_pointers: *mut EXCEPTION_POINTERS, _data: &mut InProcessExecutorHandlerData, ) { - } + }*/ impl Handler for InProcessExecutorHandlerData { + #[allow(clippy::not_unsafe_ptr_arg_deref)] fn handle(&mut self, code: ExceptionCode, exception_pointers: *mut EXCEPTION_POINTERS) { unsafe { let data = &mut GLOBAL_STATE; - (data.crash_handler)(code, exception_pointers, data) + if !data.crash_handler.is_null() { + let func: HandlerFuncPtr = transmute(data.crash_handler); + (func)(code, exception_pointers, data); + } } } @@ -718,11 +692,11 @@ mod windows_exception_handler { #[cfg(test)] mod tests { - use core::marker::PhantomData; + use core::{marker::PhantomData, ptr}; use crate::{ bolts::tuples::tuple_list, - executors::{inprocess, Executor, ExitKind, InProcessExecutor}, + executors::{Executor, ExitKind, InProcessExecutor}, inputs::NopInput, }; @@ -733,10 +707,8 @@ mod tests { let mut in_process_executor = InProcessExecutor::<_, NopInput, (), ()> { harness_fn: &mut harness, observers: tuple_list!(), - #[cfg(any(unix, all(windows, feature = "std")))] - crash_handler: inprocess::nop_handler, - #[cfg(unix)] - timeout_handler: inprocess::nop_handler, + crash_handler: ptr::null(), + timeout_handler: ptr::null(), phantom: PhantomData, }; let input = NopInput {}; diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 8044a43d75..29aa8780ae 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -14,7 +14,7 @@ pub mod combined; pub use combined::CombinedExecutor; pub mod shadow; -pub use shadow::{HasShadowObserverHooks, ShadowExecutor}; +pub use shadow::ShadowExecutor; use crate::{ inputs::{HasTargetBytes, Input}, diff --git a/libafl/src/executors/shadow.rs b/libafl/src/executors/shadow.rs index f6a93f0323..3a6c0866f8 100644 --- a/libafl/src/executors/shadow.rs +++ b/libafl/src/executors/shadow.rs @@ -9,26 +9,6 @@ use crate::{ Error, }; -pub trait HasShadowObserverHooks { - /// Run the pre exec hook for all the shadow [`crate::observers::Observer`]`s` - fn pre_exec_shadow_observers( - &mut self, - fuzzer: &mut Z, - state: &mut S, - mgr: &mut EM, - input: &I, - ) -> Result<(), Error>; - - /// Run the post exec hook for all the shadow [`crate::observers::Observer`]`s` - fn post_exec_shadow_observers( - &mut self, - fuzzer: &mut Z, - state: &mut S, - mgr: &mut EM, - input: &I, - ) -> Result<(), Error>; -} - /// A [`ShadowExecutor`] wraps an executor and a set of shadow observers pub struct ShadowExecutor { executor: E, diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 326f894568..5e4c4a740a 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -467,6 +467,61 @@ where FT: FeedbackStatesTuple, SC: Corpus, { + fn generate_initial_internal( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + generator: &mut G, + manager: &mut EM, + num: usize, + forced: bool, + ) -> Result<(), Error> + where + G: Generator, + Z: Evaluator, + EM: EventFirer, + { + let mut added = 0; + for _ in 0..num { + let input = generator.generate(self.rand_mut())?; + if forced { + let _ = fuzzer.add_input(self, executor, manager, input)?; + added += 1; + } else { + let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?; + if res != ExecuteInputResult::None { + added += 1; + } + } + } + manager.fire( + self, + Event::Log { + severity_level: LogSeverity::Debug, + message: format!("Loaded {} over {} initial testcases", added, num), + phantom: PhantomData, + }, + )?; + Ok(()) + } + + /// Generate `num` initial inputs, using the passed-in generator and force the addition to corpus. + pub fn generate_initial_inputs_forced( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + generator: &mut G, + manager: &mut EM, + num: usize, + ) -> Result<(), Error> + where + G: Generator, + Z: Evaluator, + EM: EventFirer, + { + self.generate_initial_internal(fuzzer, executor, generator, manager, num, true) + } + /// Generate `num` initial inputs, using the passed-in generator. pub fn generate_initial_inputs( &mut self, @@ -481,23 +536,7 @@ where Z: Evaluator, EM: EventFirer, { - let mut added = 0; - for _ in 0..num { - let input = generator.generate(self.rand_mut())?; - let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?; - if res != ExecuteInputResult::None { - added += 1; - } - } - manager.fire( - self, - Event::Log { - severity_level: LogSeverity::Debug, - message: format!("Loaded {} over {} initial testcases", added, num), - phantom: PhantomData, - }, - )?; - Ok(()) + self.generate_initial_internal(fuzzer, executor, generator, manager, num, false) } /// Creates a new `State`, taking ownership of all of the individual components during fuzzing. diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml new file mode 100644 index 0000000000..686ac3d228 --- /dev/null +++ b/libafl_qemu/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "libafl_qemu" +version = "0.5.0" +authors = ["Andrea Fioraldi "] +description = "QEMU user backend library for LibAFL" +documentation = "https://docs.rs/libafl_qemu" +repository = "https://github.com/AFLplusplus/LibAFL/" +readme = "../README.md" +license = "MIT OR Apache-2.0" +keywords = ["fuzzing", "qemu", "instrumentation"] +edition = "2018" + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libafl = { path = "../libafl", version = "0.5.0" } +libafl_targets = { path = "../libafl_targets", version = "0.5.0" } +serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib +hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible +num = "0.4" +num_enum = "0.5.1" +goblin = "0.4.2" +libc = "0.2.97" + +[build-dependencies] +cc = { version = "1.0" } diff --git a/libafl_qemu/build.rs b/libafl_qemu/build.rs new file mode 100644 index 0000000000..7f455f8072 --- /dev/null +++ b/libafl_qemu/build.rs @@ -0,0 +1,21 @@ +use std::{env, path::Path}; + +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_dir = out_dir.to_string_lossy().to_string(); + let src_dir = Path::new("src"); + + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + + if target_os == "linux" { + println!("cargo:rerun-if-changed=src/weaks.c"); + + cc::Build::new() + .file(src_dir.join("weaks.c")) + .compile("weaks"); + + println!("cargo:rustc-link-search=native={}", &out_dir); + } + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/libafl_qemu/src/amd64.rs b/libafl_qemu/src/amd64.rs new file mode 100644 index 0000000000..b2ac5278d2 --- /dev/null +++ b/libafl_qemu/src/amd64.rs @@ -0,0 +1,25 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] +#[repr(i32)] +#[allow(clippy::pub_enum_variant_names)] +pub enum Amd64Regs { + Rax = 0, + Rbx = 1, + Rcx = 2, + Rdx = 3, + Rsi = 4, + Rdi = 5, + Rbp = 6, + Rsp = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15, + Rip = 16, + Rflags = 17, +} diff --git a/libafl_qemu/src/elf.rs b/libafl_qemu/src/elf.rs new file mode 100644 index 0000000000..99c6884f7c --- /dev/null +++ b/libafl_qemu/src/elf.rs @@ -0,0 +1,55 @@ +//! Utilities to parse and process ELFs + +use goblin::elf::Elf; +use std::{convert::AsRef, fs::File, io::Read, path::Path, str}; + +use libafl::Error; + +pub struct EasyElf<'a> { + elf: Elf<'a>, +} + +impl<'a> EasyElf<'a> { + pub fn from_file

(path: P, buffer: &'a mut Vec) -> Result + where + P: AsRef, + { + let elf = { + let mut binary_file = File::open(path)?; + binary_file.read_to_end(buffer)?; + Elf::parse(buffer).map_err(|e| Error::Unknown(format!("{}", e))) + }?; + Ok(Self { elf }) + } + + pub fn from_slice(buffer: &'a [u8]) -> Result { + let elf = Elf::parse(buffer).map_err(|e| Error::Unknown(format!("{}", e)))?; + Ok(Self { elf }) + } + + #[must_use] + pub fn goblin(&self) -> &Elf<'a> { + &self.elf + } + + #[must_use] + pub fn goblin_mut(&mut self) -> &mut Elf<'a> { + &mut self.elf + } + + #[must_use] + pub fn resolve_symbol(&self, name: &str) -> Option { + for sym in self.elf.syms.iter() { + if let Some(sym_name) = self.elf.strtab.get_at(sym.st_name) { + if sym_name == name { + return if sym.st_value == 0 { + None + } else { + Some(sym.st_value) + }; + } + } + } + None + } +} diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs new file mode 100644 index 0000000000..366b8bc90e --- /dev/null +++ b/libafl_qemu/src/emu.rs @@ -0,0 +1,172 @@ +//! Expose QEMU user `LibAFL` C api to Rust + +use core::{convert::Into, mem::transmute, ptr::copy_nonoverlapping}; +use num::Num; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::{mem::size_of, slice::from_raw_parts, str::from_utf8_unchecked}; + +pub const SKIP_EXEC_HOOK: u32 = u32::MAX; + +extern "C" { + fn libafl_qemu_write_reg(reg: i32, val: *const u8) -> i32; + fn libafl_qemu_read_reg(reg: i32, val: *mut u8) -> i32; + fn libafl_qemu_num_regs() -> i32; + fn libafl_qemu_set_breakpoint(addr: u64) -> i32; + fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; + fn libafl_qemu_run() -> i32; + + fn strlen(s: *const u8) -> usize; + + /// abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, int flags, int fd, abi_ulong offset) + fn target_mmap(start: u64, len: u64, target_prot: i32, flags: i32, fd: i32, offset: u64) + -> u64; + + /// int target_munmap(abi_ulong start, abi_ulong len) + fn target_munmap(start: u64, len: u64) -> i32; + + static exec_path: *const u8; + static guest_base: usize; + + static mut libafl_exec_edge_hook: unsafe extern "C" fn(u32); + static mut libafl_gen_edge_hook: unsafe extern "C" fn(u64, u64) -> u32; + static mut libafl_exec_block_hook: unsafe extern "C" fn(u64); + static mut libafl_gen_block_hook: unsafe extern "C" fn(u64) -> u32; +} + +#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] +#[repr(i32)] +#[allow(clippy::pub_enum_variant_names)] +pub enum MmapPerms { + Read = libc::PROT_READ, + Write = libc::PROT_WRITE, + Execute = libc::PROT_EXEC, + ReadWrite = libc::PROT_READ | libc::PROT_WRITE, + ReadExecute = libc::PROT_READ | libc::PROT_EXEC, + WriteExecute = libc::PROT_WRITE | libc::PROT_EXEC, + ReadWriteExecute = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, +} + +pub fn write_mem(addr: u64, buf: &[T]) { + let host_addr = g2h(addr); + unsafe { + copy_nonoverlapping( + buf.as_ptr() as *const _ as *const u8, + host_addr, + buf.len() * size_of::(), + ) + } +} + +pub fn read_mem(addr: u64, buf: &mut [T]) { + let host_addr = g2h(addr); + unsafe { + copy_nonoverlapping( + host_addr as *const u8, + buf.as_mut_ptr() as *mut _ as *mut u8, + buf.len() * size_of::(), + ) + } +} + +#[must_use] +pub fn num_regs() -> i32 { + unsafe { libafl_qemu_num_regs() } +} + +pub fn write_reg(reg: R, val: T) -> Result<(), String> +where + T: Num + PartialOrd + Copy, + R: Into, +{ + let reg = reg.into(); + let success = unsafe { libafl_qemu_write_reg(reg, &val as *const _ as *const u8) }; + if success == 0 { + Err(format!("Failed to write to register {}", reg)) + } else { + Ok(()) + } +} + +pub fn read_reg(reg: R) -> Result +where + T: Num + PartialOrd + Copy, + R: Into, +{ + let reg = reg.into(); + let mut val = T::zero(); + let success = unsafe { libafl_qemu_read_reg(reg, &mut val as *mut _ as *mut u8) }; + if success == 0 { + Err(format!("Failed to read register {}", reg)) + } else { + Ok(val) + } +} + +pub fn set_breakpoint(addr: u64) { + unsafe { libafl_qemu_set_breakpoint(addr) }; +} + +pub fn remove_breakpoint(addr: u64) { + unsafe { libafl_qemu_remove_breakpoint(addr) }; +} + +pub fn run() { + unsafe { libafl_qemu_run() }; +} + +#[must_use] +pub fn g2h(addr: u64) -> *mut T { + unsafe { transmute(addr + guest_base as u64) } +} + +#[must_use] +pub fn h2g(addr: *const T) -> u64 { + unsafe { (addr as usize - guest_base) as u64 } +} + +#[must_use] +pub fn binary_path<'a>() -> &'a str { + unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) } +} + +pub fn map_private(addr: u64, size: usize, perms: MmapPerms) -> Result { + let res = unsafe { + target_mmap( + addr, + size as u64, + perms.into(), + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) + }; + if res == 0 { + Err(format!("Failed to map {}", addr)) + } else { + Ok(res) + } +} + +pub fn unmap(addr: u64, size: usize) -> Result<(), String> { + if unsafe { target_munmap(addr, size as u64) } == 0 { + Ok(()) + } else { + Err(format!("Failed to unmap {}", addr)) + } +} + +pub fn set_exec_edge_hook(hook: extern "C" fn(u32)) { + unsafe { libafl_exec_edge_hook = hook }; +} + +pub fn set_gen_edge_hook(hook: extern "C" fn(u64, u64) -> u32) { + unsafe { libafl_gen_edge_hook = hook }; +} + +pub fn set_exec_block_hook(hook: extern "C" fn(u64)) { + unsafe { libafl_exec_block_hook = hook }; +} + +pub fn set_gen_block_hook(hook: extern "C" fn(u64) -> u32) { + unsafe { libafl_gen_block_hook = hook }; +} diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs new file mode 100644 index 0000000000..1ecfedf507 --- /dev/null +++ b/libafl_qemu/src/executor.rs @@ -0,0 +1,125 @@ +use core::{ffi::c_void, mem::transmute, ptr}; + +use libafl::{ + corpus::Corpus, + events::{EventFirer, EventRestarter}, + executors::{inprocess::GLOBAL_STATE, Executor, ExitKind, HasObservers, InProcessExecutor}, + feedbacks::Feedback, + fuzzer::HasObjective, + inputs::Input, + observers::ObserversTuple, + state::{HasClientPerfStats, HasSolutions}, + Error, +}; + +use crate::{emu, emu::SKIP_EXEC_HOOK}; + +static mut GEN_EDGE_HOOK_PTR: *const c_void = ptr::null(); +static mut GEN_BLOCK_HOOK_PTR: *const c_void = ptr::null(); + +extern "C" fn gen_edge_hook_wrapper(src: u64, dst: u64) -> u32 { + unsafe { + let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); + let func: fn(&mut S, u64, u64) -> Option = transmute(GEN_EDGE_HOOK_PTR); + (func)(state, src, dst).map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +extern "C" fn gen_block_hook_wrapper(addr: u64) -> u32 { + unsafe { + let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); + let func: fn(&mut S, u64) -> Option = transmute(GEN_BLOCK_HOOK_PTR); + (func)(state, addr).map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +pub struct QemuExecutor<'a, H, I, OT, S> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, +{ + inner: InProcessExecutor<'a, H, I, OT, S>, +} + +impl<'a, H, I, OT, S> QemuExecutor<'a, H, I, OT, S> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, +{ + pub fn new( + harness_fn: &'a mut H, + observers: OT, + fuzzer: &mut Z, + state: &mut S, + event_mgr: &mut EM, + ) -> Result + where + EM: EventFirer + EventRestarter, + OC: Corpus, + OF: Feedback, + S: HasSolutions + HasClientPerfStats, + Z: HasObjective, + { + Ok(Self { + inner: InProcessExecutor::new(harness_fn, observers, fuzzer, state, event_mgr)?, + }) + } + + #[allow(clippy::unused_self)] + pub fn hook_edge_generation(&self, hook: fn(&mut S, u64, u64) -> Option) { + unsafe { GEN_EDGE_HOOK_PTR = hook as *const _ }; + emu::set_gen_edge_hook(gen_edge_hook_wrapper::); + } + + #[allow(clippy::unused_self)] + pub fn hook_edge_execution(&self, hook: extern "C" fn(u32)) { + emu::set_exec_edge_hook(hook); + } + + #[allow(clippy::unused_self)] + pub fn hook_block_generation(&self, hook: fn(&mut S, u64) -> Option) { + unsafe { GEN_BLOCK_HOOK_PTR = hook as *const _ }; + emu::set_gen_block_hook(gen_block_hook_wrapper::); + } + + #[allow(clippy::unused_self)] + pub fn hook_block_execution(&self, hook: extern "C" fn(u64)) { + emu::set_exec_block_hook(hook); + } +} + +impl<'a, EM, H, I, OT, S, Z> Executor for QemuExecutor<'a, H, I, OT, S> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result { + self.inner.run_target(fuzzer, state, mgr, input) + } +} + +impl<'a, H, I, OT, S> HasObservers for QemuExecutor<'a, H, I, OT, S> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, +{ + #[inline] + fn observers(&self) -> &OT { + self.inner.observers() + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + self.inner.observers_mut() + } +} diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs new file mode 100644 index 0000000000..8f578f5d1c --- /dev/null +++ b/libafl_qemu/src/hooks.rs @@ -0,0 +1,48 @@ +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; + +use libafl::state::HasMetadata; +pub use libafl_targets::{EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM}; + +/// A testcase metadata saying if a testcase is favored +#[derive(Default, Serialize, Deserialize)] +pub struct QemuEdgesMapMetadata { + pub map: HashMap<(u64, u64), u32>, +} + +impl QemuEdgesMapMetadata { + #[must_use] + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } +} + +libafl::impl_serdeany!(QemuEdgesMapMetadata); + +pub fn gen_unique_edges_id(state: &mut S, src: u64, dest: u64) -> Option +where + S: HasMetadata, +{ + if state.metadata().get::().is_none() { + state.add_metadata(QemuEdgesMapMetadata::new()); + } + let meta = state + .metadata_mut() + .get_mut::() + .unwrap(); + Some(*meta.map.entry((src, dest)).or_insert_with(|| unsafe { + let id = MAX_EDGES_NUM; + MAX_EDGES_NUM = (MAX_EDGES_NUM + 1) & (EDGES_MAP_SIZE - 1); + id as u32 + })) +} + +pub extern "C" fn exec_log_hitcount(id: u32) { + unsafe { EDGES_MAP[id as usize] += 1 }; +} + +pub extern "C" fn exec_log_single(id: u32) { + unsafe { EDGES_MAP[id as usize] = 1 }; +} diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs new file mode 100644 index 0000000000..975153a59c --- /dev/null +++ b/libafl_qemu/src/lib.rs @@ -0,0 +1,34 @@ +use std::env; + +pub mod amd64; +pub mod x86; + +pub mod elf; +pub mod hooks; + +#[cfg(target_os = "linux")] +pub mod executor; +#[cfg(target_os = "linux")] +pub use executor::QemuExecutor; + +#[cfg(target_os = "linux")] +pub mod emu; +#[cfg(target_os = "linux")] +pub use emu::*; + +#[must_use] +pub fn filter_qemu_args() -> Vec { + let mut args = vec![env::args().next().unwrap()]; + let mut args_iter = env::args(); + + while let Some(arg) = args_iter.next() { + if arg.starts_with("--libafl") { + args.push(arg); + args.push(args_iter.next().unwrap()); + } else if arg.starts_with("-libafl") { + args.push("-".to_owned() + &arg); + args.push(args_iter.next().unwrap()); + } + } + args +} diff --git a/libafl_qemu/src/weaks.c b/libafl_qemu/src/weaks.c new file mode 100644 index 0000000000..822f655ea0 --- /dev/null +++ b/libafl_qemu/src/weaks.c @@ -0,0 +1,66 @@ +#include +#include + +typedef int64_t abi_long; +typedef uint64_t abi_ulong; + +__attribute__((weak)) int libafl_qemu_write_reg(int reg, uint8_t* val) { + (void)reg; + (void)val; + return 0; +} + +__attribute__((weak)) int libafl_qemu_read_reg(int reg, uint8_t* val) { + (void)reg; + (void)val; + return 0; +} + +__attribute__((weak)) int libafl_qemu_num_regs(void) { + return 0; +} + +__attribute__((weak)) int libafl_qemu_set_breakpoint(uint64_t addr) { + (void)addr; + return 0; +} + +__attribute__((weak)) int libafl_qemu_remove_breakpoint(uint64_t addr) { + (void)addr; + return 0; +} + +__attribute__((weak)) int libafl_qemu_run() { + return 0; +} + +__attribute__((weak)) uint64_t libafl_load_addr() { + return 0; +} + +__attribute__((weak)) abi_long target_mmap(abi_ulong start, abi_ulong len, + int target_prot, int flags, int fd, + abi_ulong offset) { + + (void)start; + (void)len; + (void)target_prot; + (void)flags; + (void)fd; + (void)offset; + return 0; +} + +__attribute__((weak)) int target_munmap(abi_ulong start, abi_ulong len) { + (void)start; + (void)len; + return 0; +} + +__attribute__((weak)) char* exec_path = NULL; +__attribute__((weak)) size_t guest_base = 0; + +__attribute__((weak)) void (*libafl_exec_edge_hook)(uint32_t); +__attribute__((weak)) uint32_t (*libafl_gen_edge_hook)(uint64_t, uint64_t); +__attribute__((weak)) void (*libafl_exec_block_hook)(uint64_t); +__attribute__((weak)) uint32_t (*libafl_gen_block_hook)(uint64_t); diff --git a/libafl_qemu/src/x86.rs b/libafl_qemu/src/x86.rs new file mode 100644 index 0000000000..d93ab25931 --- /dev/null +++ b/libafl_qemu/src/x86.rs @@ -0,0 +1,17 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] +#[repr(i32)] +#[allow(clippy::pub_enum_variant_names)] +pub enum X86Regs { + Eax = 0, + Ebx = 1, + Ecx = 2, + Edx = 3, + Esi = 4, + Edi = 5, + Ebp = 6, + Esp = 7, + Eip = 8, + Eflags = 9, +}