From 979d14e7dabb3c806bb9b486c4fead004ba555b1 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:25:37 +0100 Subject: [PATCH 01/93] Decode with callback --- libafl_intelpt/src/lib.rs | 96 +++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index f9616d7e3b5..6390f221212 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -41,11 +41,10 @@ use caps::{CapSet, Capability}; #[cfg(target_os = "linux")] use libafl_bolts::ownedref::OwnedRefMut; use libafl_bolts::Error; -use libipt::PtError; #[cfg(target_os = "linux")] use libipt::{ - block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, BlockFlags, - ConfigBuilder, Cpu, Image, PtErrorCode, Status, + block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, Asid, BlockFlags, + ConfigBuilder, Cpu, Image, PtError, PtErrorCode, Status, }; #[cfg(target_os = "linux")] use num_enum::TryFromPrimitive; @@ -176,6 +175,12 @@ impl IntelPT { } } + /// Get the current IP filters configuration + #[must_use] + pub fn ip_filters(&self) -> Vec> { + self.ip_filters.clone() + } + fn ip_filters_to_addr_filter(&self) -> AddrFilter { let mut builder = AddrFilterBuilder::new(); let mut iter = self @@ -231,30 +236,39 @@ impl IntelPT { } } - // // let read_mem = |buf: &mut [u8], addr: u64| { - // // let src = addr as *const u8; - // // let dst = buf.as_mut_ptr(); - // // let size = buf.len(); - // // unsafe { - // // ptr::copy_nonoverlapping(src, dst, size); - // // } - // // }; - // #[allow(clippy::cast_possible_wrap)] - // fn decode_with_callback( - // &mut self, - // read_memory: F, - // copy_buffer: Option<&mut Vec>, - // ) -> Result, Error> { - // self.decode( - // Some(|buff: &mut [u8], addr: u64, _: Asid| { - // debug_assert!(i32::try_from(buff.len()).is_ok()); - // read_memory(buff, addr); - // buff.len() as i32 - // }), - // None, - // copy_buffer, - // ) - // } + /// Fill the coverage map by decoding the PT traces and reading target memory through `read_mem` + /// + /// This function consumes the traces. + /// + /// # Example + /// + /// An example `read_mem` callback function for the (inprocess) `intel_pt_babyfuzzer` could be: + /// ``` + /// let read_mem = |buf: &mut [u8], addr: u64| { + /// let src = addr as *const u8; + /// let dst = buf.as_mut_ptr(); + /// let size = buf.len(); + /// unsafe { + /// core::ptr::copy_nonoverlapping(src, dst, size); + /// } + /// }; + /// ``` + #[allow(clippy::cast_possible_wrap)] + pub fn decode_with_callback(&mut self, read_memory: F, map: &mut [T]) -> Result<(), Error> + where + F: Fn(&mut [u8], u64), + T: SaturatingAdd + From + Debug, + { + self.decode_traces_into_map_common( + None, + Some(|buff: &mut [u8], addr: u64, _: Asid| { + debug_assert!(i32::try_from(buff.len()).is_ok()); + read_memory(buff, addr); + buff.len() as i32 + }), + map, + ) + } /// Fill the coverage map by decoding the PT traces /// @@ -266,6 +280,23 @@ impl IntelPT { ) -> Result<(), Error> where T: SaturatingAdd + From + Debug, + { + self.decode_traces_into_map_common( + Some(image), + None:: i32>, + map, + ) + } + + fn decode_traces_into_map_common( + &mut self, + image: Option<&mut Image>, + read_memory: Option, + map: &mut [T], + ) -> Result<(), Error> + where + F: Fn(&mut [u8], u64, Asid) -> i32, + T: SaturatingAdd + From + Debug, { let head = unsafe { self.aux_head.read_volatile() }; let tail = unsafe { self.aux_tail.read_volatile() }; @@ -313,9 +344,14 @@ impl IntelPT { let flags = BlockFlags::END_ON_CALL.union(BlockFlags::END_ON_JUMP); config.flags(flags); let mut decoder = BlockDecoder::new(&config.finish()).map_err(error_from_pt_error)?; - decoder - .set_image(Some(image)) - .map_err(error_from_pt_error)?; + decoder.set_image(image).map_err(error_from_pt_error)?; + if let Some(rm) = read_memory { + decoder + .image() + .map_err(error_from_pt_error)? + .set_callback(Some(rm)) + .map_err(error_from_pt_error)?; + } let mut previous_block_ip = 0; let mut status; From 4b596412593dc6ca34620e32127d771592c0ccfc Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:50:28 +0100 Subject: [PATCH 02/93] WIP restore intelpt module --- libafl_qemu/Cargo.toml | 3 + .../src/modules/systemmode/intel_pt.rs | 161 ++++++++++++++++++ libafl_qemu/src/modules/systemmode/mod.rs | 3 +- 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 libafl_qemu/src/modules/systemmode/intel_pt.rs diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 6be91820f1b..f72702b22e2 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -80,6 +80,8 @@ slirp = [ "libafl_qemu_sys/slirp", ] # build qemu with host libslirp (for user networking) +intel_pt = ["systemmode", "x86_64", "libafl/intel_pt", "dep:libafl_intelpt"] + # Requires the binary's build.rs to call `build_libafl_qemu` shared = ["libafl_qemu_sys/shared"] @@ -100,6 +102,7 @@ libafl_bolts = { path = "../libafl_bolts", version = "0.13.2", default-features libafl_targets = { path = "../libafl_targets", version = "0.13.2" } libafl_qemu_sys = { path = "./libafl_qemu_sys", version = "0.13.2", default-features = false } libafl_derive = { path = "../libafl_derive", version = "0.13.2" } +libafl_intelpt = { path = "../libafl_intelpt", optional = true } serde = { workspace = true, default-features = false, features = [ "alloc", diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs new file mode 100644 index 00000000000..46199960406 --- /dev/null +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -0,0 +1,161 @@ +use std::{ + fmt::Debug, + ops::{Range, RangeInclusive}, + ptr::slice_from_raw_parts_mut, +}; + +use libafl::{inputs::UsesInput, observers::ObserversTuple, HasMetadata}; +use libafl_intelpt::IntelPT; +use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; +use num_traits::SaturatingAdd; + +use crate::{ + modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind, NopPageFilter}, + EmulatorModules, NewThreadHook, +}; + +#[derive(Debug)] +pub struct IntelPTModule +where + T: Debug, +{ + pt: Option, + map_ptr: *mut T, + map_len: usize, +} + +pub fn intel_pt_new_thread( + emulator_modules: &mut EmulatorModules, + _state: Option<&mut S>, + _env: CPUArchStatePtr, + tid: u32, +) -> bool +where + S: HasMetadata + Unpin + UsesInput, + ET: EmulatorModuleTuple, + T: Debug + 'static, +{ + let intel_pt_module = emulator_modules + .modules_mut() + .match_first_type_mut::>() + .unwrap(); + + if intel_pt_module.pt.is_some() { + panic!("Intel PT module already initialized, only single core VMs are supported ATM."); + } + + intel_pt_module.pt = Some(IntelPT::builder().pid(Some(tid as i32)).build().unwrap()); + intel_pt_module + .pt + .as_mut() + .unwrap() + .enable_tracing() + .unwrap(); + + println!("IntelPT initialized!"); + // What does this bool mean? ignore for the moment + true +} + +impl EmulatorModule for IntelPTModule +where + S: Unpin + UsesInput + HasMetadata, + T: SaturatingAdd + From + Debug + 'static, +{ + type ModuleAddressFilter = Self; + type ModulePageFilter = NopPageFilter; + + fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) + where + ET: EmulatorModuleTuple, + { + emulator_modules.thread_creation(NewThreadHook::Function(intel_pt_new_thread::)); + // TODO emulator_modules.thread_teradown + } + + fn pre_exec( + &mut self, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + _input: &::Input, + ) where + ET: EmulatorModuleTuple, + { + let pt = self.pt.as_mut().expect("Intel PT module not initialized."); + pt.enable_tracing().unwrap(); + } + + fn post_exec( + &mut self, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + _input: &S::Input, + _observers: &mut OT, + _exit_kind: &mut ExitKind, + ) where + OT: ObserversTuple, + ET: EmulatorModuleTuple, + { + let pt = self.pt.as_mut().expect("Intel PT module not initialized."); + pt.disable_tracing().unwrap(); + + // we need the memory map to decode the traces here take it in prexec. use QemuMemoryChunk + // TODO handle self modifying code + + let qemu = emulator_modules.qemu(); + pt.decode_with_callback( + |addr, out_buff| qemu.read_mem(out_buff, addr.into()).unwrap(), + unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, + ) + .unwrap(); + + // let trace_path = "trace.out"; + // let mut file = OpenOptions::new() + // .append(true) + // .create(true) + // .open(trace_path) + // .expect("Failed to open trace output file"); + // + // file.write_all(&buff).unwrap(); + } + + fn address_filter(&self) -> &Self::ModuleAddressFilter { + self + } + + fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter { + self + } + + fn page_filter(&self) -> &Self::ModulePageFilter { + unimplemented!() + } + + fn page_filter_mut(&mut self) -> &mut Self::ModulePageFilter { + unimplemented!() + } +} + +impl AddressFilter for IntelPTModule +where + T: Debug + 'static, +{ + fn register(&mut self, address_range: Range) { + let pt = self.pt.as_mut().unwrap(); + let mut filters = pt.ip_filters(); + let range_inclusive = + RangeInclusive::new(address_range.start as usize, address_range.end as usize - 1); + filters.push(range_inclusive); + pt.set_ip_filters(&filters).unwrap() + } + + fn allowed(&self, address: &GuestAddr) -> bool { + let pt = self.pt.as_ref().unwrap(); + for f in pt.ip_filters() { + if f.contains(&(*address as usize)) { + return true; + } + } + false + } +} diff --git a/libafl_qemu/src/modules/systemmode/mod.rs b/libafl_qemu/src/modules/systemmode/mod.rs index 8b137891791..07532a79e0b 100644 --- a/libafl_qemu/src/modules/systemmode/mod.rs +++ b/libafl_qemu/src/modules/systemmode/mod.rs @@ -1 +1,2 @@ - +#[cfg(feature = "intel_pt")] +pub mod intel_pt; From 731a7f5e25a44c2f9abe3ffe7fe71baccbb1d165 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:59:23 +0100 Subject: [PATCH 03/93] Fix build_target if target_dir doesn't exist --- .../binary_only/intel_pt_command_executor/Makefile.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml index 9c2d97e4eb1..77e6c3e39aa 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml +++ b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml @@ -4,7 +4,15 @@ PROFILE_DIR = "debug" [env.release] PROFILE_DIR = "release" +[tasks.target_dir] +condition = { files_not_exist = ["${TARGET_DIR}"] } +script_runner = "@shell" +script = ''' +mkdir -p ${TARGET_DIR} +''' + [tasks.build_target] +dependencies = ["target_dir"] command = "rustc" args = [ "src/target_program.rs", From 5386013c908b37e0bd00606224ed4c4931c35b40 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:01:06 +0100 Subject: [PATCH 04/93] WIP itelpt qemu/kvm example: bootloader --- .../qemu_intel_pt_bootloader/Cargo.toml | 13 ++ .../qemu_intel_pt_bootloader/Makefile.toml | 40 ++++ .../qemu_intel_pt_bootloader/corpus/hello | 1 + .../qemu_intel_pt_bootloader/src/boot.s | 41 ++++ .../qemu_intel_pt_bootloader/src/main.rs | 192 ++++++++++++++++++ .../src/modules/systemmode/intel_pt.rs | 88 ++++---- 6 files changed, 333 insertions(+), 42 deletions(-) create mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml create mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml create mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello create mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s create mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml new file mode 100644 index 00000000000..d1a12abbaf9 --- /dev/null +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bootloader" +version = "0.1.0" +authors = ["Marco Cavenati "] +edition = "2021" + +[dependencies] +libafl = { path = "../../../libafl", default-features = false } +libafl_bolts = { path = "../../../libafl_bolts" } +libafl_qemu = { path = "../../../libafl_qemu", features = [ + "intel_pt", +], default-features = false } +env_logger = "0.11.5" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml new file mode 100644 index 00000000000..70dab4dadb7 --- /dev/null +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -0,0 +1,40 @@ +[env.development] +PROFILE_DIR = "debug" + +[env.release] +PROFILE_DIR = "release" + +[tasks.target_dir] +condition = { files_not_exist = ["${CARGO_MAKE_CRATE_TARGET_DIRECTORY}"] } +script_runner = "@shell" +script = ''' +mkdir -p ${CARGO_MAKE_CRATE_TARGET_DIRECTORY} +''' + +[tasks.build_target] +dependencies = ["target_dir"] +command = "nasm" +args = [ + "-o", + "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.bin", + "${CARGO_MAKE_WORKING_DIRECTORY}/src/boot.s", +] + +[tasks.build_fuzzer] +command = "cargo" +args = ["build", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] + +[tasks.build] +dependencies = ["build_fuzzer", "build_target"] + +[tasks.setcap] +script = "sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" +dependencies = ["build_fuzzer"] + +[tasks.run] +command = "cargo" +args = ["run", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] +dependencies = ["build", "setcap"] + +[tasks.default] +alias = "run" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello b/fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello new file mode 100644 index 00000000000..802992c4220 --- /dev/null +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello @@ -0,0 +1 @@ +Hello world diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s new file mode 100644 index 00000000000..5cd58d35a41 --- /dev/null +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s @@ -0,0 +1,41 @@ +[bits 16] ; use 16 bits +[org 0x7c00] ; sets the start address + +_start: + mov cx, 0 + +bye: + mov ah, 0x0E ; BIOS teletype function + mov al, 'B' + int 0x10 + mov al, 'y' + int 0x10 + mov al, 'e' + int 0x10 + mov al, '!' + int 0x10 + +loop: + add cx, 1 + cmp cx, 0xff + jne loop + add cx, 1 + mov al, cl + add al, 0x20 + mov ah, 0x7c + jmp ax ; will this be traced as a full address? yes + +times 0x50 db 0x90 ; nop sled + +shutdown: + mov ax, 0x1000 + mov ax, ss + mov sp, 0xf000 + mov ax, 0x5307 + mov bx, 0x0001 + mov cx, 0x0003 + int 0x15 + +times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full + +dw 0xaa55 ; magic bytes that tell BIOS that this is bootable diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs new file mode 100644 index 00000000000..dbf716f8a84 --- /dev/null +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -0,0 +1,192 @@ +//! A fuzzer using qemu in systemmode for binary-only coverage of bootloaders + +use core::time::Duration; +use std::{env, path::PathBuf, process}; + +use libafl::{ + corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, + events::{launcher::Launcher, EventConfig}, + executors::ExitKind, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::BytesInput, + monitors::MultiMonitor, + mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator}, + observers::{StdMapObserver, TimeObserver}, + schedulers::QueueScheduler, + stages::StdMutationalStage, + state::{HasCorpus, StdState}, + Error, +}; +use libafl_bolts::{ + core_affinity::Cores, + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, +}; +use libafl_qemu::{ + config, config::Accelerator, executor::QemuExecutor, modules::intel_pt::IntelPTModule, + Emulator, Qemu, QemuExitReason, QemuShutdownCause, +}; +// Coverage map +const MAP_SIZE: usize = 4096; +static mut MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; +const MAX_INPUT_SIZE: usize = 50; + +#[cfg(target_os = "linux")] +fn main() { + env_logger::init(); + + // Hardcoded parameters + let timeout = Duration::from_secs(3); + let broker_port = 1337; + let cores = Cores::from_cmdline("0").unwrap(); + let corpus_dirs = [PathBuf::from("./corpus")]; + let objective_dir = PathBuf::from("./crashes"); + + let mut run_client = |state: Option<_>, mut mgr, _core_id| -> Result<(), Error> { + let target_dir = env::var("TARGET_DIR").expect("TARGET_DIR env not set"); + println!("run client"); + // Initialize QEMU + let qemu = Qemu::builder() + .no_graphic(true) + .drives([config::Drive::builder() + .format(config::DiskImageFileFormat::Raw) + .file(format!("{target_dir}/boot.bin")) + .build()]) + .accelerator(Accelerator::Kvm) + .bios(format!( + "{target_dir}/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu" + )) + .start_cpu(false) + .build() + .expect("Failed to initialize QEMU"); + + let emulator_modules = + tuple_list!(IntelPTModule::new(unsafe { MAP.as_mut_ptr() }, MAP_SIZE)); + + let emulator = Emulator::empty() + .qemu(qemu) + .modules(emulator_modules) + .build()?; + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, + _: &mut StdState, + _: &BytesInput| unsafe { + match emulator.qemu().run() { + Ok(QemuExitReason::End(QemuShutdownCause::GuestShutdown)) => { + println!("VM shut down!") + } + _ => panic!("Unexpected QEMU exit."), + } + ExitKind::Ok + }; + + // Create an observation channel using the map + let observer = + unsafe { StdMapObserver::from_mut_ptr("signals", MAP.as_mut_ptr(), MAP_SIZE) }; + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new(&observer), + // Time feedback, this one does not need a feedback state + TimeFeedback::new(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, 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 + InMemoryCorpus::new(), + // 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.clone()).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap() + }); + + // A queue policy to get testcases from the corpus + let scheduler = QueueScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // Create a QEMU in-process executor + let mut executor = QemuExecutor::new( + emulator, + &mut harness, + tuple_list!(observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + timeout, + ) + .expect("Failed to create QemuExecutor"); + + // Instead of calling the timeout handler and restart the process, trigger a breakpoint ASAP + // executor.break_on_timeout(); + + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &corpus_dirs); + process::exit(0); + }); + println!("Imported {} inputs from disk.", state.corpus().count()); + } + + // Setup an havoc mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer + .fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr) + .unwrap(); + Ok(()) + }; + + // The shared memory allocator + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + // The stats reporter for the broker + let monitor = MultiMonitor::new(|s| println!("{s}")); + + // Build and run a Launcher + match Launcher::builder() + .shmem_provider(shmem_provider) + .broker_port(broker_port) + .configuration(EventConfig::from_build_id()) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&cores) + .build() + .launch() + { + Ok(()) => println!("OK"), + Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."), + Err(err) => panic!("Failed to run launcher: {err:?}"), + } +} + +#[cfg(not(target_os = "linux"))] +pub fn main() { + panic!("qemu-user and libafl_qemu is only supported on linux!"); +} diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 46199960406..31870503a01 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -2,6 +2,7 @@ use std::{ fmt::Debug, ops::{Range, RangeInclusive}, ptr::slice_from_raw_parts_mut, + slice, }; use libafl::{inputs::UsesInput, observers::ObserversTuple, HasMetadata}; @@ -15,46 +16,20 @@ use crate::{ }; #[derive(Debug)] -pub struct IntelPTModule -where - T: Debug, -{ +pub struct IntelPTModule { pt: Option, map_ptr: *mut T, map_len: usize, } -pub fn intel_pt_new_thread( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - _env: CPUArchStatePtr, - tid: u32, -) -> bool -where - S: HasMetadata + Unpin + UsesInput, - ET: EmulatorModuleTuple, - T: Debug + 'static, -{ - let intel_pt_module = emulator_modules - .modules_mut() - .match_first_type_mut::>() - .unwrap(); - - if intel_pt_module.pt.is_some() { - panic!("Intel PT module already initialized, only single core VMs are supported ATM."); +impl IntelPTModule { + pub fn new(map_ptr: *mut T, map_len: usize) -> Self { + Self { + pt: None, + map_ptr, + map_len, + } } - - intel_pt_module.pt = Some(IntelPT::builder().pid(Some(tid as i32)).build().unwrap()); - intel_pt_module - .pt - .as_mut() - .unwrap() - .enable_tracing() - .unwrap(); - - println!("IntelPT initialized!"); - // What does this bool mean? ignore for the moment - true } impl EmulatorModule for IntelPTModule @@ -71,6 +46,7 @@ where { emulator_modules.thread_creation(NewThreadHook::Function(intel_pt_new_thread::)); // TODO emulator_modules.thread_teradown + println!("first_exec"); } fn pre_exec( @@ -83,6 +59,7 @@ where { let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.enable_tracing().unwrap(); + println!("pre_exec"); } fn post_exec( @@ -109,14 +86,8 @@ where ) .unwrap(); - // let trace_path = "trace.out"; - // let mut file = OpenOptions::new() - // .append(true) - // .create(true) - // .open(trace_path) - // .expect("Failed to open trace output file"); - // - // file.write_all(&buff).unwrap(); + let m = unsafe { slice::from_raw_parts(self.map_ptr, self.map_len) }; + println!("map: {:?}", m); } fn address_filter(&self) -> &Self::ModuleAddressFilter { @@ -159,3 +130,36 @@ where false } } + +pub fn intel_pt_new_thread( + emulator_modules: &mut EmulatorModules, + _state: Option<&mut S>, + _env: CPUArchStatePtr, + tid: u32, +) -> bool +where + S: HasMetadata + Unpin + UsesInput, + ET: EmulatorModuleTuple, + T: Debug + 'static, +{ + let intel_pt_module = emulator_modules + .modules_mut() + .match_first_type_mut::>() + .unwrap(); + + if intel_pt_module.pt.is_some() { + panic!("Intel PT module already initialized, only single core VMs are supported ATM."); + } + + intel_pt_module.pt = Some(IntelPT::builder().pid(Some(tid as i32)).build().unwrap()); + intel_pt_module + .pt + .as_mut() + .unwrap() + .enable_tracing() + .unwrap(); + + println!("IntelPT initialized!"); + // What does this bool mean? ignore for the moment + true +} From af203435b1a388b33d8cbbb47753ec4fe07555c8 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Tue, 19 Nov 2024 16:28:36 +0100 Subject: [PATCH 05/93] qemu config refactoring --- libafl_qemu/src/emu/builder.rs | 60 +++++++++++++--------------------- libafl_qemu/src/qemu/config.rs | 43 ++++++++---------------- libafl_qemu/src/qemu/mod.rs | 26 +++++++++++---- 3 files changed, 55 insertions(+), 74 deletions(-) diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index 67396d2c10a..a62b1fcf03d 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -1,4 +1,7 @@ -use std::{fmt::Debug, marker::PhantomData}; +use std::{ + fmt::{Debug, Formatter}, + marker::PhantomData, +}; use libafl::{ inputs::{HasTargetBytes, UsesInput}, @@ -17,10 +20,9 @@ use crate::{ }; #[derive(Clone, Debug)] -enum QemuBuilder { - Qemu(Qemu), - QemuConfig(QemuConfig), - QemuString(Vec), +pub enum QemuParams { + Config(QemuConfig), + Cli(Vec), } #[derive(Clone, Debug)] @@ -32,7 +34,7 @@ where driver: ED, snapshot_manager: SM, command_manager: CM, - qemu_builder: Option, + qemu_parameters: Option, phantom: PhantomData, } @@ -47,7 +49,7 @@ where driver: NopEmulatorDriver, snapshot_manager: NopSnapshotManager, command_manager: NopCommandManager, - qemu_builder: None, + qemu_parameters: None, phantom: PhantomData, } } @@ -67,7 +69,7 @@ where command_manager: StdCommandManager::default(), snapshot_manager: StdSnapshotManager::default(), driver: StdEmulatorDriver::builder().build(), - qemu_builder: None, + qemu_parameters: None, phantom: PhantomData, } } @@ -85,7 +87,7 @@ where command_manager: StdCommandManager::default(), snapshot_manager: FastSnapshotManager::default(), driver: StdEmulatorDriver::builder().build(), - qemu_builder: None, + qemu_parameters: None, phantom: PhantomData, } } @@ -99,14 +101,14 @@ where driver: ED, command_manager: CM, snapshot_manager: SM, - qemu_builder: Option, + qemu_parameters: Option, ) -> Self { Self { modules, command_manager, driver, snapshot_manager, - qemu_builder, + qemu_parameters, phantom: PhantomData, } } @@ -116,20 +118,13 @@ where CM: CommandManager, ET: EmulatorModuleTuple, { - let qemu_builder = self.qemu_builder.ok_or(QemuInitError::EmptyArgs)?; + let qemu_parameters = self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?; let mut emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; self.modules.pre_qemu_init_all(&mut emulator_hooks); - let qemu: Qemu = match qemu_builder { - QemuBuilder::Qemu(qemu) => qemu, - QemuBuilder::QemuConfig(qemu_config) => { - let res: Result = qemu_config.into(); - res? - } - QemuBuilder::QemuString(qemu_string) => Qemu::init(&qemu_string)?, - }; + let qemu = Qemu::init_with_params(&qemu_parameters)?; unsafe { Ok(Emulator::new_with_qemu( @@ -156,7 +151,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - Some(QemuBuilder::QemuConfig(qemu_config)), + Some(QemuParams::Config(qemu_config)), ) } @@ -167,18 +162,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - Some(QemuBuilder::QemuString(qemu_cli)), - ) - } - - #[must_use] - pub fn qemu(self, qemu: Qemu) -> EmulatorBuilder { - EmulatorBuilder::new( - self.modules, - self.driver, - self.command_manager, - self.snapshot_manager, - Some(QemuBuilder::Qemu(qemu)), + Some(QemuParams::Cli(qemu_cli)), ) } @@ -192,7 +176,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - self.qemu_builder, + self.qemu_parameters, ) } @@ -202,7 +186,7 @@ where driver, self.command_manager, self.snapshot_manager, - self.qemu_builder, + self.qemu_parameters, ) } @@ -215,7 +199,7 @@ where self.driver, command_manager, self.snapshot_manager, - self.qemu_builder, + self.qemu_parameters, ) } @@ -225,7 +209,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - self.qemu_builder, + self.qemu_parameters, ) } @@ -238,7 +222,7 @@ where self.driver, self.command_manager, snapshot_manager, - self.qemu_builder, + self.qemu_parameters, ) } } diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index b17b3e5913c..b53d4d541b9 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -2,20 +2,13 @@ use core::{ fmt, fmt::{Display, Formatter}, }; -use std::{ - path::{Path, PathBuf}, - sync::OnceLock, -}; +use std::path::{Path, PathBuf}; use getset::Getters; use libafl_derive; use strum_macros; use typed_builder::TypedBuilder; -use crate::{Qemu, QemuInitError}; - -pub(super) static QEMU_CONFIG: OnceLock = OnceLock::new(); - #[cfg(feature = "systemmode")] #[derive(Debug, strum_macros::Display, Clone)] #[strum(prefix = "-accel ", serialize_all = "lowercase")] @@ -304,14 +297,16 @@ impl> From for Program { } #[derive(Debug, Clone, libafl_derive::Display, TypedBuilder, Getters)] -#[builder(build_method(into = Result), builder_method(vis = "pub(crate)", +#[builder(builder_method( + vis = "pub(crate)", doc = "Since Qemu is a zero sized struct, this is not a completely standard builder pattern. \ The Qemu configuration is not stored in the Qemu struct after build() but in QEMU_CONFIG \ Therefore, to use the derived builder and avoid boilerplate a builder for QemuConfig is \ derived. \ The QemuConfig::builder is called in Qemu::builder() which is the only place where it should \ be called, in this way the one to one matching of Qemu and QemuConfig is enforced. Therefore \ - its visibility is pub(crate)"))] + its visibility is pub(crate)" +))] #[getset(get = "pub")] pub struct QemuConfig { #[cfg(feature = "systemmode")] @@ -350,40 +345,28 @@ pub struct QemuConfig { program: Program, } // Adding something here? Please leave Program as the last field -impl From for Result { - /// This method is necessary to make the API resemble a typical builder pattern, i.e. - /// `Qemu::builder().foo(bar).build()`, while still leveraging `TypedBuilder` for this - /// non-standard use case where `Qemu` doesn't store the configuration. - /// Internally, `TypedBuilder` is used to generate a builder for `QemuConfig`. - /// This `QemuConfig.into()` method is used by the derived `QemuConfigBuilder.build()` - /// to go from `QemuConfigBuilder` to `QemuConfig`, and finally to `Qemu` in one fn. - /// - /// # Errors - /// returns `QemuInitError` if the Qemu initialization fails, including cases where Qemu has - /// already been initialized. - fn from(config: QemuConfig) -> Self { - let args = config +impl From<&QemuConfig> for Vec { + /// Generate the QEMU-compatible initialization cli string from the QEMU config. + fn from(config: &QemuConfig) -> Self { + config .to_string() .split(' ') .map(ToString::to_string) - .collect::>(); - let qemu = Qemu::init(&args)?; - QEMU_CONFIG - .set(config) - .map_err(|_| unreachable!("BUG: QEMU_CONFIG was already set but Qemu was not init!"))?; - Ok(qemu) + .collect::>() } } #[cfg(test)] mod test { use super::*; + use crate::Qemu; #[test] #[cfg(feature = "usermode")] fn usermode() { let program = "/bin/pwd"; - let qemu = Qemu::builder().program("/bin/pwd").build().unwrap(); + let qemu_config = QemuConfig::builder().program("/bin/pwd").build(); + let qemu = Qemu::init_with_config(&qemu_config).unwrap(); let config = qemu.get_config().unwrap(); assert_eq!(config.to_string().trim(), program.trim()); } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index e742dfbda17..3204cb16741 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -14,6 +14,7 @@ use std::{ mem::MaybeUninit, ops::Range, pin::Pin, + sync::OnceLock, }; use libafl_bolts::os::unix_signals::Signal; @@ -28,10 +29,10 @@ use libafl_qemu_sys::{ use num_traits::Num; use strum::IntoEnumIterator; -use crate::{GuestAddrKind, GuestReg, Regs}; +use crate::{GuestAddrKind, GuestReg, QemuParams, Regs}; pub mod config; -use config::{QemuConfig, QemuConfigBuilder, QEMU_CONFIG}; +use config::QemuConfig; #[cfg(feature = "usermode")] mod usermode; @@ -49,6 +50,8 @@ pub use hooks::*; static mut QEMU_IS_INITIALIZED: bool = false; +pub(super) static QEMU_CONFIG: OnceLock = OnceLock::new(); + #[derive(Debug)] pub enum QemuError { Init(QemuInitError), @@ -574,10 +577,21 @@ impl From for HookData { #[allow(clippy::unused_self)] impl Qemu { - /// For more details about the parameters check - /// [the QEMU documentation](https://www.qemu.org/docs/master/about/). - pub fn builder() -> QemuConfigBuilder { - QemuConfig::builder() + pub fn init_with_params(params: &QemuParams) -> Result { + match params { + QemuParams::Config(config) => Self::init_with_config(config), + QemuParams::Cli(cli) => Self::init(cli.as_ref()), + } + } + + pub fn init_with_config(config: &QemuConfig) -> Result { + let qemu_args: Vec = config.into(); + + QEMU_CONFIG + .set(config.clone()) + .map_err(|_| unreachable!("BUG: QEMU_CONFIG was already set but Qemu was not init!"))?; + + Self::init(qemu_args.as_ref()) } #[allow(clippy::must_use_candidate, clippy::similar_names)] From cddc3dfc5b4ad7819e0dd20240d8e524c5c9001d Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:25:01 +0100 Subject: [PATCH 06/93] Fix intel_pt_command_executor target dir --- .../intel_pt_command_executor/Makefile.toml | 10 ++++------ .../binary_only/intel_pt_command_executor/src/main.rs | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml index 77e6c3e39aa..aca771cbca3 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml +++ b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml @@ -1,3 +1,6 @@ +[env] +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" + [env.development] PROFILE_DIR = "debug" @@ -14,12 +17,7 @@ mkdir -p ${TARGET_DIR} [tasks.build_target] dependencies = ["target_dir"] command = "rustc" -args = [ - "src/target_program.rs", - "--out-dir", - "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}", - "-O", -] +args = ["src/target_program.rs", "--out-dir", "${TARGET_DIR}", "-O"] [tasks.build_fuzzer] command = "cargo" diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index e8c977a775b..8d1523f1169 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -37,6 +37,8 @@ pub fn main() { env_logger::init(); let target_path = PathBuf::from(env::args().next().unwrap()) + .parent() + .unwrap() .parent() .unwrap() .join("target_program"); From 28655d8fa182f64d505824b9be390b4d1d16b374 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Mon, 25 Nov 2024 11:58:20 +0100 Subject: [PATCH 07/93] * QEMU error refactoring* * back to one QEMU init function * other small things --- .../qemu_baremetal/src/fuzzer_low_level.rs | 17 +- libafl_qemu/src/emu/builder.rs | 15 +- libafl_qemu/src/executor.rs | 3 +- libafl_qemu/src/qemu/config.rs | 12 - libafl_qemu/src/qemu/error.rs | 141 +++++++++++ libafl_qemu/src/qemu/mod.rs | 226 +++++------------- libafl_qemu/src/qemu/systemmode.rs | 4 +- 7 files changed, 224 insertions(+), 194 deletions(-) create mode 100644 libafl_qemu/src/qemu/error.rs diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs index fa2d6bcc31b..c8b026792c7 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs @@ -29,9 +29,9 @@ use libafl_bolts::{ AsSlice, }; use libafl_qemu::{ - config, elf::EasyElf, executor::QemuExecutor, modules::edges::StdEdgeCoverageModuleBuilder, - Emulator, GuestPhysAddr, Qemu, QemuExitError, QemuExitReason, QemuRWError, QemuShutdownCause, - Regs, + config, config::QemuConfig, elf::EasyElf, executor::QemuExecutor, + modules::edges::StdEdgeCoverageModuleBuilder, Emulator, GuestPhysAddr, QemuExitError, + QemuExitReason, QemuRWError, QemuShutdownCause, Regs, }; use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; @@ -94,7 +94,7 @@ pub fn fuzz() { }; // Initialize QEMU - let qemu = Qemu::builder() + let qemu_config = QemuConfig::builder() .machine("mps2-an385") .monitor(config::Monitor::Null) .kernel(format!("{target_dir}/example.elf")) @@ -107,18 +107,21 @@ pub fn fuzz() { .file(format!("{target_dir}/dummy.qcow2")) .build()]) .start_cpu(false) - .build() - .expect("Failed to initialized QEMU"); + .build(); + + // .expect("Failed to initialized QEMU"); let emulator_modules = tuple_list!(StdEdgeCoverageModuleBuilder::default() .map_observer(edges_observer.as_mut()) .build()?); let emulator = Emulator::empty() - .qemu(qemu) + .qemu_config(qemu_config) .modules(emulator_modules) .build()?; + let qemu = emulator.qemu(); + qemu.set_breakpoint(main_addr); unsafe { diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index a62b1fcf03d..423e2ae128e 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::{Debug, Formatter}, - marker::PhantomData, -}; +use std::{fmt::Debug, marker::PhantomData}; use libafl::{ inputs::{HasTargetBytes, UsesInput}, @@ -16,15 +13,9 @@ use crate::{ config::QemuConfig, modules::{EmulatorModule, EmulatorModuleTuple}, Emulator, EmulatorHooks, NopEmulatorDriver, NopSnapshotManager, Qemu, QemuHooks, QemuInitError, - StdEmulatorDriver, StdSnapshotManager, + QemuParams, StdEmulatorDriver, StdSnapshotManager, }; -#[derive(Clone, Debug)] -pub enum QemuParams { - Config(QemuConfig), - Cli(Vec), -} - #[derive(Clone, Debug)] pub struct EmulatorBuilder where @@ -124,7 +115,7 @@ where self.modules.pre_qemu_init_all(&mut emulator_hooks); - let qemu = Qemu::init_with_params(&qemu_parameters)?; + let qemu = Qemu::init(qemu_parameters)?; unsafe { Ok(Emulator::new_with_qemu( diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 6a56c06c4b3..4b2ddacbca3 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -9,6 +9,8 @@ use std::ptr; #[cfg(feature = "systemmode")] use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(feature = "usermode")] +use libafl::inputs::UsesInput; use libafl::{ corpus::Corpus, events::{EventFirer, EventRestarter}, @@ -20,7 +22,6 @@ use libafl::{ }, feedbacks::Feedback, fuzzer::HasObjective, - inputs::UsesInput, observers::ObserversTuple, state::{HasCorpus, HasExecutions, HasSolutions, State, UsesState}, Error, ExecutionProcessor, HasScheduler, diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index b53d4d541b9..389b4c2de70 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -298,7 +298,6 @@ impl> From for Program { #[derive(Debug, Clone, libafl_derive::Display, TypedBuilder, Getters)] #[builder(builder_method( - vis = "pub(crate)", doc = "Since Qemu is a zero sized struct, this is not a completely standard builder pattern. \ The Qemu configuration is not stored in the Qemu struct after build() but in QEMU_CONFIG \ Therefore, to use the derived builder and avoid boilerplate a builder for QemuConfig is \ @@ -345,17 +344,6 @@ pub struct QemuConfig { program: Program, } // Adding something here? Please leave Program as the last field -impl From<&QemuConfig> for Vec { - /// Generate the QEMU-compatible initialization cli string from the QEMU config. - fn from(config: &QemuConfig) -> Self { - config - .to_string() - .split(' ') - .map(ToString::to_string) - .collect::>() - } -} - #[cfg(test)] mod test { use super::*; diff --git a/libafl_qemu/src/qemu/error.rs b/libafl_qemu/src/qemu/error.rs new file mode 100644 index 00000000000..d3931c3182c --- /dev/null +++ b/libafl_qemu/src/qemu/error.rs @@ -0,0 +1,141 @@ +use core::fmt; +use std::fmt::Display; + +use libafl_qemu_sys::{CPUStatePtr, GuestAddr}; + +use crate::CallingConvention; + +#[derive(Debug)] +pub enum QemuError { + Init(QemuInitError), + Exit(QemuExitError), + RW(QemuRWError), +} + +#[derive(Debug)] +pub enum QemuInitError { + MultipleInstances, + EmptyArgs, + TooManyArgs(usize), +} + +#[derive(Debug, Clone)] +pub enum QemuExitError { + UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen. + UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example. +} + +#[derive(Debug, Clone)] +pub enum QemuRWErrorKind { + Read, + Write, +} + +#[derive(Debug, Clone)] +pub enum QemuRWErrorCause { + WrongCallingConvention(CallingConvention, CallingConvention), // expected, given + WrongArgument(i32), + CurrentCpuNotFound, + Reg(i32), + WrongMemoryLocation(GuestAddr, usize), // addr, size +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct QemuRWError { + kind: QemuRWErrorKind, + cause: QemuRWErrorCause, + cpu: Option, // Only makes sense when cause != CurrentCpuNotFound +} + +impl std::error::Error for QemuInitError {} + +impl Display for QemuInitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + QemuInitError::MultipleInstances => { + write!(f, "Only one instance of the QEMU Emulator is permitted") + } + QemuInitError::EmptyArgs => { + write!(f, "QEMU emulator args cannot be empty") + } + QemuInitError::TooManyArgs(n) => { + write!( + f, + "Too many arguments passed to QEMU emulator ({n} > i32::MAX)" + ) + } + } + } +} + +impl From for libafl::Error { + fn from(err: QemuInitError) -> Self { + libafl::Error::unknown(format!("{err}")) + } +} + +impl QemuRWError { + #[must_use] + pub fn new(kind: QemuRWErrorKind, cause: QemuRWErrorCause, cpu: Option) -> Self { + Self { kind, cause, cpu } + } + + pub fn wrong_reg(kind: QemuRWErrorKind, reg: R, cpu: Option) -> Self + where + R: Into + Clone, + { + Self::new(kind, QemuRWErrorCause::Reg(reg.into()), cpu) + } + + pub fn wrong_mem_location( + kind: QemuRWErrorKind, + cpu: CPUStatePtr, + addr: GuestAddr, + size: usize, + ) -> Self { + Self::new( + kind, + QemuRWErrorCause::WrongMemoryLocation(addr, size), + Some(cpu), + ) + } + + #[must_use] + pub fn current_cpu_not_found(kind: QemuRWErrorKind) -> Self { + Self::new(kind, QemuRWErrorCause::CurrentCpuNotFound, None) + } + + #[must_use] + pub fn new_argument_error(kind: QemuRWErrorKind, reg_id: i32) -> Self { + Self::new(kind, QemuRWErrorCause::WrongArgument(reg_id), None) + } + + pub fn check_conv( + kind: QemuRWErrorKind, + expected_conv: CallingConvention, + given_conv: CallingConvention, + ) -> Result<(), Self> { + if expected_conv != given_conv { + return Err(Self::new( + kind, + QemuRWErrorCause::WrongCallingConvention(expected_conv, given_conv), + None, + )); + } + + Ok(()) + } +} + +impl From for libafl::Error { + fn from(qemu_error: QemuError) -> Self { + libafl::Error::runtime(qemu_error) + } +} + +impl From for String { + fn from(qemu_error: QemuError) -> Self { + format!("LibAFL QEMU Error: {qemu_error:?}") + } +} diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 3204cb16741..2964d0d737f 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -29,11 +29,16 @@ use libafl_qemu_sys::{ use num_traits::Num; use strum::IntoEnumIterator; -use crate::{GuestAddrKind, GuestReg, QemuParams, Regs}; +use crate::{GuestAddrKind, GuestReg, Regs}; pub mod config; use config::QemuConfig; +pub mod error; +pub use error::{ + QemuError, QemuExitError, QemuInitError, QemuRWError, QemuRWErrorCause, QemuRWErrorKind, +}; + #[cfg(feature = "usermode")] mod usermode; #[cfg(feature = "usermode")] @@ -52,32 +57,6 @@ static mut QEMU_IS_INITIALIZED: bool = false; pub(super) static QEMU_CONFIG: OnceLock = OnceLock::new(); -#[derive(Debug)] -pub enum QemuError { - Init(QemuInitError), - Exit(QemuExitError), - RW(QemuRWError), -} - -impl From for libafl::Error { - fn from(qemu_error: QemuError) -> Self { - libafl::Error::runtime(qemu_error) - } -} - -impl From for String { - fn from(qemu_error: QemuError) -> Self { - format!("LibAFL QEMU Error: {qemu_error:?}") - } -} - -#[derive(Debug)] -pub enum QemuInitError { - MultipleInstances, - EmptyArgs, - TooManyArgs(usize), -} - #[derive(Debug, Clone)] pub enum QemuExitReason { /// QEMU ended for some internal reason @@ -93,81 +72,6 @@ pub enum QemuExitReason { Timeout, } -#[derive(Debug, Clone)] -pub enum QemuExitError { - UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen. - UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example. -} - -#[derive(Debug, Clone)] -pub enum QemuRWErrorKind { - Read, - Write, -} - -#[derive(Debug, Clone)] -pub enum QemuRWErrorCause { - WrongCallingConvention(CallingConvention, CallingConvention), // expected, given - WrongArgument(i32), - CurrentCpuNotFound, - Reg(i32), - WrongMemoryLocation(GuestAddr, usize), // addr, size -} - -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct QemuRWError { - kind: QemuRWErrorKind, - cause: QemuRWErrorCause, - cpu: Option, // Only makes sense when cause != CurrentCpuNotFound -} - -impl QemuRWError { - #[must_use] - pub fn new(kind: QemuRWErrorKind, cause: QemuRWErrorCause, cpu: Option) -> Self { - Self { kind, cause, cpu } - } - - pub fn wrong_mem_location( - kind: QemuRWErrorKind, - cpu: CPUStatePtr, - addr: GuestAddr, - size: usize, - ) -> Self { - Self::new( - kind, - QemuRWErrorCause::WrongMemoryLocation(addr, size), - Some(cpu), - ) - } - - #[must_use] - pub fn current_cpu_not_found(kind: QemuRWErrorKind) -> Self { - Self::new(kind, QemuRWErrorCause::CurrentCpuNotFound, None) - } - - #[must_use] - pub fn new_argument_error(kind: QemuRWErrorKind, reg_id: i32) -> Self { - Self::new(kind, QemuRWErrorCause::WrongArgument(reg_id), None) - } - - pub fn check_conv( - kind: QemuRWErrorKind, - expected_conv: CallingConvention, - given_conv: CallingConvention, - ) -> Result<(), Self> { - if expected_conv != given_conv { - return Err(Self::new( - kind, - QemuRWErrorCause::WrongCallingConvention(expected_conv, given_conv), - None, - )); - } - - Ok(()) - } -} - /// The thin wrapper around QEMU. /// It is considered unsafe to use it directly. /// Prefer using `Emulator` instead in case of doubt. @@ -176,6 +80,12 @@ pub struct Qemu { _private: (), } +#[derive(Clone, Debug)] +pub enum QemuParams { + Config(QemuConfig), + Cli(Vec), +} + #[derive(Debug, Clone)] pub struct QemuMemoryChunk { addr: GuestAddrKind, @@ -234,33 +144,6 @@ pub trait HookId { #[derive(Debug)] pub struct HookData(u64); -impl std::error::Error for QemuInitError {} - -impl Display for QemuInitError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - QemuInitError::MultipleInstances => { - write!(f, "Only one instance of the QEMU Emulator is permitted") - } - QemuInitError::EmptyArgs => { - write!(f, "QEMU emulator args cannot be empty") - } - QemuInitError::TooManyArgs(n) => { - write!( - f, - "Too many arguments passed to QEMU emulator ({n} > i32::MAX)" - ) - } - } - } -} - -impl From for libafl::Error { - fn from(err: QemuInitError) -> Self { - libafl::Error::unknown(format!("{err}")) - } -} - impl Display for QemuExitReason { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { @@ -272,6 +155,30 @@ impl Display for QemuExitReason { } } +impl From for QemuParams { + fn from(config: QemuConfig) -> Self { + QemuParams::Config(config) + } +} + +impl From<&[T]> for QemuParams +where + T: AsRef, +{ + fn from(cli: &[T]) -> Self { + QemuParams::Cli(cli.into_iter().map(|x| x.as_ref().into()).collect()) + } +} + +impl From> for QemuParams +where + T: AsRef, +{ + fn from(cli: Vec) -> Self { + cli.as_slice().into() + } +} + impl MemAccessInfo { #[must_use] pub fn memop(&self) -> libafl_qemu_sys::MemOp { @@ -372,11 +279,11 @@ impl CPU { let mut val = MaybeUninit::uninit(); let success = libafl_qemu_read_reg(self.ptr, reg_id, val.as_mut_ptr() as *mut u8); if success == 0 { - Err(QemuRWError { - kind: QemuRWErrorKind::Write, - cause: QemuRWErrorCause::Reg(reg.into()), - cpu: Some(self.ptr), - }) + Err(QemuRWError::wrong_reg( + QemuRWErrorKind::Write, + reg, + Some(self.ptr), + )) } else { #[cfg(feature = "be")] return Ok(GuestReg::from_be(val.assume_init()).into()); @@ -401,11 +308,11 @@ impl CPU { let success = unsafe { libafl_qemu_write_reg(self.ptr, reg_id, &raw const val as *mut u8) }; if success == 0 { - Err(QemuRWError { - kind: QemuRWErrorKind::Write, - cause: QemuRWErrorCause::Reg(reg.into()), - cpu: Some(self.ptr), - }) + Err(QemuRWError::wrong_reg( + QemuRWErrorKind::Write, + reg, + Some(self.ptr), + )) } else { Ok(()) } @@ -577,25 +484,27 @@ impl From for HookData { #[allow(clippy::unused_self)] impl Qemu { - pub fn init_with_params(params: &QemuParams) -> Result { - match params { - QemuParams::Config(config) => Self::init_with_config(config), - QemuParams::Cli(cli) => Self::init(cli.as_ref()), - } - } + #[allow(clippy::must_use_candidate, clippy::similar_names)] + pub fn init(params: T) -> Result + where + T: Into, + { + let params: QemuParams = params.into(); - pub fn init_with_config(config: &QemuConfig) -> Result { - let qemu_args: Vec = config.into(); + let args: Vec = match params { + QemuParams::Config(cfg) => { + let qemu_args: Vec = + cfg.to_string().split(" ").map(|x| x.to_string()).collect(); - QEMU_CONFIG - .set(config.clone()) - .map_err(|_| unreachable!("BUG: QEMU_CONFIG was already set but Qemu was not init!"))?; + QEMU_CONFIG.set(cfg.clone()).map_err(|_| { + unreachable!("QEMU_CONFIG was already set but Qemu was not init!") + })?; - Self::init(qemu_args.as_ref()) - } + qemu_args + } + QemuParams::Cli(cli) => cli, + }; - #[allow(clippy::must_use_candidate, clippy::similar_names)] - pub fn init(args: &[String]) -> Result { if args.is_empty() { return Err(QemuInitError::EmptyArgs); } @@ -609,6 +518,7 @@ impl Qemu { if QEMU_IS_INITIALIZED { return Err(QemuInitError::MultipleInstances); } + QEMU_IS_INITIALIZED = true; } @@ -617,7 +527,7 @@ impl Qemu { let args: Vec = args .iter() - .map(|x| CString::new(x.clone()).unwrap()) + .map(|x| CString::new(AsRef::::as_ref(x)).unwrap()) .collect(); let mut argv: Vec<*const u8> = args.iter().map(|x| x.as_ptr() as *const u8).collect(); argv.push(ptr::null()); // argv is always null terminated. @@ -921,11 +831,7 @@ impl Qemu { impl ArchExtras for Qemu { fn read_return_address(&self) -> Result { self.current_cpu() - .ok_or(QemuRWError { - kind: QemuRWErrorKind::Read, - cause: QemuRWErrorCause::CurrentCpuNotFound, - cpu: None, - })? + .ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))? .read_return_address() } diff --git a/libafl_qemu/src/qemu/systemmode.rs b/libafl_qemu/src/qemu/systemmode.rs index c481ecd16fd..25e956cb438 100644 --- a/libafl_qemu/src/qemu/systemmode.rs +++ b/libafl_qemu/src/qemu/systemmode.rs @@ -16,8 +16,8 @@ use libc::EXIT_SUCCESS; use num_traits::Zero; use crate::{ - FastSnapshotPtr, GuestAddrKind, MemAccessInfo, Qemu, QemuMemoryChunk, QemuRWError, - QemuRWErrorCause, QemuRWErrorKind, QemuSnapshotCheckResult, CPU, + FastSnapshotPtr, GuestAddrKind, MemAccessInfo, Qemu, QemuMemoryChunk, QemuSnapshotCheckResult, + CPU, }; pub(super) extern "C" fn qemu_cleanup_atexit() { From b85ab067c581b9e3b839d53dff9ab14da74680d1 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Mon, 25 Nov 2024 12:01:16 +0100 Subject: [PATCH 08/93] update test --- libafl_qemu/src/qemu/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 389b4c2de70..de43fbc6fd0 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -354,7 +354,7 @@ mod test { fn usermode() { let program = "/bin/pwd"; let qemu_config = QemuConfig::builder().program("/bin/pwd").build(); - let qemu = Qemu::init_with_config(&qemu_config).unwrap(); + let qemu = Qemu::init(&qemu_config).unwrap(); let config = qemu.get_config().unwrap(); assert_eq!(config.to_string().trim(), program.trim()); } From 8afc5fcd9a20e21b97ea0e125778eedf0435dfd4 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:13:17 +0100 Subject: [PATCH 09/93] Bump libipt --- Cargo.toml | 2 +- libafl_qemu/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef625ea2605..31f9b3f4d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ cmake = "0.1.51" document-features = "0.2.10" hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible libc = "0.2.159" # For (*nix) libc -libipt = "0.2.0" +libipt = "0.2.1-beta.1" log = "0.4.22" meminterval = "0.4.1" mimalloc = { version = "0.1.43", default-features = false } diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index eb61396de35..19c91b37ff6 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -80,7 +80,7 @@ slirp = [ "libafl_qemu_sys/slirp", ] # build qemu with host libslirp (for user networking) -intel_pt = ["systemmode", "x86_64", "libafl/intel_pt", "dep:libafl_intelpt"] +intel_pt = ["systemmode", "x86_64", "dep:libafl_intelpt"] # Requires the binary's build.rs to call `build_libafl_qemu` shared = ["libafl_qemu_sys/shared"] From 01fe20563f899e02c2cc5863090ba966c1ac4e08 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:08:23 +0100 Subject: [PATCH 10/93] waitpid_filtered to ignore SIGWINCH --- libafl/src/executors/command.rs | 75 ++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index 7e98568d109..e0111dc915d 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -32,7 +32,19 @@ use libafl_bolts::{ #[cfg(all(feature = "std", target_os = "linux"))] use libc::STDIN_FILENO; #[cfg(all(feature = "std", target_os = "linux"))] -use nix::unistd::Pid; +use nix::{ + errno::Errno, + sys::{ + ptrace, + signal::Signal, + wait::WaitStatus, + wait::{ + waitpid, WaitPidFlag, + WaitStatus::{Exited, PtraceEvent, Signaled, Stopped}, + }, + }, + unistd::Pid, +}; #[cfg(all(feature = "std", target_os = "linux"))] use typed_builder::TypedBuilder; @@ -210,8 +222,6 @@ where match unsafe { fork() } { Ok(ForkResult::Parent { child }) => Ok(child), Ok(ForkResult::Child) => { - ptrace::traceme().unwrap(); - if let Some(c) = self.cpu { c.set_affinity_forced().unwrap(); } @@ -247,6 +257,7 @@ where } } + ptrace::traceme().unwrap(); // After this STOP, the process is traced with PTrace (no hooks yet) raise(Signal::SIGSTOP).unwrap(); @@ -423,13 +434,13 @@ where T: CommandConfigurator<::Input>, { #[inline] - fn set_timeout(&mut self, timeout: Duration) { - *self.configurer.exec_timeout_mut() = timeout; + fn timeout(&self) -> Duration { + self.configurer.exec_timeout() } #[inline] - fn timeout(&self) -> Duration { - self.configurer.exec_timeout() + fn set_timeout(&mut self, timeout: Duration) { + *self.configurer.exec_timeout_mut() = timeout; } } @@ -455,32 +466,30 @@ where _mgr: &mut EM, input: &Self::Input, ) -> Result { - use nix::sys::{ - ptrace, - signal::Signal, - wait::{ - waitpid, WaitPidFlag, - WaitStatus::{Exited, PtraceEvent, Signaled, Stopped}, - }, - }; - *state.executions_mut() += 1; let child = self.configurer.spawn_child(input)?; - let wait_status = waitpid(child, Some(WaitPidFlag::WUNTRACED))?; + let wait_status = waitpid_filtered(child, Some(WaitPidFlag::WUNTRACED))?; if !matches!(wait_status, Stopped(c, Signal::SIGSTOP) if c == child) { - return Err(Error::unknown("Unexpected state of child process")); + return Err(Error::unknown(format!( + "Unexpected state of child process {:?} (while waiting for SIGSTOP)", + wait_status + ))); } - ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACEEXEC)?; + let options = ptrace::Options::PTRACE_O_TRACEEXEC | ptrace::Options::PTRACE_O_EXITKILL; + ptrace::setoptions(child, options)?; ptrace::cont(child, None)?; - let wait_status = waitpid(child, None)?; + let wait_status = waitpid_filtered(child, None)?; if !matches!(wait_status, PtraceEvent(c, Signal::SIGTRAP, e) if c == child && e == (ptrace::Event::PTRACE_EVENT_EXEC as i32) ) { - return Err(Error::unknown("Unexpected state of child process")); + return Err(Error::unknown(format!( + "Unexpected state of child process {:?} (while waiting for SIGTRAP PTRACE_EVENT_EXEC)", + wait_status + ))); } self.observers.pre_exec_child_all(state, input)?; @@ -489,6 +498,8 @@ where } self.hooks.pre_exec_all(state, input); + // todo: it might be better to keep the target ptraced in case the target handles sigalarm, + // breaking the libafl timeout ptrace::detach(child, None)?; let res = match waitpid(child, None)? { Exited(pid, 0) if pid == child => ExitKind::Ok, @@ -496,9 +507,9 @@ where Signaled(pid, Signal::SIGALRM, _has_coredump) if pid == child => ExitKind::Timeout, Signaled(pid, Signal::SIGABRT, _has_coredump) if pid == child => ExitKind::Crash, Signaled(pid, Signal::SIGKILL, _has_coredump) if pid == child => ExitKind::Oom, - Stopped(pid, Signal::SIGALRM) if pid == child => ExitKind::Timeout, - Stopped(pid, Signal::SIGABRT) if pid == child => ExitKind::Crash, - Stopped(pid, Signal::SIGKILL) if pid == child => ExitKind::Oom, + // Stopped(pid, Signal::SIGALRM) if pid == child => ExitKind::Timeout, + // Stopped(pid, Signal::SIGABRT) if pid == child => ExitKind::Crash, + // Stopped(pid, Signal::SIGKILL) if pid == child => ExitKind::Oom, s => { // TODO other cases? return Err(Error::unsupported( @@ -863,6 +874,22 @@ pub trait CommandConfigurator: Sized { } } +/// waitpid wrapper that ignores some signals sent by the ptraced child +#[cfg(all(feature = "std", target_os = "linux"))] +fn waitpid_filtered(pid: Pid, options: Option) -> Result { + loop { + let wait_status = waitpid(pid, options); + let sig = match &wait_status { + // IGNORED + Ok(Stopped(c, Signal::SIGWINCH)) if *c == pid => Signal::SIGWINCH, + // RETURNED + Ok(ws) => break Ok(*ws), + Err(e) => break Err(*e), + }; + ptrace::cont(pid, sig)?; + } +} + #[cfg(test)] mod tests { use crate::{ From eb3c63ea4c75aada94518dc04779d5b9359128d1 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:29:55 +0100 Subject: [PATCH 11/93] Fix warnings unused manifest key: *.version --- libafl_concolic/symcc_runtime/Cargo.toml | 2 +- libafl_intelpt/Cargo.toml | 2 ++ libafl_intelpt/src/lib.rs | 4 ++++ libafl_qemu/Cargo.toml | 4 ++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libafl_concolic/symcc_runtime/Cargo.toml b/libafl_concolic/symcc_runtime/Cargo.toml index c29e0362542..a0f91e025e0 100644 --- a/libafl_concolic/symcc_runtime/Cargo.toml +++ b/libafl_concolic/symcc_runtime/Cargo.toml @@ -44,7 +44,7 @@ cmake = { workspace = true } bindgen = { workspace = true } regex = { workspace = true } which = { workspace = true } -symcc_libafl = { workspace = true, default-features = true, version = "0.14.0" } +symcc_libafl = { workspace = true, default-features = true } [lints] workspace = true diff --git a/libafl_intelpt/Cargo.toml b/libafl_intelpt/Cargo.toml index 200882507ff..c50cf293fde 100644 --- a/libafl_intelpt/Cargo.toml +++ b/libafl_intelpt/Cargo.toml @@ -15,6 +15,8 @@ default = ["std", "libipt"] std = ["libafl_bolts/std"] libipt = ["std", "dep:libipt"] +# Export raw Intel PT traces on decode, useful for debug, disabled by default for best performance +export_raw = [] [dev-dependencies] static_assertions = { workspace = true } diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index c6a0a140c9c..081d76df0be 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -129,6 +129,8 @@ pub struct IntelPT { aux_tail: *mut u64, previous_decode_head: u64, ip_filters: Vec>, + #[cfg(feature = "export_raw")] + last_decode_trace: Vec, } #[cfg(target_os = "linux")] @@ -544,6 +546,8 @@ impl IntelPTBuilder { aux_tail, previous_decode_head: 0, ip_filters, + #[cfg(feature = "export_raw")] + last_decode_trace: Vec::new(), }) } diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index b699533e0e9..97eb7ac09da 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -90,7 +90,7 @@ clippy = ["libafl_qemu_sys/clippy"] [dependencies] libafl = { workspace = true, features = ["std", "derive", "regex"] } libafl_bolts = { workspace = true, features = ["std", "derive"] } -libafl_targets = { workspace = true, default-features = true, version = "0.14.0" } +libafl_targets = { workspace = true, default-features = true } libafl_qemu_sys = { workspace = true } libafl_derive = { workspace = true, default-features = true } @@ -131,7 +131,7 @@ getset = "0.1.3" document-features = { workspace = true, optional = true } [build-dependencies] -libafl_qemu_build = { workspace = true, default-features = true, version = "0.14.0" } +libafl_qemu_build = { workspace = true, default-features = true } pyo3-build-config = { version = "0.23.1", optional = true } rustversion = { workspace = true } bindgen = { workspace = true } From c8b28ba04ab18e204366f823810bc07f1351c5ad Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:55:51 +0100 Subject: [PATCH 12/93] Add export_raw feature to libafl_intelpt --- libafl_intelpt/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 081d76df0be..8d304aba7c7 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -342,6 +342,10 @@ impl IntelPT { }; } + if cfg!(feature = "export_raw") { + self.set_last_decode_trace(data.as_ref().to_vec()); + } + // Advance the trace pointer up to the latest sync point, otherwise next execution's trace // might not contain a PSB packet. decoder.sync_backward().map_err(error_from_pt_error)?; @@ -418,6 +422,20 @@ impl IntelPT { } Ok(()) } + + #[cfg(not(feature = "export_raw"))] + #[allow(clippy::unused_self)] + fn set_last_decode_trace(&mut self, _trace: Vec) {} + #[cfg(feature = "export_raw")] + fn set_last_decode_trace(&mut self, trace: Vec) { + self.last_decode_trace = trace; + } + + /// Get the raw trace used in the last decoding + #[cfg(feature = "export_raw")] + pub fn last_decode_trace(&self) -> Vec { + self.last_decode_trace.clone() + } } #[cfg(target_os = "linux")] From 42b8cfee46143cf7c709305abc6eddda47af1083 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:00:26 +0100 Subject: [PATCH 13/93] derive Debug for IntelPTHook --- libafl/src/executors/hooks/intel_pt.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index f4acce679e4..005d6ff7aa2 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -30,7 +30,7 @@ pub struct Section { } /// Hook to enable Intel Processor Trace (PT) tracing -#[derive(TypedBuilder)] +#[derive(Debug, TypedBuilder)] pub struct IntelPTHook { #[builder(default = IntelPT::builder().build().unwrap())] intel_pt: IntelPT, @@ -40,17 +40,6 @@ pub struct IntelPTHook { map_len: usize, } -//fixme: just derive(Debug) once https://github.com/sum-catnip/libipt-rs/pull/4 will be on crates.io -impl Debug for IntelPTHook { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - f.debug_struct("IntelPTHook") - .field("intel_pt", &self.intel_pt) - .field("map_ptr", &self.map_ptr) - .field("map_len", &self.map_len) - .finish() - } -} - impl ExecutorHook for IntelPTHook where S: UsesInput + Serialize, From a029fd172e0a1d1b316f84222f254552a9c0de99 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:39:52 +0100 Subject: [PATCH 14/93] Clippy --- libafl/src/executors/command.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index e0111dc915d..ba33a754058 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -473,8 +473,7 @@ where let wait_status = waitpid_filtered(child, Some(WaitPidFlag::WUNTRACED))?; if !matches!(wait_status, Stopped(c, Signal::SIGSTOP) if c == child) { return Err(Error::unknown(format!( - "Unexpected state of child process {:?} (while waiting for SIGSTOP)", - wait_status + "Unexpected state of child process {wait_status:?} (while waiting for SIGSTOP)" ))); } @@ -487,8 +486,7 @@ where if c == child && e == (ptrace::Event::PTRACE_EVENT_EXEC as i32) ) { return Err(Error::unknown(format!( - "Unexpected state of child process {:?} (while waiting for SIGTRAP PTRACE_EVENT_EXEC)", - wait_status + "Unexpected state of child process {wait_status:?} (while waiting for SIGTRAP PTRACE_EVENT_EXEC)" ))); } From 47d274139e6ea9f4357c381dba7454e40f9f7a34 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Tue, 26 Nov 2024 16:44:36 +0100 Subject: [PATCH 15/93] Light refactor of EmulatorModules * qemu is now a parameter to EmulatorModule callbacks and most function hooks. * EmulatorModules is initialized before QEMU is initialized. --- libafl_qemu/src/command/mod.rs | 4 +- libafl_qemu/src/emu/builder.rs | 18 +- libafl_qemu/src/emu/drivers.rs | 12 +- libafl_qemu/src/emu/hooks.rs | 71 +++--- libafl_qemu/src/emu/mod.rs | 22 +- libafl_qemu/src/modules/calls.rs | 34 +-- libafl_qemu/src/modules/cmplog.rs | 32 ++- libafl_qemu/src/modules/drcov.rs | 25 ++- libafl_qemu/src/modules/edges/helpers.rs | 20 +- libafl_qemu/src/modules/edges/mod.rs | 9 +- libafl_qemu/src/modules/mod.rs | 80 ++++--- libafl_qemu/src/qemu/hooks.rs | 271 ++++++++++++++++------- 12 files changed, 380 insertions(+), 218 deletions(-) diff --git a/libafl_qemu/src/command/mod.rs b/libafl_qemu/src/command/mod.rs index ae87cfcc3f7..87402a77948 100644 --- a/libafl_qemu/src/command/mod.rs +++ b/libafl_qemu/src/command/mod.rs @@ -452,8 +452,8 @@ where // Unleash hooks if locked if emu.driver_mut().unlock_hooks() { // Prepare hooks - emu.modules_mut().first_exec_all(state); - emu.modules_mut().pre_exec_all(state, input); + emu.modules_mut().first_exec_all(qemu, state); + emu.modules_mut().pre_exec_all(qemu, state, input); } // Auto page filtering if option is enabled diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index 423e2ae128e..e38be3cdfb9 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -12,8 +12,8 @@ use crate::{ command::{CommandManager, NopCommandManager, StdCommandManager}, config::QemuConfig, modules::{EmulatorModule, EmulatorModuleTuple}, - Emulator, EmulatorHooks, NopEmulatorDriver, NopSnapshotManager, Qemu, QemuHooks, QemuInitError, - QemuParams, StdEmulatorDriver, StdSnapshotManager, + Emulator, EmulatorHooks, EmulatorModules, NopEmulatorDriver, NopSnapshotManager, Qemu, + QemuHooks, QemuInitError, QemuParams, StdEmulatorDriver, StdSnapshotManager, }; #[derive(Clone, Debug)] @@ -111,17 +111,23 @@ where { let qemu_parameters = self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?; - let mut emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; + let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; - self.modules.pre_qemu_init_all(&mut emulator_hooks); + let mut emulator_modules = EmulatorModules::new(emulator_hooks, self.modules); + + // TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called + unsafe { + emulator_modules + .modules_mut() + .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked()); + } let qemu = Qemu::init(qemu_parameters)?; unsafe { Ok(Emulator::new_with_qemu( qemu, - emulator_hooks, - self.modules, + emulator_modules, self.driver, self.snapshot_manager, self.command_manager, diff --git a/libafl_qemu/src/emu/drivers.rs b/libafl_qemu/src/emu/drivers.rs index 0fe0c6f161b..18d380e9a70 100644 --- a/libafl_qemu/src/emu/drivers.rs +++ b/libafl_qemu/src/emu/drivers.rs @@ -54,7 +54,7 @@ where /// Just before calling user's harness for the first time. /// Called only once fn first_harness_exec(emulator: &mut Emulator, state: &mut S) { - emulator.modules.first_exec_all(state); + emulator.modules.first_exec_all(emulator.qemu, state); } /// Just before calling user's harness @@ -63,7 +63,7 @@ where state: &mut S, input: &S::Input, ) { - emulator.modules.pre_exec_all(state, input); + emulator.modules.pre_exec_all(emulator.qemu, state, input); } /// Just after returning from user's harness @@ -78,7 +78,7 @@ where { emulator .modules - .post_exec_all(state, input, observers, exit_kind); + .post_exec_all(emulator.qemu, state, input, observers, exit_kind); } /// Just before entering QEMU @@ -169,7 +169,7 @@ where { fn first_harness_exec(emulator: &mut Emulator, state: &mut S) { if !emulator.driver.hooks_locked { - emulator.modules.first_exec_all(state); + emulator.modules.first_exec_all(emulator.qemu, state); } } @@ -179,7 +179,7 @@ where input: &S::Input, ) { if !emulator.driver.hooks_locked { - emulator.modules.pre_exec_all(state, input); + emulator.modules.pre_exec_all(emulator.qemu, state, input); } let input_location = { emulator.driver.input_location.get().cloned() }; @@ -206,7 +206,7 @@ where if !emulator.driver.hooks_locked { emulator .modules - .post_exec_all(state, input, observers, exit_kind); + .post_exec_all(emulator.qemu, state, input, observers, exit_kind); } } diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index bb07522b601..61e3759ca94 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -3,7 +3,7 @@ use std::{fmt::Debug, marker::PhantomData, mem::transmute, pin::Pin, ptr}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple}; -use libafl_qemu_sys::{CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, GuestUsize, TCGTemp}; +use libafl_qemu_sys::{CPUStatePtr, FatPtr, GuestAddr, GuestUsize, TCGTemp}; #[cfg(feature = "usermode")] use crate::qemu::{ @@ -37,9 +37,15 @@ use crate::{ ReadExecNHook, ReadGenHook, ReadHookId, TcgHookState, WriteExecHook, WriteExecNHook, WriteGenHook, WriteHookId, }, - CpuPostRunHook, CpuPreRunHook, CpuRunHookId, HookState, MemAccessInfo, Qemu, + CpuPostRunHook, CpuPreRunHook, CpuRunHookId, HookState, MemAccessInfo, NewThreadHookFn, Qemu, }; +/// Get a C-compatible function pointer from the input hook. +/// If the hook was already a c function, nothing is done. +/// +/// h: input hook +/// replacement: C-compatible function to call when C side should call the hook +/// fntype: type used to cast h into replacement macro_rules! get_raw_hook { ($h:expr, $replacement:expr, $fntype:ty) => { match $h { @@ -100,7 +106,6 @@ pub struct EmulatorModules where S: UsesInput, { - qemu: Qemu, modules: Pin>, hooks: EmulatorHooks, phantom: PhantomData, @@ -679,10 +684,7 @@ where } } - pub fn backdoor_function( - &self, - hook: fn(&mut EmulatorModules, Option<&mut S>, cpu: CPUArchStatePtr, pc: GuestAddr), - ) -> BackdoorHookId { + pub fn backdoor_function(&self, hook: BackdoorHookFn) -> BackdoorHookId { unsafe { self.qemu_hooks .add_backdoor_hook(transmute(hook), func_backdoor_hook_wrapper::) @@ -715,15 +717,7 @@ where } } - pub fn thread_creation_function( - &mut self, - hook: fn( - &mut EmulatorModules, - Option<&mut S>, - env: CPUArchStatePtr, - tid: u32, - ) -> bool, - ) -> NewThreadHookId { + pub fn thread_creation_function(&mut self, hook: NewThreadHookFn) -> NewThreadHookId { unsafe { self.qemu_hooks .add_new_thread_hook(transmute(hook), func_new_thread_hook_wrapper::) @@ -965,7 +959,7 @@ where pub fn instruction_function( &mut self, addr: GuestAddr, - hook: fn(&mut EmulatorModules, Option<&mut S>, GuestAddr), + hook: InstructionHookFn, invalidate_block: bool, ) -> InstructionHookId { self.hooks @@ -1076,15 +1070,7 @@ where self.hooks.thread_creation(hook) } - pub fn thread_creation_function( - &mut self, - hook: fn( - &mut EmulatorModules, - Option<&mut S>, - env: CPUArchStatePtr, - tid: u32, - ) -> bool, - ) -> NewThreadHookId { + pub fn thread_creation_function(&mut self, hook: NewThreadHookFn) -> NewThreadHookId { self.hooks.thread_creation_function(hook) } @@ -1101,13 +1087,8 @@ where ET: EmulatorModuleTuple, S: UsesInput + Unpin, { - pub(super) fn new( - qemu: Qemu, - emulator_hooks: EmulatorHooks, - modules: ET, - ) -> Pin> { + pub(super) fn new(emulator_hooks: EmulatorHooks, modules: ET) -> Pin> { let mut modules = Box::pin(Self { - qemu, modules: Box::pin(modules), hooks: emulator_hooks, phantom: PhantomData, @@ -1130,35 +1111,41 @@ where modules } - pub fn post_qemu_init_all(&mut self) { + /// Run post-QEMU init module callbacks. + pub unsafe fn post_qemu_init_all(&mut self, qemu: Qemu) { // We give access to EmulatorModuleTuple during init, the compiler complains (for good reasons) // TODO: We should find a way to be able to check for a module without giving full access to the tuple. unsafe { self.modules_mut() - .post_qemu_init_all(Self::emulator_modules_mut_unchecked()); + .post_qemu_init_all(qemu, Self::emulator_modules_mut_unchecked()); } } - pub fn first_exec_all(&mut self, state: &mut S) { + pub fn first_exec_all(&mut self, qemu: Qemu, state: &mut S) { // # Safety // We assume that the emulator was initialized correctly unsafe { self.modules_mut() - .first_exec_all(Self::emulator_modules_mut_unchecked(), state); + .first_exec_all(qemu, Self::emulator_modules_mut_unchecked(), state); } } - pub fn pre_exec_all(&mut self, state: &mut S, input: &S::Input) { + pub fn pre_exec_all(&mut self, qemu: Qemu, state: &mut S, input: &S::Input) { // # Safety // We assume that the emulator was initialized correctly unsafe { - self.modules_mut() - .pre_exec_all(Self::emulator_modules_mut_unchecked(), state, input); + self.modules_mut().pre_exec_all( + qemu, + Self::emulator_modules_mut_unchecked(), + state, + input, + ); } } pub fn post_exec_all( &mut self, + qemu: Qemu, state: &mut S, input: &S::Input, observers: &mut OT, @@ -1168,6 +1155,7 @@ where { unsafe { self.modules_mut().post_exec_all( + qemu, Self::emulator_modules_mut_unchecked(), state, input, @@ -1199,11 +1187,6 @@ impl EmulatorModules where S: UsesInput, { - #[must_use] - pub fn qemu(&self) -> Qemu { - self.qemu - } - #[must_use] pub fn modules(&self) -> &ET { self.modules.as_ref().get_ref() diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 0fcf92e3f6c..e3e140c0cfb 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -329,17 +329,22 @@ where snapshot_manager: SM, command_manager: CM, ) -> Result { - let mut emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; + let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; + let mut emulator_modules = EmulatorModules::new(emulator_hooks, modules); - modules.pre_qemu_init_all(&mut emulator_hooks); + // TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called + unsafe { + emulator_modules + .modules_mut() + .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked()); + } let qemu = Qemu::init(qemu_args)?; unsafe { Ok(Self::new_with_qemu( qemu, - emulator_hooks, - modules, + emulator_modules, driver, snapshot_manager, command_manager, @@ -352,17 +357,16 @@ where /// /// # Safety /// - /// pre-init qemu hooks should be run by then. + /// pre-init qemu hooks should be run before calling this. pub(crate) unsafe fn new_with_qemu( qemu: Qemu, - emulator_hooks: EmulatorHooks, - modules: ET, + emulator_modules: Pin>>, driver: ED, snapshot_manager: SM, command_manager: CM, ) -> Self { let mut emulator = Emulator { - modules: EmulatorModules::new(qemu, emulator_hooks, modules), + modules: emulator_modules, command_manager, snapshot_manager, driver, @@ -371,7 +375,7 @@ where qemu, }; - emulator.modules.post_qemu_init_all(); + emulator.modules.post_qemu_init_all(qemu); emulator } diff --git a/libafl_qemu/src/modules/calls.rs b/libafl_qemu/src/modules/calls.rs index 1e280a07200..951f51e97f8 100644 --- a/libafl_qemu/src/modules/calls.rs +++ b/libafl_qemu/src/modules/calls.rs @@ -243,6 +243,7 @@ where } fn on_ret( + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: Option<&mut S>, pc: GuestAddr, @@ -250,7 +251,7 @@ where S: Unpin + UsesInput, ET: EmulatorModuleTuple, { - let ret_addr: GuestAddr = emulator_modules.qemu().read_return_address().unwrap(); + let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); // log::info!("RET @ 0x{:#x}", ret_addr); @@ -270,6 +271,7 @@ where } fn gen_blocks_calls( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -292,8 +294,6 @@ where .unwrap(); } - let qemu = emulator_modules.qemu(); - let mut call_addrs: Vec<(GuestAddr, usize)> = Vec::new(); let mut ret_addrs: Vec = Vec::new(); @@ -362,7 +362,10 @@ where for (call_addr, call_len) in call_addrs { // TODO do not use a closure, find a more efficient way to pass call_len let call_cb = Box::new( - move |emulator_modules: &mut EmulatorModules, state: Option<&mut S>, pc| { + move |_qemu: Qemu, + emulator_modules: &mut EmulatorModules, + state: Option<&mut S>, + pc| { // eprintln!("CALL @ 0x{:#x}", pc + call_len); let mut collectors = if let Some(h) = emulator_modules.get_mut::() { h.collectors.take() @@ -399,7 +402,7 @@ where #[cfg(feature = "systemmode")] type ModulePageFilter = NopPageFilter; - fn post_qemu_init(&self, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -412,21 +415,20 @@ where fn pre_exec( &mut self, - emulator_modules: &mut EmulatorModules, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, _state: &mut S, input: &S::Input, ) where ET: EmulatorModuleTuple, { - self.collectors - .as_mut() - .unwrap() - .pre_exec_all(emulator_modules.qemu(), input); + self.collectors.as_mut().unwrap().pre_exec_all(qemu, input); } fn post_exec( &mut self, - emulator_modules: &mut EmulatorModules, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, _state: &mut S, input: &S::Input, observers: &mut OT, @@ -435,12 +437,10 @@ where OT: ObserversTuple, ET: EmulatorModuleTuple, { - self.collectors.as_mut().unwrap().post_exec_all( - emulator_modules.qemu(), - input, - observers, - exit_kind, - ); + self.collectors + .as_mut() + .unwrap() + .post_exec_all(qemu, input, observers, exit_kind); } fn address_filter(&self) -> &Self::ModuleAddressFilter { diff --git a/libafl_qemu/src/modules/cmplog.rs b/libafl_qemu/src/modules/cmplog.rs index b595971374f..69ec5cb6059 100644 --- a/libafl_qemu/src/modules/cmplog.rs +++ b/libafl_qemu/src/modules/cmplog.rs @@ -14,11 +14,12 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "systemmode")] use crate::modules::{NopPageFilter, NOP_PAGE_FILTER}; #[cfg(feature = "usermode")] -use crate::{capstone, qemu::ArchExtras, CallingConvention, Qemu}; +use crate::{capstone, qemu::ArchExtras, CallingConvention}; use crate::{ emu::EmulatorModules, modules::{hash_me, AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter}, qemu::Hook, + Qemu, }; #[cfg_attr( @@ -74,8 +75,12 @@ where #[cfg(feature = "systemmode")] type ModulePageFilter = NopPageFilter; - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { emulator_modules.cmps( @@ -139,8 +144,12 @@ where const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { emulator_modules.cmps( @@ -172,6 +181,7 @@ where } pub fn gen_unique_cmp_ids( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, state: Option<&mut S>, pc: GuestAddr, @@ -203,6 +213,7 @@ where } pub fn gen_hashed_cmp_ids( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -296,6 +307,7 @@ impl CmpLogRoutinesModule { } fn gen_blocks_calls( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -318,8 +330,6 @@ impl CmpLogRoutinesModule { .unwrap(); } - let qemu = emulator_modules.qemu(); - if let Some(h) = emulator_modules.get::() { #[allow(unused_mut)] let mut code = { @@ -391,8 +401,12 @@ where #[cfg(feature = "systemmode")] type ModulePageFilter = NopPageFilter; - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { emulator_modules.blocks( diff --git a/libafl_qemu/src/modules/drcov.rs b/libafl_qemu/src/modules/drcov.rs index e40d3dda0ec..96212023288 100644 --- a/libafl_qemu/src/modules/drcov.rs +++ b/libafl_qemu/src/modules/drcov.rs @@ -13,6 +13,7 @@ use crate::{ emu::EmulatorModules, modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, NopAddressFilter}, qemu::Hook, + Qemu, }; static DRCOV_IDS: Mutex>> = Mutex::new(None); @@ -264,7 +265,7 @@ where #[cfg(feature = "systemmode")] type ModulePageFilter = NopPageFilter; - fn post_qemu_init(&self, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -276,15 +277,17 @@ where } #[cfg(feature = "usermode")] - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { if self.module_mapping.is_none() { log::info!("Auto-filling module mapping for DrCov module from QEMU mapping."); - let qemu = emulator_modules.qemu(); - let mut module_mapping: RangeMap = RangeMap::new(); #[allow(clippy::unnecessary_cast)] // for GuestAddr -> u64 @@ -307,8 +310,12 @@ where } #[cfg(feature = "systemmode")] - fn first_exec(&mut self, _emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { assert!( @@ -319,6 +326,7 @@ where fn post_exec( &mut self, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, @@ -359,6 +367,7 @@ where } pub fn gen_unique_block_ids( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, state: Option<&mut S>, pc: GuestAddr, @@ -408,6 +417,7 @@ where } pub fn gen_block_lengths( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -430,6 +440,7 @@ pub fn gen_block_lengths( } pub fn exec_trace_block( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, diff --git a/libafl_qemu/src/modules/edges/helpers.rs b/libafl_qemu/src/modules/edges/helpers.rs index 760fce72727..e4de6642b47 100644 --- a/libafl_qemu/src/modules/edges/helpers.rs +++ b/libafl_qemu/src/modules/edges/helpers.rs @@ -60,7 +60,7 @@ mod generators { }; use crate::{ modules::{hash_me, AddressFilter, EdgeCoverageModule, EmulatorModuleTuple, PageFilter}, - EmulatorModules, + EmulatorModules, Qemu, }; fn get_mask() -> usize { @@ -78,6 +78,7 @@ mod generators { } pub fn gen_unique_edge_ids( + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: Option<&mut S>, src: GuestAddr, @@ -108,10 +109,7 @@ mod generators { #[cfg(feature = "systemmode")] { - let paging_id = emulator_modules - .qemu() - .current_cpu() - .and_then(|cpu| cpu.current_paging_id()); + let paging_id = qemu.current_cpu().and_then(|cpu| cpu.current_paging_id()); if !module.must_instrument(src, paging_id) && !module.must_instrument(dest, paging_id) @@ -157,6 +155,7 @@ mod generators { #[allow(clippy::unnecessary_cast)] pub fn gen_hashed_edge_ids( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, src: GuestAddr, @@ -179,10 +178,7 @@ mod generators { #[cfg(feature = "systemmode")] { - let paging_id = emulator_modules - .qemu() - .current_cpu() - .and_then(|cpu| cpu.current_paging_id()); + let paging_id = qemu.current_cpu().and_then(|cpu| cpu.current_paging_id()); if !module.must_instrument(src, paging_id) && !module.must_instrument(dest, paging_id) @@ -211,6 +207,7 @@ mod generators { #[allow(clippy::unnecessary_cast)] pub fn gen_hashed_block_ids( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -234,10 +231,7 @@ mod generators { } #[cfg(feature = "systemmode")] { - let page_id = emulator_modules - .qemu() - .current_cpu() - .and_then(|cpu| cpu.current_paging_id()); + let page_id = qemu.current_cpu().and_then(|cpu| cpu.current_paging_id()); if !module.must_instrument(pc, page_id) { return None; diff --git a/libafl_qemu/src/modules/edges/mod.rs b/libafl_qemu/src/modules/edges/mod.rs index db2538ca7e3..1b9cb4026a7 100644 --- a/libafl_qemu/src/modules/edges/mod.rs +++ b/libafl_qemu/src/modules/edges/mod.rs @@ -9,6 +9,7 @@ use libafl_qemu_sys::GuestPhysAddr; use crate::{ emu::EmulatorModules, modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, PageFilter}, + Qemu, }; mod helpers; @@ -327,8 +328,12 @@ where type ModulePageFilter = PF; const HOOKS_DO_SIDE_EFFECTS: bool = V::DO_SIDE_EFFECTS; - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { if self.use_hitcounts { diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index 04b80a84eec..06dea61e094 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -40,7 +40,7 @@ pub mod drcov; #[cfg(not(cpu_target = "hexagon"))] pub use drcov::{DrCovMetadata, DrCovModule, DrCovModuleBuilder}; -use crate::{emu::EmulatorModules, EmulatorHooks, Qemu}; +use crate::{emu::EmulatorModules, Qemu}; /// A module for `libafl_qemu`. // TODO remove 'static when specialization will be stable @@ -58,7 +58,7 @@ where /// Hook run **before** QEMU is initialized. /// This is always run when Emulator gets initialized, in any case. /// Install here hooks that should be alive for the whole execution of the VM, even before QEMU gets initialized. - fn pre_qemu_init(&self, _emulator_hooks: &mut EmulatorHooks) + fn pre_qemu_init(&self, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -67,7 +67,7 @@ where /// Hook run **after** QEMU is initialized. /// This is always run when Emulator gets initialized, in any case. /// Install here hooks that should be alive for the whole execution of the VM, after QEMU gets initialized. - fn post_qemu_init(&self, _emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -77,8 +77,12 @@ where /// This call can be delayed to the point at which fuzzing is supposed to start. /// It is mostly used to avoid running hooks during VM initialization, either /// because it is useless or it would produce wrong results. - fn first_exec(&mut self, _emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { } @@ -87,6 +91,7 @@ where /// On the first run, it is executed after [`Self::first_exec`]. fn pre_exec( &mut self, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, @@ -98,6 +103,7 @@ where /// Run after a fuzzing run ends. fn post_exec( &mut self, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, @@ -146,20 +152,25 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool; - fn pre_qemu_init_all(&self, _emulator_hooks: &mut EmulatorHooks) + fn pre_qemu_init_all(&self, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple; - fn post_qemu_init_all(&self, _emulator_modules: &mut EmulatorModules) + fn post_qemu_init_all(&self, qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple; - fn first_exec_all(&mut self, emulator_modules: &mut EmulatorModules, state: &mut S) - where + fn first_exec_all( + &mut self, + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + state: &mut S, + ) where ET: EmulatorModuleTuple; fn pre_exec_all( &mut self, + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: &mut S, input: &S::Input, @@ -168,6 +179,7 @@ where fn post_exec_all( &mut self, + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: &mut S, input: &S::Input, @@ -195,30 +207,35 @@ where impl EmulatorModuleTuple for () where - S: UsesInput, + S: UsesInput + Unpin, { const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn pre_qemu_init_all(&self, _emulator_hooks: &mut EmulatorHooks) + fn pre_qemu_init_all(&self, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { } - fn post_qemu_init_all(&self, _emulator_modules: &mut EmulatorModules) + fn post_qemu_init_all(&self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { } - fn first_exec_all(&mut self, _emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec_all( + &mut self, + _qemu: Qemu, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { } fn pre_exec_all( &mut self, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, @@ -229,6 +246,7 @@ where fn post_exec_all( &mut self, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, @@ -258,44 +276,50 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = Head::HOOKS_DO_SIDE_EFFECTS || Tail::HOOKS_DO_SIDE_EFFECTS; - fn pre_qemu_init_all(&self, emulator_hooks: &mut EmulatorHooks) + fn pre_qemu_init_all(&self, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { - self.0.pre_qemu_init(emulator_hooks); - self.1.pre_qemu_init_all(emulator_hooks); + self.0.pre_qemu_init(emulator_modules); + self.1.pre_qemu_init_all(emulator_modules); } - fn post_qemu_init_all(&self, emulator_modules: &mut EmulatorModules) + fn post_qemu_init_all(&self, qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { - self.0.post_qemu_init(emulator_modules); - self.1.post_qemu_init_all(emulator_modules); + self.0.post_qemu_init(qemu, emulator_modules); + self.1.post_qemu_init_all(qemu, emulator_modules); } - fn first_exec_all(&mut self, emulator_modules: &mut EmulatorModules, state: &mut S) - where + fn first_exec_all( + &mut self, + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + state: &mut S, + ) where ET: EmulatorModuleTuple, { - self.0.first_exec(emulator_modules, state); - self.1.first_exec_all(emulator_modules, state); + self.0.first_exec(qemu, emulator_modules, state); + self.1.first_exec_all(qemu, emulator_modules, state); } fn pre_exec_all( &mut self, + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: &mut S, input: &S::Input, ) where ET: EmulatorModuleTuple, { - self.0.pre_exec(emulator_modules, state, input); - self.1.pre_exec_all(emulator_modules, state, input); + self.0.pre_exec(qemu, emulator_modules, state, input); + self.1.pre_exec_all(qemu, emulator_modules, state, input); } fn post_exec_all( &mut self, + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: &mut S, input: &S::Input, @@ -306,9 +330,9 @@ where ET: EmulatorModuleTuple, { self.0 - .post_exec(emulator_modules, state, input, observers, exit_kind); + .post_exec(qemu, emulator_modules, state, input, observers, exit_kind); self.1 - .post_exec_all(emulator_modules, state, input, observers, exit_kind); + .post_exec_all(qemu, emulator_modules, state, input, observers, exit_kind); } unsafe fn on_crash_all(&mut self) { diff --git a/libafl_qemu/src/qemu/hooks.rs b/libafl_qemu/src/qemu/hooks.rs index 4165742d17c..97cc83e96cc 100644 --- a/libafl_qemu/src/qemu/hooks.rs +++ b/libafl_qemu/src/qemu/hooks.rs @@ -100,16 +100,16 @@ impl Hook { } } -macro_rules! create_wrapper { +macro_rules! create_pre_init_wrapper { ($name:ident, ($($param:ident : $param_type:ty),*)) => { paste::paste! { - pub extern "C" fn [](hook: &mut c_void, $($param: $param_type),*) + pub extern "C" fn [](hook: &mut (), $($param: $param_type),*) where S: UsesInput + Unpin, { unsafe { let modules = EmulatorModules::::emulator_modules_mut_unchecked(); - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(ptr::from_mut::(hook)); + let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(ptr::from_mut::<()>(hook)); func(modules, inprocess_get_state::(), $($param),*); } } @@ -128,13 +128,13 @@ macro_rules! create_wrapper { }; ($name:ident, ($($param:ident : $param_type:ty),*), $ret_type:ty) => { paste::paste! { - pub extern "C" fn [](hook: &mut c_void, $($param: $param_type),*) -> $ret_type + pub extern "C" fn [](hook: &mut (), $($param: $param_type),*) -> $ret_type where S: UsesInput + Unpin, { unsafe { let modules = EmulatorModules::::emulator_modules_mut_unchecked(); - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) -> $ret_type= transmute(ptr::from_mut::(hook)); + let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) -> $ret_type= transmute(ptr::from_mut::<()>(hook)); func(modules, inprocess_get_state::(), $($param),*) } } @@ -153,6 +153,63 @@ macro_rules! create_wrapper { }; } +macro_rules! create_wrapper { + ($name:ident, ($($param:ident : $param_type:ty),*)) => { + paste::paste! { + pub extern "C" fn [](hook: &mut (), $($param: $param_type),*) + where + S: UsesInput + Unpin, + { + unsafe { + let qemu = Qemu::get_unchecked(); + let modules = EmulatorModules::::emulator_modules_mut_unchecked(); + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(ptr::from_mut::<()>(hook)); + func(qemu, modules, inprocess_get_state::(), $($param),*); + } + } + + pub extern "C" fn [](hook: &mut FatPtr, $($param: $param_type),*) + where + S: Unpin + UsesInput, + { + unsafe { + let qemu = Qemu::get_unchecked(); + let modules = EmulatorModules::::emulator_modules_mut_unchecked(); + let func: &mut Box, Option<&mut S>, $($param_type),*)> = &mut *(ptr::from_mut::(hook) as *mut Box, Option<&mut S>, $($param_type),*)>); + func(qemu, modules, inprocess_get_state::(), $($param),*); + } + } + } + }; + ($name:ident, ($($param:ident : $param_type:ty),*), $ret_type:ty) => { + paste::paste! { + pub extern "C" fn [](hook: &mut (), $($param: $param_type),*) -> $ret_type + where + S: UsesInput + Unpin, + { + unsafe { + let qemu = Qemu::get_unchecked(); + let modules = EmulatorModules::::emulator_modules_mut_unchecked(); + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) -> $ret_type= transmute(ptr::from_mut::<()>(hook)); + func(qemu, modules, inprocess_get_state::(), $($param),*) + } + } + + pub extern "C" fn [](hook: &mut FatPtr, $($param: $param_type),*) -> $ret_type + where + S: UsesInput + Unpin, + { + unsafe { + let qemu = Qemu::get_unchecked(); + let modules = EmulatorModules::::emulator_modules_mut_unchecked(); + let func: &mut Box, Option<&mut S>, $($param_type),*) -> $ret_type> = &mut *(ptr::from_mut::(hook) as *mut Box, Option<&mut S>, $($param_type),*) -> $ret_type>); + func(qemu, modules, inprocess_get_state::(), $($param),*) + } + } + } + }; +} + macro_rules! create_pre_exec_wrapper { ($name:ident, ($($param:ident : $param_type:ty),*), $hook_id:ident) => { paste::paste! { @@ -161,21 +218,22 @@ macro_rules! create_pre_exec_wrapper { S: UsesInput + Unpin, { unsafe { + let qemu = Qemu::get_unchecked(); let modules = EmulatorModules::::emulator_modules_mut_unchecked(); match &mut hook.pre_run { HookRepr::Function(ptr) => { - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) = + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(*ptr); - func(modules, inprocess_get_state::(), $($param),*) + func(qemu, modules, inprocess_get_state::(), $($param),*) } HookRepr::Closure(ptr) => { let func: &mut Box< - dyn FnMut(&mut EmulatorModules, Option<&mut S>, $($param_type),*), + dyn FnMut(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*), > = &mut *(ptr::from_mut::(ptr) as *mut Box< - dyn FnMut(&mut EmulatorModules, Option<&mut S>, $($param_type),*), + dyn FnMut(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*), >); - func(modules, inprocess_get_state::(), $($param),*) + func(qemu, modules, inprocess_get_state::(), $($param),*) } _ => (), } @@ -193,21 +251,22 @@ macro_rules! create_post_exec_wrapper { S: UsesInput + Unpin, { unsafe { + let qemu = Qemu::get_unchecked(); let modules = EmulatorModules::::emulator_modules_mut_unchecked(); match &mut hook.post_run { HookRepr::Function(ptr) => { - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) = + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(*ptr); - func(modules, inprocess_get_state::(), $($param),*); + func(qemu, modules, inprocess_get_state::(), $($param),*); } HookRepr::Closure(ptr) => { let func: &mut Box< - dyn FnMut(&mut EmulatorModules, Option<&mut S>, $($param_type),*), + dyn FnMut(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*), > = &mut *(ptr::from_mut::(ptr) as *mut Box< - dyn FnMut(&mut EmulatorModules, Option<&mut S>, $($param_type),*), + dyn FnMut(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*), >); - func(modules, inprocess_get_state::(), $($param),*); + func(qemu, modules, inprocess_get_state::(), $($param),*); } _ => (), } @@ -225,19 +284,20 @@ macro_rules! create_gen_wrapper { S: UsesInput + Unpin, { unsafe { + let qemu = Qemu::get_unchecked(); let modules = EmulatorModules::::emulator_modules_mut_unchecked(); match &mut hook.gen { HookRepr::Function(ptr) => { - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) -> Option<$ret_type> = + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) -> Option<$ret_type> = transmute(*ptr); - func(modules, inprocess_get_state::(), $($param),*).map_or(SKIP_EXEC_HOOK, |id| id) + func(qemu, modules, inprocess_get_state::(), $($param),*).map_or(SKIP_EXEC_HOOK, |id| id) } HookRepr::Closure(ptr) => { let func: &mut Box< - dyn FnMut(&mut EmulatorModules, Option<&mut S>, $($param_type),*) -> Option<$ret_type>, - > = &mut *(ptr::from_mut::(ptr) as *mut Box, Option<&mut S>, $($param_type),*) -> Option<$ret_type>>); - func(modules, inprocess_get_state::(), $($param),*).map_or(SKIP_EXEC_HOOK, |id| id) + dyn FnMut(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) -> Option<$ret_type>, + > = &mut *(ptr::from_mut::(ptr) as *mut Box, Option<&mut S>, $($param_type),*) -> Option<$ret_type>>); + func(qemu, modules, inprocess_get_state::(), $($param),*).map_or(SKIP_EXEC_HOOK, |id| id) } _ => 0, } @@ -255,18 +315,20 @@ macro_rules! create_post_gen_wrapper { S: UsesInput + Unpin, { unsafe { + let qemu = Qemu::get_unchecked(); let modules = EmulatorModules::::emulator_modules_mut_unchecked(); + match &mut hook.post_gen { HookRepr::Function(ptr) => { - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) = + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(*ptr); - func(modules, inprocess_get_state::(), $($param),*); + func(qemu, modules, inprocess_get_state::(), $($param),*); } HookRepr::Closure(ptr) => { let func: &mut Box< - dyn FnMut(&mut EmulatorModules, Option<&mut S>, $($param_type),*), - > = &mut *(ptr::from_mut::(ptr) as *mut Box, Option<&mut S>, $($param_type),*)>); - func(modules, inprocess_get_state::(), $($param),*); + dyn FnMut(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*), + > = &mut *(ptr::from_mut::(ptr) as *mut Box, Option<&mut S>, $($param_type),*)>); + func(qemu, modules, inprocess_get_state::(), $($param),*); } _ => (), } @@ -284,16 +346,18 @@ macro_rules! create_exec_wrapper { S: UsesInput + Unpin, { unsafe { + let qemu = Qemu::get_unchecked(); let modules = EmulatorModules::::emulator_modules_mut_unchecked(); + match &mut hook.execs[$execidx] { HookRepr::Function(ptr) => { - let func: fn(&mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(*ptr); - func(modules, inprocess_get_state::(), $($param),*); + let func: fn(Qemu, &mut EmulatorModules, Option<&mut S>, $($param_type),*) = transmute(*ptr); + func(qemu, modules, inprocess_get_state::(), $($param),*); } HookRepr::Closure(ptr) => { - let func: &mut Box, Option<&mut S>, $($param_type),*)> = - &mut *(ptr::from_mut::(ptr) as *mut Box, Option<&mut S>, $($param_type),*)>); - func(modules, inprocess_get_state::(), $($param),*); + let func: &mut Box, Option<&mut S>, $($param_type),*)> = + &mut *(ptr::from_mut::(ptr) as *mut Box, Option<&mut S>, $($param_type),*)>); + func(qemu, modules, inprocess_get_state::(), $($param),*); } _ => (), } @@ -359,8 +423,8 @@ macro_rules! create_hook_types { // Instruction hook wrappers create_hook_types!( Instruction, - fn(&mut EmulatorModules, Option<&mut S>, GuestAddr), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, GuestAddr)>, + fn(Qemu, &mut EmulatorModules, Option<&mut S>, GuestAddr), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr)>, extern "C" fn(*const (), pc: GuestAddr) ); create_hook_id!(Instruction, libafl_qemu_remove_instruction_hook, true); @@ -369,9 +433,9 @@ create_wrapper!(instruction, (pc: GuestAddr)); // Backdoor hook wrappers create_hook_types!( Backdoor, - fn(&mut EmulatorModules, Option<&mut S>, cpu: CPUArchStatePtr, GuestAddr), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, GuestAddr)>, - extern "C" fn(*const (), cpu: CPUArchStatePtr, pc: GuestAddr) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, cpu: CPUArchStatePtr, GuestAddr), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr)>, + extern "C" fn(libafl_qemu_opaque: *const (), cpu: CPUArchStatePtr, pc: GuestAddr) ); create_hook_id!(Backdoor, libafl_qemu_remove_backdoor_hook, true); create_wrapper!(backdoor, (cpu: CPUArchStatePtr, pc: GuestAddr)); @@ -381,6 +445,7 @@ create_wrapper!(backdoor, (cpu: CPUArchStatePtr, pc: GuestAddr)); create_hook_types!( PreSyscall, fn( + Qemu, &mut EmulatorModules, Option<&mut S>, sys_num: i32, @@ -395,6 +460,7 @@ create_hook_types!( ) -> SyscallHookResult, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&'a mut S>, i32, @@ -409,6 +475,7 @@ create_hook_types!( ) -> SyscallHookResult, >, extern "C" fn( + Qemu, *const (), i32, GuestAddr, @@ -445,6 +512,7 @@ create_wrapper!( create_hook_types!( PostSyscall, fn( + Qemu, &mut EmulatorModules, Option<&mut S>, res: GuestAddr, @@ -460,6 +528,7 @@ create_hook_types!( ) -> GuestAddr, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&mut S>, GuestAddr, @@ -475,6 +544,7 @@ create_hook_types!( ) -> GuestAddr, >, extern "C" fn( + Qemu, *const (), GuestAddr, i32, @@ -520,23 +590,23 @@ create_hook_types!( u32, ) -> bool, >, - extern "C" fn(*const (), env: CPUArchStatePtr, tid: u32) -> bool + extern "C" fn(libafl_qemu_opaque: *const (), env: CPUArchStatePtr, tid: u32) -> bool ); create_hook_id!(NewThread, libafl_qemu_remove_new_thread_hook, false); -create_wrapper!(new_thread, (env: CPUArchStatePtr, tid: u32), bool); +create_pre_init_wrapper!(new_thread, (env: CPUArchStatePtr, tid: u32), bool); // CPU Run hook wrappers create_hook_types!( CpuPreRun, - fn(&mut EmulatorModules, Option<&mut S>, cpu: CPUStatePtr), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, CPUStatePtr)>, - extern "C" fn(*const (), cpu: CPUStatePtr) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, cpu: CPUStatePtr), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, CPUStatePtr)>, + extern "C" fn(libafl_qemu_opaque: *const (), cpu: CPUStatePtr) ); create_hook_types!( CpuPostRun, - fn(&mut EmulatorModules, Option<&mut S>, cpu: CPUStatePtr), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, CPUStatePtr)>, - extern "C" fn(*const (), cpu: CPUStatePtr) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, cpu: CPUStatePtr), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, CPUStatePtr)>, + extern "C" fn(libafl_qemu_opaque: *const (), cpu: CPUStatePtr) ); create_hook_id!(CpuRun, libafl_qemu_remove_cpu_run_hook, false); create_pre_exec_wrapper!(cpu_run, (cpu: CPUStatePtr), CpuRunHookId); @@ -546,22 +616,29 @@ create_wrapper!(cpu_run, (cpu: CPUStatePtr)); // Edge hook wrappers create_hook_types!( EdgeGen, - fn(&mut EmulatorModules, Option<&mut S>, src: GuestAddr, dest: GuestAddr) -> Option, + fn( + Qemu, + &mut EmulatorModules, + Option<&mut S>, + src: GuestAddr, + dest: GuestAddr, + ) -> Option, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr, GuestAddr, ) -> Option, >, - extern "C" fn(*const (), src: GuestAddr, dest: GuestAddr) -> u64 + extern "C" fn(libafl_qemu_opaque: *const (), src: GuestAddr, dest: GuestAddr) -> u64 ); create_hook_types!( EdgeExec, - fn(&mut EmulatorModules, Option<&mut S>, id: u64), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, u64)>, - unsafe extern "C" fn(*const (), id: u64) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, id: u64), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, u64)>, + unsafe extern "C" fn(libafl_qemu_opaque: *const (), id: u64) ); create_hook_id!(Edge, libafl_qemu_remove_edge_hook, true); create_gen_wrapper!(edge, (src: GuestAddr, dest: GuestAddr), u64, 1, EdgeHookId); @@ -570,27 +647,36 @@ create_exec_wrapper!(edge, (id: u64), 0, 1, EdgeHookId); // Block hook wrappers create_hook_types!( BlockGen, - fn(&mut EmulatorModules, Option<&mut S>, pc: GuestAddr) -> Option, + fn(Qemu, &mut EmulatorModules, Option<&mut S>, pc: GuestAddr) -> Option, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr, ) -> Option, >, - unsafe extern "C" fn(*const (), pc: GuestAddr) -> u64 + unsafe extern "C" fn(libafl_qemu_opaque: *const (), pc: GuestAddr) -> u64 ); create_hook_types!( BlockPostGen, - fn(&mut EmulatorModules, Option<&mut S>, pc: GuestAddr, block_length: GuestUsize), - Box FnMut(&'a mut EmulatorModules, Option<&mut S>, GuestAddr, GuestUsize)>, - unsafe extern "C" fn(*const (), pc: GuestAddr, block_length: GuestUsize) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, pc: GuestAddr, block_length: GuestUsize), + Box< + dyn for<'a> FnMut( + Qemu, + &'a mut EmulatorModules, + Option<&mut S>, + GuestAddr, + GuestUsize, + ), + >, + unsafe extern "C" fn(libafl_qemu_opaque: *const (), pc: GuestAddr, block_length: GuestUsize) ); create_hook_types!( BlockExec, - fn(&mut EmulatorModules, Option<&mut S>, id: u64), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, u64)>, - unsafe extern "C" fn(*const (), id: u64) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, id: u64), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, u64)>, + unsafe extern "C" fn(libafl_qemu_opaque: *const (), id: u64) ); create_hook_id!(Block, libafl_qemu_remove_block_hook, true); @@ -602,7 +688,8 @@ create_exec_wrapper!(block, (id: u64), 0, 1, BlockHookId); create_hook_types!( ReadGen, fn( - qemu_modules: &mut EmulatorModules, + Qemu, + emulator_modules: &mut EmulatorModules, Option<&mut S>, pc: GuestAddr, addr: *mut TCGTemp, @@ -610,6 +697,7 @@ create_hook_types!( ) -> Option, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr, @@ -617,21 +705,33 @@ create_hook_types!( MemAccessInfo, ) -> Option, >, - unsafe extern "C" fn(*const (), pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo) -> u64 + unsafe extern "C" fn( + libafl_qemu_opaque: *const (), + pc: GuestAddr, + addr: *mut TCGTemp, + info: MemAccessInfo, + ) -> u64 ); create_hook_types!( ReadExec, - fn(&mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, u64, GuestAddr)>, - unsafe extern "C" fn(*const (), id: u64, addr: GuestAddr) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, u64, GuestAddr)>, + unsafe extern "C" fn(libafl_qemu_opaque: *const (), id: u64, addr: GuestAddr) ); create_hook_types!( ReadExecN, - fn(&mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr, size: usize), + fn(Qemu, &mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr, size: usize), Box< - dyn for<'a> FnMut(&'a mut EmulatorModules, Option<&'a mut S>, u64, GuestAddr, usize), + dyn for<'a> FnMut( + Qemu, + &'a mut EmulatorModules, + Option<&'a mut S>, + u64, + GuestAddr, + usize, + ), >, - unsafe extern "C" fn(*const (), id: u64, addr: GuestAddr, size: usize) + unsafe extern "C" fn(libafl_qemu_opaque: *const (), id: u64, addr: GuestAddr, size: usize) ); create_hook_id!(Read, libafl_qemu_remove_read_hook, true); create_gen_wrapper!(read, (pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo), u64, 5, ReadHookId); @@ -651,6 +751,7 @@ create_exec_wrapper!( create_hook_types!( WriteGen, fn( + Qemu, &mut EmulatorModules, Option<&mut S>, pc: GuestAddr, @@ -659,6 +760,7 @@ create_hook_types!( ) -> Option, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr, @@ -666,21 +768,33 @@ create_hook_types!( MemAccessInfo, ) -> Option, >, - unsafe extern "C" fn(*const (), pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo) -> u64 + unsafe extern "C" fn( + libafl_qemu_opaque: *const (), + pc: GuestAddr, + addr: *mut TCGTemp, + info: MemAccessInfo, + ) -> u64 ); create_hook_types!( WriteExec, - fn(&mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr), - Box FnMut(&'a mut EmulatorModules, Option<&'a mut S>, u64, GuestAddr)>, - unsafe extern "C" fn(*const (), id: u64, addr: GuestAddr) + fn(Qemu, &mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr), + Box FnMut(Qemu, &'a mut EmulatorModules, Option<&'a mut S>, u64, GuestAddr)>, + unsafe extern "C" fn(libafl_qemu_opaque: *const (), id: u64, addr: GuestAddr) ); create_hook_types!( WriteExecN, - fn(&mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr, size: usize), + fn(Qemu, &mut EmulatorModules, Option<&mut S>, id: u64, addr: GuestAddr, size: usize), Box< - dyn for<'a> FnMut(&'a mut EmulatorModules, Option<&'a mut S>, u64, GuestAddr, usize), + dyn for<'a> FnMut( + Qemu, + &'a mut EmulatorModules, + Option<&'a mut S>, + u64, + GuestAddr, + usize, + ), >, - unsafe extern "C" fn(*const (), id: u64, addr: GuestAddr, size: usize) + unsafe extern "C" fn(libafl_qemu_opaque: *const (), id: u64, addr: GuestAddr, size: usize) ); create_hook_id!(Write, libafl_qemu_remove_write_hook, true); create_gen_wrapper!(write, (pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo), u64, 5, WriteHookId); @@ -699,16 +813,23 @@ create_exec_wrapper!( // Cmp hook wrappers create_hook_types!( CmpGen, - fn(&mut EmulatorModules, Option<&mut S>, pc: GuestAddr, size: usize) -> Option, + fn( + Qemu, + &mut EmulatorModules, + Option<&mut S>, + pc: GuestAddr, + size: usize, + ) -> Option, Box< dyn for<'a> FnMut( + Qemu, &'a mut EmulatorModules, Option<&'a mut S>, GuestAddr, usize, ) -> Option, >, - unsafe extern "C" fn(*const (), pc: GuestAddr, size: usize) -> u64 + unsafe extern "C" fn(libafl_qemu_opaque: *const (), pc: GuestAddr, size: usize) -> u64 ); pub type CmpExecHook = Hook< fn(&mut EmulatorModules, Option<&mut S>, id: u64, v0: SZ, v1: SZ), @@ -724,9 +845,9 @@ create_exec_wrapper!(cmp, (id: u64, v0: u64, v1: u64), 3, 4, CmpHookId); // Crash hook wrappers #[cfg(feature = "usermode")] -pub type CrashHookFn = fn(&mut EmulatorModules, i32); +pub type CrashHookFn = fn(Qemu, &mut EmulatorModules, i32); #[cfg(feature = "usermode")] -pub type CrashHookClosure = Box, i32)>; +pub type CrashHookClosure = Box, i32)>; /// The thin wrapper around QEMU hooks. /// It is considered unsafe to use it directly. From bd14daef8a5411b1cc9bf8cd9f349031ebb16698 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:45:18 +0100 Subject: [PATCH 16/93] Update target program ELF offsets --- fuzzers/binary_only/intel_pt_command_executor/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index e8c977a775b..f7a747c3900 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -93,7 +93,7 @@ pub fn main() { // Set the instruction pointer (IP) filter and memory image of our target. // These information can be retrieved from `readelf -l` (for example) - let code_memory_addresses = ELF_ET_DYN_BASE + 0x14000..=ELF_ET_DYN_BASE + 0x14000 + 0x40000; + let code_memory_addresses = ELF_ET_DYN_BASE + 0x15000..=ELF_ET_DYN_BASE + 0x14000 + 0x41000; intel_pt .set_ip_filters(&[code_memory_addresses.clone()]) @@ -101,7 +101,7 @@ pub fn main() { let sections = [Section { file_path: target_path.to_string_lossy().to_string(), - file_offset: 0x13000, + file_offset: 0x14000, size: (*code_memory_addresses.end() - *code_memory_addresses.start() + 1) as u64, virtual_address: *code_memory_addresses.start() as u64, }]; From 1c61b3834b84847e2e1da863ceefcce84fd1a16c Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Tue, 26 Nov 2024 17:20:29 +0100 Subject: [PATCH 17/93] fmt --- fuzzers/baby/baby_fuzzer/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzzers/baby/baby_fuzzer/src/main.rs b/fuzzers/baby/baby_fuzzer/src/main.rs index 02a695f296e..c98b0280261 100644 --- a/fuzzers/baby/baby_fuzzer/src/main.rs +++ b/fuzzers/baby/baby_fuzzer/src/main.rs @@ -9,7 +9,7 @@ use libafl::monitors::SimpleMonitor; use libafl::{ corpus::{InMemoryCorpus, OnDiskCorpus}, events::SimpleEventManager, - executors::{InProcessExecutor, ExitKind}, + executors::{ExitKind, InProcessExecutor}, feedbacks::{CrashFeedback, MaxMapFeedback}, fuzzer::{Fuzzer, StdFuzzer}, generators::RandPrintablesGenerator, From 525fd110471f7d5276bddd3758be51c2d9abfe8c Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Tue, 26 Nov 2024 20:30:28 +0100 Subject: [PATCH 18/93] * asan fixed size accesses working with generics * continue to propagate qemu argument as hook first parameter * use pre_syscall* and post_syscall* everywhere * fix some clippy stuff --- libafl_qemu/src/emu/hooks.rs | 120 ++----- libafl_qemu/src/executor.rs | 8 +- libafl_qemu/src/modules/edges/helpers.rs | 6 +- libafl_qemu/src/modules/usermode/asan.rs | 326 ++++-------------- .../src/modules/usermode/asan_guest.rs | 13 +- .../src/modules/usermode/injections.rs | 27 +- libafl_qemu/src/modules/usermode/snapshot.rs | 17 +- libafl_qemu/src/qemu/config.rs | 2 +- libafl_qemu/src/qemu/hooks.rs | 2 - libafl_qemu/src/qemu/mod.rs | 9 +- libafl_sugar/src/qemu.rs | 17 +- 11 files changed, 165 insertions(+), 382 deletions(-) diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index 61e3759ca94..7bb703c6b19 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -9,7 +9,7 @@ use libafl_qemu_sys::{CPUStatePtr, FatPtr, GuestAddr, GuestUsize, TCGTemp}; use crate::qemu::{ closure_post_syscall_hook_wrapper, closure_pre_syscall_hook_wrapper, func_post_syscall_hook_wrapper, func_pre_syscall_hook_wrapper, PostSyscallHook, - PostSyscallHookId, PreSyscallHook, PreSyscallHookId, SyscallHookResult, + PostSyscallHookId, PreSyscallHook, PreSyscallHookId, }; #[cfg(feature = "usermode")] use crate::qemu::{ @@ -80,6 +80,7 @@ where { unsafe { let emulator_modules = EmulatorModules::::emulator_modules_mut().unwrap(); + let qemu = Qemu::get_unchecked(); let crash_hooks_ptr = &raw mut emulator_modules.hooks.crash_hooks; @@ -87,12 +88,12 @@ where match crash_hook { HookRepr::Function(ptr) => { let func: CrashHookFn = transmute(*ptr); - func(emulator_modules, target_sig); + func(qemu, emulator_modules, target_sig); } HookRepr::Closure(ptr) => { let func: &mut CrashHookClosure = &mut *(ptr::from_mut::(ptr) as *mut CrashHookClosure); - func(emulator_modules, target_sig); + func(qemu, emulator_modules, target_sig); } HookRepr::Empty => (), } @@ -762,10 +763,10 @@ where S: Unpin + UsesInput, { #[allow(clippy::type_complexity)] - pub fn syscalls(&mut self, hook: PreSyscallHook) -> Option { + pub fn pre_syscalls(&mut self, hook: PreSyscallHook) -> Option { match hook { - Hook::Function(f) => Some(self.syscalls_function(f)), - Hook::Closure(c) => Some(self.syscalls_closure(c)), + Hook::Function(f) => Some(self.pre_syscalls_function(f)), + Hook::Closure(c) => Some(self.pre_syscalls_closure(c)), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); Some(self.qemu_hooks.add_pre_syscall_hook(z, r)) @@ -775,7 +776,7 @@ where } #[allow(clippy::type_complexity)] - pub fn syscalls_function(&mut self, hook: PreSyscallHookFn) -> PreSyscallHookId { + pub fn pre_syscalls_function(&mut self, hook: PreSyscallHookFn) -> PreSyscallHookId { // # Safety // Will dereference the hook as [`FatPtr`]. unsafe { @@ -785,7 +786,7 @@ where } #[allow(clippy::type_complexity)] - pub fn syscalls_closure(&mut self, hook: PreSyscallHookClosure) -> PreSyscallHookId { + pub fn pre_syscalls_closure(&mut self, hook: PreSyscallHookClosure) -> PreSyscallHookId { // # Safety // Will dereference the hook as [`FatPtr`]. unsafe { @@ -816,10 +817,10 @@ where } #[allow(clippy::type_complexity)] - pub fn after_syscalls(&mut self, hook: PostSyscallHook) -> Option { + pub fn post_syscalls(&mut self, hook: PostSyscallHook) -> Option { match hook { - Hook::Function(f) => Some(self.after_syscalls_function(f)), - Hook::Closure(c) => Some(self.after_syscalls_closure(c)), + Hook::Function(f) => Some(self.post_syscalls_function(f)), + Hook::Closure(c) => Some(self.post_syscalls_closure(c)), Hook::Raw(r) => { let z: *const () = ptr::null::<()>(); Some(self.qemu_hooks.add_post_syscall_hook(z, r)) @@ -829,7 +830,7 @@ where } #[allow(clippy::type_complexity)] - pub fn after_syscalls_function(&mut self, hook: PostSyscallHookFn) -> PostSyscallHookId { + pub fn post_syscalls_function(&mut self, hook: PostSyscallHookFn) -> PostSyscallHookId { // # Safety // Will dereference the hook as [`FatPtr`]. This should be ok. unsafe { @@ -839,7 +840,7 @@ where } #[allow(clippy::type_complexity)] - pub fn after_syscalls_closure( + pub fn post_syscalls_closure( &mut self, hook: PostSyscallHookClosure, ) -> PostSyscallHookId { @@ -870,7 +871,7 @@ where } } - pub fn crash_function(&mut self, hook: fn(&mut EmulatorModules, target_signal: i32)) { + pub fn crash_function(&mut self, hook: CrashHookFn) { // # Safety // Will cast the valid hook to a ptr. self.qemu_hooks.set_crash_hook(crash_hook_wrapper::); @@ -1111,8 +1112,7 @@ where modules } - /// Run post-QEMU init module callbacks. - pub unsafe fn post_qemu_init_all(&mut self, qemu: Qemu) { + pub fn post_qemu_init_all(&mut self, qemu: Qemu) { // We give access to EmulatorModuleTuple during init, the compiler complains (for good reasons) // TODO: We should find a way to be able to check for a module without giving full access to the tuple. unsafe { @@ -1205,108 +1205,54 @@ where S: Unpin + UsesInput, { #[allow(clippy::type_complexity)] - pub fn syscalls(&mut self, hook: PreSyscallHook) -> Option { - self.hooks.syscalls(hook) + pub fn pre_syscalls(&mut self, hook: PreSyscallHook) -> Option { + self.hooks.pre_syscalls(hook) } /// # Safety /// Calls through to the, potentially unsafe, `syscalls_function` #[allow(clippy::type_complexity)] - pub unsafe fn syscalls_function( + pub unsafe fn pre_syscalls_function( &mut self, - hook: fn( - &mut EmulatorModules, - Option<&mut S>, - sys_num: i32, - a0: GuestAddr, - a1: GuestAddr, - a2: GuestAddr, - a3: GuestAddr, - a4: GuestAddr, - a5: GuestAddr, - a6: GuestAddr, - a7: GuestAddr, - ) -> SyscallHookResult, + hook: PreSyscallHookFn, ) -> PreSyscallHookId { - self.hooks.syscalls_function(hook) + self.hooks.pre_syscalls_function(hook) } /// # Safety /// Calls through to the, potentially unsafe, `syscalls_closure` #[allow(clippy::type_complexity)] - pub unsafe fn syscalls_closure( + pub unsafe fn pre_syscalls_closure( &mut self, - hook: Box< - dyn for<'a> FnMut( - &'a mut EmulatorModules, - Option<&'a mut S>, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> SyscallHookResult, - >, + hook: PreSyscallHookClosure, ) -> PreSyscallHookId { - self.hooks.syscalls_closure(hook) + self.hooks.pre_syscalls_closure(hook) } #[allow(clippy::type_complexity)] - pub fn after_syscalls(&mut self, hook: PostSyscallHook) -> Option { - self.hooks.after_syscalls(hook) + pub fn post_syscalls(&mut self, hook: PostSyscallHook) -> Option { + self.hooks.post_syscalls(hook) } /// # Safety /// Calls through to the, potentially unsafe, `after_syscalls_function` #[allow(clippy::type_complexity)] - pub unsafe fn after_syscalls_function( + pub unsafe fn post_syscalls_function( &mut self, - hook: fn( - &mut EmulatorModules, - Option<&mut S>, - res: GuestAddr, - sys_num: i32, - a0: GuestAddr, - a1: GuestAddr, - a2: GuestAddr, - a3: GuestAddr, - a4: GuestAddr, - a5: GuestAddr, - a6: GuestAddr, - a7: GuestAddr, - ) -> GuestAddr, + hook: PostSyscallHookFn, ) -> PostSyscallHookId { - self.hooks.after_syscalls_function(hook) + self.hooks.post_syscalls_function(hook) } #[allow(clippy::type_complexity)] - pub fn after_syscalls_closure( + pub fn post_syscalls_closure( &mut self, - hook: Box< - dyn for<'a> FnMut( - &'a mut EmulatorModules, - Option<&mut S>, - GuestAddr, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> GuestAddr, - >, + hook: PostSyscallHookClosure, ) -> PostSyscallHookId { - self.hooks.after_syscalls_closure(hook) + self.hooks.post_syscalls_closure(hook) } - pub fn crash_function(&mut self, hook: fn(&mut EmulatorModules, target_signal: i32)) { + pub fn crash_function(&mut self, hook: CrashHookFn) { self.hooks.crash_function(hook); } diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 4b2ddacbca3..e5662c913dd 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -40,7 +40,9 @@ use libc::siginfo_t; #[cfg(feature = "usermode")] use crate::EmulatorModules; -use crate::{command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver}; +use crate::{ + command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver, Qemu, +}; pub struct QemuExecutor<'a, CM, ED, ET, H, OT, S, SM> where @@ -182,12 +184,12 @@ where inner.inprocess_hooks_mut().crash_handler = inproc_qemu_crash_handler:: as *const c_void; - let handler = |emulator_modules: &mut EmulatorModules, host_sig| { + let handler = |qemu: Qemu, _emulator_modules: &mut EmulatorModules, host_sig| { eprintln!("Crashed with signal {host_sig}"); unsafe { libafl::executors::inprocess::generic_inproc_crash_handler::(); } - if let Some(cpu) = emulator_modules.qemu().current_cpu() { + if let Some(cpu) = qemu.current_cpu() { eprint!("Context:\n{}", cpu.display_context()); } }; diff --git a/libafl_qemu/src/modules/edges/helpers.rs b/libafl_qemu/src/modules/edges/helpers.rs index e4de6642b47..74b96f46eb0 100644 --- a/libafl_qemu/src/modules/edges/helpers.rs +++ b/libafl_qemu/src/modules/edges/helpers.rs @@ -78,7 +78,7 @@ mod generators { } pub fn gen_unique_edge_ids( - qemu: Qemu, + _qemu: Qemu, emulator_modules: &mut EmulatorModules, state: Option<&mut S>, src: GuestAddr, @@ -155,7 +155,7 @@ mod generators { #[allow(clippy::unnecessary_cast)] pub fn gen_hashed_edge_ids( - qemu: Qemu, + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, src: GuestAddr, @@ -207,7 +207,7 @@ mod generators { #[allow(clippy::unnecessary_cast)] pub fn gen_hashed_block_ids( - qemu: Qemu, + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 45ee1e8626c..89f1a1eb852 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -150,7 +150,6 @@ use std::pin::Pin; use libafl_qemu_sys::GuestAddr; use object::{Object, ObjectSection}; - use crate::{ emu::EmulatorModules, modules::{AddressFilter, StdAddressFilter}, @@ -255,7 +254,7 @@ impl AsanGiovese { Self::unpoison(qemu, a1, a2 as usize); } QasanAction::IsPoison => { - if Self::is_invalid_access(qemu, a1, a2 as usize) { + if Self::is_invalid_access_n(qemu, a1, a2 as usize) { r = 1; } } @@ -285,51 +284,26 @@ impl AsanGiovese { #[inline] #[must_use] - pub fn is_invalid_access_1(qemu: Qemu, addr: GuestAddr) -> bool { - unsafe { - let h = qemu.g2h::<*const c_void>(addr) as isize; - let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); - let k = *shadow_addr as isize; - k != 0 && (h & 7).wrapping_add(1) > k - } - } - - #[inline] - #[must_use] - pub fn is_invalid_access_2(qemu: Qemu, addr: GuestAddr) -> bool { - unsafe { - let h = qemu.g2h::<*const c_void>(addr) as isize; - let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); - let k = *shadow_addr as isize; - k != 0 && (h & 7).wrapping_add(2) > k - } - } - - #[inline] - #[must_use] - pub fn is_invalid_access_4(qemu: Qemu, addr: GuestAddr) -> bool { - unsafe { - let h = qemu.g2h::<*const c_void>(addr) as isize; - let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); - let k = *shadow_addr as isize; - k != 0 && (h & 7).wrapping_add(4) > k - } - } + pub fn is_invalid_access(qemu: Qemu, addr: GuestAddr) -> bool + { + const { assert!(N == 1 || N == 2 || N == 4 || N == 8) }; - #[inline] - #[must_use] - pub fn is_invalid_access_8(qemu: Qemu, addr: GuestAddr) -> bool { unsafe { let h = qemu.g2h::<*const c_void>(addr) as isize; let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET); - *shadow_addr != 0 + if N < 8 { + let k = *shadow_addr as isize; + k != 0 && (h & 7).wrapping_add(N as isize) > k + } else { + *shadow_addr != 0 + } } } #[inline] #[must_use] #[allow(clippy::cast_sign_loss)] - pub fn is_invalid_access(qemu: Qemu, addr: GuestAddr, n: usize) -> bool { + pub fn is_invalid_access_n(qemu: Qemu, addr: GuestAddr, n: usize) -> bool { unsafe { if n == 0 { return false; @@ -724,7 +698,7 @@ pub fn init_qemu_with_asan( ASAN_INITED = true; } - let qemu = Qemu::init(args)?; + let qemu = Qemu::init(args.as_slice())?; let rt = AsanGiovese::new(qemu.hooks()); Ok((qemu, rt)) @@ -843,66 +817,30 @@ impl AsanModule { #[allow(clippy::unused_self)] #[must_use] pub fn is_poisoned(&self, qemu: Qemu, addr: GuestAddr, size: usize) -> bool { - AsanGiovese::is_invalid_access(qemu, addr, size) - } - - pub fn read_1(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_1(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 1)); - } + AsanGiovese::is_invalid_access_n(qemu, addr, size) } - pub fn read_2(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_2(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 2)); - } - } - - pub fn read_4(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_4(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 4)); - } - } - - pub fn read_8(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_8(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, 8)); + pub fn read(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access::(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, N)); } } pub fn read_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) { - if self.enabled() && AsanGiovese::is_invalid_access(qemu, addr, size) { + if self.enabled() && AsanGiovese::is_invalid_access_n(qemu, addr, size) { self.rt .report_or_crash(qemu, pc, AsanError::Read(addr, size)); } } - pub fn write_1(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_1(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 1)); - } - } - - pub fn write_2(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_2(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 2)); - } - } - - pub fn write_4(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_4(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 4)); - } - } - - pub fn write_8(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { - if self.enabled() && AsanGiovese::is_invalid_access_8(qemu, addr) { - self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, 8)); + pub fn write(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) { + if self.enabled() && AsanGiovese::is_invalid_access::(qemu, addr) { + self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, N)); } } pub fn write_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) { - if self.enabled() && AsanGiovese::is_invalid_access(qemu, addr, size) { + if self.enabled() && AsanGiovese::is_invalid_access_n(qemu, addr, size) { self.rt .report_or_crash(qemu, pc, AsanError::Write(addr, size)); } @@ -929,47 +867,51 @@ where type ModuleAddressFilter = StdAddressFilter; const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn post_qemu_init(&self, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { - emulator_modules.syscalls(Hook::Function(qasan_fake_syscall::)); + emulator_modules.pre_syscalls(Hook::Function(qasan_fake_syscall::)); if self.rt.error_callback.is_some() { emulator_modules.crash_function(oncrash_asan::); } } - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { emulator_modules.reads( Hook::Function(gen_readwrite_asan::), - Hook::Function(trace_read1_asan::), - Hook::Function(trace_read2_asan::), - Hook::Function(trace_read4_asan::), - Hook::Function(trace_read8_asan::), + Hook::Function(trace_read_asan::), + Hook::Function(trace_read_asan::), + Hook::Function(trace_read_asan::), + Hook::Function(trace_read_asan::), Hook::Function(trace_read_n_asan::), ); if emulator_modules.get::().is_none() { emulator_modules.writes( Hook::Function(gen_readwrite_asan::), - Hook::Function(trace_write1_asan::), - Hook::Function(trace_write2_asan::), - Hook::Function(trace_write4_asan::), - Hook::Function(trace_write8_asan::), + Hook::Function(trace_write_asan::), + Hook::Function(trace_write_asan::), + Hook::Function(trace_write_asan::), + Hook::Function(trace_write_asan::), Hook::Function(trace_write_n_asan::), ); } else { // track writes for both modules as opt emulator_modules.writes( Hook::Function(gen_write_asan_snapshot::), - Hook::Function(trace_write1_asan_snapshot::), - Hook::Function(trace_write2_asan_snapshot::), - Hook::Function(trace_write4_asan_snapshot::), - Hook::Function(trace_write8_asan_snapshot::), + Hook::Function(trace_write_asan_snapshot::), + Hook::Function(trace_write_asan_snapshot::), + Hook::Function(trace_write_asan_snapshot::), + Hook::Function(trace_write_asan_snapshot::), Hook::Function(trace_write_n_asan_snapshot::), ); } @@ -977,21 +919,23 @@ where fn pre_exec( &mut self, - emulator_modules: &mut EmulatorModules, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, ) where ET: EmulatorModuleTuple, { if self.empty { - self.rt.snapshot(emulator_modules.qemu()); + self.rt.snapshot(qemu); self.empty = false; } } fn post_exec( &mut self, - emulator_modules: &mut EmulatorModules, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, _observers: &mut OT, @@ -1000,7 +944,7 @@ where OT: ObserversTuple, ET: EmulatorModuleTuple, { - if self.reset(emulator_modules.qemu()) == AsanRollback::HasLeaks { + if self.reset(qemu) == AsanRollback::HasLeaks { *exit_kind = ExitKind::Crash; } } @@ -1014,18 +958,21 @@ where } } -pub fn oncrash_asan(emulator_modules: &mut EmulatorModules, target_sig: i32) -where +pub fn oncrash_asan( + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + target_sig: i32, +) where ET: EmulatorModuleTuple, S: Unpin + UsesInput, { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); let pc: GuestAddr = qemu.read_reg(Regs::Pc).unwrap(); h.rt.report(qemu, pc, AsanError::Signal(target_sig)); } pub fn gen_readwrite_asan( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -1044,7 +991,8 @@ where } } -pub fn trace_read1_asan( +pub fn trace_read_asan( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, @@ -1053,54 +1001,12 @@ pub fn trace_read1_asan( ET: EmulatorModuleTuple, S: Unpin + UsesInput, { - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.read_1(qemu, id as GuestAddr, addr); -} - -pub fn trace_read2_asan( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.read_2(qemu, id as GuestAddr, addr); -} - -pub fn trace_read4_asan( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.read_4(qemu, id as GuestAddr, addr); -} - -pub fn trace_read8_asan( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); - h.read_8(qemu, id as GuestAddr, addr); + h.read::(qemu, id as GuestAddr, addr); } pub fn trace_read_n_asan( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, @@ -1110,12 +1016,12 @@ pub fn trace_read_n_asan( S: Unpin + UsesInput, ET: EmulatorModuleTuple, { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); h.read_n(qemu, id as GuestAddr, addr, size); } -pub fn trace_write1_asan( +pub fn trace_write_asan( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, @@ -1124,54 +1030,12 @@ pub fn trace_write1_asan( S: Unpin + UsesInput, ET: EmulatorModuleTuple, { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); - h.write_1(qemu, id as GuestAddr, addr); -} - -pub fn trace_write2_asan( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.write_2(qemu, id as GuestAddr, addr); -} - -pub fn trace_write4_asan( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.write_4(qemu, id as GuestAddr, addr); -} - -pub fn trace_write8_asan( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.write_8(qemu, id as GuestAddr, addr); + h.write::(qemu, id as GuestAddr, addr); } pub fn trace_write_n_asan( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, @@ -1181,12 +1045,12 @@ pub fn trace_write_n_asan( S: Unpin + UsesInput, ET: EmulatorModuleTuple, { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); h.read_n(qemu, id as GuestAddr, addr, size); } pub fn gen_write_asan_snapshot( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -1205,61 +1069,8 @@ where } } -pub fn trace_write1_asan_snapshot( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - if id != 0 { - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.write_1(qemu, id as GuestAddr, addr); - } - let h = emulator_modules.get_mut::().unwrap(); - h.access(addr, 1); -} - -pub fn trace_write2_asan_snapshot( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - if id != 0 { - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.write_2(qemu, id as GuestAddr, addr); - } - let h = emulator_modules.get_mut::().unwrap(); - h.access(addr, 2); -} - -pub fn trace_write4_asan_snapshot( - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, - addr: GuestAddr, -) where - S: Unpin + UsesInput, - ET: EmulatorModuleTuple, -{ - if id != 0 { - let qemu = emulator_modules.qemu(); - let h = emulator_modules.get_mut::().unwrap(); - h.write_4(qemu, id as GuestAddr, addr); - } - let h = emulator_modules.get_mut::().unwrap(); - h.access(addr, 4); -} - -pub fn trace_write8_asan_snapshot( +pub fn trace_write_asan_snapshot( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, @@ -1269,15 +1080,15 @@ pub fn trace_write8_asan_snapshot( ET: EmulatorModuleTuple, { if id != 0 { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); - h.write_8(qemu, id as GuestAddr, addr); + h.write::(qemu, id as GuestAddr, addr); } let h = emulator_modules.get_mut::().unwrap(); - h.access(addr, 8); + h.access(addr, N); } pub fn trace_write_n_asan_snapshot( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, id: u64, @@ -1288,7 +1099,6 @@ pub fn trace_write_n_asan_snapshot( ET: EmulatorModuleTuple, { if id != 0 { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); h.read_n(qemu, id as GuestAddr, addr, size); } @@ -1298,6 +1108,7 @@ pub fn trace_write_n_asan_snapshot( #[allow(clippy::too_many_arguments)] pub fn qasan_fake_syscall( + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, sys_num: i32, @@ -1315,7 +1126,6 @@ where ET: EmulatorModuleTuple, { if sys_num == QASAN_FAKESYS_NR { - let qemu = emulator_modules.qemu(); let h = emulator_modules.get_mut::().unwrap(); match QasanAction::try_from(a0).expect("Invalid QASan action number") { QasanAction::CheckLoad => { diff --git a/libafl_qemu/src/modules/usermode/asan_guest.rs b/libafl_qemu/src/modules/usermode/asan_guest.rs index 567d98048e0..5d53546f43d 100644 --- a/libafl_qemu/src/modules/usermode/asan_guest.rs +++ b/libafl_qemu/src/modules/usermode/asan_guest.rs @@ -91,7 +91,7 @@ pub fn init_qemu_with_asan_guest( ASAN_GUEST_INITED = true; } - let emu = Qemu::init(args)?; + let emu = Qemu::init(args.as_slice())?; Ok((emu, asan_lib)) } @@ -206,6 +206,7 @@ where } fn gen_readwrite_guest_asan( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, @@ -244,6 +245,7 @@ where unsafe fn libafl_tcg_gen_asan(addr: *mut TCGTemp, size: usize) {} fn guest_trace_error_asan( + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, _id: u64, @@ -256,6 +258,7 @@ fn guest_trace_error_asan( } fn guest_trace_error_n_asan( + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, _id: u64, @@ -275,8 +278,12 @@ where { type ModuleAddressFilter = F; - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, S: Unpin + UsesInput, { diff --git a/libafl_qemu/src/modules/usermode/injections.rs b/libafl_qemu/src/modules/usermode/injections.rs index 9d62e7b017c..fb13bb7b336 100644 --- a/libafl_qemu/src/modules/usermode/injections.rs +++ b/libafl_qemu/src/modules/usermode/injections.rs @@ -211,12 +211,15 @@ impl InjectionModule { }) } - fn on_call_check(emulator_modules: &mut EmulatorModules, id: usize, parameter: u8) - where + fn on_call_check( + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + id: usize, + parameter: u8, + ) where ET: EmulatorModuleTuple, S: Unpin + UsesInput, { - let qemu = emulator_modules.qemu(); let reg: GuestAddr = qemu .current_cpu() .unwrap() @@ -262,18 +265,21 @@ where { type ModuleAddressFilter = NopAddressFilter; - fn post_qemu_init(&self, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { - emulator_modules.syscalls(Hook::Function(syscall_hook::)); + emulator_modules.pre_syscalls(Hook::Function(syscall_hook::)); } - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn first_exec( + &mut self, + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where ET: EmulatorModuleTuple, { - let qemu = emulator_modules.qemu(); let mut libs: Vec = Vec::new(); for region in qemu.mappings() { @@ -324,8 +330,8 @@ where for hook_addr in hook_addrs { emulator_modules.instructions( hook_addr, - Hook::Closure(Box::new(move |hooks, _state, _guest_addr| { - Self::on_call_check(hooks, id, param); + Hook::Closure(Box::new(move |qemu, hooks, _state, _guest_addr| { + Self::on_call_check(qemu, hooks, id, param); })), true, ); @@ -346,6 +352,7 @@ where #[allow(clippy::too_many_arguments)] fn syscall_hook( // Our instantiated [`EmulatorModules`] + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, // Syscall number diff --git a/libafl_qemu/src/modules/usermode/snapshot.rs b/libafl_qemu/src/modules/usermode/snapshot.rs index 4eabc858c2a..b71a3cbc46c 100644 --- a/libafl_qemu/src/modules/usermode/snapshot.rs +++ b/libafl_qemu/src/modules/usermode/snapshot.rs @@ -675,7 +675,7 @@ where { type ModuleAddressFilter = NopAddressFilter; - fn post_qemu_init(&self, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -692,23 +692,24 @@ where } if !self.accurate_unmap { - emulator_modules.syscalls(Hook::Function(filter_mmap_snapshot::)); + emulator_modules.pre_syscalls(Hook::Function(filter_mmap_snapshot::)); } - emulator_modules.after_syscalls(Hook::Function(trace_mmap_snapshot::)); + emulator_modules.post_syscalls(Hook::Function(trace_mmap_snapshot::)); } fn pre_exec( &mut self, - emulator_modules: &mut EmulatorModules, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, ) where ET: EmulatorModuleTuple, { if self.empty { - self.snapshot(emulator_modules.qemu()); + self.snapshot(qemu); } else { - self.reset(emulator_modules.qemu()); + self.reset(qemu); } } @@ -722,6 +723,7 @@ where } pub fn trace_write_snapshot( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, _id: u64, @@ -735,6 +737,7 @@ pub fn trace_write_snapshot( } pub fn trace_write_n_snapshot( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, _id: u64, @@ -751,6 +754,7 @@ pub fn trace_write_n_snapshot( #[allow(clippy::too_many_arguments)] #[allow(non_upper_case_globals)] pub fn filter_mmap_snapshot( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, sys_num: i32, @@ -779,6 +783,7 @@ where #[allow(clippy::too_many_arguments, clippy::too_many_lines)] #[allow(non_upper_case_globals)] pub fn trace_mmap_snapshot( + _qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, result: GuestAddr, diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index de43fbc6fd0..6ab1e51f5d3 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -354,7 +354,7 @@ mod test { fn usermode() { let program = "/bin/pwd"; let qemu_config = QemuConfig::builder().program("/bin/pwd").build(); - let qemu = Qemu::init(&qemu_config).unwrap(); + let qemu = Qemu::init(qemu_config).unwrap(); let config = qemu.get_config().unwrap(); assert_eq!(config.to_string().trim(), program.trim()); } diff --git a/libafl_qemu/src/qemu/hooks.rs b/libafl_qemu/src/qemu/hooks.rs index 97cc83e96cc..3f37465bf0d 100644 --- a/libafl_qemu/src/qemu/hooks.rs +++ b/libafl_qemu/src/qemu/hooks.rs @@ -475,7 +475,6 @@ create_hook_types!( ) -> SyscallHookResult, >, extern "C" fn( - Qemu, *const (), i32, GuestAddr, @@ -544,7 +543,6 @@ create_hook_types!( ) -> GuestAddr, >, extern "C" fn( - Qemu, *const (), GuestAddr, i32, diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 2964d0d737f..a1c18b1b141 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -166,7 +166,7 @@ where T: AsRef, { fn from(cli: &[T]) -> Self { - QemuParams::Cli(cli.into_iter().map(|x| x.as_ref().into()).collect()) + QemuParams::Cli(cli.iter().map(|x| x.as_ref().into()).collect()) } } @@ -493,8 +493,11 @@ impl Qemu { let args: Vec = match params { QemuParams::Config(cfg) => { - let qemu_args: Vec = - cfg.to_string().split(" ").map(|x| x.to_string()).collect(); + let qemu_args: Vec = cfg + .to_string() + .split(' ') + .map(ToString::to_string) + .collect(); QEMU_CONFIG.set(cfg.clone()).map_err(|_| { unreachable!("QEMU_CONFIG was already set but Qemu was not init!") diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index f9d3958f57e..c0fbef4c595 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -118,7 +118,7 @@ where { /// Run the fuzzer #[allow(clippy::too_many_lines, clippy::similar_names)] - pub fn run(&mut self, qemu: Qemu) { + pub fn run(&mut self, qemu_cli: &[String]) { let conf = match self.configuration.as_ref() { Some(name) => EventConfig::from_name(name), None => EventConfig::AlwaysUnique, @@ -240,7 +240,10 @@ where ExitKind::Ok }; - let emulator = Emulator::empty().qemu(qemu).modules(modules).build()?; + let emulator = Emulator::empty() + .qemu_cli(qemu_cli.to_owned()) + .modules(modules) + .build()?; let executor = QemuExecutor::new( emulator, @@ -357,7 +360,10 @@ where ExitKind::Ok }; - let emulator = Emulator::empty().qemu(qemu).modules(modules).build()?; + let emulator = Emulator::empty() + .qemu_cli(qemu_cli.to_owned()) + .modules(modules) + .build()?; let mut executor = QemuExecutor::new( emulator, @@ -476,7 +482,6 @@ pub mod pybind { use std::path::PathBuf; use libafl_bolts::core_affinity::Cores; - use libafl_qemu::qemu::pybind::Qemu; use pyo3::{prelude::*, types::PyBytes}; use crate::qemu; @@ -533,7 +538,7 @@ pub mod pybind { /// Run the fuzzer #[allow(clippy::needless_pass_by_value)] - pub fn run(&self, qemu: &Qemu, harness: PyObject) { + pub fn run(&self, qemu_cli: Vec, harness: PyObject) { qemu::QemuBytesCoverageSugar::builder() .input_dirs(&self.input_dirs) .output_dir(self.output_dir.clone()) @@ -552,7 +557,7 @@ pub mod pybind { .tokens_file(self.tokens_file.clone()) .iterations(self.iterations) .build() - .run(qemu.qemu); + .run(&qemu_cli); } } From 5346f83ede3d52dc77e936deb5e68bb1dd7d7610 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Tue, 26 Nov 2024 20:30:41 +0100 Subject: [PATCH 19/93] fmt --- libafl_qemu/src/modules/usermode/asan.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 89f1a1eb852..5dc8c8e993f 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -150,6 +150,7 @@ use std::pin::Pin; use libafl_qemu_sys::GuestAddr; use object::{Object, ObjectSection}; + use crate::{ emu::EmulatorModules, modules::{AddressFilter, StdAddressFilter}, @@ -284,8 +285,7 @@ impl AsanGiovese { #[inline] #[must_use] - pub fn is_invalid_access(qemu: Qemu, addr: GuestAddr) -> bool - { + pub fn is_invalid_access(qemu: Qemu, addr: GuestAddr) -> bool { const { assert!(N == 1 || N == 2 || N == 4 || N == 8) }; unsafe { From 02a2f1fd0c1a8d9ec15b5acd3ac82d04964736e4 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:07:03 +0100 Subject: [PATCH 20/93] Add comment to KVM pt_mode check --- libafl_intelpt/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 8d304aba7c7..25e4cb63d26 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -739,6 +739,9 @@ pub fn availability_in_qemu_kvm() -> Result<(), String> { #[cfg(target_os = "linux")] { let kvm_pt_mode_path = "/sys/module/kvm_intel/parameters/pt_mode"; + // Ignore the case when the file does not exist since it has been removed. + // KVM default is `System` mode + // https://lore.kernel.org/all/20241101185031.1799556-1-seanjc@google.com/t/#u if let Ok(s) = fs::read_to_string(kvm_pt_mode_path) { match s.trim().parse::().map(TryInto::try_into) { Ok(Ok(KvmPTMode::System)) => (), From 9a84af48e2144a1047745dfdfdd012baf0082dee Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:44:39 +0100 Subject: [PATCH 21/93] refactor --- libafl_intelpt/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 25e4cb63d26..2b529a76bdb 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -342,8 +342,9 @@ impl IntelPT { }; } - if cfg!(feature = "export_raw") { - self.set_last_decode_trace(data.as_ref().to_vec()); + #[cfg(feature = "export_raw")] + { + self.last_decode_trace = data.as_ref().to_vec(); } // Advance the trace pointer up to the latest sync point, otherwise next execution's trace @@ -423,14 +424,6 @@ impl IntelPT { Ok(()) } - #[cfg(not(feature = "export_raw"))] - #[allow(clippy::unused_self)] - fn set_last_decode_trace(&mut self, _trace: Vec) {} - #[cfg(feature = "export_raw")] - fn set_last_decode_trace(&mut self, trace: Vec) { - self.last_decode_trace = trace; - } - /// Get the raw trace used in the last decoding #[cfg(feature = "export_raw")] pub fn last_decode_trace(&self) -> Vec { From 761964e3beb9bee85473d9e7f415c10015cfc936 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:43:26 +0100 Subject: [PATCH 22/93] Add intel_pt_export_raw feature in libafl --- libafl/Cargo.toml | 2 ++ libafl/src/executors/hooks/intel_pt.rs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 9c3aa8fabda..4108d442d97 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -115,6 +115,8 @@ intel_pt = [ "dep:nix", "dep:num_enum", ] +# Save all the Intel PT raw traces to files, use only for debug +intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"] ## Enables features for corpus minimization cmin = ["z3"] diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index 005d6ff7aa2..640b9b1a1c6 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -59,9 +59,29 @@ where .intel_pt .decode_traces_into_map(&mut self.image.0, slice) .inspect_err(|e| log::warn!("Intel PT trace decoding failed: {e}")); + #[cfg(feature = "intel_pt_export_raw")] + { + let _ = trace_to_file(&self.intel_pt) + .inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}")); + } } } +#[cfg(feature = "intel_pt_export_raw")] +fn trace_to_file(pt: &IntelPT) -> Result<(), Error> { + use std::{fs, io::Write, path::Path, time}; + + let traces_dir = Path::new("traces"); + fs::create_dir_all(traces_dir)?; + let timestamp = time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .unwrap() + .as_micros(); + let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?; + file.write_all(&*pt.last_decode_trace())?; + Ok(()) +} + // It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't // like this (and `TryFromIter` is not a thing atm) fn sections_to_image( From 17059d728c1c509d335eed7d31f29e9f27e48d32 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 27 Nov 2024 13:43:14 +0100 Subject: [PATCH 23/93] fix fuzzers --- .../binary_only/fuzzbench_qemu/src/fuzzer.rs | 48 ++++++++--------- fuzzers/binary_only/qemu_cmin/src/fuzzer.rs | 51 ++++++++++--------- libafl_qemu/src/qemu/mod.rs | 11 +++- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs index a26a588bb47..1d522261ba6 100644 --- a/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs @@ -55,7 +55,6 @@ use libafl_qemu::{ GuestReg, //snapshot::QemuSnapshotHelper, MmapPerms, - Qemu, QemuExecutor, QemuExitError, QemuExitReason, @@ -175,7 +174,30 @@ fn fuzz( env::remove_var("LD_LIBRARY_PATH"); let args: Vec = env::args().collect(); - let qemu = Qemu::init(&args).expect("QEMU init failed"); + + // Create an observation channel using the coverage map + let mut edges_observer = unsafe { + HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( + "edges", + OwnedMutSlice::from_raw_parts_mut(edges_map_mut_ptr(), EDGES_MAP_ALLOCATED_SIZE), + &raw mut MAX_EDGES_FOUND, + )) + .track_indices() + }; + + let modules = tuple_list!( + StdEdgeCoverageModule::builder() + .map_observer(edges_observer.as_mut()) + .build() + .unwrap(), + CmpLogModule::default(), + // QemuAsanHelper::default(asan), + //QemuSnapshotHelper::new() + ); + + let emulator = Emulator::empty().qemu_cli(args).modules(modules).build()?; + let qemu = emulator.qemu(); + // let qemu = Qemu::init(&args).expect("QEMU init failed"); // let (emu, asan) = init_with_asan(&mut args, &mut env).unwrap(); let mut elf_buffer = Vec::new(); @@ -255,16 +277,6 @@ fn fuzz( }, }; - // Create an observation channel using the coverage map - let mut edges_observer = unsafe { - HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( - "edges", - OwnedMutSlice::from_raw_parts_mut(edges_map_mut_ptr(), EDGES_MAP_ALLOCATED_SIZE), - &raw mut MAX_EDGES_FOUND, - )) - .track_indices() - }; - // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); @@ -364,18 +376,6 @@ fn fuzz( ExitKind::Ok }; - let modules = tuple_list!( - StdEdgeCoverageModule::builder() - .map_observer(edges_observer.as_mut()) - .build() - .unwrap(), - CmpLogModule::default(), - // QemuAsanHelper::default(asan), - //QemuSnapshotHelper::new() - ); - - let emulator = Emulator::empty().qemu(qemu).modules(modules).build()?; - // Create the executor for an in-process function with one observer for edge coverage and one for the execution time let executor = QemuExecutor::new( emulator, diff --git a/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs b/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs index ba00041efd6..6aba2d5dfd4 100644 --- a/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs @@ -28,8 +28,8 @@ use libafl_bolts::{ }; use libafl_qemu::{ elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, CallingConvention, - Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitError, QemuExitReason, - QemuForkExecutor, QemuShutdownCause, Regs, + Emulator, GuestAddr, GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuForkExecutor, + QemuShutdownCause, Regs, }; use libafl_targets::{EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR}; @@ -113,7 +113,31 @@ pub fn fuzz() -> Result<(), Error> { log::debug!("ARGS: {:#?}", options.args); env::remove_var("LD_LIBRARY_PATH"); - let qemu = Qemu::init(&options.args).unwrap(); + + let mut shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + let mut edges_shmem = shmem_provider.new_shmem(EDGES_MAP_DEFAULT_SIZE).unwrap(); + let edges = edges_shmem.as_slice_mut(); + unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() }; + + let mut edges_observer = unsafe { + HitcountsMapObserver::new(ConstMapObserver::from_mut_ptr( + "edges", + NonNull::new(edges.as_mut_ptr()) + .expect("The edge map pointer is null.") + .cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(), + )) + }; + + let modules = tuple_list!(StdEdgeCoverageChildModule::builder() + .const_map_observer(edges_observer.as_mut()) + .build()?); + + let emulator = Emulator::empty() + .qemu_cli(options.args) + .modules(modules) + .build()?; + let qemu = emulator.qemu(); let mut elf_buffer = Vec::new(); let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); @@ -139,8 +163,6 @@ pub fn fuzz() -> Result<(), Error> { let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); - let mut shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); - let monitor = SimpleMonitor::with_user_monitor(|s| { println!("{s}"); }); @@ -157,19 +179,6 @@ pub fn fuzz() -> Result<(), Error> { }, }; - let mut edges_shmem = shmem_provider.new_shmem(EDGES_MAP_DEFAULT_SIZE).unwrap(); - let edges = edges_shmem.as_slice_mut(); - unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() }; - - let mut edges_observer = unsafe { - HitcountsMapObserver::new(ConstMapObserver::from_mut_ptr( - "edges", - NonNull::new(edges.as_mut_ptr()) - .expect("The edge map pointer is null.") - .cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(), - )) - }; - let mut feedback = MaxMapFeedback::new(&edges_observer); #[allow(clippy::let_unit_value)] @@ -223,12 +232,6 @@ pub fn fuzz() -> Result<(), Error> { ExitKind::Ok }; - let modules = tuple_list!(StdEdgeCoverageChildModule::builder() - .const_map_observer(edges_observer.as_mut()) - .build()?); - - let emulator = Emulator::empty().qemu(qemu).modules(modules).build()?; - let mut executor = QemuForkExecutor::new( emulator, &mut harness, diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index a1c18b1b141..73ea2a71f6e 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -170,12 +170,21 @@ where } } +impl From<&Vec> for QemuParams +where + T: AsRef, +{ + fn from(cli: &Vec) -> Self { + cli.as_slice().into() + } +} + impl From> for QemuParams where T: AsRef, { fn from(cli: Vec) -> Self { - cli.as_slice().into() + (&cli).into() } } From c50ba05d8299385cd1c1accb8727287afc5c2c06 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 27 Nov 2024 19:00:14 +0100 Subject: [PATCH 24/93] * refactor asan and asanguest modules to avoid custom init of QEMU and use the module interface instead. * adapt qemu_launcher example to fully work with emulator, since qemu must now be initialized by emulator. --- .../binary_only/qemu_launcher/src/client.rs | 45 +--- .../binary_only/qemu_launcher/src/instance.rs | 33 ++- libafl_concolic/symcc_runtime/Cargo.toml | 2 +- libafl_qemu/Cargo.toml | 4 +- libafl_qemu/src/emu/builder.rs | 22 +- libafl_qemu/src/emu/hooks.rs | 8 + libafl_qemu/src/emu/mod.rs | 12 +- libafl_qemu/src/executor.rs | 4 +- libafl_qemu/src/modules/calls.rs | 2 +- libafl_qemu/src/modules/drcov.rs | 2 +- libafl_qemu/src/modules/edges/helpers.rs | 11 +- libafl_qemu/src/modules/mod.rs | 25 +- libafl_qemu/src/modules/usermode/asan.rs | 172 ++++++------ .../src/modules/usermode/asan_guest.rs | 246 +++++++++--------- .../src/modules/usermode/injections.rs | 2 +- libafl_qemu/src/modules/usermode/mod.rs | 4 +- libafl_qemu/src/modules/usermode/snapshot.rs | 2 +- libafl_qemu/src/qemu/mod.rs | 33 ++- libafl_qemu/src/qemu/systemmode.rs | 2 +- 19 files changed, 321 insertions(+), 310 deletions(-) diff --git a/fuzzers/binary_only/qemu_launcher/src/client.rs b/fuzzers/binary_only/qemu_launcher/src/client.rs index b41c5339bce..02e15a4fe1e 100644 --- a/fuzzers/binary_only/qemu_launcher/src/client.rs +++ b/fuzzers/binary_only/qemu_launcher/src/client.rs @@ -10,15 +10,12 @@ use libafl::{ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list}; #[cfg(feature = "injections")] use libafl_qemu::modules::injections::InjectionModule; -use libafl_qemu::{ - modules::{ - asan::{init_qemu_with_asan, AsanModule}, - asan_guest::{init_qemu_with_asan_guest, AsanGuestModule}, - cmplog::CmpLogModule, - DrCovModule, - }, - Qemu, -}; +use libafl_qemu::{modules::{ + asan::AsanModule, + asan_guest::AsanGuestModule, + cmplog::CmpLogModule, + DrCovModule, +}}; use crate::{ harness::Harness, @@ -78,18 +75,6 @@ impl Client<'_> { Err(Error::empty_optional("Multiple ASAN modes configured"))?; } - let (qemu, mut asan, mut asan_lib) = { - if is_asan { - let (emu, asan) = init_qemu_with_asan(&mut args, &mut env)?; - (emu, Some(asan), None) - } else if is_asan_guest { - let (emu, asan_lib) = init_qemu_with_asan_guest(&mut args, &mut env)?; - (emu, None, Some(asan_lib)) - } else { - (Qemu::init(&args)?, None, None) - } - }; - #[cfg(not(feature = "injections"))] let injection_module = None; @@ -109,8 +94,6 @@ impl Client<'_> { } }); - let harness = Harness::init(qemu).expect("Error setting up harness."); - let is_cmplog = self.options.is_cmplog_core(core_id); let extra_tokens = injection_module @@ -120,8 +103,6 @@ impl Client<'_> { let instance_builder = Instance::builder() .options(self.options) - .qemu(qemu) - .harness(harness) .mgr(mgr) .core_id(core_id) .extra_tokens(extra_tokens); @@ -140,7 +121,7 @@ impl Client<'_> { instance_builder.build().run( tuple_list!( CmpLogModule::default(), - AsanModule::default(asan.take().unwrap()), + AsanModule::default(&env), injection_module, ), state, @@ -149,7 +130,7 @@ impl Client<'_> { instance_builder.build().run( tuple_list!( CmpLogModule::default(), - AsanModule::default(asan.take().unwrap()), + AsanModule::default(&env), ), state, ) @@ -159,7 +140,7 @@ impl Client<'_> { instance_builder.build().run( tuple_list!( CmpLogModule::default(), - AsanGuestModule::default(qemu, &asan_lib.take().unwrap()), + AsanGuestModule::default(&env), injection_module ), state, @@ -168,7 +149,7 @@ impl Client<'_> { instance_builder.build().run( tuple_list!( CmpLogModule::default(), - AsanGuestModule::default(qemu, &asan_lib.take().unwrap()), + AsanGuestModule::default(&env), ), state, ) @@ -176,17 +157,17 @@ impl Client<'_> { } else if is_asan { if let Some(injection_module) = injection_module { instance_builder.build().run( - tuple_list!(AsanModule::default(asan.take().unwrap()), injection_module), + tuple_list!(AsanModule::default(&env), injection_module), state, ) } else { instance_builder.build().run( - tuple_list!(AsanModule::default(asan.take().unwrap()),), + tuple_list!(AsanModule::default(&env),), state, ) } } else if is_asan_guest { - let modules = tuple_list!(AsanGuestModule::default(qemu, &asan_lib.take().unwrap())); + let modules = tuple_list!(AsanGuestModule::default(&env)); instance_builder.build().run(modules, state) } else if is_cmplog { if let Some(injection_module) = injection_module { diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index ab6e5588d69..2fa2c63054d 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -37,16 +37,14 @@ use libafl_bolts::{ rands::StdRand, tuples::{tuple_list, Merge, Prepend}, }; -use libafl_qemu::{ - elf::EasyElf, - modules::{ - cmplog::CmpLogObserver, EmulatorModuleTuple, StdAddressFilter, StdEdgeCoverageModule, - }, - Emulator, GuestAddr, Qemu, QemuExecutor, -}; +use libafl_qemu::{elf::EasyElf, modules::{ + cmplog::CmpLogObserver, EmulatorModuleTuple, StdAddressFilter, StdEdgeCoverageModule, +}, Emulator, GuestAddr, Qemu, QemuExecutor}; use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; use typed_builder::TypedBuilder; - +use libafl_bolts::tuples::MatchFirstType; +use libafl_qemu::modules::{EdgeCoverageModule, EmulatorModule, NopPageFilter}; +use libafl_qemu::modules::edges::EdgeCoverageFullVariant; use crate::{harness::Harness, options::FuzzerOptions}; pub type ClientState = @@ -62,9 +60,6 @@ pub type ClientMgr = pub struct Instance<'a, M: Monitor> { options: &'a FuzzerOptions, /// The harness. We create it before forking, then `take()` it inside the client. - #[builder(setter(strip_option))] - harness: Option, - qemu: Qemu, mgr: ClientMgr, core_id: CoreId, #[builder(default)] @@ -124,10 +119,18 @@ impl Instance<'_, M> { let edge_coverage_module = StdEdgeCoverageModule::builder() .map_observer(edges_observer.as_mut()) - .address_filter(self.coverage_filter(self.qemu)?) .build()?; let modules = modules.prepend(edge_coverage_module); + let mut emulator = Emulator::empty().modules(modules).build()?; + let harness = Harness::init(emulator.qemu()).expect("Error setting up harness."); + let qemu = emulator.qemu(); + + // update address filter after qemu has been initialized + as EmulatorModule>::update_address_filter(emulator.modules_mut() + .modules_mut() + .match_first_type_mut::>() + .expect("Could not find back the edge module"), qemu, self.coverage_filter(qemu)?); // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); @@ -195,10 +198,6 @@ impl Instance<'_, M> { state.add_metadata(tokens); - let harness = self - .harness - .take() - .expect("The harness can never be None here!"); harness.post_fork(); let mut harness = |_emulator: &mut Emulator<_, _, _, _, _>, @@ -208,8 +207,6 @@ impl Instance<'_, M> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let emulator = Emulator::empty().qemu(self.qemu).modules(modules).build()?; - if let Some(rerun_input) = &self.options.rerun_input { // TODO: We might want to support non-bytes inputs at some point? let bytes = fs::read(rerun_input) diff --git a/libafl_concolic/symcc_runtime/Cargo.toml b/libafl_concolic/symcc_runtime/Cargo.toml index c29e0362542..a0f91e025e0 100644 --- a/libafl_concolic/symcc_runtime/Cargo.toml +++ b/libafl_concolic/symcc_runtime/Cargo.toml @@ -44,7 +44,7 @@ cmake = { workspace = true } bindgen = { workspace = true } regex = { workspace = true } which = { workspace = true } -symcc_libafl = { workspace = true, default-features = true, version = "0.14.0" } +symcc_libafl = { workspace = true, default-features = true } [lints] workspace = true diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index b699533e0e9..97eb7ac09da 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -90,7 +90,7 @@ clippy = ["libafl_qemu_sys/clippy"] [dependencies] libafl = { workspace = true, features = ["std", "derive", "regex"] } libafl_bolts = { workspace = true, features = ["std", "derive"] } -libafl_targets = { workspace = true, default-features = true, version = "0.14.0" } +libafl_targets = { workspace = true, default-features = true } libafl_qemu_sys = { workspace = true } libafl_derive = { workspace = true, default-features = true } @@ -131,7 +131,7 @@ getset = "0.1.3" document-features = { workspace = true, optional = true } [build-dependencies] -libafl_qemu_build = { workspace = true, default-features = true, version = "0.14.0" } +libafl_qemu_build = { workspace = true, default-features = true } pyo3-build-config = { version = "0.23.1", optional = true } rustversion = { workspace = true } bindgen = { workspace = true } diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index e38be3cdfb9..222f9ef1780 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -4,7 +4,7 @@ use libafl::{ inputs::{HasTargetBytes, UsesInput}, state::{HasExecutions, State}, }; -use libafl_bolts::tuples::{tuple_list, Prepend}; +use libafl_bolts::tuples::{tuple_list, Append, Prepend}; #[cfg(feature = "systemmode")] use crate::FastSnapshotManager; @@ -109,7 +109,7 @@ where CM: CommandManager, ET: EmulatorModuleTuple, { - let qemu_parameters = self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?; + let mut qemu_parameters = self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?; let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; @@ -119,7 +119,7 @@ where unsafe { emulator_modules .modules_mut() - .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked()); + .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked(), &mut qemu_parameters); } let qemu = Qemu::init(qemu_parameters)?; @@ -163,7 +163,7 @@ where ) } - pub fn add_module(self, module: EM) -> EmulatorBuilder + pub fn prepend_module(self, module: EM) -> EmulatorBuilder where EM: EmulatorModule + Unpin, ET: EmulatorModuleTuple, @@ -177,6 +177,20 @@ where ) } + pub fn append_module(self, module: EM) -> EmulatorBuilder + where + EM: EmulatorModule + Unpin, + ET: EmulatorModuleTuple, + { + EmulatorBuilder::new( + self.modules.append(module), + self.driver, + self.command_manager, + self.snapshot_manager, + self.qemu_parameters, + ) + } + pub fn driver(self, driver: ED2) -> EmulatorBuilder { EmulatorBuilder::new( self.modules, diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index 7bb703c6b19..3973bbc079d 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -176,6 +176,10 @@ where } } + pub fn qemu_hooks(&self) -> QemuHooks { + self.qemu_hooks + } + pub fn instruction_closure( &mut self, addr: GuestAddr, @@ -1192,6 +1196,10 @@ where self.modules.as_ref().get_ref() } + pub fn hooks(&mut self) -> &EmulatorHooks { + &self.hooks + } + pub fn hooks_mut(&mut self) -> &mut EmulatorHooks { &mut self.hooks } diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index e3e140c0cfb..e111d4c739a 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -14,14 +14,7 @@ use libafl::{ }; use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestUsize, GuestVirtAddr}; -use crate::{ - breakpoint::{Breakpoint, BreakpointId}, - command::{CommandError, CommandManager, NopCommandManager, StdCommandManager}, - modules::EmulatorModuleTuple, - sync_exit::SyncExit, - Qemu, QemuExitError, QemuExitReason, QemuHooks, QemuInitError, QemuMemoryChunk, - QemuShutdownCause, Regs, CPU, -}; +use crate::{breakpoint::{Breakpoint, BreakpointId}, command::{CommandError, CommandManager, NopCommandManager, StdCommandManager}, modules::EmulatorModuleTuple, sync_exit::SyncExit, Qemu, QemuExitError, QemuExitReason, QemuHooks, QemuInitError, QemuMemoryChunk, QemuParams, QemuShutdownCause, Regs, CPU}; mod hooks; pub use hooks::*; @@ -329,6 +322,7 @@ where snapshot_manager: SM, command_manager: CM, ) -> Result { + let mut qemu_parameters: QemuParams = qemu_args.into(); let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; let mut emulator_modules = EmulatorModules::new(emulator_hooks, modules); @@ -336,7 +330,7 @@ where unsafe { emulator_modules .modules_mut() - .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked()); + .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked(), &mut qemu_parameters); } let qemu = Qemu::init(qemu_args)?; diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index e5662c913dd..7aa9542bc0e 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -41,8 +41,10 @@ use libc::siginfo_t; #[cfg(feature = "usermode")] use crate::EmulatorModules; use crate::{ - command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver, Qemu, + command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver, }; +#[cfg(feature = "usermode")] +use crate::Qemu; pub struct QemuExecutor<'a, CM, ED, ET, H, OT, S, SM> where diff --git a/libafl_qemu/src/modules/calls.rs b/libafl_qemu/src/modules/calls.rs index 951f51e97f8..8088891050d 100644 --- a/libafl_qemu/src/modules/calls.rs +++ b/libafl_qemu/src/modules/calls.rs @@ -402,7 +402,7 @@ where #[cfg(feature = "systemmode")] type ModulePageFilter = NopPageFilter; - fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&mut self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { diff --git a/libafl_qemu/src/modules/drcov.rs b/libafl_qemu/src/modules/drcov.rs index 96212023288..8530575d808 100644 --- a/libafl_qemu/src/modules/drcov.rs +++ b/libafl_qemu/src/modules/drcov.rs @@ -265,7 +265,7 @@ where #[cfg(feature = "systemmode")] type ModulePageFilter = NopPageFilter; - fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&mut self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { diff --git a/libafl_qemu/src/modules/edges/helpers.rs b/libafl_qemu/src/modules/edges/helpers.rs index 74b96f46eb0..8cb8a8109f6 100644 --- a/libafl_qemu/src/modules/edges/helpers.rs +++ b/libafl_qemu/src/modules/edges/helpers.rs @@ -77,8 +77,9 @@ mod generators { } } + #[allow(unused_variables)] pub fn gen_unique_edge_ids( - _qemu: Qemu, + qemu: Qemu, emulator_modules: &mut EmulatorModules, state: Option<&mut S>, src: GuestAddr, @@ -153,9 +154,9 @@ mod generators { } } - #[allow(clippy::unnecessary_cast)] + #[allow(clippy::unnecessary_cast, unused_variables)] pub fn gen_hashed_edge_ids( - _qemu: Qemu, + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, src: GuestAddr, @@ -205,9 +206,9 @@ mod generators { } } - #[allow(clippy::unnecessary_cast)] + #[allow(clippy::unnecessary_cast, unused_variables)] pub fn gen_hashed_block_ids( - _qemu: Qemu, + qemu: Qemu, emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, pc: GuestAddr, diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index 06dea61e094..67cc2f80419 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -40,7 +40,7 @@ pub mod drcov; #[cfg(not(cpu_target = "hexagon"))] pub use drcov::{DrCovMetadata, DrCovModule, DrCovModuleBuilder}; -use crate::{emu::EmulatorModules, Qemu}; +use crate::{emu::EmulatorModules, Qemu, QemuParams}; /// A module for `libafl_qemu`. // TODO remove 'static when specialization will be stable @@ -58,7 +58,10 @@ where /// Hook run **before** QEMU is initialized. /// This is always run when Emulator gets initialized, in any case. /// Install here hooks that should be alive for the whole execution of the VM, even before QEMU gets initialized. - fn pre_qemu_init(&self, _emulator_modules: &mut EmulatorModules) + /// + /// It is also possible to edit QEMU parameters, just before QEMU gets initialized. + /// Thus, the module can modify options for QEMU just before it gets initialized. + fn pre_qemu_init(&mut self, _emulator_modules: &mut EmulatorModules, _qemu_params: &mut QemuParams) where ET: EmulatorModuleTuple, { @@ -67,7 +70,7 @@ where /// Hook run **after** QEMU is initialized. /// This is always run when Emulator gets initialized, in any case. /// Install here hooks that should be alive for the whole execution of the VM, after QEMU gets initialized. - fn post_qemu_init(&self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -152,11 +155,11 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool; - fn pre_qemu_init_all(&self, emulator_modules: &mut EmulatorModules) + fn pre_qemu_init_all(&mut self, emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) where ET: EmulatorModuleTuple; - fn post_qemu_init_all(&self, qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn post_qemu_init_all(&mut self, qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple; @@ -211,13 +214,13 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn pre_qemu_init_all(&self, _emulator_modules: &mut EmulatorModules) + fn pre_qemu_init_all(&mut self, _emulator_modules: &mut EmulatorModules, _qemu_params: &mut QemuParams) where ET: EmulatorModuleTuple, { } - fn post_qemu_init_all(&self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) + fn post_qemu_init_all(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { @@ -276,15 +279,15 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = Head::HOOKS_DO_SIDE_EFFECTS || Tail::HOOKS_DO_SIDE_EFFECTS; - fn pre_qemu_init_all(&self, emulator_modules: &mut EmulatorModules) + fn pre_qemu_init_all(&mut self, emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) where ET: EmulatorModuleTuple, { - self.0.pre_qemu_init(emulator_modules); - self.1.pre_qemu_init_all(emulator_modules); + self.0.pre_qemu_init(emulator_modules, qemu_params); + self.1.pre_qemu_init_all(emulator_modules, qemu_params); } - fn post_qemu_init_all(&self, qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn post_qemu_init_all(&mut self, qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 5dc8c8e993f..2024da93977 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -11,15 +11,10 @@ use meminterval::{Interval, IntervalTree}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use rangemap::RangeMap; -use crate::{ - modules::{ - calls::FullBacktraceCollector, snapshot::SnapshotModule, EmulatorModule, - EmulatorModuleTuple, - }, - qemu::{MemAccessInfo, QemuInitError}, - sys::TCGTemp, - Qemu, Regs, -}; +use crate::{modules::{ + calls::FullBacktraceCollector, snapshot::SnapshotModule, EmulatorModule, + EmulatorModuleTuple, +}, qemu::MemAccessInfo, sys::TCGTemp, Qemu, QemuParams, Regs}; // TODO at some point, merge parts with libafl_frida @@ -176,8 +171,8 @@ impl core::fmt::Debug for AsanGiovese { } impl AsanGiovese { - unsafe fn map_shadow() { - assert!( + unsafe fn init(self: &mut Pin>, qemu_hooks: QemuHooks) { + assert_ne!( libc::mmap( HIGH_SHADOW_ADDR, HIGH_SHADOW_SIZE, @@ -185,9 +180,9 @@ impl AsanGiovese { MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON, -1, 0 - ) != MAP_FAILED + ), MAP_FAILED ); - assert!( + assert_ne!( libc::mmap( LOW_SHADOW_ADDR, LOW_SHADOW_SIZE, @@ -195,9 +190,9 @@ impl AsanGiovese { MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON, -1, 0 - ) != MAP_FAILED + ), MAP_FAILED ); - assert!( + assert_ne!( libc::mmap( GAP_SHADOW_ADDR, GAP_SHADOW_SIZE, @@ -205,12 +200,14 @@ impl AsanGiovese { MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON, -1, 0 - ) != MAP_FAILED + ), MAP_FAILED ); + + qemu_hooks.add_pre_syscall_hook(self.as_mut(), Self::fake_syscall); } #[must_use] - fn new(qemu_hooks: QemuHooks) -> Pin> { + fn new() -> Pin> { let res = Self { alloc_tree: Mutex::new(IntervalTree::new()), saved_tree: IntervalTree::new(), @@ -219,9 +216,7 @@ impl AsanGiovese { saved_shadow: HashMap::default(), snapshot_shadow: true, // By default, track the dirty shadow pages }; - let mut boxed = Box::pin(res); - qemu_hooks.add_pre_syscall_hook(boxed.as_mut(), Self::fake_syscall); - boxed + Box::pin(res) } extern "C" fn fake_syscall( @@ -646,64 +641,6 @@ impl AsanGiovese { } } -static mut ASAN_INITED: bool = false; - -pub fn init_qemu_with_asan( - args: &mut Vec, - env: &mut [(String, String)], -) -> Result<(Qemu, Pin>), QemuInitError> { - let current = env::current_exe().unwrap(); - let asan_lib = fs::canonicalize(current) - .unwrap() - .parent() - .unwrap() - .join("libqasan.so"); - let asan_lib = asan_lib - .to_str() - .expect("The path to the asan lib is invalid") - .to_string(); - let add_asan = - |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; - - // TODO: adapt since qemu does not take envp anymore as parameter - let mut added = false; - for (k, v) in &mut *env { - if k == "QEMU_SET_ENV" { - let mut new_v = vec![]; - for e in v.split(',') { - if e.starts_with("LD_PRELOAD=") { - added = true; - new_v.push(add_asan(e)); - } else { - new_v.push(e.to_string()); - } - } - *v = new_v.join(","); - } - } - for i in 0..args.len() { - if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { - added = true; - args[i + 1] = add_asan(&args[i + 1]); - } - } - - if !added { - args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); - args.insert(1, "-E".into()); - } - - unsafe { - AsanGiovese::map_shadow(); - ASAN_INITED = true; - } - - let qemu = Qemu::init(args.as_slice())?; - let rt = AsanGiovese::new(qemu.hooks()); - - Ok((qemu, rt)) -} - pub enum QemuAsanOptions { None, Snapshot, @@ -715,6 +652,7 @@ pub type AsanChildModule = AsanModule; #[derive(Debug)] pub struct AsanModule { + env: Vec<(String, String)>, enabled: bool, detect_leaks: bool, empty: bool, @@ -724,25 +662,28 @@ pub struct AsanModule { impl AsanModule { #[must_use] - pub fn default(rt: Pin>) -> Self { - Self::new(rt, StdAddressFilter::default(), &QemuAsanOptions::Snapshot) + pub fn default(env: &[(String, String)]) -> Self { + Self::new(StdAddressFilter::default(), &QemuAsanOptions::Snapshot, env) } #[must_use] pub fn new( - mut rt: Pin>, filter: StdAddressFilter, options: &QemuAsanOptions, + env: &[(String, String)], ) -> Self { - assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_qemu_with_asan(...) instead of just Qemu::init(...)"); let (snapshot, detect_leaks) = match options { QemuAsanOptions::None => (false, false), QemuAsanOptions::Snapshot => (true, false), QemuAsanOptions::DetectLeaks => (false, true), QemuAsanOptions::SnapshotDetectLeaks => (true, true), }; + + let mut rt = AsanGiovese::new(); rt.set_snapshot_shadow(snapshot); + Self { + env: env.to_vec(), enabled: true, detect_leaks, empty: true, @@ -753,21 +694,24 @@ impl AsanModule { #[must_use] pub fn with_error_callback( - mut rt: Pin>, filter: StdAddressFilter, error_callback: AsanErrorCallback, options: &QemuAsanOptions, + env: &[(String, String)], ) -> Self { - assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_qemu_with_asan(...) instead of just Qemu::init(...)"); let (snapshot, detect_leaks) = match options { QemuAsanOptions::None => (false, false), QemuAsanOptions::Snapshot => (true, false), QemuAsanOptions::DetectLeaks => (false, true), QemuAsanOptions::SnapshotDetectLeaks => (true, true), }; + + let mut rt = AsanGiovese::new(); rt.set_snapshot_shadow(snapshot); rt.set_error_callback(error_callback); + Self { + env: env.to_vec(), enabled: true, detect_leaks, empty: true, @@ -780,15 +724,15 @@ impl AsanModule { /// The `ASan` error report accesses [`FullBacktraceCollector`] #[must_use] pub unsafe fn with_asan_report( - rt: Pin>, filter: StdAddressFilter, options: &QemuAsanOptions, + env: &[(String, String)], ) -> Self { Self::with_error_callback( - rt, filter, Box::new(|rt, qemu, pc, err| unsafe { asan_report(rt, qemu, pc, &err) }), options, + env, ) } @@ -867,7 +811,61 @@ where type ModuleAddressFilter = StdAddressFilter; const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn pre_qemu_init(&mut self, emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) + where + ET: EmulatorModuleTuple, + { + let mut args: Vec = qemu_params.to_cli(); + + let current = env::current_exe().unwrap(); + let asan_lib = fs::canonicalize(current) + .unwrap() + .parent() + .unwrap() + .join("libqasan.so"); + let asan_lib = asan_lib + .to_str() + .expect("The path to the asan lib is invalid") + .to_string(); + let add_asan = + |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; + + // TODO: adapt since qemu does not take envp anymore as parameter + let mut added = false; + for (k, v) in &mut self.env { + if k == "QEMU_SET_ENV" { + let mut new_v = vec![]; + for e in v.split(',') { + if e.starts_with("LD_PRELOAD=") { + added = true; + new_v.push(add_asan(e)); + } else { + new_v.push(e.to_string()); + } + } + *v = new_v.join(","); + } + } + for i in 0..args.len() { + if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { + added = true; + args[i + 1] = add_asan(&args[i + 1]); + } + } + + if !added { + args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); + args.insert(1, "-E".into()); + } + + unsafe { + AsanGiovese::init(&mut self.rt, emulator_modules.hooks().qemu_hooks()); + } + + *qemu_params = QemuParams::Cli(args); + } + + fn post_qemu_init(&mut self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { diff --git a/libafl_qemu/src/modules/usermode/asan_guest.rs b/libafl_qemu/src/modules/usermode/asan_guest.rs index 5d53546f43d..6b482f72983 100644 --- a/libafl_qemu/src/modules/usermode/asan_guest.rs +++ b/libafl_qemu/src/modules/usermode/asan_guest.rs @@ -12,88 +12,7 @@ use libafl_qemu_sys::{GuestAddr, MapInfo}; #[cfg(not(feature = "clippy"))] use crate::sys::libafl_tcg_gen_asan; -use crate::{ - emu::EmulatorModules, - modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter}, - qemu::{Hook, MemAccessInfo, Qemu, QemuInitError}, - sys::TCGTemp, -}; - -static mut ASAN_GUEST_INITED: bool = false; - -pub fn init_qemu_with_asan_guest( - args: &mut Vec, - env: &mut [(String, String)], -) -> Result<(Qemu, String), QemuInitError> { - let current = env::current_exe().unwrap(); - let asan_lib = fs::canonicalize(current) - .unwrap() - .parent() - .unwrap() - .join("libgasan.so"); - - let asan_lib = env::var_os("CUSTOM_ASAN_PATH") - .map_or(asan_lib, |x| PathBuf::from(x.to_string_lossy().to_string())); - - assert!( - asan_lib.as_path().exists(), - "The ASAN library doesn't exist: {asan_lib:#?}" - ); - - let asan_lib = asan_lib - .to_str() - .expect("The path to the asan lib is invalid") - .to_string(); - - println!("Loading ASAN: {asan_lib:}"); - - let add_asan = - |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; - - let mut added = false; - for (k, v) in &mut *env { - if k == "QEMU_SET_ENV" { - let mut new_v = vec![]; - for e in v.split(',') { - if e.starts_with("LD_PRELOAD=") { - added = true; - new_v.push(add_asan(e)); - } else { - new_v.push(e.to_string()); - } - } - *v = new_v.join(","); - } - } - for i in 0..args.len() { - if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { - added = true; - args[i + 1] = add_asan(&args[i + 1]); - } - } - - if !added { - args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); - args.insert(1, "-E".into()); - } - - if env::var("QASAN_DEBUG").is_ok() { - args.push("-E".into()); - args.push("QASAN_DEBUG=1".into()); - } - - if env::var("QASAN_LOG").is_ok() { - args.push("-E".into()); - args.push("QASAN_LOG=1".into()); - } - - unsafe { - ASAN_GUEST_INITED = true; - } - - let emu = Qemu::init(args.as_slice())?; - Ok((emu, asan_lib)) -} +use crate::{emu::EmulatorModules, modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter}, qemu::{Hook, MemAccessInfo, Qemu}, sys::TCGTemp, QemuParams}; #[derive(Clone)] struct QemuAsanGuestMapping { @@ -119,8 +38,10 @@ impl From<&MapInfo> for QemuAsanGuestMapping { #[derive(Debug)] pub struct AsanGuestModule { + env: Vec<(String, String)>, filter: F, - mappings: Vec, + mappings: Option>, + asan_lib: Option, } #[cfg(any( @@ -152,8 +73,8 @@ impl AsanGuestModule { impl AsanGuestModule { #[must_use] - pub fn default(qemu: Qemu, asan: &str) -> Self { - Self::new(qemu, asan, StdAddressFilter::default()) + pub fn default(env: &[(String, String)]) -> Self { + Self::new(env, StdAddressFilter::default()) } } @@ -162,41 +83,8 @@ where F: AddressFilter, { #[must_use] - pub fn new(qemu: Qemu, asan: &str, filter: F) -> Self { - for mapping in qemu.mappings() { - println!("mapping: {mapping:#?}"); - } - - let mappings = qemu - .mappings() - .map(|m| QemuAsanGuestMapping::from(&m)) - .collect::>(); - - for mapping in &mappings { - println!("guest mapping: {mapping:#?}"); - } - - mappings - .iter() - .find(|m| m.start <= Self::HIGH_SHADOW_START && m.end > Self::HIGH_SHADOW_END) - .expect("HighShadow not found, confirm ASAN DSO is loaded in the guest"); - - mappings - .iter() - .find(|m| m.start <= Self::LOW_SHADOW_START && m.end > Self::LOW_SHADOW_END) - .expect("LowShadow not found, confirm ASAN DSO is loaded in the guest"); - - let mappings = mappings - .iter() - .filter(|m| m.path == asan) - .cloned() - .collect::>(); - - for mapping in &mappings { - println!("asan mapping: {mapping:#?}"); - } - - Self { filter, mappings } + pub fn new(env: &[(String, String)], filter: F) -> Self { + Self { env: env.to_vec(), filter, mappings: None, asan_lib: None } } #[must_use] @@ -224,8 +112,10 @@ where } /* Don't sanitize the sanitizer! */ - if h.mappings.iter().any(|m| m.start <= pc && pc < m.end) { - return None; + unsafe { + if h.mappings.as_mut().unwrap_unchecked().iter().any(|m| m.start <= pc && pc < m.end) { + return None; + } } let size = info.size(); @@ -278,6 +168,118 @@ where { type ModuleAddressFilter = F; + fn pre_qemu_init(&mut self, _emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) + where + ET: EmulatorModuleTuple, + { + let mut args = qemu_params.to_cli(); + + let current = env::current_exe().unwrap(); + let asan_lib = fs::canonicalize(current) + .unwrap() + .parent() + .unwrap() + .join("libgasan.so"); + + let asan_lib = env::var_os("CUSTOM_ASAN_PATH") + .map_or(asan_lib, |x| PathBuf::from(x.to_string_lossy().to_string())); + + assert!( + asan_lib.as_path().exists(), + "The ASAN library doesn't exist: {asan_lib:#?}" + ); + + let asan_lib = asan_lib + .to_str() + .expect("The path to the asan lib is invalid") + .to_string(); + + println!("Loading ASAN: {asan_lib:}"); + + let add_asan = + |e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..]; + + let mut added = false; + for (k, v) in &mut self.env { + if k == "QEMU_SET_ENV" { + let mut new_v = vec![]; + for e in v.split(',') { + if e.starts_with("LD_PRELOAD=") { + added = true; + new_v.push(add_asan(e)); + } else { + new_v.push(e.to_string()); + } + } + *v = new_v.join(","); + } + } + for i in 0..args.len() { + if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") { + added = true; + args[i + 1] = add_asan(&args[i + 1]); + } + } + + if !added { + args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib); + args.insert(1, "-E".into()); + } + + if env::var("QASAN_DEBUG").is_ok() { + args.push("-E".into()); + args.push("QASAN_DEBUG=1".into()); + } + + if env::var("QASAN_LOG").is_ok() { + args.push("-E".into()); + args.push("QASAN_LOG=1".into()); + } + + *qemu_params = QemuParams::Cli(args); + + self.asan_lib = Some(asan_lib); + } + + fn post_qemu_init(&mut self, qemu: Qemu, _emulator_modules: &mut EmulatorModules) + where + ET: EmulatorModuleTuple, + { + for mapping in qemu.mappings() { + println!("mapping: {mapping:#?}"); + } + + let mappings = qemu + .mappings() + .map(|m| QemuAsanGuestMapping::from(&m)) + .collect::>(); + + for mapping in &mappings { + println!("guest mapping: {mapping:#?}"); + } + + mappings + .iter() + .find(|m| m.start <= Self::HIGH_SHADOW_START && m.end > Self::HIGH_SHADOW_END) + .expect("HighShadow not found, confirm ASAN DSO is loaded in the guest"); + + mappings + .iter() + .find(|m| m.start <= Self::LOW_SHADOW_START && m.end > Self::LOW_SHADOW_END) + .expect("LowShadow not found, confirm ASAN DSO is loaded in the guest"); + + let mappings = mappings + .iter() + .filter(|m| &m.path == self.asan_lib.as_ref().unwrap()) + .cloned() + .collect::>(); + + for mapping in &mappings { + println!("asan mapping: {mapping:#?}"); + } + + } + fn first_exec( &mut self, _qemu: Qemu, diff --git a/libafl_qemu/src/modules/usermode/injections.rs b/libafl_qemu/src/modules/usermode/injections.rs index fb13bb7b336..9de76866e2f 100644 --- a/libafl_qemu/src/modules/usermode/injections.rs +++ b/libafl_qemu/src/modules/usermode/injections.rs @@ -265,7 +265,7 @@ where { type ModuleAddressFilter = NopAddressFilter; - fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&mut self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { diff --git a/libafl_qemu/src/modules/usermode/mod.rs b/libafl_qemu/src/modules/usermode/mod.rs index 1d9cc503d2c..91dd6eb4910 100644 --- a/libafl_qemu/src/modules/usermode/mod.rs +++ b/libafl_qemu/src/modules/usermode/mod.rs @@ -11,9 +11,9 @@ pub use snapshot::{IntervalSnapshotFilter, SnapshotModule}; #[cfg(not(cpu_target = "hexagon"))] pub mod asan; #[cfg(not(cpu_target = "hexagon"))] -pub use asan::{init_qemu_with_asan, AsanModule}; +pub use asan::{AsanModule}; #[cfg(not(cpu_target = "hexagon"))] pub mod asan_guest; #[cfg(not(cpu_target = "hexagon"))] -pub use asan_guest::{init_qemu_with_asan_guest, AsanGuestModule}; +pub use asan_guest::AsanGuestModule; diff --git a/libafl_qemu/src/modules/usermode/snapshot.rs b/libafl_qemu/src/modules/usermode/snapshot.rs index b71a3cbc46c..ca85ad26720 100644 --- a/libafl_qemu/src/modules/usermode/snapshot.rs +++ b/libafl_qemu/src/modules/usermode/snapshot.rs @@ -675,7 +675,7 @@ where { type ModuleAddressFilter = NopAddressFilter; - fn post_qemu_init(&self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&mut self, _qemu: Qemu, emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, { diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 73ea2a71f6e..fcf4b1c3a3b 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -188,6 +188,23 @@ where } } +impl QemuParams { + pub fn to_cli(&self) -> Vec { + match self { + QemuParams::Config(cfg) => { + cfg + .to_string() + .split(' ') + .map(ToString::to_string) + .collect() + } + QemuParams::Cli(cli) => { + cli.clone() + } + } + } +} + impl MemAccessInfo { #[must_use] pub fn memop(&self) -> libafl_qemu_sys::MemOp { @@ -500,23 +517,17 @@ impl Qemu { { let params: QemuParams = params.into(); - let args: Vec = match params { + match ¶ms { QemuParams::Config(cfg) => { - let qemu_args: Vec = cfg - .to_string() - .split(' ') - .map(ToString::to_string) - .collect(); - QEMU_CONFIG.set(cfg.clone()).map_err(|_| { unreachable!("QEMU_CONFIG was already set but Qemu was not init!") })?; - - qemu_args - } - QemuParams::Cli(cli) => cli, + }, + QemuParams::Cli(_) => {}, }; + let args = params.to_cli(); + if args.is_empty() { return Err(QemuInitError::EmptyArgs); } diff --git a/libafl_qemu/src/qemu/systemmode.rs b/libafl_qemu/src/qemu/systemmode.rs index 25e956cb438..ddb2bfcf155 100644 --- a/libafl_qemu/src/qemu/systemmode.rs +++ b/libafl_qemu/src/qemu/systemmode.rs @@ -163,7 +163,7 @@ impl CPU { /// # Safety /// no check is done on the correctness of the operation. /// if a problem occurred during the operation, there will be no feedback - pub fn write_mem_unchecked(&self, addr: GuestAddr, buf: &[u8]) { + pub unsafe fn write_mem_unchecked(&self, addr: GuestAddr, buf: &[u8]) { // TODO use gdbstub's target_cpu_memory_rw_debug unsafe { libafl_qemu_sys::cpu_memory_rw_debug( From 8d8631eb55aa26a0351143a8ad2f5d8e91311f99 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 27 Nov 2024 19:02:46 +0100 Subject: [PATCH 25/93] fmt --- .../binary_only/qemu_launcher/src/client.rs | 26 +++++--------- .../binary_only/qemu_launcher/src/instance.rs | 17 +++++---- libafl_qemu/src/emu/builder.rs | 7 ++-- libafl_qemu/src/emu/mod.rs | 16 ++++++--- libafl_qemu/src/executor.rs | 4 +-- libafl_qemu/src/modules/mod.rs | 35 +++++++++++++------ libafl_qemu/src/modules/usermode/asan.rs | 29 ++++++++++----- .../src/modules/usermode/asan_guest.rs | 30 ++++++++++++---- libafl_qemu/src/modules/usermode/mod.rs | 2 +- libafl_qemu/src/qemu/mod.rs | 20 +++++------ 10 files changed, 113 insertions(+), 73 deletions(-) diff --git a/fuzzers/binary_only/qemu_launcher/src/client.rs b/fuzzers/binary_only/qemu_launcher/src/client.rs index 02e15a4fe1e..77e982eb537 100644 --- a/fuzzers/binary_only/qemu_launcher/src/client.rs +++ b/fuzzers/binary_only/qemu_launcher/src/client.rs @@ -10,12 +10,9 @@ use libafl::{ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list}; #[cfg(feature = "injections")] use libafl_qemu::modules::injections::InjectionModule; -use libafl_qemu::{modules::{ - asan::AsanModule, - asan_guest::AsanGuestModule, - cmplog::CmpLogModule, - DrCovModule, -}}; +use libafl_qemu::modules::{ + asan::AsanModule, asan_guest::AsanGuestModule, cmplog::CmpLogModule, DrCovModule, +}; use crate::{ harness::Harness, @@ -128,10 +125,7 @@ impl Client<'_> { ) } else { instance_builder.build().run( - tuple_list!( - CmpLogModule::default(), - AsanModule::default(&env), - ), + tuple_list!(CmpLogModule::default(), AsanModule::default(&env),), state, ) } @@ -147,10 +141,7 @@ impl Client<'_> { ) } else { instance_builder.build().run( - tuple_list!( - CmpLogModule::default(), - AsanGuestModule::default(&env), - ), + tuple_list!(CmpLogModule::default(), AsanGuestModule::default(&env),), state, ) } @@ -161,10 +152,9 @@ impl Client<'_> { state, ) } else { - instance_builder.build().run( - tuple_list!(AsanModule::default(&env),), - state, - ) + instance_builder + .build() + .run(tuple_list!(AsanModule::default(&env),), state) } } else if is_asan_guest { let modules = tuple_list!(AsanGuestModule::default(&env)); diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index 2fa2c63054d..36be5f44f33 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -35,16 +35,19 @@ use libafl_bolts::{ core_affinity::CoreId, ownedref::OwnedMutSlice, rands::StdRand, - tuples::{tuple_list, Merge, Prepend}, + tuples::{tuple_list, MatchFirstType, Merge, Prepend}, +}; +use libafl_qemu::{ + elf::EasyElf, + modules::{ + cmplog::CmpLogObserver, edges::EdgeCoverageFullVariant, EdgeCoverageModule, EmulatorModule, + EmulatorModuleTuple, NopPageFilter, StdAddressFilter, StdEdgeCoverageModule, + }, + Emulator, GuestAddr, Qemu, QemuExecutor, }; -use libafl_qemu::{elf::EasyElf, modules::{ - cmplog::CmpLogObserver, EmulatorModuleTuple, StdAddressFilter, StdEdgeCoverageModule, -}, Emulator, GuestAddr, Qemu, QemuExecutor}; use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; use typed_builder::TypedBuilder; -use libafl_bolts::tuples::MatchFirstType; -use libafl_qemu::modules::{EdgeCoverageModule, EmulatorModule, NopPageFilter}; -use libafl_qemu::modules::edges::EdgeCoverageFullVariant; + use crate::{harness::Harness, options::FuzzerOptions}; pub type ClientState = diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index 222f9ef1780..f77ae71fa25 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -117,9 +117,10 @@ where // TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called unsafe { - emulator_modules - .modules_mut() - .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked(), &mut qemu_parameters); + emulator_modules.modules_mut().pre_qemu_init_all( + EmulatorModules::::emulator_modules_mut_unchecked(), + &mut qemu_parameters, + ); } let qemu = Qemu::init(qemu_parameters)?; diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index e111d4c739a..75d66a59fe0 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -14,7 +14,14 @@ use libafl::{ }; use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestUsize, GuestVirtAddr}; -use crate::{breakpoint::{Breakpoint, BreakpointId}, command::{CommandError, CommandManager, NopCommandManager, StdCommandManager}, modules::EmulatorModuleTuple, sync_exit::SyncExit, Qemu, QemuExitError, QemuExitReason, QemuHooks, QemuInitError, QemuMemoryChunk, QemuParams, QemuShutdownCause, Regs, CPU}; +use crate::{ + breakpoint::{Breakpoint, BreakpointId}, + command::{CommandError, CommandManager, NopCommandManager, StdCommandManager}, + modules::EmulatorModuleTuple, + sync_exit::SyncExit, + Qemu, QemuExitError, QemuExitReason, QemuHooks, QemuInitError, QemuMemoryChunk, QemuParams, + QemuShutdownCause, Regs, CPU, +}; mod hooks; pub use hooks::*; @@ -328,9 +335,10 @@ where // TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called unsafe { - emulator_modules - .modules_mut() - .pre_qemu_init_all(EmulatorModules::::emulator_modules_mut_unchecked(), &mut qemu_parameters); + emulator_modules.modules_mut().pre_qemu_init_all( + EmulatorModules::::emulator_modules_mut_unchecked(), + &mut qemu_parameters, + ); } let qemu = Qemu::init(qemu_args)?; diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 7aa9542bc0e..c0fc3f44c3a 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -40,11 +40,9 @@ use libc::siginfo_t; #[cfg(feature = "usermode")] use crate::EmulatorModules; -use crate::{ - command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver, -}; #[cfg(feature = "usermode")] use crate::Qemu; +use crate::{command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver}; pub struct QemuExecutor<'a, CM, ED, ET, H, OT, S, SM> where diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index 67cc2f80419..8b470b81420 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -61,8 +61,11 @@ where /// /// It is also possible to edit QEMU parameters, just before QEMU gets initialized. /// Thus, the module can modify options for QEMU just before it gets initialized. - fn pre_qemu_init(&mut self, _emulator_modules: &mut EmulatorModules, _qemu_params: &mut QemuParams) - where + fn pre_qemu_init( + &mut self, + _emulator_modules: &mut EmulatorModules, + _qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple, { } @@ -155,8 +158,11 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool; - fn pre_qemu_init_all(&mut self, emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) - where + fn pre_qemu_init_all( + &mut self, + emulator_modules: &mut EmulatorModules, + qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple; fn post_qemu_init_all(&mut self, qemu: Qemu, emulator_modules: &mut EmulatorModules) @@ -214,14 +220,20 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn pre_qemu_init_all(&mut self, _emulator_modules: &mut EmulatorModules, _qemu_params: &mut QemuParams) - where + fn pre_qemu_init_all( + &mut self, + _emulator_modules: &mut EmulatorModules, + _qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple, { } - fn post_qemu_init_all(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) - where + fn post_qemu_init_all( + &mut self, + _qemu: Qemu, + _emulator_modules: &mut EmulatorModules, + ) where ET: EmulatorModuleTuple, { } @@ -279,8 +291,11 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = Head::HOOKS_DO_SIDE_EFFECTS || Tail::HOOKS_DO_SIDE_EFFECTS; - fn pre_qemu_init_all(&mut self, emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) - where + fn pre_qemu_init_all( + &mut self, + emulator_modules: &mut EmulatorModules, + qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple, { self.0.pre_qemu_init(emulator_modules, qemu_params); diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 2024da93977..a62940d925e 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -11,10 +11,15 @@ use meminterval::{Interval, IntervalTree}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use rangemap::RangeMap; -use crate::{modules::{ - calls::FullBacktraceCollector, snapshot::SnapshotModule, EmulatorModule, - EmulatorModuleTuple, -}, qemu::MemAccessInfo, sys::TCGTemp, Qemu, QemuParams, Regs}; +use crate::{ + modules::{ + calls::FullBacktraceCollector, snapshot::SnapshotModule, EmulatorModule, + EmulatorModuleTuple, + }, + qemu::MemAccessInfo, + sys::TCGTemp, + Qemu, QemuParams, Regs, +}; // TODO at some point, merge parts with libafl_frida @@ -180,7 +185,8 @@ impl AsanGiovese { MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON, -1, 0 - ), MAP_FAILED + ), + MAP_FAILED ); assert_ne!( libc::mmap( @@ -190,7 +196,8 @@ impl AsanGiovese { MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON, -1, 0 - ), MAP_FAILED + ), + MAP_FAILED ); assert_ne!( libc::mmap( @@ -200,7 +207,8 @@ impl AsanGiovese { MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON, -1, 0 - ), MAP_FAILED + ), + MAP_FAILED ); qemu_hooks.add_pre_syscall_hook(self.as_mut(), Self::fake_syscall); @@ -811,8 +819,11 @@ where type ModuleAddressFilter = StdAddressFilter; const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn pre_qemu_init(&mut self, emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) - where + fn pre_qemu_init( + &mut self, + emulator_modules: &mut EmulatorModules, + qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple, { let mut args: Vec = qemu_params.to_cli(); diff --git a/libafl_qemu/src/modules/usermode/asan_guest.rs b/libafl_qemu/src/modules/usermode/asan_guest.rs index 6b482f72983..3f3c67bd312 100644 --- a/libafl_qemu/src/modules/usermode/asan_guest.rs +++ b/libafl_qemu/src/modules/usermode/asan_guest.rs @@ -12,7 +12,13 @@ use libafl_qemu_sys::{GuestAddr, MapInfo}; #[cfg(not(feature = "clippy"))] use crate::sys::libafl_tcg_gen_asan; -use crate::{emu::EmulatorModules, modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter}, qemu::{Hook, MemAccessInfo, Qemu}, sys::TCGTemp, QemuParams}; +use crate::{ + emu::EmulatorModules, + modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter}, + qemu::{Hook, MemAccessInfo, Qemu}, + sys::TCGTemp, + QemuParams, +}; #[derive(Clone)] struct QemuAsanGuestMapping { @@ -84,7 +90,12 @@ where { #[must_use] pub fn new(env: &[(String, String)], filter: F) -> Self { - Self { env: env.to_vec(), filter, mappings: None, asan_lib: None } + Self { + env: env.to_vec(), + filter, + mappings: None, + asan_lib: None, + } } #[must_use] @@ -113,7 +124,12 @@ where /* Don't sanitize the sanitizer! */ unsafe { - if h.mappings.as_mut().unwrap_unchecked().iter().any(|m| m.start <= pc && pc < m.end) { + if h.mappings + .as_mut() + .unwrap_unchecked() + .iter() + .any(|m| m.start <= pc && pc < m.end) + { return None; } } @@ -168,8 +184,11 @@ where { type ModuleAddressFilter = F; - fn pre_qemu_init(&mut self, _emulator_modules: &mut EmulatorModules, qemu_params: &mut QemuParams) - where + fn pre_qemu_init( + &mut self, + _emulator_modules: &mut EmulatorModules, + qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple, { let mut args = qemu_params.to_cli(); @@ -277,7 +296,6 @@ where for mapping in &mappings { println!("asan mapping: {mapping:#?}"); } - } fn first_exec( diff --git a/libafl_qemu/src/modules/usermode/mod.rs b/libafl_qemu/src/modules/usermode/mod.rs index 91dd6eb4910..93a8f5247ce 100644 --- a/libafl_qemu/src/modules/usermode/mod.rs +++ b/libafl_qemu/src/modules/usermode/mod.rs @@ -11,7 +11,7 @@ pub use snapshot::{IntervalSnapshotFilter, SnapshotModule}; #[cfg(not(cpu_target = "hexagon"))] pub mod asan; #[cfg(not(cpu_target = "hexagon"))] -pub use asan::{AsanModule}; +pub use asan::AsanModule; #[cfg(not(cpu_target = "hexagon"))] pub mod asan_guest; diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index fcf4b1c3a3b..2cd93f44a99 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -191,16 +191,12 @@ where impl QemuParams { pub fn to_cli(&self) -> Vec { match self { - QemuParams::Config(cfg) => { - cfg - .to_string() - .split(' ') - .map(ToString::to_string) - .collect() - } - QemuParams::Cli(cli) => { - cli.clone() - } + QemuParams::Config(cfg) => cfg + .to_string() + .split(' ') + .map(ToString::to_string) + .collect(), + QemuParams::Cli(cli) => cli.clone(), } } } @@ -522,8 +518,8 @@ impl Qemu { QEMU_CONFIG.set(cfg.clone()).map_err(|_| { unreachable!("QEMU_CONFIG was already set but Qemu was not init!") })?; - }, - QemuParams::Cli(_) => {}, + } + QemuParams::Cli(_) => {} }; let args = params.to_cli(); From f2491c1eb98afdc095d7a6dd996dda613c3cf256 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 27 Nov 2024 19:03:37 +0100 Subject: [PATCH 26/93] clippy --- libafl_qemu/src/emu/hooks.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index 3973bbc079d..64c035695de 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -176,6 +176,7 @@ where } } + #[must_use] pub fn qemu_hooks(&self) -> QemuHooks { self.qemu_hooks } From 76480c191b065d5e62e1af4cc3f5d64be5b9921d Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 27 Nov 2024 19:33:17 +0100 Subject: [PATCH 27/93] fix qemu_coverage --- .../binary_only/qemu_coverage/src/fuzzer.rs | 137 +++++++++--------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs index eb6a97c7afe..10dbbefe2b0 100644 --- a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs @@ -28,7 +28,7 @@ use libafl_bolts::{ use libafl_qemu::{ elf::EasyElf, modules::{drcov::DrCovModule, StdAddressFilter}, - ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor, + ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, QemuExecutor, QemuExitReason, QemuRWError, QemuShutdownCause, Regs, }; @@ -120,79 +120,92 @@ pub fn fuzz() { env::remove_var("LD_LIBRARY_PATH"); - let qemu = Qemu::init(&options.args).unwrap(); + let mut run_client = + |state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _, _>, core_id| { + let mut cov_path = options.coverage_path.clone(); - let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); + let emulator_modules = tuple_list!(DrCovModule::builder() + .filter(StdAddressFilter::default()) + .filename(cov_path.clone()) + .full_trace(false) + .build()); - let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) - .expect("Symbol LLVMFuzzerTestOneInput not found"); - log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); + let emulator = Emulator::empty() + .qemu_cli(options.args.clone()) + .modules(emulator_modules) + .build() + .expect("QEMU initialization failed"); + let qemu = emulator.qemu(); + + let mut elf_buffer = Vec::new(); + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); - qemu.entry_break(test_one_input_ptr); + let test_one_input_ptr = elf + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) + .expect("Symbol LLVMFuzzerTestOneInput not found"); + log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - for m in qemu.mappings() { - log::debug!( + qemu.entry_break(test_one_input_ptr); + + for m in qemu.mappings() { + log::debug!( "Mapping: 0x{:016x}-0x{:016x}, {}", m.start(), m.end(), m.path().unwrap_or(&"".to_string()) ); - } + } - let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); - log::debug!("Break at {pc:#x}"); + let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); + log::debug!("Break at {pc:#x}"); - let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); - log::debug!("Return address = {ret_addr:#x}"); + let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); + log::debug!("Return address = {ret_addr:#x}"); - qemu.set_breakpoint(ret_addr); + qemu.set_breakpoint(ret_addr); - let input_addr = qemu - .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) - .unwrap(); - log::debug!("Placing input at {input_addr:#x}"); + let input_addr = qemu + .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) + .unwrap(); + log::debug!("Placing input at {input_addr:#x}"); - let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); + let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); - let reset = |buf: &[u8], len: GuestReg| -> Result<(), QemuRWError> { - unsafe { - let _ = qemu.write_mem(input_addr, buf); - qemu.write_reg(Regs::Pc, test_one_input_ptr)?; - qemu.write_reg(Regs::Sp, stack_ptr)?; - qemu.write_return_address(ret_addr)?; - qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; - qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?; + let reset = |buf: &[u8], len: GuestReg| -> Result<(), QemuRWError> { + unsafe { + let _ = qemu.write_mem(input_addr, buf); + qemu.write_reg(Regs::Pc, test_one_input_ptr)?; + qemu.write_reg(Regs::Sp, stack_ptr)?; + qemu.write_return_address(ret_addr)?; + qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; + qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?; - match qemu.run() { - Ok(QemuExitReason::Breakpoint(_)) => {} - Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { - process::exit(0) - } - _ => panic!("Unexpected QEMU exit."), - } + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { + process::exit(0) + } + _ => panic!("Unexpected QEMU exit."), + } - Ok(()) - } - }; - - let mut harness = - |_emulator: &mut Emulator<_, _, _, _, _>, _state: &mut _, input: &BytesInput| { - let target = input.target_bytes(); - let mut buf = target.as_slice(); - let mut len = buf.len(); - if len > MAX_INPUT_SIZE { - buf = &buf[0..MAX_INPUT_SIZE]; - len = MAX_INPUT_SIZE; - } - let len = len as GuestReg; - reset(buf, len).unwrap(); - ExitKind::Ok - }; + Ok(()) + } + }; + + let mut harness = + |_emulator: &mut Emulator<_, _, _, _, _>, _state: &mut _, input: &BytesInput| { + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let mut len = buf.len(); + if len > MAX_INPUT_SIZE { + buf = &buf[0..MAX_INPUT_SIZE]; + len = MAX_INPUT_SIZE; + } + let len = len as GuestReg; + reset(buf, len).unwrap(); + ExitKind::Ok + }; - let mut run_client = - |state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _, _>, core_id| { let core_idx = options .cores .position(core_id) @@ -229,23 +242,11 @@ pub fn fuzz() { let scheduler = QueueScheduler::new(); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let mut cov_path = options.coverage_path.clone(); let coverage_name = cov_path.file_stem().unwrap().to_str().unwrap(); let coverage_extension = cov_path.extension().unwrap_or_default().to_str().unwrap(); let core = core_id.0; cov_path.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}")); - let emulator_modules = tuple_list!(DrCovModule::builder() - .filter(StdAddressFilter::default()) - .filename(cov_path) - .full_trace(false) - .build()); - - let emulator = Emulator::empty() - .qemu(qemu) - .modules(emulator_modules) - .build()?; - let mut executor = QemuExecutor::new( emulator, &mut harness, From 46f0da7778a44d158113795dac92feb4812f1124 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 27 Nov 2024 19:39:31 +0100 Subject: [PATCH 28/93] fmt --- fuzzers/binary_only/qemu_coverage/src/fuzzer.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs index 10dbbefe2b0..3905458b334 100644 --- a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs @@ -149,11 +149,11 @@ pub fn fuzz() { for m in qemu.mappings() { log::debug!( - "Mapping: 0x{:016x}-0x{:016x}, {}", - m.start(), - m.end(), - m.path().unwrap_or(&"".to_string()) - ); + "Mapping: 0x{:016x}-0x{:016x}, {}", + m.start(), + m.end(), + m.path().unwrap_or(&"".to_string()) + ); } let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); @@ -182,9 +182,9 @@ pub fn fuzz() { match qemu.run() { Ok(QemuExitReason::Breakpoint(_)) => {} - Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { - process::exit(0) - } + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal( + Signal::SigInterrupt, + ))) => process::exit(0), _ => panic!("Unexpected QEMU exit."), } From 3464e44e351ac03b53950df0edf36b4e5ac6bcb5 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 28 Nov 2024 10:04:09 +0100 Subject: [PATCH 29/93] forgot qemu args in launcher --- fuzzers/binary_only/qemu_launcher/src/instance.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index 36be5f44f33..3166f45082e 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -125,7 +125,10 @@ impl Instance<'_, M> { .build()?; let modules = modules.prepend(edge_coverage_module); - let mut emulator = Emulator::empty().modules(modules).build()?; + let mut emulator = Emulator::empty() + .qemu_cli(self.options.args.clone()) + .modules(modules) + .build()?; let harness = Harness::init(emulator.qemu()).expect("Error setting up harness."); let qemu = emulator.qemu(); From d7bfbe71579221bb810df06cbd63be90f8364c02 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:21:01 +0100 Subject: [PATCH 30/93] map_error instead of unwrap --- libafl/src/executors/hooks/intel_pt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index 640b9b1a1c6..a526aa8661f 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -75,7 +75,7 @@ fn trace_to_file(pt: &IntelPT) -> Result<(), Error> { fs::create_dir_all(traces_dir)?; let timestamp = time::SystemTime::now() .duration_since(time::UNIX_EPOCH) - .unwrap() + .map_err(|e| Error::unknown(e.to_string()))? .as_micros(); let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?; file.write_all(&*pt.last_decode_trace())?; From 005475b16279af5c6b8bec48dbbc44148dc87c0e Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 28 Nov 2024 11:22:05 +0100 Subject: [PATCH 31/93] use correct args --- .../binary_only/qemu_launcher/src/client.rs | 23 ++++++++---- .../binary_only/qemu_launcher/src/instance.rs | 12 ++++--- libafl_qemu/src/emu/builder.rs | 36 +++++-------------- libafl_qemu/src/emu/mod.rs | 18 ++++++---- 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/fuzzers/binary_only/qemu_launcher/src/client.rs b/fuzzers/binary_only/qemu_launcher/src/client.rs index 77e982eb537..8c63a778c42 100644 --- a/fuzzers/binary_only/qemu_launcher/src/client.rs +++ b/fuzzers/binary_only/qemu_launcher/src/client.rs @@ -112,10 +112,13 @@ impl Client<'_> { .filename(drcov.clone()) .full_trace(true) .build(); - instance_builder.build().run(tuple_list!(drcov), state) + instance_builder + .build() + .run(args, tuple_list!(drcov), state) } else if is_asan && is_cmplog { if let Some(injection_module) = injection_module { instance_builder.build().run( + args, tuple_list!( CmpLogModule::default(), AsanModule::default(&env), @@ -125,6 +128,7 @@ impl Client<'_> { ) } else { instance_builder.build().run( + args, tuple_list!(CmpLogModule::default(), AsanModule::default(&env),), state, ) @@ -132,6 +136,7 @@ impl Client<'_> { } else if is_asan_guest && is_cmplog { if let Some(injection_module) = injection_module { instance_builder.build().run( + args, tuple_list!( CmpLogModule::default(), AsanGuestModule::default(&env), @@ -141,6 +146,7 @@ impl Client<'_> { ) } else { instance_builder.build().run( + args, tuple_list!(CmpLogModule::default(), AsanGuestModule::default(&env),), state, ) @@ -148,34 +154,37 @@ impl Client<'_> { } else if is_asan { if let Some(injection_module) = injection_module { instance_builder.build().run( + args, tuple_list!(AsanModule::default(&env), injection_module), state, ) } else { instance_builder .build() - .run(tuple_list!(AsanModule::default(&env),), state) + .run(args, tuple_list!(AsanModule::default(&env),), state) } } else if is_asan_guest { - let modules = tuple_list!(AsanGuestModule::default(&env)); - instance_builder.build().run(modules, state) + instance_builder + .build() + .run(args, tuple_list!(AsanGuestModule::default(&env)), state) } else if is_cmplog { if let Some(injection_module) = injection_module { instance_builder.build().run( + args, tuple_list!(CmpLogModule::default(), injection_module), state, ) } else { instance_builder .build() - .run(tuple_list!(CmpLogModule::default()), state) + .run(args, tuple_list!(CmpLogModule::default()), state) } } else if let Some(injection_module) = injection_module { instance_builder .build() - .run(tuple_list!(injection_module), state) + .run(args, tuple_list!(injection_module), state) } else { - instance_builder.build().run(tuple_list!(), state) + instance_builder.build().run(args, tuple_list!(), state) } } } diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index 3166f45082e..de3ee3b43d5 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -106,7 +106,12 @@ impl Instance<'_, M> { } #[allow(clippy::too_many_lines)] - pub fn run(&mut self, modules: ET, state: Option) -> Result<(), Error> + pub fn run( + &mut self, + args: Vec, + modules: ET, + state: Option, + ) -> Result<(), Error> where ET: EmulatorModuleTuple + Debug, { @@ -125,10 +130,7 @@ impl Instance<'_, M> { .build()?; let modules = modules.prepend(edge_coverage_module); - let mut emulator = Emulator::empty() - .qemu_cli(self.options.args.clone()) - .modules(modules) - .build()?; + let mut emulator = Emulator::empty().qemu_cli(args).modules(modules).build()?; let harness = Harness::init(emulator.qemu()).expect("Error setting up harness."); let qemu = emulator.qemu(); diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index f77ae71fa25..5a7c7883b50 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -12,8 +12,8 @@ use crate::{ command::{CommandManager, NopCommandManager, StdCommandManager}, config::QemuConfig, modules::{EmulatorModule, EmulatorModuleTuple}, - Emulator, EmulatorHooks, EmulatorModules, NopEmulatorDriver, NopSnapshotManager, Qemu, - QemuHooks, QemuInitError, QemuParams, StdEmulatorDriver, StdSnapshotManager, + Emulator, NopEmulatorDriver, NopSnapshotManager, QemuInitError, QemuParams, StdEmulatorDriver, + StdSnapshotManager, }; #[derive(Clone, Debug)] @@ -109,31 +109,13 @@ where CM: CommandManager, ET: EmulatorModuleTuple, { - let mut qemu_parameters = self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?; - - let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; - - let mut emulator_modules = EmulatorModules::new(emulator_hooks, self.modules); - - // TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called - unsafe { - emulator_modules.modules_mut().pre_qemu_init_all( - EmulatorModules::::emulator_modules_mut_unchecked(), - &mut qemu_parameters, - ); - } - - let qemu = Qemu::init(qemu_parameters)?; - - unsafe { - Ok(Emulator::new_with_qemu( - qemu, - emulator_modules, - self.driver, - self.snapshot_manager, - self.command_manager, - )) - } + Emulator::new( + self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?, + self.modules, + self.driver, + self.snapshot_manager, + self.command_manager, + ) } } diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 75d66a59fe0..e0468245371 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -322,14 +322,18 @@ where S: UsesInput + Unpin, { #[allow(clippy::must_use_candidate, clippy::similar_names)] - pub fn new( - qemu_args: &[String], + pub fn new( + qemu_params: T, modules: ET, driver: ED, snapshot_manager: SM, command_manager: CM, - ) -> Result { - let mut qemu_parameters: QemuParams = qemu_args.into(); + ) -> Result + where + T: Into, + { + let mut qemu_params = qemu_params.into(); + let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; let mut emulator_modules = EmulatorModules::new(emulator_hooks, modules); @@ -337,11 +341,11 @@ where unsafe { emulator_modules.modules_mut().pre_qemu_init_all( EmulatorModules::::emulator_modules_mut_unchecked(), - &mut qemu_parameters, + &mut qemu_params, ); } - let qemu = Qemu::init(qemu_args)?; + let qemu = Qemu::init(qemu_params)?; unsafe { Ok(Self::new_with_qemu( @@ -360,7 +364,7 @@ where /// # Safety /// /// pre-init qemu hooks should be run before calling this. - pub(crate) unsafe fn new_with_qemu( + unsafe fn new_with_qemu( qemu: Qemu, emulator_modules: Pin>>, driver: ED, From 6bb2867ff5fe0e5ee42a148b90f68a4aba0c9492 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:24:39 +0100 Subject: [PATCH 32/93] Update to new libafl_qemu --- .../qemu_intel_pt_bootloader/Makefile.toml | 7 +++- .../qemu_intel_pt_bootloader/src/main.rs | 39 ++++++++++--------- .../src/modules/systemmode/intel_pt.rs | 24 +++++++----- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index 70dab4dadb7..6a5044dc90d 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -1,3 +1,6 @@ +[env] +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" + [env.development] PROFILE_DIR = "debug" @@ -5,10 +8,10 @@ PROFILE_DIR = "debug" PROFILE_DIR = "release" [tasks.target_dir] -condition = { files_not_exist = ["${CARGO_MAKE_CRATE_TARGET_DIRECTORY}"] } +condition = { files_not_exist = ["${TARGET_DIR}"] } script_runner = "@shell" script = ''' -mkdir -p ${CARGO_MAKE_CRATE_TARGET_DIRECTORY} +mkdir -p ${TARGET_DIR} ''' [tasks.build_target] diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index dbf716f8a84..e92cad4380d 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -1,4 +1,4 @@ -//! A fuzzer using qemu in systemmode for binary-only coverage of bootloaders +//! A fuzzer using qemu in systemmode with intel PT use core::time::Duration; use std::{env, path::PathBuf, process}; @@ -27,15 +27,17 @@ use libafl_bolts::{ tuples::tuple_list, }; use libafl_qemu::{ - config, config::Accelerator, executor::QemuExecutor, modules::intel_pt::IntelPTModule, - Emulator, Qemu, QemuExitReason, QemuShutdownCause, + config, + config::{Accelerator, QemuConfig}, + executor::QemuExecutor, + modules::intel_pt::IntelPTModule, + Emulator, EmulatorBuilder, Qemu, QemuExitReason, QemuShutdownCause, }; + // Coverage map const MAP_SIZE: usize = 4096; static mut MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; const MAX_INPUT_SIZE: usize = 50; - -#[cfg(target_os = "linux")] fn main() { env_logger::init(); @@ -48,9 +50,13 @@ fn main() { let mut run_client = |state: Option<_>, mut mgr, _core_id| -> Result<(), Error> { let target_dir = env::var("TARGET_DIR").expect("TARGET_DIR env not set"); - println!("run client"); - // Initialize QEMU - let qemu = Qemu::builder() + let target_subdir = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + // Configure QEMU + let qemu = QemuConfig::builder() .no_graphic(true) .drives([config::Drive::builder() .format(config::DiskImageFileFormat::Raw) @@ -58,21 +64,19 @@ fn main() { .build()]) .accelerator(Accelerator::Kvm) .bios(format!( - "{target_dir}/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu" + "{target_dir}/{target_subdir}/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu" )) .start_cpu(false) - .build() - .expect("Failed to initialize QEMU"); + .build(); let emulator_modules = tuple_list!(IntelPTModule::new(unsafe { MAP.as_mut_ptr() }, MAP_SIZE)); - let emulator = Emulator::empty() - .qemu(qemu) + let emulator = EmulatorBuilder::empty() + .qemu_config(qemu) .modules(emulator_modules) .build()?; - // The wrapped harness function, calling out to the LLVM-style harness let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, _: &mut StdState, _: &BytesInput| unsafe { @@ -177,6 +181,8 @@ fn main() { .monitor(monitor) .run_client(&mut run_client) .cores(&cores) + .stdout_file(Some("./stdout.txt")) + .stderr_file(Some("./stderr.txt")) .build() .launch() { @@ -185,8 +191,3 @@ fn main() { Err(err) => panic!("Failed to run launcher: {err:?}"), } } - -#[cfg(not(target_os = "linux"))] -pub fn main() { - panic!("qemu-user and libafl_qemu is only supported on linux!"); -} diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 31870503a01..47411f9086f 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -12,7 +12,7 @@ use num_traits::SaturatingAdd; use crate::{ modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind, NopPageFilter}, - EmulatorModules, NewThreadHook, + EmulatorModules, NewThreadHook, Qemu, QemuParams, }; #[derive(Debug)] @@ -40,31 +40,38 @@ where type ModuleAddressFilter = Self; type ModulePageFilter = NopPageFilter; - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where + fn pre_qemu_init( + &mut self, + emulator_modules: &mut EmulatorModules, + _qemu_params: &mut QemuParams, + ) where ET: EmulatorModuleTuple, { - emulator_modules.thread_creation(NewThreadHook::Function(intel_pt_new_thread::)); + println!("pre_qemu_init"); + emulator_modules + .thread_creation(NewThreadHook::Function(intel_pt_new_thread::)) + .unwrap(); // TODO emulator_modules.thread_teradown - println!("first_exec"); } fn pre_exec( &mut self, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, - _input: &::Input, + _input: &S::Input, ) where ET: EmulatorModuleTuple, { + println!("pre_exec"); let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.enable_tracing().unwrap(); - println!("pre_exec"); } fn post_exec( &mut self, - emulator_modules: &mut EmulatorModules, + qemu: Qemu, + _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, _observers: &mut OT, @@ -79,7 +86,6 @@ where // we need the memory map to decode the traces here take it in prexec. use QemuMemoryChunk // TODO handle self modifying code - let qemu = emulator_modules.qemu(); pt.decode_with_callback( |addr, out_buff| qemu.read_mem(out_buff, addr.into()).unwrap(), unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, From ea840cfb97734f4c85854d2a0f4ac6d4c6322b66 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 28 Nov 2024 15:18:27 +0100 Subject: [PATCH 33/93] adapt api --- fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs index f77e91507ae..85099189764 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs @@ -108,7 +108,7 @@ pub fn fuzz() { // Initialize QEMU Emulator let emu = Emulator::builder() .qemu_cli(args) - .add_module( + .append_module( StdEdgeCoverageModule::builder() .map_observer(edges_observer.as_mut()) .build()?, From 51d506150ade795f77aca5911262845ea2f61675 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:04:51 +0100 Subject: [PATCH 34/93] borrow checker friendly join_split_trace and copy trace before deocde to prevent decoding failures --- libafl_intelpt/src/lib.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 2b529a76bdb..0d88ac9a528 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -19,6 +19,7 @@ use std::{ }; #[cfg(target_os = "linux")] use std::{ + boxed::Box, ffi::{CStr, CString}, fmt::Debug, format, fs, @@ -304,8 +305,12 @@ impl IntelPT { } } else { // Head pointer wrapped, the trace is split - unsafe { self.join_split_trace(head_wrap, tail_wrap) } + OwnedRefMut::Owned(self.join_split_trace(head_wrap, tail_wrap)) }; + #[cfg(feature = "export_raw")] + { + self.last_decode_trace = data.as_ref().to_vec(); + } let mut config = ConfigBuilder::new(data.as_mut()).map_err(error_from_pt_error)?; config.filter(self.ip_filters_to_addr_filter()); @@ -342,11 +347,6 @@ impl IntelPT { }; } - #[cfg(feature = "export_raw")] - { - self.last_decode_trace = data.as_ref().to_vec(); - } - // Advance the trace pointer up to the latest sync point, otherwise next execution's trace // might not contain a PSB packet. decoder.sync_backward().map_err(error_from_pt_error)?; @@ -358,19 +358,17 @@ impl IntelPT { #[inline] #[must_use] - unsafe fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> OwnedRefMut<[u8]> { - let first_ptr = self.perf_aux_buffer.add(tail_wrap as usize) as *mut u8; + fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> Box<[u8]> { + let first_ptr = unsafe { self.perf_aux_buffer.add(tail_wrap as usize) as *mut u8 }; let first_len = self.perf_aux_buffer_size - tail_wrap as usize; + let second_ptr = self.perf_aux_buffer as *mut u8; let second_len = head_wrap as usize; - OwnedRefMut::Owned( - [ - slice::from_raw_parts(first_ptr, first_len), - slice::from_raw_parts(second_ptr, second_len), - ] - .concat() - .into_boxed_slice(), - ) + + let mut vec = Vec::with_capacity(first_len + second_len); + vec.extend_from_slice(unsafe { slice::from_raw_parts(first_ptr, first_len) }); + vec.extend_from_slice(unsafe { slice::from_raw_parts(second_ptr, second_len) }); + vec.into_boxed_slice() } #[inline] From 2998445e02aa27686af1757b545920d3f7a75a13 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 28 Nov 2024 19:22:49 +0100 Subject: [PATCH 35/93] testing stuff --- fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs index 85099189764..af876f411d4 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs @@ -108,13 +108,12 @@ pub fn fuzz() { // Initialize QEMU Emulator let emu = Emulator::builder() .qemu_cli(args) - .append_module( + .prepend_module( StdEdgeCoverageModule::builder() .map_observer(edges_observer.as_mut()) .build()?, ) - .build() - .unwrap(); + .build()?; // Set breakpoints of interest with corresponding commands. emu.add_breakpoint( From b28d47358bcbeb585c1818c6ef42059ec34cc7c5 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:21:14 +0100 Subject: [PATCH 36/93] Set ip_filters (also) with builder --- .../intel_pt_command_executor/src/main.rs | 11 ++++---- libafl_intelpt/src/lib.rs | 28 ++++++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index f7a747c3900..f91ed0644df 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -84,19 +84,20 @@ pub fn main() { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let mut intel_pt = IntelPT::builder().cpu(cpu.0).inherit(true).build().unwrap(); - // The target is a ET_DYN elf, it will be relocated by the loader with this offset. // see https://github.com/torvalds/linux/blob/c1e939a21eb111a6d6067b38e8e04b8809b64c4e/arch/x86/include/asm/elf.h#L234C1-L239C38 const DEFAULT_MAP_WINDOW: usize = (1 << 47) - PAGE_SIZE; - const ELF_ET_DYN_BASE: usize = DEFAULT_MAP_WINDOW / 3 * 2 & !(PAGE_SIZE - 1); + const ELF_ET_DYN_BASE: usize = (DEFAULT_MAP_WINDOW / 3 * 2) & !(PAGE_SIZE - 1); // Set the instruction pointer (IP) filter and memory image of our target. // These information can be retrieved from `readelf -l` (for example) let code_memory_addresses = ELF_ET_DYN_BASE + 0x15000..=ELF_ET_DYN_BASE + 0x14000 + 0x41000; - intel_pt - .set_ip_filters(&[code_memory_addresses.clone()]) + let intel_pt = IntelPT::builder() + .cpu(cpu.0) + .inherit(true) + .ip_filters(&[code_memory_addresses.clone()]) + .build() .unwrap(); let sections = [Section { diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 0d88ac9a528..43f79b43cd9 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -452,6 +452,7 @@ pub struct IntelPTBuilder { inherit: bool, perf_buffer_size: usize, perf_aux_buffer_size: usize, + ip_filters: Vec>, } #[cfg(target_os = "linux")] @@ -461,14 +462,15 @@ impl Default for IntelPTBuilder { /// The default configuration corresponds to: /// ```rust /// use libafl_intelpt::{IntelPTBuilder, PAGE_SIZE}; - /// let builder = unsafe { std::mem::zeroed::() } + /// let builder = IntelPTBuilder::default() /// .pid(None) /// .all_cpus() /// .exclude_kernel(true) /// .exclude_hv(true) /// .inherit(false) /// .perf_buffer_size(128 * PAGE_SIZE + PAGE_SIZE).unwrap() - /// .perf_aux_buffer_size(2 * 1024 * 1024).unwrap(); + /// .perf_aux_buffer_size(2 * 1024 * 1024).unwrap() + /// .ip_filters(&[]); /// assert_eq!(builder, IntelPTBuilder::default()); /// ``` fn default() -> Self { @@ -480,6 +482,7 @@ impl Default for IntelPTBuilder { inherit: false, perf_buffer_size: 128 * PAGE_SIZE + PAGE_SIZE, perf_aux_buffer_size: 2 * 1024 * 1024, + ip_filters: Vec::new(), } } } @@ -543,9 +546,7 @@ impl IntelPTBuilder { let aux_head = unsafe { &raw mut (*buff_metadata).aux_head }; let aux_tail = unsafe { &raw mut (*buff_metadata).aux_tail }; - let ip_filters = Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize); - - Ok(IntelPT { + let mut intel_pt = IntelPT { fd, perf_buffer, perf_aux_buffer, @@ -554,10 +555,14 @@ impl IntelPTBuilder { aux_head, aux_tail, previous_decode_head: 0, - ip_filters, + ip_filters: Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize), #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), - }) + }; + if !self.ip_filters.is_empty() { + intel_pt.set_ip_filters(&self.ip_filters)?; + } + Ok(intel_pt) } /// Warn if the configuration is not recommended @@ -651,6 +656,15 @@ impl IntelPTBuilder { self.perf_aux_buffer_size = perf_aux_buffer_size; Ok(self) } + + #[must_use] + /// Set filters based on Instruction Pointer (IP) + /// + /// Only instructions in `filters` ranges will be traced. + pub fn ip_filters(mut self, filters: &[RangeInclusive]) -> Self { + self.ip_filters = filters.to_vec(); + self + } } /// Perf event config for `IntelPT` From 9222eabca6f065e07ada711090d1b6461ea1e0ee Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:56:54 +0100 Subject: [PATCH 37/93] Move trace to file --- libafl/src/executors/hooks/intel_pt.rs | 24 +++++------------------- libafl_intelpt/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index a526aa8661f..a712972e24b 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -52,36 +52,22 @@ where } fn post_exec(&mut self, _state: &mut S, _input: &S::Input) { - self.intel_pt.disable_tracing().unwrap(); + let pt = &mut self.intel_pt; + pt.disable_tracing().unwrap(); let slice = unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }; - let _ = self - .intel_pt + let _ = pt .decode_traces_into_map(&mut self.image.0, slice) .inspect_err(|e| log::warn!("Intel PT trace decoding failed: {e}")); #[cfg(feature = "intel_pt_export_raw")] { - let _ = trace_to_file(&self.intel_pt) + let _ = pt + .dump_last_trace_to_file() .inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}")); } } } -#[cfg(feature = "intel_pt_export_raw")] -fn trace_to_file(pt: &IntelPT) -> Result<(), Error> { - use std::{fs, io::Write, path::Path, time}; - - let traces_dir = Path::new("traces"); - fs::create_dir_all(traces_dir)?; - let timestamp = time::SystemTime::now() - .duration_since(time::UNIX_EPOCH) - .map_err(|e| Error::unknown(e.to_string()))? - .as_micros(); - let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?; - file.write_all(&*pt.last_decode_trace())?; - Ok(()) -} - // It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't // like this (and `TryFromIter` is not a thing atm) fn sections_to_image( diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 43f79b43cd9..8701e020731 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -427,6 +427,23 @@ impl IntelPT { pub fn last_decode_trace(&self) -> Vec { self.last_decode_trace.clone() } + + /// Dump the raw trace used in the last decoding to the file + /// /// `./traces/trace_` + #[cfg(feature = "export_raw")] + pub fn dump_last_trace_to_file(&self) -> Result<(), Error> { + use std::{fs, io::Write, path::Path, time}; + + let traces_dir = Path::new("traces"); + fs::create_dir_all(traces_dir)?; + let timestamp = time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .map_err(|e| Error::unknown(e.to_string()))? + .as_micros(); + let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?; + file.write_all(&self.last_decode_trace())?; + Ok(()) + } } #[cfg(target_os = "linux")] From 53a241fd76167b03473d7fe8f28551a77b4d46de Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:13:12 +0100 Subject: [PATCH 38/93] Store a pt_builder in module enable the setting of filters and other pt settings --- .../qemu_intel_pt_bootloader/src/main.rs | 14 +++-- libafl_qemu/Cargo.toml | 1 + .../src/modules/systemmode/intel_pt.rs | 51 ++++++++++--------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index e92cad4380d..d58c84c883a 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -31,13 +31,13 @@ use libafl_qemu::{ config::{Accelerator, QemuConfig}, executor::QemuExecutor, modules::intel_pt::IntelPTModule, - Emulator, EmulatorBuilder, Qemu, QemuExitReason, QemuShutdownCause, + Emulator, EmulatorBuilder, QemuExitReason, QemuShutdownCause, }; // Coverage map const MAP_SIZE: usize = 4096; static mut MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; -const MAX_INPUT_SIZE: usize = 50; + fn main() { env_logger::init(); @@ -49,7 +49,7 @@ fn main() { let objective_dir = PathBuf::from("./crashes"); let mut run_client = |state: Option<_>, mut mgr, _core_id| -> Result<(), Error> { - let target_dir = env::var("TARGET_DIR").expect("TARGET_DIR env not set"); + let target_dir = env::var("TARGET_DIR").unwrap_or("target".to_string()); let target_subdir = if cfg!(debug_assertions) { "debug" } else { @@ -69,8 +69,12 @@ fn main() { .start_cpu(false) .build(); - let emulator_modules = - tuple_list!(IntelPTModule::new(unsafe { MAP.as_mut_ptr() }, MAP_SIZE)); + let intel_pt_builder = IntelPTModule::default_pt_builder().ip_filters(&[0x7c00..=0x7e00]); + let emulator_modules = tuple_list!(IntelPTModule::builder() + .map_ptr(unsafe { MAP.as_mut_ptr() }) + .map_len(MAP_SIZE) + .intel_pt_builder(intel_pt_builder) + .build()); let emulator = EmulatorBuilder::empty() .qemu_config(qemu) diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index fccdcdad8cc..dc9b82c45b1 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -81,6 +81,7 @@ slirp = [ ] # build qemu with host libslirp (for user networking) intel_pt = ["systemmode", "x86_64", "dep:libafl_intelpt"] +intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"] # Requires the binary's build.rs to call `build_libafl_qemu` shared = ["libafl_qemu_sys/shared"] diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 47411f9086f..85e2e6d57c7 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -6,29 +6,29 @@ use std::{ }; use libafl::{inputs::UsesInput, observers::ObserversTuple, HasMetadata}; -use libafl_intelpt::IntelPT; +pub use libafl_intelpt::{IntelPT, IntelPTBuilder}; use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; use num_traits::SaturatingAdd; +use typed_builder::TypedBuilder; use crate::{ modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind, NopPageFilter}, EmulatorModules, NewThreadHook, Qemu, QemuParams, }; -#[derive(Debug)] +#[derive(Debug, TypedBuilder)] pub struct IntelPTModule { + #[builder(setter(skip), default)] pt: Option, + #[builder(default = IntelPTModule::default_pt_builder())] + intel_pt_builder: IntelPTBuilder, map_ptr: *mut T, map_len: usize, } -impl IntelPTModule { - pub fn new(map_ptr: *mut T, map_len: usize) -> Self { - Self { - pt: None, - map_ptr, - map_len, - } +impl IntelPTModule { + pub fn default_pt_builder() -> IntelPTBuilder { + IntelPT::builder().exclude_kernel(false) } } @@ -47,7 +47,6 @@ where ) where ET: EmulatorModuleTuple, { - println!("pre_qemu_init"); emulator_modules .thread_creation(NewThreadHook::Function(intel_pt_new_thread::)) .unwrap(); @@ -63,7 +62,6 @@ where ) where ET: EmulatorModuleTuple, { - println!("pre_exec"); let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.enable_tracing().unwrap(); } @@ -83,15 +81,22 @@ where let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.disable_tracing().unwrap(); - // we need the memory map to decode the traces here take it in prexec. use QemuMemoryChunk // TODO handle self modifying code - pt.decode_with_callback( - |addr, out_buff| qemu.read_mem(out_buff, addr.into()).unwrap(), + // TODO log errors or panic or smth + let _ = pt.decode_with_callback( + |addr, out_buff| { + let _ = qemu.read_mem(out_buff, addr.into()); + }, unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, - ) - .unwrap(); + ); + #[cfg(feature = "intel_pt_export_raw")] + { + let _ = pt + .dump_last_trace_to_file() + .inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}")); + } let m = unsafe { slice::from_raw_parts(self.map_ptr, self.map_len) }; println!("map: {:?}", m); } @@ -157,15 +162,15 @@ where panic!("Intel PT module already initialized, only single core VMs are supported ATM."); } - intel_pt_module.pt = Some(IntelPT::builder().pid(Some(tid as i32)).build().unwrap()); - intel_pt_module - .pt - .as_mut() - .unwrap() - .enable_tracing() + let pt = intel_pt_module + .intel_pt_builder + .clone() + .pid(Some(tid as i32)) + .build() .unwrap(); - println!("IntelPT initialized!"); + intel_pt_module.pt = Some(pt); + // What does this bool mean? ignore for the moment true } From 7c8ded97654695f0866dc8cd6e67b4b14018ff4f Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:00:09 +0100 Subject: [PATCH 39/93] baby_bootloader target --- .../qemu_intel_pt_bootloader/Cargo.toml | 2 +- .../qemu_intel_pt_bootloader/src/boot.s | 62 ++++++++++++---- .../qemu_intel_pt_bootloader/src/main.rs | 30 ++++++-- libafl_intelpt/src/lib.rs | 28 ++++++-- .../src/modules/systemmode/intel_pt.rs | 70 ++++++++++++++++--- 5 files changed, 154 insertions(+), 38 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml index d1a12abbaf9..4f6c4d761bc 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml @@ -8,6 +8,6 @@ edition = "2021" libafl = { path = "../../../libafl", default-features = false } libafl_bolts = { path = "../../../libafl_bolts" } libafl_qemu = { path = "../../../libafl_qemu", features = [ - "intel_pt", + "intel_pt_export_raw", ], default-features = false } env_logger = "0.11.5" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s index 5cd58d35a41..1c72462b61e 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s @@ -2,32 +2,59 @@ [org 0x7c00] ; sets the start address _start: - mov cx, 0 + mov dx, [0x041e] ; BIOS Data Area (BDA) keyboard buffer + mov cx, [0x0420] -bye: - mov ah, 0x0E ; BIOS teletype function - mov al, 'B' + mov ah, 0x0E + mov al, dh int 0x10 - mov al, 'y' + mov al, dl int 0x10 - mov al, 'e' + mov al, ch int 0x10 - mov al, '!' + mov al, cl + int 0x10 + mov al, 0x0d ; CR + int 0x10 + mov al, 0x0a ; LF int 0x10 + cmp dh, 'a' + jne sleep_forever + cmp dl, 'b' + jne sleep_forever + cmp ch, 'c' + jne sleep_forever + loop: add cx, 1 - cmp cx, 0xff + cmp cx, 'a' jne loop add cx, 1 - mov al, cl - add al, 0x20 - mov ah, 0x7c - jmp ax ; will this be traced as a full address? yes - -times 0x50 db 0x90 ; nop sled shutdown: + ; say bye to user + mov ah, 0x0E + mov al, 'B' + int 0x10 + mov al, 'y' + int 0x10 + mov al, 'e' + int 0x10 + mov al, '!' + int 0x10 + mov al, 0x0d ; CR + int 0x10 + mov al, 0x0a ; LF + int 0x10 + + ; sleep a bit to make sure output is printed + xor cx, cx + mov dx, 0xffff + mov ah, 0x86 + int 0x15 + + ; actual shutdown mov ax, 0x1000 mov ax, ss mov sp, 0xf000 @@ -36,6 +63,13 @@ shutdown: mov cx, 0x0003 int 0x15 +sleep_forever: + mov cx, 0xffff + mov dx, 0xffff + mov ah, 0x86 + int 0x15 + jmp sleep_forever + times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full dw 0xaa55 ; magic bytes that tell BIOS that this is bootable diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index d58c84c883a..e1101b8ed31 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -1,7 +1,11 @@ //! A fuzzer using qemu in systemmode with intel PT use core::time::Duration; -use std::{env, path::PathBuf, process}; +use std::{ + env, + path::{Path, PathBuf}, + process, +}; use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, @@ -30,13 +34,15 @@ use libafl_qemu::{ config, config::{Accelerator, QemuConfig}, executor::QemuExecutor, - modules::intel_pt::IntelPTModule, + modules::intel_pt::{IntelPTModule, Section}, Emulator, EmulatorBuilder, QemuExitReason, QemuShutdownCause, }; // Coverage map -const MAP_SIZE: usize = 4096; -static mut MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; +const MAP_SIZE: usize = 128; +static mut MAP: [u16; MAP_SIZE] = [0; MAP_SIZE]; + +const BOOTLOADER_START: u64 = 0x7c00; fn main() { env_logger::init(); @@ -69,17 +75,31 @@ fn main() { .start_cpu(false) .build(); - let intel_pt_builder = IntelPTModule::default_pt_builder().ip_filters(&[0x7c00..=0x7e00]); + let file_path = Path::new(&target_dir) + .join("boot.bin") + .to_string_lossy() + .to_string(); + let image = [Section { + file_path, + file_offset: 0, + size: 512 - 3, + virtual_address: BOOTLOADER_START, + }]; + let intel_pt_builder = IntelPTModule::default_pt_builder() + .ip_filters(&[BOOTLOADER_START as usize..=0x7e00 - 3]); let emulator_modules = tuple_list!(IntelPTModule::builder() .map_ptr(unsafe { MAP.as_mut_ptr() }) .map_len(MAP_SIZE) .intel_pt_builder(intel_pt_builder) + .image(&image) .build()); let emulator = EmulatorBuilder::empty() .qemu_config(qemu) .modules(emulator_modules) .build()?; + let qemu = emulator.qemu(); + qemu.set_breakpoint(BOOTLOADER_START); let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, _: &mut StdState, diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 57ff012c228..df8c248cd78 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -44,10 +44,12 @@ use libafl_bolts::ownedref::OwnedRefMut; use libafl_bolts::Error; #[cfg(target_os = "linux")] use libipt::{ - block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, Asid, BlockFlags, - ConfigBuilder, Cpu, Image, PtError, PtErrorCode, Status, + block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, BlockFlags, + ConfigBuilder, Cpu, PtError, PtErrorCode, Status, }; #[cfg(target_os = "linux")] +pub use libipt::{Asid, Image, SectionCache}; +#[cfg(target_os = "linux")] use num_enum::TryFromPrimitive; #[cfg(target_os = "linux")] use num_traits::{Euclid, SaturatingAdd}; @@ -435,25 +437,37 @@ impl IntelPT { Ok((b, s)) => { *status = s; let offset = decoder.offset().map_err(error_from_pt_error)?; - + //todo: remove !b.speculative() if !b.speculative() && skip < offset { let id = hash_me(*previous_block_end_ip) ^ hash_me(b.ip()); // SAFETY: the index is < map.len() since the modulo operation is applied let map_loc = unsafe { map.get_unchecked_mut(id as usize % map.len()) }; *map_loc = (*map_loc).saturating_add(&1u8.into()); + log::trace!( + "previous block ip: {:x} current: {:x} asid: {:?}", + previous_block_end_ip, + b.ip(), + decoder.asid().unwrap_or_default() + ); + log::trace!("writing map at {} with value {:?}", id, *map_loc); *previous_block_end_ip = b.end_ip(); } + + if status.eos() { + break 'block; + } } Err(e) => { if e.code() != PtErrorCode::Eos { - log::trace!("PT error in block next {e:?}"); + log::trace!( + "PT error in block next {e:?} last decoded block end {:x}", + previous_block_end_ip + ); } + break 'block; } } - if status.eos() { - break 'block; - } } Ok(()) } diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 85e2e6d57c7..44d9220cc6c 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -5,8 +5,8 @@ use std::{ slice, }; -use libafl::{inputs::UsesInput, observers::ObserversTuple, HasMetadata}; -pub use libafl_intelpt::{IntelPT, IntelPTBuilder}; +use libafl::{inputs::UsesInput, observers::ObserversTuple, Error, HasMetadata}; +use libafl_intelpt::{error_from_pt_error, Asid, Image, IntelPT, IntelPTBuilder, SectionCache}; use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; use num_traits::SaturatingAdd; use typed_builder::TypedBuilder; @@ -22,6 +22,8 @@ pub struct IntelPTModule { pt: Option, #[builder(default = IntelPTModule::default_pt_builder())] intel_pt_builder: IntelPTBuilder, + #[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))] + image: (Image<'static>, SectionCache<'static>), map_ptr: *mut T, map_len: usize, } @@ -80,16 +82,18 @@ where { let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.disable_tracing().unwrap(); - // TODO handle self modifying code // TODO log errors or panic or smth - let _ = pt.decode_with_callback( - |addr, out_buff| { - let _ = qemu.read_mem(out_buff, addr.into()); - }, - unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, - ); + // let _ = pt.decode_with_callback( + // |addr, out_buff| { + // let _ = qemu.read_mem(out_buff, addr.into()); + // }, + // unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, + // ); + + let map = unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }; + let _ = pt.decode_traces_into_map(&mut self.image.0, map); #[cfg(feature = "intel_pt_export_raw")] { @@ -97,8 +101,7 @@ where .dump_last_trace_to_file() .inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}")); } - let m = unsafe { slice::from_raw_parts(self.map_ptr, self.map_len) }; - println!("map: {:?}", m); + println!("map: {:?}", map); } fn address_filter(&self) -> &Self::ModuleAddressFilter { @@ -174,3 +177,48 @@ where // What does this bool mean? ignore for the moment true } + +// It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't +// like this (and `TryFromIter` is not a thing atm) +fn sections_to_image( + sections: &[Section], +) -> Result<(Image<'static>, SectionCache<'static>), Error> { + let mut image_cache = SectionCache::new(Some("image_cache")).map_err(error_from_pt_error)?; + let mut image = Image::new(Some("image")).map_err(error_from_pt_error)?; + + for s in sections { + let isid = image_cache.add_file(&s.file_path, s.file_offset, s.size, s.virtual_address); + if let Err(e) = isid { + log::warn!( + "Error while caching {} {} - skipped", + s.file_path, + e.to_string() + ); + continue; + } + + if let Err(e) = image.add_cached(&mut image_cache, isid.unwrap(), Asid::default()) { + log::warn!( + "Error while adding cache to image {} {} - skipped", + s.file_path, + e.to_string() + ); + continue; + } + } + + Ok((image, image_cache)) +} + +/// Info of a binary's section that can be used during `Intel PT` traces decoding +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Section { + /// Path of the binary + pub file_path: String, + /// Offset of the section in the file + pub file_offset: u64, + /// Size of the section + pub size: u64, + /// Start virtual address of the section once loaded in memory + pub virtual_address: u64, +} From 570030f14cf641dd5839dc0ca827c40839a4f822 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:35:29 +0100 Subject: [PATCH 40/93] Best bootloader ever --- .../qemu_intel_pt_bootloader/src/boot.s | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s index 1c72462b61e..f3e4b787138 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s @@ -1,52 +1,46 @@ [bits 16] ; use 16 bits -[org 0x7c00] ; sets the start address +[org 0x7c00] ; sets the start address -_start: - mov dx, [0x041e] ; BIOS Data Area (BDA) keyboard buffer - mov cx, [0x0420] - - mov ah, 0x0E - mov al, dh - int 0x10 - mov al, dl - int 0x10 - mov al, ch - int 0x10 - mov al, cl +%macro print_string 1 ; %1: Pointer to the string (null-terminated) + mov si, %1 ; Load the pointer to the string +.print_char: + lodsb ; Load the next byte from [SI] into AL + or al, al ; Check if it's the null terminator + jz .done ; If zero, we are done + mov ah, 0x0E ; BIOS teletype function + int 0x10 ; Call BIOS interrupt + jmp .print_char ; Repeat for the next character +.done: + mov al, 0x0d ; CR int 0x10 - mov al, 0x0d ; CR - int 0x10 - mov al, 0x0a ; LF + mov al, 0x0a ; LF int 0x10 +%endmacro + +start: + mov ah, 0xc0 + int 0x15 ; ask for the system configuration parameters + jc fail ; carry must be 0 + cmp ah, 0 ; ah must be 0 + jne fail + + mov ax, [es:bx] ; byte count + cmp ax, 8 + jl fail - cmp dh, 'a' - jne sleep_forever - cmp dl, 'b' - jne sleep_forever - cmp ch, 'c' - jne sleep_forever + mov ch, [es:bx + 2] ; Model + mov cl, [es:bx + 3] ; Submodel + mov dh, [es:bx + 4] ; BIOS revision -loop: - add cx, 1 - cmp cx, 'a' - jne loop - add cx, 1 + cmp ch, 'a' + jne fail + cmp cl, 'b' + jne fail + cmp dh, 'c' + jne fail shutdown: - ; say bye to user - mov ah, 0x0E - mov al, 'B' - int 0x10 - mov al, 'y' - int 0x10 - mov al, 'e' - int 0x10 - mov al, '!' - int 0x10 - mov al, 0x0d ; CR - int 0x10 - mov al, 0x0a ; LF - int 0x10 + print_string bye ; sleep a bit to make sure output is printed xor cx, cx @@ -63,6 +57,8 @@ shutdown: mov cx, 0x0003 int 0x15 +fail: + print_string fail_msg sleep_forever: mov cx, 0xffff mov dx, 0xffff @@ -70,6 +66,9 @@ sleep_forever: int 0x15 jmp sleep_forever -times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full +fail_msg db "I don't like your BIOS. :(", 0 +bye db "Amazing <3 Bye!", 0 + +times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full -dw 0xaa55 ; magic bytes that tell BIOS that this is bootable +dw 0xaa55 ; magic bytes that tell BIOS that this is bootable From 0fe297748d310f1d0805aaf7c49c82a11ca18c5e Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 5 Dec 2024 15:35:05 +0100 Subject: [PATCH 41/93] new builder? --- .../binary_only/qemu_coverage/src/fuzzer.rs | 198 +++++++++--------- libafl_qemu/Cargo.toml | 1 + libafl_qemu/src/emu/builder.rs | 63 +++--- libafl_qemu/src/emu/mod.rs | 5 +- libafl_qemu/src/qemu/config.rs | 16 +- libafl_qemu/src/qemu/error.rs | 5 + libafl_qemu/src/qemu/mod.rs | 103 +++++---- 7 files changed, 207 insertions(+), 184 deletions(-) diff --git a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs index 18ee14db0b1..5017b0360cb 100644 --- a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs @@ -126,101 +126,101 @@ pub fn fuzz() { let mut run_client = |state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _, _>, client_description: ClientDescription| { - let mut cov_path = options.coverage_path.clone(); - - let emulator_modules = tuple_list!(DrCovModule::builder() - .filter(StdAddressFilter::default()) - .filename(cov_path.clone()) - .full_trace(false) - .build()); - - let emulator = Emulator::empty() - .qemu_cli(options.args.clone()) - .modules(emulator_modules) - .build() - .expect("QEMU initialization failed"); - let qemu = emulator.qemu(); - - let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); - - let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) - .expect("Symbol LLVMFuzzerTestOneInput not found"); - log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - - qemu.entry_break(test_one_input_ptr); - - for m in qemu.mappings() { - log::debug!( - "Mapping: 0x{:016x}-0x{:016x}, {}", - m.start(), - m.end(), - m.path().unwrap_or(&"".to_string()) - ); - } + let mut cov_path = options.coverage_path.clone(); + + let emulator_modules = tuple_list!(DrCovModule::builder() + .filter(StdAddressFilter::default()) + .filename(cov_path.clone()) + .full_trace(false) + .build()); + + let emulator = Emulator::empty() + .qemu_cli(options.args.clone()) + .modules(emulator_modules) + .build() + .expect("QEMU initialization failed"); + let qemu = emulator.qemu(); + + let mut elf_buffer = Vec::new(); + let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap(); + + let test_one_input_ptr = elf + .resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr()) + .expect("Symbol LLVMFuzzerTestOneInput not found"); + log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); + + qemu.entry_break(test_one_input_ptr); + + for m in qemu.mappings() { + log::debug!( + "Mapping: 0x{:016x}-0x{:016x}, {}", + m.start(), + m.end(), + m.path().unwrap_or(&"".to_string()) + ); + } - let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); - log::debug!("Break at {pc:#x}"); + let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap(); + log::debug!("Break at {pc:#x}"); - let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); - log::debug!("Return address = {ret_addr:#x}"); + let ret_addr: GuestAddr = qemu.read_return_address().unwrap(); + log::debug!("Return address = {ret_addr:#x}"); - qemu.set_breakpoint(ret_addr); + qemu.set_breakpoint(ret_addr); - let input_addr = qemu - .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) - .unwrap(); - log::debug!("Placing input at {input_addr:#x}"); + let input_addr = qemu + .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) + .unwrap(); + log::debug!("Placing input at {input_addr:#x}"); - let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); + let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap(); - let reset = |buf: &[u8], len: GuestReg| -> Result<(), QemuRWError> { - unsafe { - let _ = qemu.write_mem(input_addr, buf); - qemu.write_reg(Regs::Pc, test_one_input_ptr)?; - qemu.write_reg(Regs::Sp, stack_ptr)?; - qemu.write_return_address(ret_addr)?; - qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; - qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?; + let reset = |buf: &[u8], len: GuestReg| -> Result<(), QemuRWError> { + unsafe { + let _ = qemu.write_mem(input_addr, buf); + qemu.write_reg(Regs::Pc, test_one_input_ptr)?; + qemu.write_reg(Regs::Sp, stack_ptr)?; + qemu.write_return_address(ret_addr)?; + qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; + qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?; - match qemu.run() { - Ok(QemuExitReason::Breakpoint(_)) => {} - Ok(QemuExitReason::End(QemuShutdownCause::HostSignal( - Signal::SigInterrupt, - ))) => process::exit(0), - _ => panic!("Unexpected QEMU exit."), - } + match qemu.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + Ok(QemuExitReason::End(QemuShutdownCause::HostSignal( + Signal::SigInterrupt, + ))) => process::exit(0), + _ => panic!("Unexpected QEMU exit."), + } - Ok(()) + Ok(()) + } + }; + + let mut harness = + |_emulator: &mut Emulator<_, _, _, _, _>, _state: &mut _, input: &BytesInput| { + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let mut len = buf.len(); + if len > MAX_INPUT_SIZE { + buf = &buf[0..MAX_INPUT_SIZE]; + len = MAX_INPUT_SIZE; } + let len = len as GuestReg; + reset(buf, len).unwrap(); + ExitKind::Ok }; - let mut harness = - |_emulator: &mut Emulator<_, _, _, _, _>, _state: &mut _, input: &BytesInput| { - let target = input.target_bytes(); - let mut buf = target.as_slice(); - let mut len = buf.len(); - if len > MAX_INPUT_SIZE { - buf = &buf[0..MAX_INPUT_SIZE]; - len = MAX_INPUT_SIZE; - } - let len = len as GuestReg; - reset(buf, len).unwrap(); - ExitKind::Ok - }; - - let core_id = client_description.core_id(); - let core_idx = options - .cores - .position(core_id) - .expect("Failed to get core index"); - let files = corpus_files - .iter() - .skip(files_per_core * core_idx) - .take(files_per_core) - .map(|x| x.path()) - .collect::>(); + let core_id = client_description.core_id(); + let core_idx = options + .cores + .position(core_id) + .expect("Failed to get core index"); + let files = corpus_files + .iter() + .skip(files_per_core * core_idx) + .take(files_per_core) + .map(|x| x.path()) + .collect::>(); if files.is_empty() { mgr.send_exiting()?; @@ -247,21 +247,21 @@ pub fn fuzz() { let scheduler = QueueScheduler::new(); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let coverage_name = cov_path.file_stem().unwrap().to_str().unwrap(); - let coverage_extension = cov_path.extension().unwrap_or_default().to_str().unwrap(); - let core = core_id.0; - cov_path.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}")); - - let mut executor = QemuExecutor::new( - emulator, - &mut harness, - (), - &mut fuzzer, - &mut state, - &mut mgr, - options.timeout, - ) - .expect("Failed to create QemuExecutor"); + let coverage_name = cov_path.file_stem().unwrap().to_str().unwrap(); + let coverage_extension = cov_path.extension().unwrap_or_default().to_str().unwrap(); + let core = core_id.0; + cov_path.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}")); + + let mut executor = QemuExecutor::new( + emulator, + &mut harness, + (), + &mut fuzzer, + &mut state, + &mut mgr, + options.timeout, + ) + .expect("Failed to create QemuExecutor"); if state.must_load_initial_inputs() { state diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index d107dc4d0b2..d61d478a954 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -125,6 +125,7 @@ bytes-utils = "0.1.4" typed-builder = { workspace = true } memmap2 = "0.9.5" getset = "0.1.3" +derive_builder = "0.20.2" # Document all features of this crate (for `cargo doc`) document-features = { workspace = true, optional = true } diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index 5a7c7883b50..767071f2179 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, marker::PhantomData}; +use std::marker::PhantomData; use libafl::{ inputs::{HasTargetBytes, UsesInput}, @@ -10,14 +10,14 @@ use libafl_bolts::tuples::{tuple_list, Append, Prepend}; use crate::FastSnapshotManager; use crate::{ command::{CommandManager, NopCommandManager, StdCommandManager}, - config::QemuConfig, modules::{EmulatorModule, EmulatorModuleTuple}, Emulator, NopEmulatorDriver, NopSnapshotManager, QemuInitError, QemuParams, StdEmulatorDriver, StdSnapshotManager, }; +use crate::config::QemuConfigBuilder; -#[derive(Clone, Debug)] -pub struct EmulatorBuilder +#[derive(Clone)] +pub struct EmulatorBuilder where S: UsesInput, { @@ -25,11 +25,11 @@ where driver: ED, snapshot_manager: SM, command_manager: CM, - qemu_parameters: Option, + qemu_parameters: QB, phantom: PhantomData, } -impl EmulatorBuilder +impl EmulatorBuilder where S: UsesInput, { @@ -40,14 +40,14 @@ where driver: NopEmulatorDriver, snapshot_manager: NopSnapshotManager, command_manager: NopCommandManager, - qemu_parameters: None, + qemu_parameters: QemuConfigBuilder::default(), phantom: PhantomData, } } } #[cfg(feature = "usermode")] -impl EmulatorBuilder, StdEmulatorDriver, (), S, StdSnapshotManager> +impl EmulatorBuilder, StdEmulatorDriver, (), QemuConfigBuilder, S, StdSnapshotManager> where S: State + HasExecutions + Unpin, S::Input: HasTargetBytes, @@ -60,14 +60,14 @@ where command_manager: StdCommandManager::default(), snapshot_manager: StdSnapshotManager::default(), driver: StdEmulatorDriver::builder().build(), - qemu_parameters: None, + qemu_parameters: QemuConfigBuilder::default(), phantom: PhantomData, } } } #[cfg(feature = "systemmode")] -impl EmulatorBuilder, StdEmulatorDriver, (), S, StdSnapshotManager> +impl EmulatorBuilder, StdEmulatorDriver, (), QemuConfigBuilder, S, StdSnapshotManager> where S: State + HasExecutions + Unpin, S::Input: HasTargetBytes, @@ -78,12 +78,12 @@ where command_manager: StdCommandManager::default(), snapshot_manager: FastSnapshotManager::default(), driver: StdEmulatorDriver::builder().build(), - qemu_parameters: None, + qemu_parameters: Some(QemuConfigBuilder::default()), phantom: PhantomData, } } } -impl EmulatorBuilder +impl EmulatorBuilder where S: UsesInput + Unpin, { @@ -92,7 +92,7 @@ where driver: ED, command_manager: CM, snapshot_manager: SM, - qemu_parameters: Option, + qemu_parameters: QB, ) -> Self { Self { modules, @@ -108,9 +108,12 @@ where where CM: CommandManager, ET: EmulatorModuleTuple, + QB: TryInto { + let qemu_params: QemuParams = self.qemu_parameters.try_into()?; + Emulator::new( - self.qemu_parameters.ok_or(QemuInitError::EmptyArgs)?, + qemu_params, self.modules, self.driver, self.snapshot_manager, @@ -119,34 +122,26 @@ where } } -impl EmulatorBuilder +impl EmulatorBuilder where CM: CommandManager, S: UsesInput + Unpin, { #[must_use] - pub fn qemu_config(self, qemu_config: QemuConfig) -> EmulatorBuilder { - EmulatorBuilder::new( - self.modules, - self.driver, - self.command_manager, - self.snapshot_manager, - Some(QemuParams::Config(qemu_config)), - ) - } - - #[must_use] - pub fn qemu_cli(self, qemu_cli: Vec) -> EmulatorBuilder { + pub fn qemu_builder(self, qemu_config: QB2) -> EmulatorBuilder + where + QB2: Into + { EmulatorBuilder::new( self.modules, self.driver, self.command_manager, self.snapshot_manager, - Some(QemuParams::Cli(qemu_cli)), + qemu_config, ) } - pub fn prepend_module(self, module: EM) -> EmulatorBuilder + pub fn prepend_module(self, module: EM) -> EmulatorBuilder where EM: EmulatorModule + Unpin, ET: EmulatorModuleTuple, @@ -160,7 +155,7 @@ where ) } - pub fn append_module(self, module: EM) -> EmulatorBuilder + pub fn append_module(self, module: EM) -> EmulatorBuilder where EM: EmulatorModule + Unpin, ET: EmulatorModuleTuple, @@ -174,7 +169,7 @@ where ) } - pub fn driver(self, driver: ED2) -> EmulatorBuilder { + pub fn driver(self, driver: ED2) -> EmulatorBuilder { EmulatorBuilder::new( self.modules, driver, @@ -184,7 +179,7 @@ where ) } - pub fn command_manager(self, command_manager: CM2) -> EmulatorBuilder + pub fn command_manager(self, command_manager: CM2) -> EmulatorBuilder where CM2: CommandManager, { @@ -197,7 +192,7 @@ where ) } - pub fn modules(self, modules: ET2) -> EmulatorBuilder { + pub fn modules(self, modules: ET2) -> EmulatorBuilder { EmulatorBuilder::new( modules, self.driver, @@ -210,7 +205,7 @@ where pub fn snapshot_manager( self, snapshot_manager: SM2, - ) -> EmulatorBuilder { + ) -> EmulatorBuilder { EmulatorBuilder::new( self.modules, self.driver, diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index e0468245371..37916591fc0 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -44,6 +44,7 @@ pub use usermode::*; mod systemmode; #[cfg(feature = "systemmode")] pub use systemmode::*; +use crate::config::QemuConfigBuilder; #[derive(Clone, Copy)] pub enum GuestAddrKind { @@ -244,7 +245,7 @@ where { #[must_use] pub fn empty( - ) -> EmulatorBuilder { + ) -> EmulatorBuilder { EmulatorBuilder::empty() } } @@ -256,7 +257,7 @@ where { #[must_use] pub fn builder( - ) -> EmulatorBuilder, StdEmulatorDriver, (), S, StdSnapshotManager> { + ) -> EmulatorBuilder, StdEmulatorDriver, (), QemuConfigBuilder, S, StdSnapshotManager> { EmulatorBuilder::default() } } diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 6ab1e51f5d3..28c48f81e04 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -3,11 +3,10 @@ use core::{ fmt::{Display, Formatter}, }; use std::path::{Path, PathBuf}; - +use derive_builder::Builder; use getset::Getters; use libafl_derive; use strum_macros; -use typed_builder::TypedBuilder; #[cfg(feature = "systemmode")] #[derive(Debug, strum_macros::Display, Clone)] @@ -37,7 +36,7 @@ pub enum DiskImageFileFormat { Raw, } -#[derive(Debug, Clone, Default, TypedBuilder)] +#[derive(Debug, Clone, Default, Builder)] pub struct Drive { #[builder(default, setter(strip_option, into))] file: Option, @@ -296,16 +295,7 @@ impl> From for Program { } } -#[derive(Debug, Clone, libafl_derive::Display, TypedBuilder, Getters)] -#[builder(builder_method( - doc = "Since Qemu is a zero sized struct, this is not a completely standard builder pattern. \ - The Qemu configuration is not stored in the Qemu struct after build() but in QEMU_CONFIG \ - Therefore, to use the derived builder and avoid boilerplate a builder for QemuConfig is \ - derived. \ - The QemuConfig::builder is called in Qemu::builder() which is the only place where it should \ - be called, in this way the one to one matching of Qemu and QemuConfig is enforced. Therefore \ - its visibility is pub(crate)" -))] +#[derive(Debug, Clone, libafl_derive::Display, Builder, Getters)] #[getset(get = "pub")] pub struct QemuConfig { #[cfg(feature = "systemmode")] diff --git a/libafl_qemu/src/qemu/error.rs b/libafl_qemu/src/qemu/error.rs index d3931c3182c..31d7d183304 100644 --- a/libafl_qemu/src/qemu/error.rs +++ b/libafl_qemu/src/qemu/error.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use libafl_qemu_sys::{CPUStatePtr, GuestAddr}; use crate::CallingConvention; +use crate::config::QemuConfigBuilderError; #[derive(Debug)] pub enum QemuError { @@ -16,6 +17,7 @@ pub enum QemuError { pub enum QemuInitError { MultipleInstances, EmptyArgs, + ConfigurationError(QemuConfigBuilderError), TooManyArgs(usize), } @@ -59,6 +61,9 @@ impl Display for QemuInitError { QemuInitError::EmptyArgs => { write!(f, "QEMU emulator args cannot be empty") } + QemuInitError::ConfigurationError(config_error) => { + write!(f, "QEMU Configuration error: {config_error}") + } QemuInitError::TooManyArgs(n) => { write!( f, diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 7e5bca26f70..e9f14704cf5 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -52,11 +52,39 @@ pub use systemmode::*; mod hooks; pub use hooks::*; +use crate::config::QemuConfigBuilder; static mut QEMU_IS_INITIALIZED: bool = false; pub(super) static QEMU_CONFIG: OnceLock = OnceLock::new(); +#[allow(clippy::vec_box)] +static mut GDB_COMMANDS: Vec> = Vec::new(); + +pub trait HookId { + fn remove(&self, invalidate_block: bool) -> bool; +} + +pub trait ArchExtras { + fn read_return_address(&self) -> Result; + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> + where + T: Into; + fn read_function_argument( + &self, + conv: CallingConvention, + idx: u8, + ) -> Result; + fn write_function_argument( + &self, + conv: CallingConvention, + idx: i32, + val: T, + ) -> Result<(), QemuRWError> + where + T: Into; +} + #[derive(Debug, Clone)] pub enum QemuExitReason { /// QEMU ended for some internal reason @@ -93,18 +121,6 @@ pub struct QemuMemoryChunk { cpu: Option, } -#[allow(clippy::vec_box)] -static mut GDB_COMMANDS: Vec> = Vec::new(); - -unsafe extern "C" fn gdb_cmd(data: *mut c_void, buf: *mut u8, len: usize) -> bool { - unsafe { - let closure = &mut *(data as *mut Box FnMut(Qemu, &'r str) -> bool>); - let cmd = std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf, len)); - let qemu = Qemu::get_unchecked(); - closure(qemu, cmd) - } -} - #[derive(Debug, Clone)] pub enum QemuShutdownCause { None, @@ -137,13 +153,18 @@ pub enum CallingConvention { Cdecl, } -pub trait HookId { - fn remove(&self, invalidate_block: bool) -> bool; -} - #[derive(Debug)] pub struct HookData(u64); +unsafe extern "C" fn gdb_cmd(data: *mut c_void, buf: *mut u8, len: usize) -> bool { + unsafe { + let closure = &mut *(data as *mut Box FnMut(Qemu, &'r str) -> bool>); + let cmd = std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf, len)); + let qemu = Qemu::get_unchecked(); + closure(qemu, cmd) + } +} + impl Display for QemuExitReason { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { @@ -161,6 +182,15 @@ impl From for QemuParams { } } +impl TryFrom for QemuParams { + type Error = QemuInitError; + + fn try_from(config_builder: QemuConfigBuilder) -> Result { + Ok(QemuParams::Config(config_builder.build() + .or_else(|e| Err(QemuInitError::ConfigurationError(e)))?)) + } +} + impl From<&[T]> for QemuParams where T: AsRef, @@ -251,26 +281,6 @@ impl From for MemAccessInfo { } } -pub trait ArchExtras { - fn read_return_address(&self) -> Result; - fn write_return_address(&self, val: T) -> Result<(), QemuRWError> - where - T: Into; - fn read_function_argument( - &self, - conv: CallingConvention, - idx: u8, - ) -> Result; - fn write_function_argument( - &self, - conv: CallingConvention, - idx: i32, - val: T, - ) -> Result<(), QemuRWError> - where - T: Into; -} - #[allow(clippy::unused_self)] impl CPU { #[must_use] @@ -504,8 +514,29 @@ impl From for HookData { } } +// impl QemuBuilder { +// pub fn new(params: T) -> Self { +// Self { +// params +// } +// } +// } + +// impl QemuBuilder +// where +// T: Into +// { +// pub fn build(self) -> Result { +// +// } +// } + #[allow(clippy::unused_self)] impl Qemu { + // pub fn builder() -> QemuBuilder { + // QemuBuilder::default() + // } + #[allow(clippy::must_use_candidate, clippy::similar_names)] pub fn init(params: T) -> Result where From b7f5240a0231e8c75663539a370a693fac3590a7 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 5 Dec 2024 16:44:03 +0100 Subject: [PATCH 42/93] use closure for qemu config from emulator builder. --- .../fuzzbench_fork_qemu/src/fuzzer.rs | 2 +- fuzzers/binary_only/qemu_cmin/src/fuzzer.rs | 2 +- .../binary_only/qemu_coverage/src/fuzzer.rs | 2 +- .../binary_only/qemu_launcher/src/instance.rs | 5 +- .../qemu_baremetal/src/fuzzer_breakpoint.rs | 2 +- .../qemu_baremetal/src/fuzzer_low_level.rs | 42 +++++---- .../qemu_baremetal/src/fuzzer_sync_exit.rs | 2 +- .../qemu_linux_kernel/src/fuzzer.rs | 7 +- .../qemu_linux_process/src/fuzzer.rs | 2 +- libafl_qemu/Cargo.toml | 5 +- libafl_qemu/libafl_qemu_build/Cargo.toml | 5 +- libafl_qemu/libafl_qemu_sys/Cargo.toml | 5 +- libafl_qemu/src/emu/builder.rs | 89 ++++++++++++++----- libafl_qemu/src/emu/mod.rs | 21 ++++- libafl_qemu/src/qemu/config.rs | 22 ++++- libafl_qemu/src/qemu/error.rs | 23 ++++- libafl_qemu/src/qemu/mod.rs | 37 +++----- libafl_sugar/src/qemu.rs | 4 +- 18 files changed, 177 insertions(+), 100 deletions(-) diff --git a/fuzzers/binary_only/fuzzbench_fork_qemu/src/fuzzer.rs b/fuzzers/binary_only/fuzzbench_fork_qemu/src/fuzzer.rs index 838f8d8df57..c6ec8acfd69 100644 --- a/fuzzers/binary_only/fuzzbench_fork_qemu/src/fuzzer.rs +++ b/fuzzers/binary_only/fuzzbench_fork_qemu/src/fuzzer.rs @@ -176,7 +176,7 @@ fn fuzz( ); let emulator = Emulator::empty() - .qemu_cli(args) + .qemu_config(|_| args) .modules(emulator_modules) .build()?; diff --git a/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs b/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs index 6aba2d5dfd4..5c797249a71 100644 --- a/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_cmin/src/fuzzer.rs @@ -134,7 +134,7 @@ pub fn fuzz() -> Result<(), Error> { .build()?); let emulator = Emulator::empty() - .qemu_cli(options.args) + .qemu_config(|_| options.args) .modules(modules) .build()?; let qemu = emulator.qemu(); diff --git a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs index 5017b0360cb..8cd99d6c41e 100644 --- a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs @@ -135,7 +135,7 @@ pub fn fuzz() { .build()); let emulator = Emulator::empty() - .qemu_cli(options.args.clone()) + .qemu_config(|_| options.args.clone()) .modules(emulator_modules) .build() .expect("QEMU initialization failed"); diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index d42eb1b2a75..d55cabafb21 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -129,7 +129,10 @@ impl Instance<'_, M> { .build()?; let modules = modules.prepend(edge_coverage_module); - let mut emulator = Emulator::empty().qemu_cli(args).modules(modules).build()?; + let mut emulator = Emulator::empty() + .modules(modules) + .qemu_config(|_| args) + .build()?; let harness = Harness::init(emulator.qemu()).expect("Error setting up harness."); let qemu = emulator.qemu(); diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs index ea93264cab9..f6e5b09687b 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_breakpoint.rs @@ -107,7 +107,7 @@ pub fn fuzz() { // Initialize QEMU Emulator let emu = Emulator::builder() - .qemu_cli(args) + .qemu_config(|_| args) .prepend_module( StdEdgeCoverageModule::builder() .map_observer(edges_observer.as_mut()) diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs index a5b41589efe..787fa9fb840 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs @@ -29,9 +29,8 @@ use libafl_bolts::{ AsSlice, }; use libafl_qemu::{ - config, config::QemuConfig, elf::EasyElf, executor::QemuExecutor, - modules::edges::StdEdgeCoverageModuleBuilder, Emulator, GuestPhysAddr, QemuExitError, - QemuExitReason, QemuRWError, QemuShutdownCause, Regs, + config, elf::EasyElf, executor::QemuExecutor, modules::edges::StdEdgeCoverageModuleBuilder, + Emulator, GuestPhysAddr, QemuExitError, QemuExitReason, QemuRWError, QemuShutdownCause, Regs, }; use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; @@ -93,30 +92,29 @@ pub fn fuzz() { .track_indices() }; - // Initialize QEMU - let qemu_config = QemuConfig::builder() - .machine("mps2-an385") - .monitor(config::Monitor::Null) - .kernel(format!("{target_dir}/example.elf")) - .serial(config::Serial::Null) - .no_graphic(true) - .snapshot(true) - .drives([config::Drive::builder() - .interface(config::DriveInterface::None) - .format(config::DiskImageFileFormat::Qcow2) - .file(format!("{target_dir}/dummy.qcow2")) - .build()]) - .start_cpu(false) - .build(); - - // .expect("Failed to initialized QEMU"); - let emulator_modules = tuple_list!(StdEdgeCoverageModuleBuilder::default() .map_observer(edges_observer.as_mut()) .build()?); let emulator = Emulator::empty() - .qemu_config(qemu_config) + .qemu_config(|mut qemu_config| { + qemu_config + .machine("mps2-an385") + .monitor(config::Monitor::Null) + .kernel(format!("{target_dir}/example.elf")) + .serial(config::Serial::Null) + .no_graphic(true) + .snapshot(true) + .drives([config::Drive::builder() + .interface(config::DriveInterface::None) + .format(config::DiskImageFileFormat::Qcow2) + .file(format!("{target_dir}/dummy.qcow2")) + .build() + .expect("Could not build drives")]) + .start_cpu(false); + + qemu_config + }) .modules(emulator_modules) .build()?; diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_sync_exit.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_sync_exit.rs index fe6bc314f41..411de715dab 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_sync_exit.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_sync_exit.rs @@ -63,7 +63,7 @@ pub fn fuzz() { .build()?); let emu = Emulator::builder() - .qemu_cli(args) + .qemu_config(|_| args) .modules(modules) .build()?; diff --git a/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs b/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs index bb33be5f5f9..633714ec94d 100644 --- a/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs +++ b/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs @@ -67,14 +67,9 @@ pub fn fuzz() { CmpLogModule::default(), ); - // let driver = StdEmulatorDriver::builder() - // .print_commands(true) - // .build(); - let emu = Emulator::builder() - .qemu_cli(args) + .qemu_config(|_| args) .modules(modules) - // .driver(driver) .build()?; let devices = emu.list_devices(); diff --git a/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs b/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs index ec8cf7beedb..b64eca7ecf1 100644 --- a/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs +++ b/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs @@ -70,7 +70,7 @@ pub fn fuzz() { ); let emu = Emulator::builder() - .qemu_cli(args) + .qemu_config(|_| args) .modules(modules) .build()?; diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index d61d478a954..773a06de7f1 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "libafl_qemu" version.workspace = true -authors = ["Andrea Fioraldi "] +authors = [ + "Andrea Fioraldi ", + "Romain Malmain ", +] description = "QEMU user backend library for LibAFL" documentation = "https://docs.rs/libafl_qemu" repository = "https://github.com/AFLplusplus/LibAFL/" diff --git a/libafl_qemu/libafl_qemu_build/Cargo.toml b/libafl_qemu/libafl_qemu_build/Cargo.toml index 7dbca22934f..8b6c31b0741 100644 --- a/libafl_qemu/libafl_qemu_build/Cargo.toml +++ b/libafl_qemu/libafl_qemu_build/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "libafl_qemu_build" version.workspace = true -authors = ["Andrea Fioraldi "] +authors = [ + "Andrea Fioraldi ", + "Romain Malmain ", +] description = "Builder for LibAFL QEMU" documentation = "https://docs.rs/libafl_qemu_build" repository = "https://github.com/AFLplusplus/LibAFL/" diff --git a/libafl_qemu/libafl_qemu_sys/Cargo.toml b/libafl_qemu/libafl_qemu_sys/Cargo.toml index 0ad98885bd5..b5730812d87 100644 --- a/libafl_qemu/libafl_qemu_sys/Cargo.toml +++ b/libafl_qemu/libafl_qemu_sys/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "libafl_qemu_sys" version.workspace = true -authors = ["Andrea Fioraldi "] +authors = [ + "Andrea Fioraldi ", + "Romain Malmain ", +] description = "C to Rust bindings for the LibAFL QEMU bridge" documentation = "https://docs.rs/libafl_qemu_sys" repository = "https://github.com/AFLplusplus/LibAFL/" diff --git a/libafl_qemu/src/emu/builder.rs b/libafl_qemu/src/emu/builder.rs index 767071f2179..65e3d51b13e 100644 --- a/libafl_qemu/src/emu/builder.rs +++ b/libafl_qemu/src/emu/builder.rs @@ -10,12 +10,15 @@ use libafl_bolts::tuples::{tuple_list, Append, Prepend}; use crate::FastSnapshotManager; use crate::{ command::{CommandManager, NopCommandManager, StdCommandManager}, + config::QemuConfigBuilder, modules::{EmulatorModule, EmulatorModuleTuple}, Emulator, NopEmulatorDriver, NopSnapshotManager, QemuInitError, QemuParams, StdEmulatorDriver, StdSnapshotManager, }; -use crate::config::QemuConfigBuilder; +/// `Emulator` builder. +/// +/// The default configuration of QEMU is always empty. #[derive(Clone)] pub struct EmulatorBuilder where @@ -25,11 +28,19 @@ where driver: ED, snapshot_manager: SM, command_manager: CM, - qemu_parameters: QB, + qemu_config: QB, phantom: PhantomData, } -impl EmulatorBuilder +impl + EmulatorBuilder< + NopCommandManager, + NopEmulatorDriver, + (), + QemuConfigBuilder, + S, + NopSnapshotManager, + > where S: UsesInput, { @@ -40,14 +51,22 @@ where driver: NopEmulatorDriver, snapshot_manager: NopSnapshotManager, command_manager: NopCommandManager, - qemu_parameters: QemuConfigBuilder::default(), + qemu_config: QemuConfigBuilder::default(), phantom: PhantomData, } } } #[cfg(feature = "usermode")] -impl EmulatorBuilder, StdEmulatorDriver, (), QemuConfigBuilder, S, StdSnapshotManager> +impl + EmulatorBuilder< + StdCommandManager, + StdEmulatorDriver, + (), + QemuConfigBuilder, + S, + StdSnapshotManager, + > where S: State + HasExecutions + Unpin, S::Input: HasTargetBytes, @@ -60,14 +79,22 @@ where command_manager: StdCommandManager::default(), snapshot_manager: StdSnapshotManager::default(), driver: StdEmulatorDriver::builder().build(), - qemu_parameters: QemuConfigBuilder::default(), + qemu_config: QemuConfigBuilder::default(), phantom: PhantomData, } } } #[cfg(feature = "systemmode")] -impl EmulatorBuilder, StdEmulatorDriver, (), QemuConfigBuilder, S, StdSnapshotManager> +impl + EmulatorBuilder< + StdCommandManager, + StdEmulatorDriver, + (), + QemuConfigBuilder, + S, + StdSnapshotManager, + > where S: State + HasExecutions + Unpin, S::Input: HasTargetBytes, @@ -78,7 +105,7 @@ where command_manager: StdCommandManager::default(), snapshot_manager: FastSnapshotManager::default(), driver: StdEmulatorDriver::builder().build(), - qemu_parameters: Some(QemuConfigBuilder::default()), + qemu_config: QemuConfigBuilder::default(), phantom: PhantomData, } } @@ -92,25 +119,26 @@ where driver: ED, command_manager: CM, snapshot_manager: SM, - qemu_parameters: QB, + qemu_config: QB, ) -> Self { Self { modules, command_manager, driver, snapshot_manager, - qemu_parameters, + qemu_config, phantom: PhantomData, } } - pub fn build(self) -> Result, QemuInitError> + pub fn build(self) -> Result, QemuInitError> where CM: CommandManager, ET: EmulatorModuleTuple, - QB: TryInto + QB: TryInto, + QemuInitError: From, { - let qemu_params: QemuParams = self.qemu_parameters.try_into()?; + let qemu_params: QemuParams = self.qemu_config.try_into()?; Emulator::new( qemu_params, @@ -127,17 +155,27 @@ where CM: CommandManager, S: UsesInput + Unpin, { - #[must_use] - pub fn qemu_builder(self, qemu_config: QB2) -> EmulatorBuilder + /// Main QEMU config function for building `Emulator`. + /// + /// The closure takes as parameter the current qemu configuration object and must return the new + /// QEMU configurator. For now, two configurators are supported: + /// - `QemuConfigBuilder` + /// - `Vec` + /// + /// Please check the documentation of `QemuConfig` for more information. + pub fn qemu_config( + self, + qemu_config_builder: F, + ) -> EmulatorBuilder where - QB2: Into + F: FnOnce(QB) -> QB2, { EmulatorBuilder::new( self.modules, self.driver, self.command_manager, self.snapshot_manager, - qemu_config, + qemu_config_builder(self.qemu_config), ) } @@ -151,7 +189,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - self.qemu_parameters, + self.qemu_config, ) } @@ -165,7 +203,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - self.qemu_parameters, + self.qemu_config, ) } @@ -175,11 +213,14 @@ where driver, self.command_manager, self.snapshot_manager, - self.qemu_parameters, + self.qemu_config, ) } - pub fn command_manager(self, command_manager: CM2) -> EmulatorBuilder + pub fn command_manager( + self, + command_manager: CM2, + ) -> EmulatorBuilder where CM2: CommandManager, { @@ -188,7 +229,7 @@ where self.driver, command_manager, self.snapshot_manager, - self.qemu_parameters, + self.qemu_config, ) } @@ -198,7 +239,7 @@ where self.driver, self.command_manager, self.snapshot_manager, - self.qemu_parameters, + self.qemu_config, ) } @@ -211,7 +252,7 @@ where self.driver, self.command_manager, snapshot_manager, - self.qemu_parameters, + self.qemu_config, ) } } diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 37916591fc0..38728aadd97 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -44,6 +44,7 @@ pub use usermode::*; mod systemmode; #[cfg(feature = "systemmode")] pub use systemmode::*; + use crate::config::QemuConfigBuilder; #[derive(Clone, Copy)] @@ -244,8 +245,14 @@ where S: UsesInput, { #[must_use] - pub fn empty( - ) -> EmulatorBuilder { + pub fn empty() -> EmulatorBuilder< + NopCommandManager, + NopEmulatorDriver, + (), + QemuConfigBuilder, + S, + NopSnapshotManager, + > { EmulatorBuilder::empty() } } @@ -256,8 +263,14 @@ where S::Input: HasTargetBytes, { #[must_use] - pub fn builder( - ) -> EmulatorBuilder, StdEmulatorDriver, (), QemuConfigBuilder, S, StdSnapshotManager> { + pub fn builder() -> EmulatorBuilder< + StdCommandManager, + StdEmulatorDriver, + (), + QemuConfigBuilder, + S, + StdSnapshotManager, + > { EmulatorBuilder::default() } } diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 28c48f81e04..44520344b5f 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -3,6 +3,7 @@ use core::{ fmt::{Display, Formatter}, }; use std::path::{Path, PathBuf}; + use derive_builder::Builder; use getset::Getters; use libafl_derive; @@ -46,6 +47,12 @@ pub struct Drive { interface: Option, } +impl Drive { + pub fn builder() -> DriveBuilder { + DriveBuilder::default() + } +} + impl Display for Drive { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "-drive")?; @@ -295,6 +302,10 @@ impl> From for Program { } } +/// Programmatic configurator for QEMU. +/// +/// It is supposed to be an equivalent to QEMU's CLI usual configuration, usable in a more +/// programmatic way and following the builder pattern. #[derive(Debug, Clone, libafl_derive::Display, Builder, Getters)] #[getset(get = "pub")] pub struct QemuConfig { @@ -334,6 +345,12 @@ pub struct QemuConfig { program: Program, } // Adding something here? Please leave Program as the last field +impl QemuConfig { + pub fn builder() -> QemuConfigBuilder { + QemuConfigBuilder::default() + } +} + #[cfg(test)] mod test { use super::*; @@ -343,7 +360,10 @@ mod test { #[cfg(feature = "usermode")] fn usermode() { let program = "/bin/pwd"; - let qemu_config = QemuConfig::builder().program("/bin/pwd").build(); + let qemu_config = QemuConfig::builder() + .program("/bin/pwd") + .build() + .expect("QEMU config failed."); let qemu = Qemu::init(qemu_config).unwrap(); let config = qemu.get_config().unwrap(); assert_eq!(config.to_string().trim(), program.trim()); diff --git a/libafl_qemu/src/qemu/error.rs b/libafl_qemu/src/qemu/error.rs index 31d7d183304..5a4c240dabb 100644 --- a/libafl_qemu/src/qemu/error.rs +++ b/libafl_qemu/src/qemu/error.rs @@ -1,10 +1,9 @@ use core::fmt; -use std::fmt::Display; +use std::{convert::Infallible, fmt::Display}; use libafl_qemu_sys::{CPUStatePtr, GuestAddr}; -use crate::CallingConvention; -use crate::config::QemuConfigBuilderError; +use crate::{config::QemuConfigBuilderError, CallingConvention}; #[derive(Debug)] pub enum QemuError { @@ -18,9 +17,16 @@ pub enum QemuInitError { MultipleInstances, EmptyArgs, ConfigurationError(QemuConfigBuilderError), + Infallible, TooManyArgs(usize), } +impl From for QemuInitError { + fn from(_: Infallible) -> Self { + QemuInitError::Infallible + } +} + #[derive(Debug, Clone)] pub enum QemuExitError { UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen. @@ -70,13 +76,22 @@ impl Display for QemuInitError { "Too many arguments passed to QEMU emulator ({n} > i32::MAX)" ) } + QemuInitError::Infallible => { + write!(f, "Infallible error, should never be reached.") + } } } } impl From for libafl::Error { fn from(err: QemuInitError) -> Self { - libafl::Error::unknown(format!("{err}")) + libafl::Error::runtime(format!("QEMU Init error: {err}")) + } +} + +impl From for libafl::Error { + fn from(err: QemuRWError) -> Self { + libafl::Error::runtime(format!("QEMU Runtime error: {err:?}")) } } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index e9f14704cf5..26ebdaf1d90 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -52,6 +52,7 @@ pub use systemmode::*; mod hooks; pub use hooks::*; + use crate::config::QemuConfigBuilder; static mut QEMU_IS_INITIALIZED: bool = false; @@ -186,8 +187,11 @@ impl TryFrom for QemuParams { type Error = QemuInitError; fn try_from(config_builder: QemuConfigBuilder) -> Result { - Ok(QemuParams::Config(config_builder.build() - .or_else(|e| Err(QemuInitError::ConfigurationError(e)))?)) + Ok(QemuParams::Config( + config_builder + .build() + .or_else(|e| Err(QemuInitError::ConfigurationError(e)))?, + )) } } @@ -199,7 +203,6 @@ where QemuParams::Cli(cli.iter().map(|x| x.as_ref().into()).collect()) } } - impl From<&Vec> for QemuParams where T: AsRef, @@ -514,29 +517,8 @@ impl From for HookData { } } -// impl QemuBuilder { -// pub fn new(params: T) -> Self { -// Self { -// params -// } -// } -// } - -// impl QemuBuilder -// where -// T: Into -// { -// pub fn build(self) -> Result { -// -// } -// } - #[allow(clippy::unused_self)] impl Qemu { - // pub fn builder() -> QemuBuilder { - // QemuBuilder::default() - // } - #[allow(clippy::must_use_candidate, clippy::similar_names)] pub fn init(params: T) -> Result where @@ -546,9 +528,10 @@ impl Qemu { match ¶ms { QemuParams::Config(cfg) => { - QEMU_CONFIG.set(cfg.clone()).map_err(|_| { - unreachable!("QEMU_CONFIG was already set but Qemu was not init!") - })?; + QEMU_CONFIG + .set(cfg.clone()) + .map_err(|_| unreachable!("QEMU_CONFIG was already set but Qemu was not init!")) + .unwrap(); } QemuParams::Cli(_) => {} }; diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index 52b41bf5581..bb56a004613 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -241,7 +241,7 @@ where }; let emulator = Emulator::empty() - .qemu_cli(qemu_cli.to_owned()) + .qemu_config(|_| qemu_cli.to_owned()) .modules(modules) .build()?; @@ -361,7 +361,7 @@ where }; let emulator = Emulator::empty() - .qemu_cli(qemu_cli.to_owned()) + .qemu_config(|_| qemu_cli.to_owned()) .modules(modules) .build()?; From 413767feab1ca24ab117d794962f941de8ba4299 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 5 Dec 2024 17:35:33 +0100 Subject: [PATCH 43/93] better format --- fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs | 5 ++++- fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs | 6 ++---- libafl_qemu/src/qemu/config.rs | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs index 1d522261ba6..c1ce8a76573 100644 --- a/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs @@ -195,7 +195,10 @@ fn fuzz( //QemuSnapshotHelper::new() ); - let emulator = Emulator::empty().qemu_cli(args).modules(modules).build()?; + let emulator = Emulator::empty() + .qemu_args(|_| args) + .modules(modules) + .build()?; let qemu = emulator.qemu(); // let qemu = Qemu::init(&args).expect("QEMU init failed"); // let (emu, asan) = init_with_asan(&mut args, &mut env).unwrap(); diff --git a/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs b/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs index 787fa9fb840..5dcf8103800 100644 --- a/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs +++ b/fuzzers/full_system/qemu_baremetal/src/fuzzer_low_level.rs @@ -97,7 +97,7 @@ pub fn fuzz() { .build()?); let emulator = Emulator::empty() - .qemu_config(|mut qemu_config| { + .qemu_config(|qemu_config| { qemu_config .machine("mps2-an385") .monitor(config::Monitor::Null) @@ -111,9 +111,7 @@ pub fn fuzz() { .file(format!("{target_dir}/dummy.qcow2")) .build() .expect("Could not build drives")]) - .start_cpu(false); - - qemu_config + .start_cpu(false) }) .modules(emulator_modules) .build()?; diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 44520344b5f..854fe5363da 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -308,6 +308,7 @@ impl> From for Program { /// programmatic way and following the builder pattern. #[derive(Debug, Clone, libafl_derive::Display, Builder, Getters)] #[getset(get = "pub")] +#[builder(pattern = "owned")] pub struct QemuConfig { #[cfg(feature = "systemmode")] #[builder(default, setter(strip_option))] From 8b04e3f0ccb9c5d42ab75656763e08a6d3e33273 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Thu, 5 Dec 2024 17:54:02 +0100 Subject: [PATCH 44/93] clippy + fmt --- libafl_qemu/src/qemu/config.rs | 5 ++++- libafl_qemu/src/qemu/mod.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 854fe5363da..5fb0ed4b3d3 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -48,6 +48,7 @@ pub struct Drive { } impl Drive { + #[must_use] pub fn builder() -> DriveBuilder { DriveBuilder::default() } @@ -347,6 +348,7 @@ pub struct QemuConfig { } // Adding something here? Please leave Program as the last field impl QemuConfig { + #[must_use] pub fn builder() -> QemuConfigBuilder { QemuConfigBuilder::default() } @@ -375,7 +377,8 @@ mod test { let drive = Drive::builder() .format(DiskImageFileFormat::Raw) .interface(DriveInterface::Ide) - .build(); + .build() + .expect("Drive builder failed."); assert_eq!(drive.to_string(), "-drive format=raw,if=ide"); } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 26ebdaf1d90..28157315ca3 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -190,7 +190,7 @@ impl TryFrom for QemuParams { Ok(QemuParams::Config( config_builder .build() - .or_else(|e| Err(QemuInitError::ConfigurationError(e)))?, + .map_err(QemuInitError::ConfigurationError)?, )) } } From 576a41933ea1b58d72b1a2ae7ffeddec7cee9d87 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:25:45 +0100 Subject: [PATCH 45/93] Fix build target Create target directory if doesn't exist --- .../intel_pt_command_executor/Makefile.toml | 18 ++++++++++++------ .../intel_pt_command_executor/src/main.rs | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml index 9c2d97e4eb1..aca771cbca3 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml +++ b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml @@ -1,17 +1,23 @@ +[env] +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" + [env.development] PROFILE_DIR = "debug" [env.release] PROFILE_DIR = "release" +[tasks.target_dir] +condition = { files_not_exist = ["${TARGET_DIR}"] } +script_runner = "@shell" +script = ''' +mkdir -p ${TARGET_DIR} +''' + [tasks.build_target] +dependencies = ["target_dir"] command = "rustc" -args = [ - "src/target_program.rs", - "--out-dir", - "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}", - "-O", -] +args = ["src/target_program.rs", "--out-dir", "${TARGET_DIR}", "-O"] [tasks.build_fuzzer] command = "cargo" diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index f91ed0644df..ae38206a8fa 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -37,6 +37,8 @@ pub fn main() { env_logger::init(); let target_path = PathBuf::from(env::args().next().unwrap()) + .parent() + .unwrap() .parent() .unwrap() .join("target_program"); From 395b643c2cfd1672173916d01c004de54cb4247e Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:29:00 +0100 Subject: [PATCH 46/93] Remove filter on speculatively exec blocks since also committed blocks can have this flag --- libafl_intelpt/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 920e7aeb5c7..0f1616cc95a 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -400,7 +400,7 @@ impl IntelPT { *status = s; let offset = decoder.offset().map_err(error_from_pt_error)?; - if b.ninsn() > 0 && !b.speculative() && skip < offset { + if b.ninsn() > 0 && skip < offset { let id = hash_me(*previous_block_end_ip) ^ hash_me(b.ip()); // SAFETY: the index is < map.len() since the modulo operation is applied let map_loc = unsafe { map.get_unchecked_mut(id as usize % map.len()) }; From fb036ce95456cc442b465d0d8887d9bd999652d0 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:42:05 +0100 Subject: [PATCH 47/93] Add current ip_filters getter --- libafl_intelpt/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 0f1616cc95a..6424fcd7d6d 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -179,6 +179,12 @@ impl IntelPT { } } + /// Get the current IP filters configuration + #[must_use] + pub fn ip_filters(&self) -> Vec> { + self.ip_filters.clone() + } + fn ip_filters_to_addr_filter(&self) -> AddrFilter { let mut builder = AddrFilterBuilder::new(); let mut iter = self From 89a6bf8aeacbd9cbaad13fb0baf4b15fb4c8a84f Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:46:49 +0100 Subject: [PATCH 48/93] Fix possibile infinite loop in trace decode --- libafl_intelpt/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 6424fcd7d6d..0530aa33fce 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -414,16 +414,18 @@ impl IntelPT { *previous_block_end_ip = b.end_ip(); } + + if status.eos() { + break 'block; + } } Err(e) => { if e.code() != PtErrorCode::Eos { log::trace!("PT error in block next {e:?}"); } + break 'block; } } - if status.eos() { - break 'block; - } } Ok(()) } From 34ecd12661ff9133a661d148772697b861921f51 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:07:57 +0100 Subject: [PATCH 49/93] HW breakpoint + snapshot --- .../qemu_intel_pt_bootloader/src/main.rs | 55 +++++++++++++++---- libafl/src/events/llmp/mgr.rs | 2 +- libafl_intelpt/src/lib.rs | 18 +++--- .../src/modules/systemmode/intel_pt.rs | 1 - libafl_qemu/src/qemu/mod.rs | 18 ++++++ 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 6f4c24437e9..d4b07ab7b28 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -35,11 +35,11 @@ use libafl_qemu::{ config::{Accelerator, QemuConfig}, executor::QemuExecutor, modules::intel_pt::{IntelPTModule, Section}, - Emulator, EmulatorBuilder, QemuExitReason, QemuShutdownCause, + Emulator, EmulatorBuilder, QemuExitError, QemuExitReason, QemuShutdownCause, }; // Coverage map -const MAP_SIZE: usize = 128; +const MAP_SIZE: usize = 32; static mut MAP: [u16; MAP_SIZE] = [0; MAP_SIZE]; const BOOTLOADER_START: u64 = 0x7c00; @@ -65,12 +65,13 @@ fn main() { let qemu = QemuConfig::builder() .no_graphic(true) .drives([config::Drive::builder() - .format(config::DiskImageFileFormat::Raw) - .file(format!("{target_dir}/boot.bin")) - .build().unwrap()]) + .format(config::DiskImageFileFormat::Qcow2) + .file(format!("{target_dir}/boot.qcow2")) + .build() + .unwrap()]) .accelerator(Accelerator::Kvm) .bios(format!( - "{target_dir}/{target_subdir}/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu" + "/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/" )) .start_cpu(false); @@ -81,11 +82,11 @@ fn main() { let image = [Section { file_path, file_offset: 0, - size: 512 - 3, + size: 0x80, virtual_address: BOOTLOADER_START, }]; let intel_pt_builder = IntelPTModule::default_pt_builder() - .ip_filters(&[BOOTLOADER_START as usize..=0x7e00 - 3]); + .ip_filters(&[BOOTLOADER_START as usize..=BOOTLOADER_START as usize + 512]); let emulator_modules = tuple_list!(IntelPTModule::builder() .map_ptr(unsafe { MAP.as_mut_ptr() }) .map_len(MAP_SIZE) @@ -98,7 +99,22 @@ fn main() { .modules(emulator_modules) .build()?; let qemu = emulator.qemu(); - qemu.set_breakpoint(BOOTLOADER_START); + qemu.set_hw_breakpoint(BOOTLOADER_START); + + // todo: there is smth broken somewhere, QemuExitReason::Breakpoint reports a wrong address + unsafe { + match qemu.run() { + Ok(QemuExitReason::Breakpoint(ba)) => { + println!("break1 at {ba:x}") + } + _ => panic!("Pre-harness Unexpected QEMU exit."), + } + } + qemu.remove_hw_breakpoint(BOOTLOADER_START); + + qemu.set_hw_breakpoint(0x7c7e); + + qemu.save_snapshot("bootloader_start", true); let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, _: &mut StdState, @@ -107,8 +123,21 @@ fn main() { Ok(QemuExitReason::End(QemuShutdownCause::GuestShutdown)) => { println!("VM shut down!") } - _ => panic!("Unexpected QEMU exit."), + Ok(QemuExitReason::Breakpoint(ba)) => { + println!("break2 at {ba:x}") + } + Ok(QemuExitReason::Timeout) => { + panic!("Harness Unexpected QEMU exit. Timeout") + } + Ok(QemuExitReason::SyncExit) => { + panic!("Harness Unexpected QEMU exit. SyncExit") + } + Ok(QemuExitReason::End(e)) => { + panic!("Harness Unexpected QEMU exit. End {e:?}") + } + Err(e) => panic!("Harness Unexpected QEMU exit. Error {e:?}"), } + qemu.load_snapshot("bootloader_start", true); ExitKind::Ok }; @@ -129,7 +158,7 @@ fn main() { ); // A feedback to choose if an input is a solution or not - let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + let mut objective = feedback_or_fast!(CrashFeedback::new()); // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -187,10 +216,12 @@ fn main() { fuzzer .fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr) - .unwrap(); + .expect("Error in the fuzzing loop"); Ok(()) }; + //todo use simpleEventManager for debug + // The shared memory allocator let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); // The stats reporter for the broker diff --git a/libafl/src/events/llmp/mgr.rs b/libafl/src/events/llmp/mgr.rs index 1153c306106..57f72e2ef4a 100644 --- a/libafl/src/events/llmp/mgr.rs +++ b/libafl/src/events/llmp/mgr.rs @@ -367,7 +367,7 @@ where Ok(_) => (), Err(e) => log::error!("Failed to send tcp message {:#?}", e), } - log::debug!("Asking he broker to be disconnected"); + log::debug!("Asking the broker to be disconnected"); Ok(()) } diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 6d32f85b1ba..e0d55bcf817 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -444,14 +444,13 @@ impl IntelPT { let map_loc = unsafe { map.get_unchecked_mut(id as usize % map.len()) }; *map_loc = (*map_loc).saturating_add(&1u8.into()); - log::trace!( - "previous block ip: {:x} current: {:x} asid: {:?}", - previous_block_end_ip, - b.ip(), - decoder.asid().unwrap_or_default() - ); - log::trace!("writing map at {} with value {:?}", id, *map_loc); - *previous_block_end_ip = b.end_ip(); + // log::trace!( + // "previous block ip: {:x} current: {:x} offset: {offset:x}", + // previous_block_end_ip, + // b.ip() + // ); + // log::trace!("writing map at {id:x} with value {:?}", *map_loc); + // *previous_block_end_ip = b.end_ip(); } if status.eos() { @@ -460,8 +459,9 @@ impl IntelPT { } Err(e) => { if e.code() != PtErrorCode::Eos { + let offset = decoder.offset().map_err(error_from_pt_error)?; log::trace!( - "PT error in block next {e:?} last decoded block end {:x}", + "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", previous_block_end_ip ); } diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 44d9220cc6c..b9248440a90 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -2,7 +2,6 @@ use std::{ fmt::Debug, ops::{Range, RangeInclusive}, ptr::slice_from_raw_parts_mut, - slice, }; use libafl::{inputs::UsesInput, observers::ObserversTuple, Error, HasMetadata}; diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 28157315ca3..c84ebb96109 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -26,6 +26,8 @@ use libafl_qemu_sys::{ libafl_qemu_write_reg, CPUArchState, CPUStatePtr, FatPtr, GuestAddr, GuestPhysAddr, GuestUsize, GuestVirtAddr, }; +#[cfg(feature = "systemmode")] +use libafl_qemu_sys::{libafl_qemu_remove_hw_breakpoint, libafl_qemu_set_hw_breakpoint}; use num_traits::Num; use strum::IntoEnumIterator; @@ -815,6 +817,22 @@ impl Qemu { } } + #[cfg(feature = "systemmode")] + pub fn set_hw_breakpoint(&self, addr: GuestAddr) { + unsafe { + libafl_qemu_set_hw_breakpoint(addr.into()); + } + // TODO don't be lame, return a Result + } + + #[cfg(feature = "systemmode")] + pub fn remove_hw_breakpoint(&self, addr: GuestAddr) { + unsafe { + libafl_qemu_remove_hw_breakpoint(addr.into()); + } + // TODO don't be lame, return a Result + } + pub fn entry_break(&self, addr: GuestAddr) { self.set_breakpoint(addr); unsafe { From 378da3fc4b0a9fce55d6876980544d3ae3592d95 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:15:53 +0100 Subject: [PATCH 50/93] add snapshot and exit at first objective --- .../qemu_intel_pt_bootloader/Cargo.toml | 2 +- .../qemu_intel_pt_bootloader/Makefile.toml | 12 +- .../qemu_intel_pt_bootloader/corpus/hello | 1 - .../qemu_intel_pt_bootloader/src/boot.s | 4 +- .../qemu_intel_pt_bootloader/src/main.rs | 356 ++++++++---------- libafl_intelpt/src/lib.rs | 2 +- .../src/modules/systemmode/intel_pt.rs | 3 +- 7 files changed, 173 insertions(+), 207 deletions(-) delete mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml index 4f6c4d761bc..d1a12abbaf9 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml @@ -8,6 +8,6 @@ edition = "2021" libafl = { path = "../../../libafl", default-features = false } libafl_bolts = { path = "../../../libafl_bolts" } libafl_qemu = { path = "../../../libafl_qemu", features = [ - "intel_pt_export_raw", + "intel_pt", ], default-features = false } env_logger = "0.11.5" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index 6a5044dc90d..847918d0511 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -30,6 +30,16 @@ args = ["build", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] [tasks.build] dependencies = ["build_fuzzer", "build_target"] +[tasks.convert_target_image] +command = "qemu-img" +args = [ + "convert", + "-O", + "qcow2", + "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.bin", + "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.qcow2", +] + [tasks.setcap] script = "sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" dependencies = ["build_fuzzer"] @@ -37,7 +47,7 @@ dependencies = ["build_fuzzer"] [tasks.run] command = "cargo" args = ["run", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] -dependencies = ["build", "setcap"] +dependencies = ["build", "setcap", "convert_target_image"] [tasks.default] alias = "run" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello b/fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello deleted file mode 100644 index 802992c4220..00000000000 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/corpus/hello +++ /dev/null @@ -1 +0,0 @@ -Hello world diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s index f3e4b787138..15710b9a8e5 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s @@ -24,7 +24,7 @@ start: cmp ah, 0 ; ah must be 0 jne fail - mov ax, [es:bx] ; byte count + mov ax, [es:bx] ; byte count of the system configuration parameters cmp ax, 8 jl fail @@ -67,7 +67,7 @@ sleep_forever: jmp sleep_forever fail_msg db "I don't like your BIOS. :(", 0 -bye db "Amazing <3 Bye!", 0 +bye db "Artificial bug triggered =)", 0 times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index d4b07ab7b28..aa3b15ca54d 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -3,245 +3,203 @@ use core::time::Duration; use std::{ env, + num::NonZero, + ops::RangeInclusive, path::{Path, PathBuf}, - process, }; use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, - events::{launcher::Launcher, EventConfig}, + events::{ProgressReporter, SimpleEventManager}, executors::ExitKind, feedback_or, feedback_or_fast, - feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, - inputs::BytesInput, - monitors::MultiMonitor, + generators::RandPrintablesGenerator, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator}, observers::{StdMapObserver, TimeObserver}, schedulers::QueueScheduler, stages::StdMutationalStage, - state::{HasCorpus, StdState}, - Error, -}; -use libafl_bolts::{ - core_affinity::Cores, - current_nanos, - rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, - tuples::tuple_list, + state::{HasSolutions, StdState}, }; +use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice}; use libafl_qemu::{ config, config::{Accelerator, QemuConfig}, executor::QemuExecutor, modules::intel_pt::{IntelPTModule, Section}, - Emulator, EmulatorBuilder, QemuExitError, QemuExitReason, QemuShutdownCause, + Emulator, EmulatorBuilder, GuestAddr, QemuExitReason, QemuShutdownCause, }; // Coverage map const MAP_SIZE: usize = 32; static mut MAP: [u16; MAP_SIZE] = [0; MAP_SIZE]; -const BOOTLOADER_START: u64 = 0x7c00; +const BOOTLOADER_CODE: RangeInclusive = 0x7c00..=0x7c80; +const SLEEP_FN_ADDR: GuestAddr = 0x7c60; fn main() { + // Initialize the logger (use env variable RUST_LOG=trace for maximum logging) env_logger::init(); // Hardcoded parameters let timeout = Duration::from_secs(3); - let broker_port = 1337; - let cores = Cores::from_cmdline("0").unwrap(); - let corpus_dirs = [PathBuf::from("./corpus")]; let objective_dir = PathBuf::from("./crashes"); - let mut run_client = |state: Option<_>, mut mgr, _core_id| -> Result<(), Error> { - let target_dir = env::var("TARGET_DIR").unwrap_or("target".to_string()); - let target_subdir = if cfg!(debug_assertions) { - "debug" - } else { - "release" - }; - // Configure QEMU - let qemu = QemuConfig::builder() - .no_graphic(true) - .drives([config::Drive::builder() - .format(config::DiskImageFileFormat::Qcow2) - .file(format!("{target_dir}/boot.qcow2")) - .build() - .unwrap()]) - .accelerator(Accelerator::Kvm) - .bios(format!( - "/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/" - )) - .start_cpu(false); - - let file_path = Path::new(&target_dir) - .join("boot.bin") - .to_string_lossy() - .to_string(); - let image = [Section { - file_path, - file_offset: 0, - size: 0x80, - virtual_address: BOOTLOADER_START, - }]; - let intel_pt_builder = IntelPTModule::default_pt_builder() - .ip_filters(&[BOOTLOADER_START as usize..=BOOTLOADER_START as usize + 512]); - let emulator_modules = tuple_list!(IntelPTModule::builder() - .map_ptr(unsafe { MAP.as_mut_ptr() }) - .map_len(MAP_SIZE) - .intel_pt_builder(intel_pt_builder) - .image(&image) - .build()); - - let emulator = EmulatorBuilder::empty() - .qemu_config(|_| qemu) - .modules(emulator_modules) - .build()?; - let qemu = emulator.qemu(); - qemu.set_hw_breakpoint(BOOTLOADER_START); - - // todo: there is smth broken somewhere, QemuExitReason::Breakpoint reports a wrong address - unsafe { - match qemu.run() { - Ok(QemuExitReason::Breakpoint(ba)) => { - println!("break1 at {ba:x}") - } - _ => panic!("Pre-harness Unexpected QEMU exit."), + let mon = SimpleMonitor::new(|s| println!("{s}")); + + // The event manager handle the various events generated during the fuzzing loop + // such as the notification of the addition of a new item to the corpus + let mut mgr = SimpleEventManager::new(mon); + + let target_dir = env::var("TARGET_DIR").unwrap_or("target".to_string()); + + // Configure QEMU + let qemu = QemuConfig::builder() + .no_graphic(true) + .drives([config::Drive::builder() + .format(config::DiskImageFileFormat::Qcow2) + .file(format!("{target_dir}/boot.qcow2")) + .build() + .unwrap()]) + .accelerator(Accelerator::Kvm) + .bios("/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") + .start_cpu(false); + + let file_path = Path::new(&target_dir) + .join("boot.bin") + .to_string_lossy() + .to_string(); + let image = [Section { + file_path, + file_offset: 0, + size: (BOOTLOADER_CODE.end() - BOOTLOADER_CODE.start()) as u64 + 1, + virtual_address: *BOOTLOADER_CODE.start() as u64, + }]; + let intel_pt_builder = IntelPTModule::default_pt_builder().ip_filters(&[BOOTLOADER_CODE]); + let emulator_modules = tuple_list!(IntelPTModule::builder() + .map_ptr(unsafe { MAP.as_mut_ptr() }) + .map_len(MAP_SIZE) + .intel_pt_builder(intel_pt_builder) + .image(&image) + .build()); + + let emulator = EmulatorBuilder::empty() + .qemu_config(|_| qemu) + .modules(emulator_modules) + .build() + .unwrap(); + let qemu = emulator.qemu(); + qemu.set_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr); + + // todo: there is smth broken somewhere, QemuExitReason::Breakpoint reports a wrong address + unsafe { + match qemu.run() { + Ok(QemuExitReason::Breakpoint(ba)) => { + println!("break1 at {ba:x}") } + _ => panic!("Pre-harness Unexpected QEMU exit."), } - qemu.remove_hw_breakpoint(BOOTLOADER_START); - - qemu.set_hw_breakpoint(0x7c7e); - - qemu.save_snapshot("bootloader_start", true); - - let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, - _: &mut StdState, - _: &BytesInput| unsafe { - match emulator.qemu().run() { - Ok(QemuExitReason::End(QemuShutdownCause::GuestShutdown)) => { - println!("VM shut down!") - } - Ok(QemuExitReason::Breakpoint(ba)) => { - println!("break2 at {ba:x}") - } - Ok(QemuExitReason::Timeout) => { - panic!("Harness Unexpected QEMU exit. Timeout") - } - Ok(QemuExitReason::SyncExit) => { - panic!("Harness Unexpected QEMU exit. SyncExit") - } - Ok(QemuExitReason::End(e)) => { - panic!("Harness Unexpected QEMU exit. End {e:?}") - } - Err(e) => panic!("Harness Unexpected QEMU exit. Error {e:?}"), + } + qemu.remove_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr); + + qemu.set_hw_breakpoint(SLEEP_FN_ADDR); + + qemu.save_snapshot("bootloader_start", true); + + let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, + _: &mut StdState, + input: &BytesInput| unsafe { + let mut fixed_len_input = input.target_bytes().as_slice().to_vec(); + fixed_len_input.resize(3, 0); + + qemu.load_snapshot("bootloader_start", true); + qemu.write_phys_mem(0xfe6f7, &fixed_len_input); + match emulator.qemu().run() { + Ok(QemuExitReason::End(QemuShutdownCause::GuestShutdown)) => { + println!( + "crashing input: {}", + String::from_utf8_lossy(&fixed_len_input) + ); + ExitKind::Crash } - qemu.load_snapshot("bootloader_start", true); - ExitKind::Ok - }; - - // Create an observation channel using the map - let observer = - unsafe { StdMapObserver::from_mut_ptr("signals", MAP.as_mut_ptr(), MAP_SIZE) }; - - // Create an observation channel to keep track of the execution time - let time_observer = TimeObserver::new("time"); - - // Feedback to rate the interestingness of an input - // This one is composed by two Feedbacks in OR - let mut feedback = feedback_or!( - // New maximization map feedback linked to the edges observer and the feedback state - MaxMapFeedback::new(&observer), - // Time feedback, this one does not need a feedback state - TimeFeedback::new(&time_observer) - ); - - // A feedback to choose if an input is a solution or not - let mut objective = feedback_or_fast!(CrashFeedback::new()); - - // If not restarting, 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 - InMemoryCorpus::new(), - // 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.clone()).unwrap(), - // States of the feedbacks. - // The feedbacks can report the data that should persist in the State. - &mut feedback, - // Same for objective feedbacks - &mut objective, - ) - .unwrap() - }); - - // A queue policy to get testcases from the corpus - let scheduler = QueueScheduler::new(); - - // A fuzzer with feedbacks and a corpus scheduler - let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - - // Create a QEMU in-process executor - let mut executor = QemuExecutor::new( - emulator, - &mut harness, - tuple_list!(observer, time_observer), - &mut fuzzer, - &mut state, - &mut mgr, - timeout, - ) - .expect("Failed to create QemuExecutor"); - - // Instead of calling the timeout handler and restart the process, trigger a breakpoint ASAP - // executor.break_on_timeout(); - - if state.must_load_initial_inputs() { - state - .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) - .unwrap_or_else(|_| { - println!("Failed to load initial corpus at {:?}", &corpus_dirs); - process::exit(0); - }); - println!("Imported {} inputs from disk.", state.corpus().count()); + Ok(QemuExitReason::Breakpoint(_)) => ExitKind::Ok, + e => panic!("Harness Unexpected QEMU exit. {e:?}"), } + }; - // Setup an havoc mutator with a mutational stage - let mutator = StdScheduledMutator::new(havoc_mutations()); - let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + // Create an observation channel using the map + let observer = unsafe { StdMapObserver::from_mut_ptr("signals", MAP.as_mut_ptr(), MAP_SIZE) }; + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let mut feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new(&observer), + // Time feedback, this one does not need a feedback state + TimeFeedback::new(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let mut objective = feedback_or_fast!(CrashFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir.clone()).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap(); + + // A queue policy to get testcases from the corpus + let scheduler = QueueScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // Create a QEMU in-process executor + let mut executor = QemuExecutor::new( + emulator, + &mut harness, + tuple_list!(observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + timeout, + ) + .expect("Failed to create QemuExecutor"); + + // Generator of printable bytearrays of max size 3 + let mut generator = RandPrintablesGenerator::new(NonZero::new(3).unwrap()); + + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 4) + .expect("Failed to generate the initial corpus"); + + // Setup an havoc mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + while state.solutions().is_empty() { + mgr.maybe_report_progress(&mut state, Duration::from_secs(5)) + .unwrap(); fuzzer .fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr) .expect("Error in the fuzzing loop"); - Ok(()) - }; - - //todo use simpleEventManager for debug - - // The shared memory allocator - let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); - // The stats reporter for the broker - let monitor = MultiMonitor::new(|s| println!("{s}")); - - // Build and run a Launcher - match Launcher::builder() - .shmem_provider(shmem_provider) - .broker_port(broker_port) - .configuration(EventConfig::from_build_id()) - .monitor(monitor) - .run_client(&mut run_client) - .cores(&cores) - .stdout_file(Some("./stdout.txt")) - .stderr_file(Some("./stderr.txt")) - .build() - .launch() - { - Ok(()) => println!("OK"), - Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."), - Err(err) => panic!("Failed to run launcher: {err:?}"), } } diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index e0d55bcf817..862dab8b3bf 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -450,7 +450,7 @@ impl IntelPT { // b.ip() // ); // log::trace!("writing map at {id:x} with value {:?}", *map_loc); - // *previous_block_end_ip = b.end_ip(); + *previous_block_end_ip = b.end_ip(); } if status.eos() { diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index b9248440a90..3b79abc84d4 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -69,7 +69,7 @@ where fn post_exec( &mut self, - qemu: Qemu, + _qemu: Qemu, _emulator_modules: &mut EmulatorModules, _state: &mut S, _input: &S::Input, @@ -100,7 +100,6 @@ where .dump_last_trace_to_file() .inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}")); } - println!("map: {:?}", map); } fn address_filter(&self) -> &Self::ModuleAddressFilter { From 2bde27968a905f5dcb1385a7df2913aeb13bf4d0 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:17:59 +0100 Subject: [PATCH 51/93] prefer raw pointers to slice_from_raw_parts_mut since the latter is highly unsafe and allows more potentially dangerous reordering --- libafl/src/executors/hooks/intel_pt.rs | 3 +- libafl_intelpt/src/lib.rs | 104 ++++++++++-------- .../tests/integration_tests_linux.rs | 3 +- .../src/modules/systemmode/intel_pt.rs | 3 +- 4 files changed, 62 insertions(+), 51 deletions(-) diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index a712972e24b..86562454b19 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -55,9 +55,8 @@ where let pt = &mut self.intel_pt; pt.disable_tracing().unwrap(); - let slice = unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }; let _ = pt - .decode_traces_into_map(&mut self.image.0, slice) + .decode_traces_into_map(&mut self.image.0, self.map_ptr, self.map_len) .inspect_err(|e| log::warn!("Intel PT trace decoding failed: {e}")); #[cfg(feature = "intel_pt_export_raw")] { diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 862dab8b3bf..4eacc50ed5a 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -241,39 +241,39 @@ impl IntelPT { } } - /// Fill the coverage map by decoding the PT traces and reading target memory through `read_mem` - /// - /// This function consumes the traces. - /// - /// # Example - /// - /// An example `read_mem` callback function for the (inprocess) `intel_pt_babyfuzzer` could be: - /// ``` - /// let read_mem = |buf: &mut [u8], addr: u64| { - /// let src = addr as *const u8; - /// let dst = buf.as_mut_ptr(); - /// let size = buf.len(); - /// unsafe { - /// core::ptr::copy_nonoverlapping(src, dst, size); - /// } - /// }; - /// ``` - #[allow(clippy::cast_possible_wrap)] - pub fn decode_with_callback(&mut self, read_memory: F, map: &mut [T]) -> Result<(), Error> - where - F: Fn(&mut [u8], u64), - T: SaturatingAdd + From + Debug, - { - self.decode_traces_into_map_common( - None, - Some(|buff: &mut [u8], addr: u64, _: Asid| { - debug_assert!(i32::try_from(buff.len()).is_ok()); - read_memory(buff, addr); - buff.len() as i32 - }), - map, - ) - } + // /// Fill the coverage map by decoding the PT traces and reading target memory through `read_mem` + // /// + // /// This function consumes the traces. + // /// + // /// # Example + // /// + // /// An example `read_mem` callback function for the (inprocess) `intel_pt_babyfuzzer` could be: + // /// ``` + // /// let read_mem = |buf: &mut [u8], addr: u64| { + // /// let src = addr as *const u8; + // /// let dst = buf.as_mut_ptr(); + // /// let size = buf.len(); + // /// unsafe { + // /// core::ptr::copy_nonoverlapping(src, dst, size); + // /// } + // /// }; + // /// ``` + // #[allow(clippy::cast_possible_wrap)] + // pub fn decode_with_callback(&mut self, read_memory: F, map: &mut [T]) -> Result<(), Error> + // where + // F: Fn(&mut [u8], u64), + // T: SaturatingAdd + From + Debug, + // { + // self.decode_traces_into_map_common( + // None, + // Some(|buff: &mut [u8], addr: u64, _: Asid| { + // debug_assert!(i32::try_from(buff.len()).is_ok()); + // read_memory(buff, addr); + // buff.len() as i32 + // }), + // map, + // ) + // } /// Fill the coverage map by decoding the PT traces /// @@ -281,7 +281,8 @@ impl IntelPT { pub fn decode_traces_into_map( &mut self, image: &mut Image, - map: &mut [T], + map_ptr: *mut T, + map_len: usize, ) -> Result<(), Error> where T: SaturatingAdd + From + Debug, @@ -289,7 +290,8 @@ impl IntelPT { self.decode_traces_into_map_common( Some(image), None:: i32>, - map, + map_ptr, + map_len, ) } @@ -297,7 +299,8 @@ impl IntelPT { &mut self, image: Option<&mut Image>, read_memory: Option, - map: &mut [T], + map_ptr: *mut T, + map_len: usize, ) -> Result<(), Error> where F: Fn(&mut [u8], u64, Asid) -> i32, @@ -373,7 +376,8 @@ impl IntelPT { &mut status, &mut previous_block_end_ip, skip, - map, + map_ptr, + map_len, )?; } Err(e) => { @@ -403,10 +407,16 @@ impl IntelPT { let second_ptr = self.perf_aux_buffer as *mut u8; let second_len = head_wrap as usize; - let mut vec = Vec::with_capacity(first_len + second_len); - vec.extend_from_slice(unsafe { slice::from_raw_parts(first_ptr, first_len) }); - vec.extend_from_slice(unsafe { slice::from_raw_parts(second_ptr, second_len) }); - vec.into_boxed_slice() + let mut data = Box::<[u8]>::new_uninit_slice(first_len + second_len); + unsafe { + ptr::copy_nonoverlapping(first_ptr, data.as_mut_ptr().cast(), first_len); + ptr::copy_nonoverlapping( + second_ptr, + data.as_mut_ptr().add(first_len).cast(), + second_len, + ); + data.assume_init() + } } #[inline] @@ -415,7 +425,8 @@ impl IntelPT { status: &mut Status, previous_block_end_ip: &mut u64, skip: u64, - map: &mut [T], + map_ptr: *mut T, + map_len: usize, ) -> Result<(), Error> where T: SaturatingAdd + From + Debug, @@ -440,10 +451,11 @@ impl IntelPT { if b.ninsn() > 0 && skip < offset { let id = hash_me(*previous_block_end_ip) ^ hash_me(b.ip()); - // SAFETY: the index is < map.len() since the modulo operation is applied - let map_loc = unsafe { map.get_unchecked_mut(id as usize % map.len()) }; - *map_loc = (*map_loc).saturating_add(&1u8.into()); - + // SAFETY: the index is < map_len since the modulo operation is applied + unsafe { + let map_loc = map_ptr.add(id as usize % map_len); + *map_loc = (*map_loc).saturating_add(&1u8.into()); + } // log::trace!( // "previous block ip: {:x} current: {:x} offset: {offset:x}", // previous_block_end_ip, diff --git a/libafl_intelpt/tests/integration_tests_linux.rs b/libafl_intelpt/tests/integration_tests_linux.rs index ebd6f7c1096..8d4d4725b41 100644 --- a/libafl_intelpt/tests/integration_tests_linux.rs +++ b/libafl_intelpt/tests/integration_tests_linux.rs @@ -85,7 +85,8 @@ fn intel_pt_trace_fork() { } let mut map = vec![0u16; 0x10_00]; - pt.decode_traces_into_map(&mut image, &mut map).unwrap(); + pt.decode_traces_into_map(&mut image, map.as_mut_ptr(), map.len()) + .unwrap(); let assembly_jump_id = map.iter().position(|count| *count >= 254); assert!( diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 3b79abc84d4..31bfa1d1bc9 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -91,8 +91,7 @@ where // unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, // ); - let map = unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }; - let _ = pt.decode_traces_into_map(&mut self.image.0, map); + let _ = pt.decode_traces_into_map(&mut self.image.0, self.map_ptr, self.map_len); #[cfg(feature = "intel_pt_export_raw")] { From 1e7c1a85f912c54f681703b41e174a18c485d9f3 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:27:24 +0100 Subject: [PATCH 52/93] Add cpu option to QEMU config --- libafl_qemu/src/qemu/config.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 5fb0ed4b3d3..69f898555bc 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -182,6 +182,25 @@ impl> From for Machine { } } +#[derive(Debug, Clone)] +pub struct Cpu { + model: String, +} + +impl Display for Cpu { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "-cpu {}", self.model) + } +} + +impl> From for Cpu { + fn from(model: R) -> Self { + Self { + model: model.as_ref().to_string(), + } + } +} + #[derive(Debug, Clone, strum_macros::Display)] pub enum Snapshot { #[strum(serialize = "-snapshot")] @@ -317,6 +336,8 @@ pub struct QemuConfig { #[cfg(feature = "systemmode")] #[builder(default, setter(strip_option, into))] bios: Option, + #[builder(default, setter(strip_option, into))] + cpu: Option, #[builder(default, setter(into))] drives: Vec, #[cfg(feature = "systemmode")] From 7f80bc599c49682c5ae36ae2d3a2d913714ee3fe Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:10:30 +0100 Subject: [PATCH 53/93] Add cpu option and minor improvements --- .../qemu_intel_pt_bootloader/src/main.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index aa3b15ca54d..2c302eb4ca3 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -34,18 +34,19 @@ use libafl_qemu::{ }; // Coverage map -const MAP_SIZE: usize = 32; +const MAP_SIZE: usize = 256; static mut MAP: [u16; MAP_SIZE] = [0; MAP_SIZE]; +// Bootloader code section and sleep fn address can be retrieved with `ndisasm target/boot.bin` const BOOTLOADER_CODE: RangeInclusive = 0x7c00..=0x7c80; -const SLEEP_FN_ADDR: GuestAddr = 0x7c60; +const BOOTLOADER_SLEEP_FN_ADDR: GuestAddr = 0x7c60; fn main() { // Initialize the logger (use env variable RUST_LOG=trace for maximum logging) env_logger::init(); // Hardcoded parameters - let timeout = Duration::from_secs(3); + let timeout = Duration::from_secs(5); let objective_dir = PathBuf::from("./crashes"); let mon = SimpleMonitor::new(|s| println!("{s}")); @@ -59,12 +60,17 @@ fn main() { // Configure QEMU let qemu = QemuConfig::builder() .no_graphic(true) + .monitor(config::Monitor::Null) + .serial(config::Serial::Null) + .cpu("host") + .ram_size(config::RamSize::MB(1)) .drives([config::Drive::builder() .format(config::DiskImageFileFormat::Qcow2) .file(format!("{target_dir}/boot.qcow2")) .build() .unwrap()]) .accelerator(Accelerator::Kvm) + //.snapshot(true) todo: doesnt work .bios("/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") .start_cpu(false); @@ -105,7 +111,7 @@ fn main() { } qemu.remove_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr); - qemu.set_hw_breakpoint(SLEEP_FN_ADDR); + qemu.set_hw_breakpoint(BOOTLOADER_SLEEP_FN_ADDR); qemu.save_snapshot("bootloader_start", true); From 6f6a2adfaa7b43be20935f598db616645a13465d Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:12:40 +0100 Subject: [PATCH 54/93] fix cargo run causing recompile --- fuzzers/binary_only/intel_pt_command_executor/Makefile.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml index aca771cbca3..cec43736eea 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml +++ b/fuzzers/binary_only/intel_pt_command_executor/Makefile.toml @@ -31,8 +31,7 @@ script = "sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep ${ dependencies = ["build_fuzzer"] [tasks.run] -command = "cargo" -args = ["run", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] +command = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" dependencies = ["build", "setcap"] [tasks.default] From 4a696c0f02d224bc6963b24967b4288e2debebf2 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:12:37 +0100 Subject: [PATCH 55/93] no default devices --- .../qemu_intel_pt_bootloader/src/boot.s | 4 ++-- .../qemu_intel_pt_bootloader/src/main.rs | 1 + .../src/modules/systemmode/intel_pt.rs | 1 - libafl_qemu/src/qemu/config.rs | 23 +++++++++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s index 15710b9a8e5..397d3310207 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s @@ -1,5 +1,5 @@ -[bits 16] ; use 16 bits -[org 0x7c00] ; sets the start address +[bits 16] ; use 16 bits +[org 0x7c00] ; sets the start address %macro print_string 1 ; %1: Pointer to the string (null-terminated) mov si, %1 ; Load the pointer to the string diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 2c302eb4ca3..77bb85ae676 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -71,6 +71,7 @@ fn main() { .unwrap()]) .accelerator(Accelerator::Kvm) //.snapshot(true) todo: doesnt work + .default_devices(false) .bios("/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") .start_cpu(false); diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 31bfa1d1bc9..47a4b3a86c7 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -1,7 +1,6 @@ use std::{ fmt::Debug, ops::{Range, RangeInclusive}, - ptr::slice_from_raw_parts_mut, }; use libafl::{inputs::UsesInput, observers::ObserversTuple, Error, HasMetadata}; diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 69f898555bc..3725d70227b 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -300,6 +300,26 @@ impl From for VgaPci { } } +#[cfg(feature = "systemmode")] +#[derive(Debug, Clone, strum_macros::Display)] +pub enum DefaultDevices { + #[strum(serialize = "")] + ENABLE, + #[strum(serialize = "-nodefaults")] + DISABLE, +} + +#[cfg(feature = "systemmode")] +impl From for DefaultDevices { + fn from(default_devices: bool) -> Self { + if default_devices { + DefaultDevices::ENABLE + } else { + DefaultDevices::DISABLE + } + } +} + #[cfg(feature = "usermode")] #[derive(Debug, Clone)] pub struct Program { @@ -363,6 +383,9 @@ pub struct QemuConfig { vga_pci: Option, #[builder(default, setter(strip_option, into))] start_cpu: Option, + #[cfg(feature = "systemmode")] + #[builder(default, setter(strip_option, into))] + default_devices: Option, #[cfg(feature = "usermode")] #[builder(setter(into))] program: Program, From 1fcf6badec616c3867f69927b1bef020eb31d358 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:32:36 +0100 Subject: [PATCH 56/93] windows clippy fix --- libafl_intelpt/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 045bc68cc8d..f6626939a97 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -39,9 +39,9 @@ use arbitrary_int::u4; use bitbybit::bitfield; #[cfg(target_os = "linux")] use caps::{CapSet, Capability}; +use libafl_bolts::Error; #[cfg(target_os = "linux")] -use libafl_bolts::ownedref::OwnedRefMut; -use libafl_bolts::{hash_64_fast, Error}; +use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut}; #[cfg(target_os = "linux")] use libipt::{ block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, BlockFlags, @@ -851,6 +851,7 @@ pub fn availability_in_qemu_kvm() -> Result<(), String> { } /// Convert [`PtError`] into [`Error`] +#[cfg(target_os = "linux")] #[inline] #[must_use] pub fn error_from_pt_error(err: PtError) -> Error { From 22e03e0f117864406c563dccbdbcbc2b8c92644e Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:35:40 +0100 Subject: [PATCH 57/93] Exclude intel_pt feature from CI as all systemmode feats --- scripts/parallellize_cargo_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/parallellize_cargo_check.py b/scripts/parallellize_cargo_check.py index f736c20a0cb..5241e616b1c 100755 --- a/scripts/parallellize_cargo_check.py +++ b/scripts/parallellize_cargo_check.py @@ -23,7 +23,7 @@ "--exclude-features=prelude,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive " "--no-dev-deps --exclude libafl_libfuzzer --exclude libafl_qemu --exclude libafl_qemu_sys --print-command-list;" "DOCS_RS=1 cargo hack check -p libafl_qemu -p libafl_qemu_sys --each-feature --clean-per-run " - "--exclude-features=prelude,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive,slirp " + "--exclude-features=prelude,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive,slirp,intel_pt,intel_pt_export_raw " "--no-dev-deps --features usermode --print-command-list" ) From 59f8341e5fc59698c177d3ece6e17cd77cb6cbb9 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:47:23 +0100 Subject: [PATCH 58/93] Add qemu_intel_pt_bootloader to CI --- .github/workflows/build_and_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5fa33cf0981..3ab11f10fbe 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -280,6 +280,7 @@ jobs: - ./fuzzers/forkserver/fuzzbench_forkserver_cmplog - ./fuzzers/forkserver/libafl-fuzz - ./fuzzers/forkserver/baby_fuzzer_with_forkexecutor + - ./fuzzers/full_system/qemu_intel_pt_bootloader # Full-system - ./fuzzers/full_system/nyx_libxml2_standalone From 68aad5254adf73c0967caf154002f589b97d54a9 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:19:15 +0100 Subject: [PATCH 59/93] Fix NopPageFilter --- libafl_qemu/src/modules/systemmode/intel_pt.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 47a4b3a86c7..2ee55781175 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -10,7 +10,9 @@ use num_traits::SaturatingAdd; use typed_builder::TypedBuilder; use crate::{ - modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind, NopPageFilter}, + modules::{ + utils::filters::NopPageFilter, AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind, + }, EmulatorModules, NewThreadHook, Qemu, QemuParams, }; From 648094cd0d263945940686411f4ebe3a2386e9e0 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:56:20 +0100 Subject: [PATCH 60/93] Fix qemu_config --- .../full_system/qemu_intel_pt_bootloader/src/main.rs | 10 +++++----- libafl_qemu/src/modules/mod.rs | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 77bb85ae676..9afd5b33ce7 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -58,7 +58,7 @@ fn main() { let target_dir = env::var("TARGET_DIR").unwrap_or("target".to_string()); // Configure QEMU - let qemu = QemuConfig::builder() + let qemu_config = QemuConfig::builder() .no_graphic(true) .monitor(config::Monitor::Null) .serial(config::Serial::Null) @@ -67,13 +67,13 @@ fn main() { .drives([config::Drive::builder() .format(config::DiskImageFileFormat::Qcow2) .file(format!("{target_dir}/boot.qcow2")) - .build() - .unwrap()]) + .build()]) .accelerator(Accelerator::Kvm) //.snapshot(true) todo: doesnt work .default_devices(false) .bios("/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") - .start_cpu(false); + .start_cpu(false) + .build(); let file_path = Path::new(&target_dir) .join("boot.bin") @@ -94,7 +94,7 @@ fn main() { .build()); let emulator = EmulatorBuilder::empty() - .qemu_config(|_| qemu) + .qemu_parameters(qemu_config) .modules(emulator_modules) .build() .unwrap(); diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index a63f3276b40..3fb7c74e637 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -19,8 +19,7 @@ pub use usermode::*; #[cfg(feature = "systemmode")] pub mod systemmode; -#[cfg(feature = "systemmode")] -#[expect(unused_imports)] +#[cfg(all(feature = "systemmode", feature = "intel_pt"))] pub use systemmode::*; pub mod edges; From 0b43a8bbbb55ea439fe5ae2e6942352a9038fd2c Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:34:58 +0100 Subject: [PATCH 61/93] Restore HW breakpoints --- libafl_qemu/src/qemu/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 1204e77bd19..940e627171e 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -26,6 +26,8 @@ use libafl_qemu_sys::{ libafl_qemu_write_reg, CPUArchState, CPUStatePtr, FatPtr, GuestAddr, GuestPhysAddr, GuestUsize, GuestVirtAddr, }; +#[cfg(feature = "systemmode")] +use libafl_qemu_sys::{libafl_qemu_remove_hw_breakpoint, libafl_qemu_set_hw_breakpoint}; use num_traits::Num; use strum::IntoEnumIterator; @@ -811,6 +813,22 @@ impl Qemu { } } + #[cfg(feature = "systemmode")] + pub fn set_hw_breakpoint(&self, addr: GuestAddr) { + unsafe { + libafl_qemu_set_hw_breakpoint(addr.into()); + } + // TODO don't be lame, return a Result + } + + #[cfg(feature = "systemmode")] + pub fn remove_hw_breakpoint(&self, addr: GuestAddr) { + unsafe { + libafl_qemu_remove_hw_breakpoint(addr.into()); + } + // TODO don't be lame, return a Result + } + pub fn entry_break(&self, addr: GuestAddr) { self.set_breakpoint(addr); unsafe { From 5a6891b33fd63c3b08c46e11fe543ba9cad85336 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:49:19 +0100 Subject: [PATCH 62/93] Lints --- fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml | 4 ++-- libafl_intelpt/src/lib.rs | 6 ------ libafl_intelpt/tests/integration_tests_linux.rs | 6 +----- libafl_qemu/Cargo.toml | 1 - 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml index d1a12abbaf9..e498abc7100 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "bootloader" +name = "qemu_intel_pt_bootloader" version = "0.1.0" -authors = ["Marco Cavenati "] +authors = ["Marco Cavenati "] edition = "2021" [dependencies] diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index f6626939a97..e0e49a77722 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -456,12 +456,6 @@ impl IntelPT { let map_loc = map_ptr.add(id as usize % map_len); *map_loc = (*map_loc).saturating_add(&1u8.into()); } - // log::trace!( - // "previous block ip: {:x} current: {:x} offset: {offset:x}", - // previous_block_end_ip, - // b.ip() - // ); - // log::trace!("writing map at {id:x} with value {:?}", *map_loc); *previous_block_end_ip = b.end_ip(); } diff --git a/libafl_intelpt/tests/integration_tests_linux.rs b/libafl_intelpt/tests/integration_tests_linux.rs index 8d4d4725b41..2e54b5d14e0 100644 --- a/libafl_intelpt/tests/integration_tests_linux.rs +++ b/libafl_intelpt/tests/integration_tests_linux.rs @@ -69,11 +69,7 @@ fn intel_pt_trace_fork() { None, map.start() as u64, ) { - Err(e) => println!( - "Error adding mapping for {:?}: {:?}, skipping", - map.filename().unwrap(), - e - ), + Err(e) => println!("skipping mapping of {:?}: {:?}", map.filename().unwrap(), e), Ok(()) => println!( "mapping for {:?} added successfully {:#x} - {:#x}", map.filename().unwrap(), diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 4210e106a83..a75199e22be 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -135,7 +135,6 @@ bytes-utils = "0.1.4" typed-builder = { workspace = true } memmap2 = "0.9.5" getset = "0.1.3" -derive_builder = "0.20.2" # Document all features of this crate (for `cargo doc`) document-features = { workspace = true, optional = true } From b4f052c5a7784feadec35adcc5a1106d1308a3d8 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:33:14 +0100 Subject: [PATCH 63/93] return Result for hw bp set/remove --- .../qemu_intel_pt_bootloader/src/main.rs | 8 ++++--- libafl_qemu/src/qemu/mod.rs | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 9afd5b33ce7..e3591907da1 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -99,7 +99,8 @@ fn main() { .build() .unwrap(); let qemu = emulator.qemu(); - qemu.set_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr); + qemu.set_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr) + .unwrap(); // todo: there is smth broken somewhere, QemuExitReason::Breakpoint reports a wrong address unsafe { @@ -110,9 +111,10 @@ fn main() { _ => panic!("Pre-harness Unexpected QEMU exit."), } } - qemu.remove_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr); + qemu.remove_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr) + .unwrap(); - qemu.set_hw_breakpoint(BOOTLOADER_SLEEP_FN_ADDR); + qemu.set_hw_breakpoint(BOOTLOADER_SLEEP_FN_ADDR).unwrap(); qemu.save_snapshot("bootloader_start", true); diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 940e627171e..b3d294c5c63 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -18,6 +18,8 @@ use std::{ }; use libafl_bolts::os::unix_signals::Signal; +#[cfg(feature = "systemmode")] +use libafl_bolts::Error; use libafl_qemu_sys::{ libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd, libafl_qemu_cpu_index, libafl_qemu_current_cpu, libafl_qemu_gdb_reply, libafl_qemu_get_cpu, @@ -814,19 +816,25 @@ impl Qemu { } #[cfg(feature = "systemmode")] - pub fn set_hw_breakpoint(&self, addr: GuestAddr) { - unsafe { - libafl_qemu_set_hw_breakpoint(addr.into()); + pub fn set_hw_breakpoint(&self, addr: GuestAddr) -> Result<(), Error> { + let ret = unsafe { libafl_qemu_set_hw_breakpoint(addr.into()) }; + match ret { + 0 => Ok(()), + errno => Err(Error::unsupported(format!( + "Failed to set hw breakpoint errno: {errno}" + ))), } - // TODO don't be lame, return a Result } #[cfg(feature = "systemmode")] - pub fn remove_hw_breakpoint(&self, addr: GuestAddr) { - unsafe { - libafl_qemu_remove_hw_breakpoint(addr.into()); + pub fn remove_hw_breakpoint(&self, addr: GuestAddr) -> Result<(), Error> { + let ret = unsafe { libafl_qemu_remove_hw_breakpoint(addr.into()) }; + match ret { + 0 => Ok(()), + errno => Err(Error::unsupported(format!( + "Failed to set hw breakpoint errno: {errno}" + ))), } - // TODO don't be lame, return a Result } pub fn entry_break(&self, addr: GuestAddr) { From 26242497627cf66046016e600a563283298d3fb0 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:26:17 +0100 Subject: [PATCH 64/93] mark join_split_trace as unsafe --- libafl_intelpt/src/lib.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index e0e49a77722..c7b9dba8fd4 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -334,14 +334,14 @@ impl IntelPT { // https://manpages.debian.org/bookworm/manpages-dev/perf_event_open.2.en.html#data_head smp_rmb(); - let mut data = if head_wrap >= tail_wrap { - unsafe { - let ptr = self.perf_aux_buffer.add(tail_wrap as usize) as *mut u8; + let mut data = unsafe { + if head_wrap >= tail_wrap { + let ptr = self.perf_aux_buffer.add(tail_wrap as usize).cast::(); OwnedRefMut::Ref(slice::from_raw_parts_mut(ptr, len)) + } else { + // Head pointer wrapped, the trace is split + OwnedRefMut::Owned(self.join_split_trace(head_wrap, tail_wrap)) } - } else { - // Head pointer wrapped, the trace is split - OwnedRefMut::Owned(self.join_split_trace(head_wrap, tail_wrap)) }; #[cfg(feature = "export_raw")] { @@ -398,13 +398,23 @@ impl IntelPT { Ok(()) } + /// # Safety: + /// + /// The caller must ensure that `head_wrap` and `tail_wrap` have been wrapped properly, in other + /// words, this ensures that `head_wrap` and `tail_wrap` are < `self.perf_aux_buffer_size`. #[inline] #[must_use] - fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> Box<[u8]> { - let first_ptr = unsafe { self.perf_aux_buffer.add(tail_wrap as usize) as *mut u8 }; + unsafe fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> Box<[u8]> { + // this function is unsafe, but let's make it safe when compiling in debug mode + debug_assert!(head_wrap < self.perf_aux_buffer_size as u64); + debug_assert!(tail_wrap < self.perf_aux_buffer_size as u64); + + // SAFETY: tail_wrap is guaranteed to be < `self.perf_aux_buffer_size` from the fn safety + // preconditions + let first_ptr = unsafe { self.perf_aux_buffer.add(tail_wrap as usize) }.cast::(); let first_len = self.perf_aux_buffer_size - tail_wrap as usize; - let second_ptr = self.perf_aux_buffer as *mut u8; + let second_ptr = self.perf_aux_buffer.cast::(); let second_len = head_wrap as usize; let mut data = Box::<[u8]>::new_uninit_slice(first_len + second_len); From 0c4ab15c853ffdc39f845db59001ea4d7a41bb0b Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:38:49 +0100 Subject: [PATCH 65/93] Put the qcow2 in a tmpfs ramdisk 10x exec/sec --- .../qemu_intel_pt_bootloader/Makefile.toml | 14 +++++++++++--- .../qemu_intel_pt_bootloader/src/main.rs | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index 847918d0511..20e77d9e498 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -14,6 +14,14 @@ script = ''' mkdir -p ${TARGET_DIR} ''' +[tasks.ram_disk] +condition = { files_not_exist = ["/mnt/libafl_qemu_tmpfs"] } +script = "sudo mkdir -p /mnt/libafl_qemu_tmpfs && sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs" + +[tasks.remove_ram_disk] +condition = { files_exist = ["/mnt/libafl_qemu_tmpfs"] } +script = "sudo umount /mnt/libafl_qemu_tmpfs && sudo rm -r /mnt/libafl_qemu_tmpfs" + [tasks.build_target] dependencies = ["target_dir"] command = "nasm" @@ -37,16 +45,16 @@ args = [ "-O", "qcow2", "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.bin", - "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.qcow2", + "/mnt/libafl_qemu_tmpfs/boot.qcow2", ] +dependencies = ["build_target", "ram_disk"] [tasks.setcap] script = "sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" dependencies = ["build_fuzzer"] [tasks.run] -command = "cargo" -args = ["run", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] +command = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" dependencies = ["build", "setcap", "convert_target_image"] [tasks.default] diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index e3591907da1..01280fb4acc 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -66,7 +66,7 @@ fn main() { .ram_size(config::RamSize::MB(1)) .drives([config::Drive::builder() .format(config::DiskImageFileFormat::Qcow2) - .file(format!("{target_dir}/boot.qcow2")) + .file("/mnt/libafl_qemu_tmpfs/boot.qcow2") .build()]) .accelerator(Accelerator::Kvm) //.snapshot(true) todo: doesnt work From 6cfb6d526e4fb0dedad2e6141f091ef02baa59a0 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:16:55 +0100 Subject: [PATCH 66/93] Post merge fixes --- .../qemu_intel_pt_bootloader/src/main.rs | 2 +- .../src/modules/systemmode/intel_pt.rs | 30 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 01280fb4acc..7ea439134f0 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -118,7 +118,7 @@ fn main() { qemu.save_snapshot("bootloader_start", true); - let mut harness = |emulator: &mut Emulator<_, _, _, StdState, _>, + let mut harness = |emulator: &mut Emulator<_, _, _, _, _, _, _>, _: &mut StdState, input: &BytesInput| unsafe { let mut fixed_len_input = input.target_bytes().as_slice().to_vec(); diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 2ee55781175..f6ddd3a080d 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -34,8 +34,9 @@ impl IntelPTModule { } } -impl EmulatorModule for IntelPTModule +impl EmulatorModule for IntelPTModule where + I: Unpin, S: Unpin + UsesInput + HasMetadata, T: SaturatingAdd + From + Debug + 'static, { @@ -44,13 +45,13 @@ where fn pre_qemu_init( &mut self, - emulator_modules: &mut EmulatorModules, + emulator_modules: &mut EmulatorModules, _qemu_params: &mut QemuParams, ) where - ET: EmulatorModuleTuple, + ET: EmulatorModuleTuple, { emulator_modules - .thread_creation(NewThreadHook::Function(intel_pt_new_thread::)) + .thread_creation(NewThreadHook::Function(intel_pt_new_thread::)) .unwrap(); // TODO emulator_modules.thread_teradown } @@ -58,11 +59,11 @@ where fn pre_exec( &mut self, _qemu: Qemu, - _emulator_modules: &mut EmulatorModules, + _emulator_modules: &mut EmulatorModules, _state: &mut S, - _input: &S::Input, + _input: &I, ) where - ET: EmulatorModuleTuple, + ET: EmulatorModuleTuple, { let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.enable_tracing().unwrap(); @@ -71,14 +72,14 @@ where fn post_exec( &mut self, _qemu: Qemu, - _emulator_modules: &mut EmulatorModules, + _emulator_modules: &mut EmulatorModules, _state: &mut S, - _input: &S::Input, + _input: &I, _observers: &mut OT, _exit_kind: &mut ExitKind, ) where - OT: ObserversTuple, - ET: EmulatorModuleTuple, + OT: ObserversTuple, + ET: EmulatorModuleTuple, { let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.disable_tracing().unwrap(); @@ -143,15 +144,16 @@ where } } -pub fn intel_pt_new_thread( - emulator_modules: &mut EmulatorModules, +pub fn intel_pt_new_thread( + emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, _env: CPUArchStatePtr, tid: u32, ) -> bool where + I: Unpin, S: HasMetadata + Unpin + UsesInput, - ET: EmulatorModuleTuple, + ET: EmulatorModuleTuple, T: Debug + 'static, { let intel_pt_module = emulator_modules From 4f27051b7c054a570163ce16beefc9d8f95fdff5 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:49:31 +0100 Subject: [PATCH 67/93] Try out libipt 0.3 alpha --- Cargo.toml | 2 +- libafl_intelpt/src/lib.rs | 30 ++++++++-------- .../src/modules/systemmode/intel_pt.rs | 34 +++++++++---------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66cd22bc302..18e26f702a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ document-features = "0.2.10" fastbloom = { version = "0.8.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible libc = "0.2.159" # For (*nix) libc -libipt = "0.2.0" +libipt = { git = "https://github.com/Marcondiro/libipt-rs.git" } log = "0.4.22" meminterval = "0.4.1" mimalloc = { version = "0.1.43", default-features = false } diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index c7b9dba8fd4..db5cd45ba4e 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -44,8 +44,8 @@ use libafl_bolts::Error; use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut}; #[cfg(target_os = "linux")] use libipt::{ - block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, BlockFlags, - ConfigBuilder, Cpu, PtError, PtErrorCode, Status, + block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, Cpu, + PtEncoderDecoder, PtError, PtErrorCode, Status, }; #[cfg(target_os = "linux")] pub use libipt::{Asid, Image, SectionCache}; @@ -334,6 +334,7 @@ impl IntelPT { // https://manpages.debian.org/bookworm/manpages-dev/perf_event_open.2.en.html#data_head smp_rmb(); + // todo: Just use a pointer let mut data = unsafe { if head_wrap >= tail_wrap { let ptr = self.perf_aux_buffer.add(tail_wrap as usize).cast::(); @@ -348,21 +349,20 @@ impl IntelPT { self.last_decode_trace = data.as_ref().to_vec(); } - let mut config = ConfigBuilder::new(data.as_mut()).map_err(error_from_pt_error)?; - config.filter(self.ip_filters_to_addr_filter()); + let mut builder = unsafe { + BlockDecoder::builder().buffer_from_raw(data.as_mut().as_mut_ptr(), data.as_ref().len()) + } + .filter(self.ip_filters_to_addr_filter()) + .set_end_on_call(true) + .set_end_on_jump(true); if let Some(cpu) = &*CURRENT_CPU { - config.cpu(*cpu); + builder = builder.cpu(*cpu); } - let flags = BlockFlags::END_ON_CALL.union(BlockFlags::END_ON_JUMP); - config.flags(flags); - let mut decoder = BlockDecoder::new(&config.finish()).map_err(error_from_pt_error)?; + + let mut decoder = builder.build().map_err(error_from_pt_error)?; decoder.set_image(image).map_err(error_from_pt_error)?; if let Some(rm) = read_memory { - decoder - .image() - .map_err(error_from_pt_error)? - .set_callback(Some(rm)) - .map_err(error_from_pt_error)?; + decoder.image().set_callback(Some(rm)) } let mut previous_block_end_ip = 0; @@ -431,7 +431,7 @@ impl IntelPT { #[inline] fn decode_blocks( - decoder: &mut BlockDecoder<()>, + decoder: &mut BlockDecoder, status: &mut Status, previous_block_end_ip: &mut u64, skip: u64, @@ -454,7 +454,7 @@ impl IntelPT { }; } - match decoder.next() { + match decoder.decode_next() { Ok((b, s)) => { *status = s; let offset = decoder.offset().map_err(error_from_pt_error)?; diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index f6ddd3a080d..ab9dfedcaf9 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -1,10 +1,11 @@ use std::{ fmt::Debug, ops::{Range, RangeInclusive}, + rc::Rc, }; use libafl::{inputs::UsesInput, observers::ObserversTuple, Error, HasMetadata}; -use libafl_intelpt::{error_from_pt_error, Asid, Image, IntelPT, IntelPTBuilder, SectionCache}; +use libafl_intelpt::{error_from_pt_error, Image, IntelPT, IntelPTBuilder, SectionCache}; use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; use num_traits::SaturatingAdd; use typed_builder::TypedBuilder; @@ -23,7 +24,7 @@ pub struct IntelPTModule { #[builder(default = IntelPTModule::default_pt_builder())] intel_pt_builder: IntelPTBuilder, #[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))] - image: (Image<'static>, SectionCache<'static>), + image: Image, map_ptr: *mut T, map_len: usize, } @@ -93,7 +94,7 @@ where // unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, // ); - let _ = pt.decode_traces_into_map(&mut self.image.0, self.map_ptr, self.map_len); + let _ = pt.decode_traces_into_map(&mut self.image, self.map_ptr, self.map_len); #[cfg(feature = "intel_pt_export_raw")] { @@ -180,34 +181,31 @@ where // It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't // like this (and `TryFromIter` is not a thing atm) -fn sections_to_image( - sections: &[Section], -) -> Result<(Image<'static>, SectionCache<'static>), Error> { +fn sections_to_image(sections: &[Section]) -> Result { let mut image_cache = SectionCache::new(Some("image_cache")).map_err(error_from_pt_error)?; let mut image = Image::new(Some("image")).map_err(error_from_pt_error)?; + let mut isids = Vec::with_capacity(sections.len()); for s in sections { let isid = image_cache.add_file(&s.file_path, s.file_offset, s.size, s.virtual_address); - if let Err(e) = isid { - log::warn!( + match isid { + Err(e) => log::warn!( "Error while caching {} {} - skipped", s.file_path, e.to_string() - ); - continue; + ), + Ok(id) => isids.push(id), } + } - if let Err(e) = image.add_cached(&mut image_cache, isid.unwrap(), Asid::default()) { - log::warn!( - "Error while adding cache to image {} {} - skipped", - s.file_path, - e.to_string() - ); - continue; + let rc_cache = Rc::new(image_cache); + for isid in isids { + if let Err(e) = image.add_cached(rc_cache.clone(), isid, None) { + log::warn!("Error while adding cache to image {}", e.to_string()); } } - Ok((image, image_cache)) + Ok(image) } /// Info of a binary's section that can be used during `Intel PT` traces decoding From 181d8acf9958c722959eea7a9323ccd452d16598 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:35:27 +0100 Subject: [PATCH 68/93] Try out libipt 0.3 alpha also in hook --- libafl/src/executors/hooks/intel_pt.rs | 36 ++++++++++++-------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index 16e38c92c51..cd39e572a0d 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -1,11 +1,12 @@ +use alloc::rc::Rc; use core::fmt::Debug; use std::{ - ptr::slice_from_raw_parts_mut, string::{String, ToString}, + vec::Vec, }; use libafl_intelpt::{error_from_pt_error, IntelPT}; -use libipt::{Asid, Image, SectionCache}; +use libipt::{Image, SectionCache}; use num_traits::SaturatingAdd; use serde::Serialize; use typed_builder::TypedBuilder; @@ -31,7 +32,7 @@ pub struct IntelPTHook { #[builder(default = IntelPT::builder().build().unwrap())] intel_pt: IntelPT, #[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))] - image: (Image<'static>, SectionCache<'static>), + image: Image, map_ptr: *mut T, map_len: usize, } @@ -52,7 +53,7 @@ where pt.disable_tracing().unwrap(); let _ = pt - .decode_traces_into_map(&mut self.image.0, self.map_ptr, self.map_len) + .decode_traces_into_map(&mut self.image, self.map_ptr, self.map_len) .inspect_err(|e| log::warn!("Intel PT trace decoding failed: {e}")); #[cfg(feature = "intel_pt_export_raw")] { @@ -65,32 +66,29 @@ where // It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't // like this (and `TryFromIter` is not a thing atm) -fn sections_to_image( - sections: &[Section], -) -> Result<(Image<'static>, SectionCache<'static>), Error> { +fn sections_to_image(sections: &[Section]) -> Result { let mut image_cache = SectionCache::new(Some("image_cache")).map_err(error_from_pt_error)?; let mut image = Image::new(Some("image")).map_err(error_from_pt_error)?; + let mut isids = Vec::with_capacity(sections.len()); for s in sections { let isid = image_cache.add_file(&s.file_path, s.file_offset, s.size, s.virtual_address); - if let Err(e) = isid { - log::warn!( + match isid { + Err(e) => log::warn!( "Error while caching {} {} - skipped", s.file_path, e.to_string() - ); - continue; + ), + Ok(id) => isids.push(id), } + } - if let Err(e) = image.add_cached(&mut image_cache, isid.unwrap(), Asid::default()) { - log::warn!( - "Error while adding cache to image {} {} - skipped", - s.file_path, - e.to_string() - ); - continue; + let rc_cache = Rc::new(image_cache); + for isid in isids { + if let Err(e) = image.add_cached(rc_cache.clone(), isid, None) { + log::warn!("Error while adding cache to image {}", e.to_string()); } } - Ok((image, image_cache)) + Ok(image) } From f85eba7610f2ca153c63b1d240c1d0030a3370f2 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:56:51 +0100 Subject: [PATCH 69/93] Clippy --- libafl_intelpt/src/lib.rs | 2 +- libafl_qemu/src/modules/systemmode/intel_pt.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index db5cd45ba4e..619219cccf9 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -362,7 +362,7 @@ impl IntelPT { let mut decoder = builder.build().map_err(error_from_pt_error)?; decoder.set_image(image).map_err(error_from_pt_error)?; if let Some(rm) = read_memory { - decoder.image().set_callback(Some(rm)) + decoder.image().set_callback(Some(rm)); } let mut previous_block_end_ip = 0; diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index ab9dfedcaf9..12935a0c738 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -4,7 +4,7 @@ use std::{ rc::Rc, }; -use libafl::{inputs::UsesInput, observers::ObserversTuple, Error, HasMetadata}; +use libafl::{observers::ObserversTuple, Error, HasMetadata}; use libafl_intelpt::{error_from_pt_error, Image, IntelPT, IntelPTBuilder, SectionCache}; use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; use num_traits::SaturatingAdd; @@ -38,7 +38,7 @@ impl IntelPTModule { impl EmulatorModule for IntelPTModule where I: Unpin, - S: Unpin + UsesInput + HasMetadata, + S: Unpin + HasMetadata, T: SaturatingAdd + From + Debug + 'static, { type ModuleAddressFilter = Self; @@ -153,7 +153,7 @@ pub fn intel_pt_new_thread( ) -> bool where I: Unpin, - S: HasMetadata + Unpin + UsesInput, + S: HasMetadata + Unpin, ET: EmulatorModuleTuple, T: Debug + 'static, { From ed8a981d624c4ae63243fa14229e4f8df218f487 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:28:30 +0100 Subject: [PATCH 70/93] New libipt --- libafl/Cargo.toml | 9 +-------- libafl/src/executors/hooks/intel_pt.rs | 3 +-- libafl_intelpt/src/lib.rs | 10 +++++----- libafl_intelpt/tests/integration_tests_linux.rs | 2 +- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index d5ed573eb18..e89c72fcbdf 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -110,13 +110,7 @@ regex = ["std", "dep:regex"] casr = ["libcasr", "std", "regex"] ## Intel Processor Trace -intel_pt = [ - "std", - "dep:libafl_intelpt", - "dep:libipt", - "dep:nix", - "dep:num_enum", -] +intel_pt = ["std", "dep:libafl_intelpt", "dep:nix", "dep:num_enum"] ## Save all the Intel PT raw traces to files, use only for debug intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"] @@ -297,7 +291,6 @@ document-features = { workspace = true, optional = true } # Optional clap = { workspace = true, optional = true } num_enum = { workspace = true, optional = true } -libipt = { workspace = true, optional = true } fastbloom = { workspace = true, optional = true } [lints] diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index cd39e572a0d..96de8a7b361 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -5,8 +5,7 @@ use std::{ vec::Vec, }; -use libafl_intelpt::{error_from_pt_error, IntelPT}; -use libipt::{Image, SectionCache}; +use libafl_intelpt::{error_from_pt_error, Image, IntelPT, SectionCache}; use num_traits::SaturatingAdd; use serde::Serialize; use typed_builder::TypedBuilder; diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 619219cccf9..23a8d9573a8 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -43,13 +43,13 @@ use libafl_bolts::Error; #[cfg(target_os = "linux")] use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut}; #[cfg(target_os = "linux")] +pub use libipt::{asid::Asid, image::Image, image::SectionCache, status::Status}; +#[cfg(target_os = "linux")] use libipt::{ - block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, Cpu, - PtEncoderDecoder, PtError, PtErrorCode, Status, + block::BlockDecoder, config::AddrConfig, config::AddrFilter, config::AddrFilterBuilder, + config::AddrRange, config::Cpu, config::PtEncoderDecoder, error::PtError, error::PtErrorCode, }; #[cfg(target_os = "linux")] -pub use libipt::{Asid, Image, SectionCache}; -#[cfg(target_os = "linux")] use num_enum::TryFromPrimitive; #[cfg(target_os = "linux")] use num_traits::{Euclid, SaturatingAdd}; @@ -204,7 +204,7 @@ impl IntelPT { } } } - builder.finish() + builder.build() } /// Start tracing diff --git a/libafl_intelpt/tests/integration_tests_linux.rs b/libafl_intelpt/tests/integration_tests_linux.rs index 2e54b5d14e0..2802a94130f 100644 --- a/libafl_intelpt/tests/integration_tests_linux.rs +++ b/libafl_intelpt/tests/integration_tests_linux.rs @@ -5,7 +5,7 @@ use std::{arch::asm, process}; use libafl_intelpt::{availability, IntelPT}; -use libipt::Image; +use libipt::image::Image; use nix::{ sys::{ signal::{kill, raise, Signal}, From 0422d33c9af7d1977d40e7a126ff67ca6ff02b6a Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:33:21 +0100 Subject: [PATCH 71/93] Post merge fixes --- .../qemu_intel_pt_bootloader/Makefile.toml | 12 ++++++++++-- .../full_system/qemu_intel_pt_bootloader/src/main.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index 20e77d9e498..29c970e14d5 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -1,5 +1,6 @@ [env] TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" +USER = { script = ["logname"] } [env.development] PROFILE_DIR = "debug" @@ -16,11 +17,18 @@ mkdir -p ${TARGET_DIR} [tasks.ram_disk] condition = { files_not_exist = ["/mnt/libafl_qemu_tmpfs"] } -script = "sudo mkdir -p /mnt/libafl_qemu_tmpfs && sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs" +script = """ +sudo mkdir -p /mnt/libafl_qemu_tmpfs && +sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs && +sudo chown "${USER}:${USER}" "/mnt/libafl_qemu_tmpfs" +""" [tasks.remove_ram_disk] condition = { files_exist = ["/mnt/libafl_qemu_tmpfs"] } -script = "sudo umount /mnt/libafl_qemu_tmpfs && sudo rm -r /mnt/libafl_qemu_tmpfs" +script = """ +sudo umount /mnt/libafl_qemu_tmpfs; +sudo rm -r /mnt/libafl_qemu_tmpfs +""" [tasks.build_target] dependencies = ["target_dir"] diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 7ea439134f0..c6899286d39 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -119,7 +119,7 @@ fn main() { qemu.save_snapshot("bootloader_start", true); let mut harness = |emulator: &mut Emulator<_, _, _, _, _, _, _>, - _: &mut StdState, + _: &mut StdState<_, _, _, _>, input: &BytesInput| unsafe { let mut fixed_len_input = input.target_bytes().as_slice().to_vec(); fixed_len_input.resize(3, 0); From a428d0e8420e23fb574de3221068190065da2460 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:19:03 +0100 Subject: [PATCH 72/93] Bump libipt --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 18e26f702a5..a739940aef9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ document-features = "0.2.10" fastbloom = { version = "0.8.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible libc = "0.2.159" # For (*nix) libc -libipt = { git = "https://github.com/Marcondiro/libipt-rs.git" } +libipt = "0.3.0-beta.2" log = "0.4.22" meminterval = "0.4.1" mimalloc = { version = "0.1.43", default-features = false } From 09bfffd49a521c269f23530a80676417dc86a564 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:53:57 +0100 Subject: [PATCH 73/93] Drive cache None --- .../qemu_intel_pt_bootloader/Makefile.toml | 7 +++---- .../qemu_intel_pt_bootloader/src/main.rs | 3 ++- libafl_qemu/src/qemu/config.rs | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index 29c970e14d5..a5f62799224 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -16,11 +16,10 @@ mkdir -p ${TARGET_DIR} ''' [tasks.ram_disk] -condition = { files_not_exist = ["/mnt/libafl_qemu_tmpfs"] } script = """ -sudo mkdir -p /mnt/libafl_qemu_tmpfs && -sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs && -sudo chown "${USER}:${USER}" "/mnt/libafl_qemu_tmpfs" +sudo mkdir -p /mnt/libafl_qemu_tmpfs || echo "/mnt/libafl_qemu_tmpfs creation failed"; +sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs || echo "tmpfs mount failed"; +sudo chown "${USER}:${USER}" "/mnt/libafl_qemu_tmpfs"; """ [tasks.remove_ram_disk] diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index c6899286d39..0f378703abd 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -27,7 +27,7 @@ use libafl::{ use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice}; use libafl_qemu::{ config, - config::{Accelerator, QemuConfig}, + config::{Accelerator, DriveCache, QemuConfig}, executor::QemuExecutor, modules::intel_pt::{IntelPTModule, Section}, Emulator, EmulatorBuilder, GuestAddr, QemuExitReason, QemuShutdownCause, @@ -67,6 +67,7 @@ fn main() { .drives([config::Drive::builder() .format(config::DiskImageFileFormat::Qcow2) .file("/mnt/libafl_qemu_tmpfs/boot.qcow2") + .cache(DriveCache::None) .build()]) .accelerator(Accelerator::Kvm) //.snapshot(true) todo: doesnt work diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 5b98bd2b7b0..0cab904adc5 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -37,6 +37,16 @@ pub enum DiskImageFileFormat { Raw, } +#[derive(Debug, strum_macros::Display, Clone)] +#[strum(prefix = "cache=", serialize_all = "lowercase")] +pub enum DriveCache { + WriteBack, + None, + WriteThrough, + DirectSync, + Unsafe, +} + #[derive(Debug, Clone, Default, TypedBuilder)] pub struct Drive { #[builder(default, setter(strip_option, into))] @@ -45,6 +55,8 @@ pub struct Drive { format: Option, #[builder(default, setter(strip_option))] interface: Option, + #[builder(default, setter(strip_option))] + cache: Option, } impl Display for Drive { @@ -70,6 +82,9 @@ impl Display for Drive { if let Some(interface) = &self.interface { write!(f, "{}{interface}", separator())?; } + if let Some(cache) = &self.cache { + write!(f, "{}{cache}", separator())?; + } Ok(()) } From 06f8d7431ba93f77fb5f85ff9ce2cf261b3407bd Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:39:05 +0100 Subject: [PATCH 74/93] Post merge fixes --- libafl_qemu/src/modules/mod.rs | 5 +-- .../src/modules/systemmode/intel_pt.rs | 38 +++++++++---------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index 6b4a2ae061b..12904aeba20 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -1,10 +1,7 @@ -use core::{fmt::Debug, ops::Range}; +use core::fmt::Debug; use libafl::{executors::ExitKind, observers::ObserversTuple}; use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType}; -use libafl_qemu_sys::GuestAddr; -#[cfg(feature = "systemmode")] -use libafl_qemu_sys::GuestPhysAddr; use crate::{ emu::EmulatorModules, diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 12935a0c738..ad73afa7d08 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -11,9 +11,7 @@ use num_traits::SaturatingAdd; use typed_builder::TypedBuilder; use crate::{ - modules::{ - utils::filters::NopPageFilter, AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind, - }, + modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind}, EmulatorModules, NewThreadHook, Qemu, QemuParams, }; @@ -41,8 +39,8 @@ where S: Unpin + HasMetadata, T: SaturatingAdd + From + Debug + 'static, { - type ModuleAddressFilter = Self; - type ModulePageFilter = NopPageFilter; + // type ModuleAddressFilter = Self; + // type ModulePageFilter = NopPageFilter; fn pre_qemu_init( &mut self, @@ -104,21 +102,21 @@ where } } - fn address_filter(&self) -> &Self::ModuleAddressFilter { - self - } - - fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter { - self - } - - fn page_filter(&self) -> &Self::ModulePageFilter { - unimplemented!() - } - - fn page_filter_mut(&mut self) -> &mut Self::ModulePageFilter { - unimplemented!() - } + // fn address_filter(&self) -> &Self::ModuleAddressFilter { + // self + // } + // + // fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter { + // self + // } + // + // fn page_filter(&self) -> &Self::ModulePageFilter { + // unimplemented!() + // } + // + // fn page_filter_mut(&mut self) -> &mut Self::ModulePageFilter { + // unimplemented!() + // } } impl AddressFilter for IntelPTModule From ee7172d757505b43627369315ee5ef452f6bd5ef Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:40:57 +0100 Subject: [PATCH 75/93] Use SectionInfo from libipt --- Cargo.toml | 2 +- .../intel_pt_baby_fuzzer/src/main.rs | 10 ++-- .../intel_pt_command_executor/src/main.rs | 8 +-- .../qemu_intel_pt_bootloader/src/main.rs | 10 ++-- libafl/src/executors/hooks/intel_pt.rs | 58 +++---------------- libafl_intelpt/src/lib.rs | 4 +- .../tests/integration_tests_linux.rs | 3 +- .../src/modules/systemmode/intel_pt.rs | 54 +++-------------- 8 files changed, 35 insertions(+), 114 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11fc238bd61..a7120659bae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ document-features = "0.2.10" fastbloom = { version = "0.8.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible libc = "0.2.159" # For (*nix) libc -libipt = "0.3.0-beta.2" +libipt = { git = "https://github.com/Marcondiro/libipt-rs" } log = "0.4.22" meminterval = "0.4.1" mimalloc = { version = "0.1.43", default-features = false } diff --git a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs index 0614a5e9bdd..e677f1a9d00 100644 --- a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs +++ b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs @@ -8,7 +8,7 @@ use libafl::{ corpus::{InMemoryCorpus, OnDiskCorpus}, events::SimpleEventManager, executors::{ - hooks::intel_pt::{IntelPTHook, Section}, + hooks::intel_pt::{IntelPTHook, SectionInfo}, inprocess::GenericInProcessExecutor, ExitKind, }, @@ -100,10 +100,10 @@ pub fn main() { let sections = process_maps .iter() .filter_map(|pm| { - if pm.is_exec() && pm.filename().is_some() { - Some(Section { - file_path: pm.filename().unwrap().to_string_lossy().to_string(), - file_offset: pm.offset as u64, + if pm.is_exec() && pm.filename().is_some() && pm.inode != 0 { + Some(SectionInfo { + filename: pm.filename().unwrap().to_string_lossy().to_string(), + offset: pm.offset as u64, size: pm.size() as u64, virtual_address: pm.start() as u64, }) diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index f2e8f54d183..723bda0e8ab 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -7,7 +7,7 @@ use libafl::{ events::SimpleEventManager, executors::{ command::{CommandConfigurator, PTraceCommandConfigurator}, - hooks::intel_pt::{IntelPTHook, Section}, + hooks::intel_pt::{IntelPTHook, SectionInfo}, }, feedbacks::{CrashFeedback, MaxMapFeedback}, fuzzer::{Fuzzer, StdFuzzer}, @@ -105,9 +105,9 @@ pub fn main() { .build() .unwrap(); - let sections = [Section { - file_path: target_path.to_string_lossy().to_string(), - file_offset: 0x14000, + let sections = [SectionInfo { + filename: target_path.to_string_lossy().to_string(), + offset: 0x14000, size: (*code_memory_addresses.end() - *code_memory_addresses.start() + 1) as u64, virtual_address: *code_memory_addresses.start() as u64, }]; diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 0f378703abd..15336a56391 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -29,7 +29,7 @@ use libafl_qemu::{ config, config::{Accelerator, DriveCache, QemuConfig}, executor::QemuExecutor, - modules::intel_pt::{IntelPTModule, Section}, + modules::intel_pt::{IntelPTModule, SectionInfo}, Emulator, EmulatorBuilder, GuestAddr, QemuExitReason, QemuShutdownCause, }; @@ -76,13 +76,13 @@ fn main() { .start_cpu(false) .build(); - let file_path = Path::new(&target_dir) + let filename = Path::new(&target_dir) .join("boot.bin") .to_string_lossy() .to_string(); - let image = [Section { - file_path, - file_offset: 0, + let image = [SectionInfo { + filename, + offset: 0, size: (BOOTLOADER_CODE.end() - BOOTLOADER_CODE.start()) as u64 + 1, virtual_address: *BOOTLOADER_CODE.start() as u64, }]; diff --git a/libafl/src/executors/hooks/intel_pt.rs b/libafl/src/executors/hooks/intel_pt.rs index d26dcad3b94..092d93346c1 100644 --- a/libafl/src/executors/hooks/intel_pt.rs +++ b/libafl/src/executors/hooks/intel_pt.rs @@ -1,36 +1,23 @@ -use alloc::rc::Rc; use core::fmt::Debug; -use std::{ - string::{String, ToString}, - vec::Vec, -}; -use libafl_intelpt::{error_from_pt_error, Image, IntelPT, SectionCache}; +pub use libafl_intelpt::SectionInfo; +use libafl_intelpt::{Image, IntelPT}; use num_traits::SaturatingAdd; use serde::Serialize; use typed_builder::TypedBuilder; -use crate::{executors::hooks::ExecutorHook, Error}; - -/// Info of a binary's section that can be used during `Intel PT` traces decoding -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Section { - /// Path of the binary - pub file_path: String, - /// Offset of the section in the file - pub file_offset: u64, - /// Size of the section - pub size: u64, - /// Start virtual address of the section once loaded in memory - pub virtual_address: u64, -} +use crate::executors::hooks::ExecutorHook; /// Hook to enable Intel Processor Trace (PT) tracing #[derive(Debug, TypedBuilder)] pub struct IntelPTHook { #[builder(default = IntelPT::builder().build().unwrap())] intel_pt: IntelPT, - #[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))] + #[builder(setter(transform = |sections: &[SectionInfo]| { + let mut i = Image::new(None).unwrap(); + i.add_files_cached(sections, None).unwrap(); + i + }))] image: Image, map_ptr: *mut T, map_len: usize, @@ -62,32 +49,3 @@ where } } } - -// It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't -// like this (and `TryFromIter` is not a thing atm) -fn sections_to_image(sections: &[Section]) -> Result { - let mut image_cache = SectionCache::new(Some("image_cache")).map_err(error_from_pt_error)?; - let mut image = Image::new(Some("image")).map_err(error_from_pt_error)?; - - let mut isids = Vec::with_capacity(sections.len()); - for s in sections { - let isid = image_cache.add_file(&s.file_path, s.file_offset, s.size, s.virtual_address); - match isid { - Err(e) => log::warn!( - "Error while caching {} {} - skipped", - s.file_path, - e.to_string() - ), - Ok(id) => isids.push(id), - } - } - - let rc_cache = Rc::new(image_cache); - for isid in isids { - if let Err(e) = image.add_cached(rc_cache.clone(), isid, None) { - log::warn!("Error while adding cache to image {}", e.to_string()); - } - } - - Ok(image) -} diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index b4a765a8e97..016bc152d8a 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -43,7 +43,9 @@ use libafl_bolts::Error; #[cfg(target_os = "linux")] use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut}; #[cfg(target_os = "linux")] -pub use libipt::{asid::Asid, image::Image, image::SectionCache, status::Status}; +pub use libipt::{ + asid::Asid, image::Image, image::SectionCache, image::SectionInfo, status::Status, +}; #[cfg(target_os = "linux")] use libipt::{ block::BlockDecoder, enc_dec_builder::AddrFilterRange, enc_dec_builder::AddrFilterType, diff --git a/libafl_intelpt/tests/integration_tests_linux.rs b/libafl_intelpt/tests/integration_tests_linux.rs index 2802a94130f..f4b06f1061f 100644 --- a/libafl_intelpt/tests/integration_tests_linux.rs +++ b/libafl_intelpt/tests/integration_tests_linux.rs @@ -4,8 +4,7 @@ use std::{arch::asm, process}; -use libafl_intelpt::{availability, IntelPT}; -use libipt::image::Image; +use libafl_intelpt::{availability, Image, IntelPT}; use nix::{ sys::{ signal::{kill, raise, Signal}, diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index ad73afa7d08..4beb20bfa1d 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -1,11 +1,11 @@ use std::{ fmt::Debug, ops::{Range, RangeInclusive}, - rc::Rc, }; -use libafl::{observers::ObserversTuple, Error, HasMetadata}; -use libafl_intelpt::{error_from_pt_error, Image, IntelPT, IntelPTBuilder, SectionCache}; +use libafl::{observers::ObserversTuple, HasMetadata}; +pub use libafl_intelpt::SectionInfo; +use libafl_intelpt::{Image, IntelPT, IntelPTBuilder}; use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; use num_traits::SaturatingAdd; use typed_builder::TypedBuilder; @@ -21,7 +21,11 @@ pub struct IntelPTModule { pt: Option, #[builder(default = IntelPTModule::default_pt_builder())] intel_pt_builder: IntelPTBuilder, - #[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))] + #[builder(setter(transform = |sections: &[SectionInfo]| { + let mut i = Image::new(None).unwrap(); + i.add_files_cached(sections, None).unwrap(); + i + }))] image: Image, map_ptr: *mut T, map_len: usize, @@ -176,45 +180,3 @@ where // What does this bool mean? ignore for the moment true } - -// It would be nice to have this as a `TryFrom>`, but Rust's orphan rule doesn't -// like this (and `TryFromIter` is not a thing atm) -fn sections_to_image(sections: &[Section]) -> Result { - let mut image_cache = SectionCache::new(Some("image_cache")).map_err(error_from_pt_error)?; - let mut image = Image::new(Some("image")).map_err(error_from_pt_error)?; - - let mut isids = Vec::with_capacity(sections.len()); - for s in sections { - let isid = image_cache.add_file(&s.file_path, s.file_offset, s.size, s.virtual_address); - match isid { - Err(e) => log::warn!( - "Error while caching {} {} - skipped", - s.file_path, - e.to_string() - ), - Ok(id) => isids.push(id), - } - } - - let rc_cache = Rc::new(image_cache); - for isid in isids { - if let Err(e) = image.add_cached(rc_cache.clone(), isid, None) { - log::warn!("Error while adding cache to image {}", e.to_string()); - } - } - - Ok(image) -} - -/// Info of a binary's section that can be used during `Intel PT` traces decoding -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Section { - /// Path of the binary - pub file_path: String, - /// Offset of the section in the file - pub file_offset: u64, - /// Size of the section - pub size: u64, - /// Start virtual address of the section once loaded in memory - pub virtual_address: u64, -} From bcfa02084cd8e4dcd72a3914ca6d809371fc414b Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:37:42 +0100 Subject: [PATCH 76/93] No slice::from_raw_parts_mut, just use raw pointer --- libafl_intelpt/src/lib.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 016bc152d8a..7cf17ee7a54 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -29,7 +29,7 @@ use std::{ raw::c_void, }, path::Path, - ptr, slice, + ptr, sync::LazyLock, }; @@ -39,9 +39,9 @@ use arbitrary_int::u4; use bitbybit::bitfield; #[cfg(target_os = "linux")] use caps::{CapSet, Capability}; -use libafl_bolts::Error; #[cfg(target_os = "linux")] -use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut}; +use libafl_bolts::hash_64_fast; +use libafl_bolts::Error; #[cfg(target_os = "linux")] pub use libipt::{ asid::Asid, image::Image, image::SectionCache, image::SectionInfo, status::Status, @@ -336,27 +336,29 @@ impl IntelPT { // https://manpages.debian.org/bookworm/manpages-dev/perf_event_open.2.en.html#data_head smp_rmb(); - // todo: Just use a pointer - let mut data = unsafe { + let (data_ptr, _owned_data) = unsafe { if head_wrap >= tail_wrap { let ptr = self.perf_aux_buffer.add(tail_wrap as usize).cast::(); - OwnedRefMut::Ref(slice::from_raw_parts_mut(ptr, len)) + (ptr, None) } else { // Head pointer wrapped, the trace is split - OwnedRefMut::Owned(self.join_split_trace(head_wrap, tail_wrap)) + let mut owned_data = self.join_split_trace(head_wrap, tail_wrap); + (owned_data.as_mut_ptr(), Some(owned_data)) } }; #[cfg(feature = "export_raw")] { - self.last_decode_trace = data.as_ref().to_vec(); + self.last_decode_trace = Vec::with_capacity(len); + unsafe { + ptr::copy_nonoverlapping(data_ptr, self.last_decode_trace.as_mut_ptr(), len); + self.last_decode_trace.set_len(len); + } } - let mut builder = unsafe { - BlockDecoder::builder().buffer_from_raw(data.as_mut().as_mut_ptr(), data.as_ref().len()) - } - .filter(self.ip_filters_to_addr_filter()) - .set_end_on_call(true) - .set_end_on_jump(true); + let mut builder = unsafe { BlockDecoder::builder().buffer_from_raw(data_ptr, len) } + .filter(self.ip_filters_to_addr_filter()) + .set_end_on_call(true) + .set_end_on_jump(true); if let Some(cpu) = &*CURRENT_CPU { builder = builder.cpu(*cpu); } From b1febfccd782192c58e5dfe035e1af2729c4a354 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:57:01 +0100 Subject: [PATCH 77/93] Cache the decoder builder --- libafl_intelpt/src/lib.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 7cf17ee7a54..541b3ef1b45 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -50,7 +50,7 @@ pub use libipt::{ use libipt::{ block::BlockDecoder, enc_dec_builder::AddrFilterRange, enc_dec_builder::AddrFilterType, enc_dec_builder::AddrFilters, enc_dec_builder::AddrFiltersBuilder, enc_dec_builder::Cpu, - enc_dec_builder::PtEncoderDecoder, error::PtError, error::PtErrorCode, + enc_dec_builder::EncoderDecoderBuilder, error::PtError, error::PtErrorCode, }; #[cfg(target_os = "linux")] use num_enum::TryFromPrimitive; @@ -89,14 +89,6 @@ static NR_ADDR_FILTERS: LazyLock> = LazyLock::new(|| { s2.trim().parse::().map_err(|_| err) }); -#[cfg(target_os = "linux")] -static CURRENT_CPU: LazyLock> = LazyLock::new(|| { - let cpuid = CpuId::new(); - cpuid - .get_feature_info() - .map(|fi| Cpu::intel(fi.family_id().into(), fi.model_id(), fi.stepping_id())) -}); - #[cfg(target_os = "linux")] static PERF_EVENT_TYPE: LazyLock> = LazyLock::new(|| { let path = format!("{PT_EVENT_PATH}/type"); @@ -134,6 +126,8 @@ pub struct IntelPT { aux_tail: *mut u64, previous_decode_head: u64, ip_filters: Vec>, + // The lifetime of BlockDecoder<'a> is irrelevant during the building phase. + decoder_builder: EncoderDecoderBuilder>, #[cfg(feature = "export_raw")] last_decode_trace: Vec, } @@ -354,14 +348,8 @@ impl IntelPT { self.last_decode_trace.set_len(len); } } - - let mut builder = unsafe { BlockDecoder::builder().buffer_from_raw(data_ptr, len) } - .filter(self.ip_filters_to_addr_filter()) - .set_end_on_call(true) - .set_end_on_jump(true); - if let Some(cpu) = &*CURRENT_CPU { - builder = builder.cpu(*cpu); - } + let builder = unsafe { self.decoder_builder.clone().buffer_from_raw(data_ptr, len) } + .filter(self.ip_filters_to_addr_filter()); let mut decoder = builder.build().map_err(error_from_pt_error)?; decoder.set_image(image).map_err(error_from_pt_error)?; @@ -633,6 +621,13 @@ impl IntelPTBuilder { let aux_head = unsafe { &raw mut (*buff_metadata).aux_head }; let aux_tail = unsafe { &raw mut (*buff_metadata).aux_tail }; + let mut decoder_builder = EncoderDecoderBuilder::new() + .set_end_on_call(true) + .set_end_on_jump(true); + if let Some(cpu) = current_cpu() { + decoder_builder = decoder_builder.cpu(cpu); + } + let mut intel_pt = IntelPT { fd, perf_buffer, @@ -643,6 +638,7 @@ impl IntelPTBuilder { aux_tail, previous_decode_head: 0, ip_filters: Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize), + decoder_builder, #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), }; @@ -1039,6 +1035,15 @@ const fn wrap_aux_pointer(ptr: u64, perf_aux_buffer_size: usize) -> u64 { ptr & (perf_aux_buffer_size as u64 - 1) } +#[cfg(target_os = "linux")] +#[inline] +fn current_cpu() -> Option { + let cpuid = CpuId::new(); + cpuid + .get_feature_info() + .map(|fi| Cpu::intel(fi.family_id().into(), fi.model_id(), fi.stepping_id())) +} + #[cfg(test)] mod test { #[cfg(target_os = "linux")] From 02382cf84d016185868cb94f9496bae598f10e8d Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:32:31 +0100 Subject: [PATCH 78/93] Update qemu-bridge --- fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs | 2 +- libafl/src/events/llmp/mgr.rs | 0 libafl_qemu/libafl_qemu_build/src/build.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 libafl/src/events/llmp/mgr.rs diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 15336a56391..2ca98844c0e 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -63,7 +63,7 @@ fn main() { .monitor(config::Monitor::Null) .serial(config::Serial::Null) .cpu("host") - .ram_size(config::RamSize::MB(1)) + .ram_size(config::RamSize::MB(2)) .drives([config::Drive::builder() .format(config::DiskImageFileFormat::Qcow2) .file("/mnt/libafl_qemu_tmpfs/boot.qcow2") diff --git a/libafl/src/events/llmp/mgr.rs b/libafl/src/events/llmp/mgr.rs deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index c7233fefb4f..b990affcdf1 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -11,7 +11,7 @@ use crate::cargo_add_rpath; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -pub const QEMU_REVISION: &str = "2b5e4bfcff875571b2813a9494de8b2e4c56120e"; +pub const QEMU_REVISION: &str = "7e0dc68430c509ad50c6b0c9887f7e642a4bba2d"; pub struct BuildResult { pub qemu_path: PathBuf, From 9498c7762fca4d7c43cb1b11d92f0ec64ac927df Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:09:21 +0100 Subject: [PATCH 79/93] Add qemu -append param --- libafl_qemu/src/qemu/config.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 0cab904adc5..da782017d7c 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -152,6 +152,28 @@ impl> From for Kernel { } } +#[cfg(feature = "systemmode")] +#[derive(Debug, Clone)] +pub struct AppendKernelCmd { + cmdline: String, +} + +#[cfg(feature = "systemmode")] +impl Display for AppendKernelCmd { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "-append {}", self.cmdline) + } +} + +#[cfg(feature = "systemmode")] +impl> From for AppendKernelCmd { + fn from(cmdline: R) -> Self { + Self { + cmdline: cmdline.as_ref().to_string(), + } + } +} + #[derive(Debug, Clone)] pub struct LoadVM { path: PathBuf, @@ -388,6 +410,9 @@ pub struct QemuConfig { #[cfg(feature = "systemmode")] #[builder(default, setter(strip_option, into))] default_devices: Option, + #[cfg(feature = "systemmode")] + #[builder(default, setter(strip_option, into))] + append_kernel_cmd: Option, #[cfg(feature = "usermode")] #[builder(setter(into))] program: Program, From 64eb7b9134aaba3950ddc29766a16f0aa294ea12 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:25:50 +0100 Subject: [PATCH 80/93] Move linux specific code to a mod, less #[cfg]s --- libafl_intelpt/src/lib.rs | 1009 +---------------------------------- libafl_intelpt/src/linux.rs | 980 ++++++++++++++++++++++++++++++++++ 2 files changed, 986 insertions(+), 1003 deletions(-) create mode 100644 libafl_intelpt/src/linux.rs diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 541b3ef1b45..f736d187dd7 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -12,763 +12,19 @@ #[macro_use] extern crate std; -use std::{ - borrow::ToOwned, - string::{String, ToString}, - vec::Vec, -}; #[cfg(target_os = "linux")] -use std::{ - boxed::Box, - ffi::{CStr, CString}, - fmt::Debug, - format, fs, - ops::RangeInclusive, - os::{ - fd::{AsRawFd, FromRawFd, OwnedFd}, - raw::c_void, - }, - path::Path, - ptr, - sync::LazyLock, -}; +use std::fs; +use std::{borrow::ToOwned, string::String, vec::Vec}; -#[cfg(target_os = "linux")] -use arbitrary_int::u4; -#[cfg(target_os = "linux")] -use bitbybit::bitfield; -#[cfg(target_os = "linux")] -use caps::{CapSet, Capability}; -#[cfg(target_os = "linux")] -use libafl_bolts::hash_64_fast; -use libafl_bolts::Error; -#[cfg(target_os = "linux")] -pub use libipt::{ - asid::Asid, image::Image, image::SectionCache, image::SectionInfo, status::Status, -}; -#[cfg(target_os = "linux")] -use libipt::{ - block::BlockDecoder, enc_dec_builder::AddrFilterRange, enc_dec_builder::AddrFilterType, - enc_dec_builder::AddrFilters, enc_dec_builder::AddrFiltersBuilder, enc_dec_builder::Cpu, - enc_dec_builder::EncoderDecoderBuilder, error::PtError, error::PtErrorCode, -}; -#[cfg(target_os = "linux")] -use num_enum::TryFromPrimitive; -#[cfg(target_os = "linux")] -use num_traits::{Euclid, SaturatingAdd}; -#[cfg(target_os = "linux")] -use perf_event_open_sys::{ - bindings::{perf_event_attr, perf_event_mmap_page, PERF_FLAG_FD_CLOEXEC}, - ioctls::{DISABLE, ENABLE, SET_FILTER}, - perf_event_open, -}; use raw_cpuid::CpuId; -/// Size of a memory page -pub const PAGE_SIZE: usize = 4096; - -#[cfg(target_os = "linux")] -const PT_EVENT_PATH: &str = "/sys/bus/event_source/devices/intel_pt"; - -#[cfg(target_os = "linux")] -static NR_ADDR_FILTERS: LazyLock> = LazyLock::new(|| { - // This info is available in two different files, use the second path as fail-over - let path = format!("{PT_EVENT_PATH}/nr_addr_filters"); - let path2 = format!("{PT_EVENT_PATH}/caps/num_address_ranges"); - let err = format!("Failed to read Intel PT number of address filters from {path} and {path2}"); - - let s = fs::read_to_string(&path); - if let Ok(s) = s { - let n = s.trim().parse::(); - if let Ok(n) = n { - return Ok(n); - } - } - - let s2 = fs::read_to_string(&path2).map_err(|_| err.clone())?; - s2.trim().parse::().map_err(|_| err) -}); - -#[cfg(target_os = "linux")] -static PERF_EVENT_TYPE: LazyLock> = LazyLock::new(|| { - let path = format!("{PT_EVENT_PATH}/type"); - let s = fs::read_to_string(&path) - .map_err(|_| format!("Failed to read Intel PT perf event type from {path}"))?; - s.trim() - .parse::() - .map_err(|_| format!("Failed to parse Intel PT perf event type in {path}")) -}); - -/// Intel PT mode of operation with KVM -/// -/// Check out -/// for more details -#[cfg(target_os = "linux")] -#[derive(TryFromPrimitive, Debug)] -#[repr(i32)] -pub enum KvmPTMode { - /// trace both host/guest and output to host buffer - System = 0, - /// trace host and guest simultaneously and output to their respective buffer - HostGuest = 1, -} - -/// Intel Processor Trace (PT) -#[cfg(target_os = "linux")] -#[derive(Debug)] -pub struct IntelPT { - fd: OwnedFd, - perf_buffer: *mut c_void, - perf_aux_buffer: *mut c_void, - perf_buffer_size: usize, - perf_aux_buffer_size: usize, - aux_head: *mut u64, - aux_tail: *mut u64, - previous_decode_head: u64, - ip_filters: Vec>, - // The lifetime of BlockDecoder<'a> is irrelevant during the building phase. - decoder_builder: EncoderDecoderBuilder>, - #[cfg(feature = "export_raw")] - last_decode_trace: Vec, -} - -#[cfg(target_os = "linux")] -impl IntelPT { - /// Create a default builder - /// - /// Checkout [`IntelPTBuilder::default()`] for more details - #[must_use] - pub fn builder() -> IntelPTBuilder { - IntelPTBuilder::default() - } - - /// Set filters based on Instruction Pointer (IP) - /// - /// Only instructions in `filters` ranges will be traced. - pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { - let str_filter = filters - .iter() - .map(|filter| { - let size = filter.end() - filter.start(); - format!("filter {:#016x}/{:#016x} ", filter.start(), size) - }) - .reduce(|acc, s| acc + &s) - .unwrap_or_default(); - - // SAFETY: CString::from_vec_unchecked is safe because no null bytes are added to str_filter - let c_str_filter = unsafe { CString::from_vec_unchecked(str_filter.into_bytes()) }; - match unsafe { SET_FILTER(self.fd.as_raw_fd(), c_str_filter.into_raw()) } { - -1 => { - let availability = match availability() { - Ok(()) => String::new(), - Err(reasons) => format!(" Possible reasons: {reasons}"), - }; - Err(Error::last_os_error(format!( - "Failed to set IP filters.{availability}" - ))) - } - 0 => { - self.ip_filters = filters.to_vec(); - Ok(()) - } - ret => Err(Error::unsupported(format!( - "Failed to set IP filter, ioctl returned unexpected value {ret}" - ))), - } - } - - /// Get the current IP filters configuration - #[must_use] - pub fn ip_filters(&self) -> Vec> { - self.ip_filters.clone() - } - - fn ip_filters_to_addr_filter(&self) -> AddrFilters { - let mut builder = AddrFiltersBuilder::new(); - let mut iter = self.ip_filters.iter().map(|f| { - AddrFilterRange::new(*f.start() as u64, *f.end() as u64, AddrFilterType::FILTER) - }); - if let Some(f) = iter.next() { - builder.addr0(f); - if let Some(f) = iter.next() { - builder.addr1(f); - if let Some(f) = iter.next() { - builder.addr2(f); - if let Some(f) = iter.next() { - builder.addr3(f); - } - } - } - } - builder.build() - } - - /// Start tracing - /// - /// Be aware that the tracing is not started on [`IntelPT`] construction. - pub fn enable_tracing(&mut self) -> Result<(), Error> { - match unsafe { ENABLE(self.fd.as_raw_fd(), 0) } { - -1 => { - let availability = match availability() { - Ok(()) => String::new(), - Err(reasons) => format!(" Possible reasons: {reasons}"), - }; - Err(Error::last_os_error(format!( - "Failed to enable tracing.{availability}" - ))) - } - 0 => Ok(()), - ret => Err(Error::unsupported(format!( - "Failed to enable tracing, ioctl returned unexpected value {ret}" - ))), - } - } - - /// Stop tracing - /// - /// This doesn't drop [`IntelPT`], the configuration will be preserved. - pub fn disable_tracing(&mut self) -> Result<(), Error> { - match unsafe { DISABLE(self.fd.as_raw_fd(), 0) } { - -1 => Err(Error::last_os_error("Failed to disable tracing")), - 0 => Ok(()), - ret => Err(Error::unsupported(format!( - "Failed to disable tracing, ioctl returned unexpected value {ret}" - ))), - } - } - - // /// Fill the coverage map by decoding the PT traces and reading target memory through `read_mem` - // /// - // /// This function consumes the traces. - // /// - // /// # Example - // /// - // /// An example `read_mem` callback function for the (inprocess) `intel_pt_babyfuzzer` could be: - // /// ``` - // /// let read_mem = |buf: &mut [u8], addr: u64| { - // /// let src = addr as *const u8; - // /// let dst = buf.as_mut_ptr(); - // /// let size = buf.len(); - // /// unsafe { - // /// core::ptr::copy_nonoverlapping(src, dst, size); - // /// } - // /// }; - // /// ``` - // #[allow(clippy::cast_possible_wrap)] - // pub fn decode_with_callback(&mut self, read_memory: F, map: &mut [T]) -> Result<(), Error> - // where - // F: Fn(&mut [u8], u64), - // T: SaturatingAdd + From + Debug, - // { - // self.decode_traces_into_map_common( - // None, - // Some(|buff: &mut [u8], addr: u64, _: Asid| { - // debug_assert!(i32::try_from(buff.len()).is_ok()); - // read_memory(buff, addr); - // buff.len() as i32 - // }), - // map, - // ) - // } - - /// Fill the coverage map by decoding the PT traces - /// - /// This function consumes the traces. - pub fn decode_traces_into_map( - &mut self, - image: &mut Image, - map_ptr: *mut T, - map_len: usize, - ) -> Result<(), Error> - where - T: SaturatingAdd + From + Debug, - { - self.decode_traces_into_map_common( - Some(image), - None:: i32>, - map_ptr, - map_len, - ) - } - - fn decode_traces_into_map_common( - &mut self, - image: Option<&mut Image>, - read_memory: Option, - map_ptr: *mut T, - map_len: usize, - ) -> Result<(), Error> - where - F: Fn(&mut [u8], u64, Asid) -> i32, - T: SaturatingAdd + From + Debug, - { - let head = unsafe { self.aux_head.read_volatile() }; - let tail = unsafe { self.aux_tail.read_volatile() }; - if head < tail { - return Err(Error::unknown( - "Intel PT: aux buffer head is behind aux tail.", - )); - }; - if self.previous_decode_head < tail { - return Err(Error::unknown( - "Intel PT: aux previous head is behind aux tail.", - )); - }; - let len = (head - tail) as usize; - if len >= self.perf_aux_buffer_size { - log::warn!( - "The fuzzer run filled the entire PT buffer. Consider increasing the aux buffer \ - size or refining the IP filters." - ); - } - let skip = self.previous_decode_head - tail; - - let head_wrap = wrap_aux_pointer(head, self.perf_aux_buffer_size); - let tail_wrap = wrap_aux_pointer(tail, self.perf_aux_buffer_size); - - // after reading the data_head value, user space should issue an rmb() - // https://manpages.debian.org/bookworm/manpages-dev/perf_event_open.2.en.html#data_head - smp_rmb(); - - let (data_ptr, _owned_data) = unsafe { - if head_wrap >= tail_wrap { - let ptr = self.perf_aux_buffer.add(tail_wrap as usize).cast::(); - (ptr, None) - } else { - // Head pointer wrapped, the trace is split - let mut owned_data = self.join_split_trace(head_wrap, tail_wrap); - (owned_data.as_mut_ptr(), Some(owned_data)) - } - }; - #[cfg(feature = "export_raw")] - { - self.last_decode_trace = Vec::with_capacity(len); - unsafe { - ptr::copy_nonoverlapping(data_ptr, self.last_decode_trace.as_mut_ptr(), len); - self.last_decode_trace.set_len(len); - } - } - let builder = unsafe { self.decoder_builder.clone().buffer_from_raw(data_ptr, len) } - .filter(self.ip_filters_to_addr_filter()); - - let mut decoder = builder.build().map_err(error_from_pt_error)?; - decoder.set_image(image).map_err(error_from_pt_error)?; - if let Some(rm) = read_memory { - decoder.image().set_callback(Some(rm)); - } - - let mut previous_block_end_ip = 0; - let mut status; - 'sync: loop { - match decoder.sync_forward() { - Ok(s) => { - status = s; - Self::decode_blocks( - &mut decoder, - &mut status, - &mut previous_block_end_ip, - skip, - map_ptr, - map_len, - )?; - } - Err(e) => { - if e.code() != PtErrorCode::Eos { - log::trace!("PT error in sync forward {e:?}"); - } - break 'sync; - } - }; - } - - // Advance the trace pointer up to the latest sync point, otherwise next execution's trace - // might not contain a PSB packet. - decoder.sync_backward().map_err(error_from_pt_error)?; - let offset = decoder.sync_offset().map_err(error_from_pt_error)?; - unsafe { self.aux_tail.write_volatile(tail + offset) }; - self.previous_decode_head = head; - Ok(()) - } - - /// # Safety: - /// - /// The caller must ensure that `head_wrap` and `tail_wrap` have been wrapped properly, in other - /// words, this ensures that `head_wrap` and `tail_wrap` are < `self.perf_aux_buffer_size`. - #[inline] - #[must_use] - unsafe fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> Box<[u8]> { - // this function is unsafe, but let's make it safe when compiling in debug mode - debug_assert!(head_wrap < self.perf_aux_buffer_size as u64); - debug_assert!(tail_wrap < self.perf_aux_buffer_size as u64); - - // SAFETY: tail_wrap is guaranteed to be < `self.perf_aux_buffer_size` from the fn safety - // preconditions - let first_ptr = unsafe { self.perf_aux_buffer.add(tail_wrap as usize) }.cast::(); - let first_len = self.perf_aux_buffer_size - tail_wrap as usize; - - let second_ptr = self.perf_aux_buffer.cast::(); - let second_len = head_wrap as usize; - - let mut data = Box::<[u8]>::new_uninit_slice(first_len + second_len); - unsafe { - ptr::copy_nonoverlapping(first_ptr, data.as_mut_ptr().cast(), first_len); - ptr::copy_nonoverlapping( - second_ptr, - data.as_mut_ptr().add(first_len).cast(), - second_len, - ); - data.assume_init() - } - } - - #[inline] - fn decode_blocks( - decoder: &mut BlockDecoder, - status: &mut Status, - previous_block_end_ip: &mut u64, - skip: u64, - map_ptr: *mut T, - map_len: usize, - ) -> Result<(), Error> - where - T: SaturatingAdd + From + Debug, - { - 'block: loop { - while status.event_pending() { - match decoder.event() { - Ok((_, s)) => { - *status = s; - } - Err(e) => { - log::trace!("PT error in event {e:?}"); - break 'block; - } - }; - } - - match decoder.decode_next() { - Ok((b, s)) => { - *status = s; - let offset = decoder.offset().map_err(error_from_pt_error)?; - - if b.ninsn() > 0 && skip < offset { - let id = hash_64_fast(*previous_block_end_ip) ^ hash_64_fast(b.ip()); - // SAFETY: the index is < map_len since the modulo operation is applied - unsafe { - let map_loc = map_ptr.add(id as usize % map_len); - *map_loc = (*map_loc).saturating_add(&1u8.into()); - } - *previous_block_end_ip = b.end_ip(); - } - - if status.eos() { - break 'block; - } - } - Err(e) => { - if e.code() != PtErrorCode::Eos { - let offset = decoder.offset().map_err(error_from_pt_error)?; - log::trace!( - "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", - previous_block_end_ip - ); - } - break 'block; - } - } - } - Ok(()) - } - - /// Get the raw trace used in the last decoding - #[cfg(feature = "export_raw")] - pub fn last_decode_trace(&self) -> Vec { - self.last_decode_trace.clone() - } - - /// Dump the raw trace used in the last decoding to the file - /// /// `./traces/trace_` - #[cfg(feature = "export_raw")] - pub fn dump_last_trace_to_file(&self) -> Result<(), Error> { - use std::{fs, io::Write, path::Path, time}; - - let traces_dir = Path::new("traces"); - fs::create_dir_all(traces_dir)?; - let timestamp = time::SystemTime::now() - .duration_since(time::UNIX_EPOCH) - .map_err(|e| Error::unknown(e.to_string()))? - .as_micros(); - let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?; - file.write_all(&self.last_decode_trace())?; - Ok(()) - } -} - #[cfg(target_os = "linux")] -impl Drop for IntelPT { - fn drop(&mut self) { - unsafe { - let ret = libc::munmap(self.perf_aux_buffer, self.perf_aux_buffer_size); - assert_eq!(ret, 0, "Intel PT: Failed to unmap perf aux buffer"); - let ret = libc::munmap(self.perf_buffer, self.perf_buffer_size); - assert_eq!(ret, 0, "Intel PT: Failed to unmap perf buffer"); - } - } -} - -/// Builder for [`IntelPT`] +mod linux; #[cfg(target_os = "linux")] -#[derive(Debug, Clone, PartialEq)] -pub struct IntelPTBuilder { - pid: Option, - cpu: i32, - exclude_kernel: bool, - exclude_hv: bool, - inherit: bool, - perf_buffer_size: usize, - perf_aux_buffer_size: usize, - ip_filters: Vec>, -} - -#[cfg(target_os = "linux")] -impl Default for IntelPTBuilder { - /// Create a default builder for [`IntelPT`] - /// - /// The default configuration corresponds to: - /// ```rust - /// use libafl_intelpt::{IntelPTBuilder, PAGE_SIZE}; - /// let builder = IntelPTBuilder::default() - /// .pid(None) - /// .all_cpus() - /// .exclude_kernel(true) - /// .exclude_hv(true) - /// .inherit(false) - /// .perf_buffer_size(128 * PAGE_SIZE + PAGE_SIZE).unwrap() - /// .perf_aux_buffer_size(2 * 1024 * 1024).unwrap() - /// .ip_filters(&[]); - /// assert_eq!(builder, IntelPTBuilder::default()); - /// ``` - fn default() -> Self { - Self { - pid: None, - cpu: -1, - exclude_kernel: true, - exclude_hv: true, - inherit: false, - perf_buffer_size: 128 * PAGE_SIZE + PAGE_SIZE, - perf_aux_buffer_size: 2 * 1024 * 1024, - ip_filters: Vec::new(), - } - } -} - -#[cfg(target_os = "linux")] -impl IntelPTBuilder { - /// Build the [`IntelPT`] struct - pub fn build(&self) -> Result { - self.check_config(); - let mut perf_event_attr = new_perf_event_attr_intel_pt()?; - perf_event_attr.set_exclude_kernel(self.exclude_kernel.into()); - perf_event_attr.set_exclude_hv(self.exclude_hv.into()); - perf_event_attr.set_inherit(self.inherit.into()); - - // SAFETY: perf_event_attr is properly initialized - let fd = match unsafe { - perf_event_open( - ptr::from_mut(&mut perf_event_attr), - self.pid.unwrap_or(0), - self.cpu, - -1, - PERF_FLAG_FD_CLOEXEC.into(), - ) - } { - -1 => { - let availability = match availability() { - Ok(()) => String::new(), - Err(reasons) => format!(" Possible reasons: {reasons}"), - }; - return Err(Error::last_os_error(format!( - "Failed to open Intel PT perf event.{availability}" - ))); - } - fd => { - // SAFETY: On success, perf_event_open() returns a new file descriptor. - // On error, -1 is returned, and it is checked above - unsafe { OwnedFd::from_raw_fd(fd) } - } - }; - - let perf_buffer = setup_perf_buffer(&fd, self.perf_buffer_size)?; - - // the first perf_buff page is a metadata page - let buff_metadata = perf_buffer.cast::(); - let aux_offset = unsafe { &raw mut (*buff_metadata).aux_offset }; - let aux_size = unsafe { &raw mut (*buff_metadata).aux_size }; - let data_offset = unsafe { &raw mut (*buff_metadata).data_offset }; - let data_size = unsafe { &raw mut (*buff_metadata).data_size }; - - unsafe { - aux_offset.write_volatile(next_page_aligned_addr( - data_offset.read_volatile() + data_size.read_volatile(), - )); - aux_size.write_volatile(self.perf_aux_buffer_size as u64); - } - - let perf_aux_buffer = unsafe { - setup_perf_aux_buffer(&fd, aux_size.read_volatile(), aux_offset.read_volatile())? - }; +pub use linux::*; - let aux_head = unsafe { &raw mut (*buff_metadata).aux_head }; - let aux_tail = unsafe { &raw mut (*buff_metadata).aux_tail }; - - let mut decoder_builder = EncoderDecoderBuilder::new() - .set_end_on_call(true) - .set_end_on_jump(true); - if let Some(cpu) = current_cpu() { - decoder_builder = decoder_builder.cpu(cpu); - } - - let mut intel_pt = IntelPT { - fd, - perf_buffer, - perf_aux_buffer, - perf_buffer_size: self.perf_buffer_size, - perf_aux_buffer_size: self.perf_aux_buffer_size, - aux_head, - aux_tail, - previous_decode_head: 0, - ip_filters: Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize), - decoder_builder, - #[cfg(feature = "export_raw")] - last_decode_trace: Vec::new(), - }; - if !self.ip_filters.is_empty() { - intel_pt.set_ip_filters(&self.ip_filters)?; - } - Ok(intel_pt) - } - - /// Warn if the configuration is not recommended - #[inline] - fn check_config(&self) { - if self.inherit && self.cpu == -1 { - log::warn!( - "IntelPT set up on all CPUs with process inheritance enabled. This configuration \ - is not recommended and might not work as expected" - ); - } - } - - #[must_use] - /// Set the process to be traced via its `PID`. Set to `None` to trace the current process. - pub fn pid(mut self, pid: Option) -> Self { - self.pid = pid; - self - } - - #[must_use] - /// Set the CPU to be traced - /// - /// # Panics - /// - /// The function will panic if `cpu` is greater than `i32::MAX` - pub fn cpu(mut self, cpu: usize) -> Self { - self.cpu = cpu.try_into().unwrap(); - self - } - - #[must_use] - /// Trace all the CPUs - pub fn all_cpus(mut self) -> Self { - self.cpu = -1; - self - } - - #[must_use] - /// Do not trace kernel code - pub fn exclude_kernel(mut self, exclude_kernel: bool) -> Self { - self.exclude_kernel = exclude_kernel; - self - } - - #[must_use] - /// Do not trace Hypervisor code - pub fn exclude_hv(mut self, exclude_hv: bool) -> Self { - self.exclude_hv = exclude_hv; - self - } - - #[must_use] - /// Child processes are traced - pub fn inherit(mut self, inherit: bool) -> Self { - self.inherit = inherit; - self - } - - /// Set the size of the perf buffer - pub fn perf_buffer_size(mut self, perf_buffer_size: usize) -> Result { - let err = Err(Error::illegal_argument( - "IntelPT perf_buffer_size should be 1+2^n pages", - )); - if perf_buffer_size < PAGE_SIZE { - return err; - } - let (q, r) = (perf_buffer_size - PAGE_SIZE).div_rem_euclid(&PAGE_SIZE); - if !q.is_power_of_two() || r != 0 { - return err; - } - - self.perf_buffer_size = perf_buffer_size; - Ok(self) - } - - /// Set the size of the perf aux buffer (actual PT traces buffer) - pub fn perf_aux_buffer_size(mut self, perf_aux_buffer_size: usize) -> Result { - // todo:replace with is_multiple_of once stable - if perf_aux_buffer_size % PAGE_SIZE != 0 { - return Err(Error::illegal_argument( - "IntelPT perf_aux_buffer must be page aligned", - )); - } - if !perf_aux_buffer_size.is_power_of_two() { - return Err(Error::illegal_argument( - "IntelPT perf_aux_buffer must be a power of two", - )); - } - - self.perf_aux_buffer_size = perf_aux_buffer_size; - Ok(self) - } - - #[must_use] - /// Set filters based on Instruction Pointer (IP) - /// - /// Only instructions in `filters` ranges will be traced. - pub fn ip_filters(mut self, filters: &[RangeInclusive]) -> Self { - self.ip_filters = filters.to_vec(); - self - } -} - -/// Perf event config for `IntelPT` -/// -/// (This is almost mapped to `IA32_RTIT_CTL MSR` by perf) -#[cfg(target_os = "linux")] -#[bitfield(u64, default = 0)] -struct PtConfig { - /// Disable call return address compression. AKA DisRETC in Intel SDM. - #[bit(11, rw)] - noretcomp: bool, - /// Indicates the frequency of PSB packets. AKA PSBFreq in Intel SDM. - #[bits(24..=27, rw)] - psb_period: u4, -} - -/// Number of address filters available on the running CPU -#[cfg(target_os = "linux")] -pub fn nr_addr_filters() -> Result { - NR_ADDR_FILTERS.clone() -} +/// Size of a memory page +pub const PAGE_SIZE: usize = 4096; /// Check if Intel PT is available on the current system. /// @@ -854,200 +110,8 @@ pub fn availability_in_qemu_kvm() -> Result<(), String> { } } -/// Convert [`PtError`] into [`Error`] -#[cfg(target_os = "linux")] -#[inline] -#[must_use] -pub fn error_from_pt_error(err: PtError) -> Error { - Error::unknown(err.to_string()) -} - -#[cfg(target_os = "linux")] -fn availability_in_linux() -> Result<(), String> { - let mut reasons = Vec::new(); - match linux_version() { - // https://docs.rs/perf-event-open-sys/4.0.0/perf_event_open_sys/#kernel-versions - Ok(ver) if ver >= (5, 19, 4) => {} - Ok((major, minor, patch)) => reasons.push(format!( - "Kernel version {major}.{minor}.{patch} is older than 5.19.4 and might not work." - )), - Err(()) => reasons.push("Failed to retrieve kernel version".to_owned()), - } - - if let Err(e) = &*PERF_EVENT_TYPE { - reasons.push(e.clone()); - } - - if let Err(e) = &*NR_ADDR_FILTERS { - reasons.push(e.clone()); - } - - // official way of knowing if perf_event_open() support is enabled - // https://man7.org/linux/man-pages/man2/perf_event_open.2.html - let perf_event_support_path = "/proc/sys/kernel/perf_event_paranoid"; - if !Path::new(perf_event_support_path).exists() { - reasons.push(format!( - "perf_event_open() support is not enabled: {perf_event_support_path} not found" - )); - } - - // TODO check also the value of perf_event_paranoid, check which values are required by pt - // https://www.kernel.org/doc/Documentation/sysctl/kernel.txt - // also, looks like it is distribution dependent - // https://askubuntu.com/questions/1400874/what-does-perf-paranoia-level-four-do - // CAP_SYS_ADMIN might make this check useless - - match caps::read(None, CapSet::Permitted) { - Ok(current_capabilities) => { - let required_caps = [ - Capability::CAP_IPC_LOCK, - Capability::CAP_SYS_PTRACE, - Capability::CAP_SYS_ADMIN, // TODO: CAP_PERFMON doesn't look to be enough!? - Capability::CAP_SYSLOG, - ]; - - for rc in required_caps { - if !current_capabilities.contains(&rc) { - reasons.push(format!("Required capability {rc} missing")); - } - } - } - Err(e) => reasons.push(format!("Failed to read linux capabilities: {e}")), - }; - - if reasons.is_empty() { - Ok(()) - } else { - Err(reasons.join("; ")) - } -} - -#[cfg(target_os = "linux")] -fn new_perf_event_attr_intel_pt() -> Result { - let type_ = match &*PERF_EVENT_TYPE { - Ok(t) => Ok(*t), - Err(e) => Err(Error::unsupported(e.clone())), - }?; - let config = PtConfig::builder() - .with_noretcomp(true) - .with_psb_period(u4::new(0)) - .build() - .raw_value; - - let mut attr = perf_event_attr { - size: size_of::() as u32, - type_, - config, - ..Default::default() - }; - - // Do not enable tracing as soon as the perf_event_open syscall is issued - attr.set_disabled(true.into()); - - Ok(attr) -} - -#[cfg(target_os = "linux")] -fn setup_perf_buffer(fd: &OwnedFd, perf_buffer_size: usize) -> Result<*mut c_void, Error> { - match unsafe { - libc::mmap( - ptr::null_mut(), - perf_buffer_size, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED, - fd.as_raw_fd(), - 0, - ) - } { - libc::MAP_FAILED => Err(Error::last_os_error("IntelPT: Failed to mmap perf buffer")), - mmap_addr => Ok(mmap_addr), - } -} - -#[cfg(target_os = "linux")] -fn setup_perf_aux_buffer(fd: &OwnedFd, size: u64, offset: u64) -> Result<*mut c_void, Error> { - match unsafe { - libc::mmap( - ptr::null_mut(), - size as usize, - // PROT_WRITE sets PT to stop when the buffer is full - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED, - fd.as_raw_fd(), - i64::try_from(offset)?, - ) - } { - libc::MAP_FAILED => Err(Error::last_os_error( - "IntelPT: Failed to mmap perf aux buffer", - )), - mmap_addr => Ok(mmap_addr), - } -} - -#[cfg(target_os = "linux")] -fn linux_version() -> Result<(usize, usize, usize), ()> { - let mut uname_data = libc::utsname { - sysname: [0; 65], - nodename: [0; 65], - release: [0; 65], - version: [0; 65], - machine: [0; 65], - domainname: [0; 65], - }; - - if unsafe { libc::uname(&mut uname_data) } != 0 { - return Err(()); - } - - let release = unsafe { CStr::from_ptr(uname_data.release.as_ptr()) }; - let mut parts = release - .to_bytes() - .split(|&c| c == b'.' || c == b'-') - .take(3) - .map(|s| String::from_utf8_lossy(s).parse::()); - if let (Some(Ok(major)), Some(Ok(minor)), Some(Ok(patch))) = - (parts.next(), parts.next(), parts.next()) - { - Ok((major, minor, patch)) - } else { - Err(()) - } -} - -#[cfg(target_os = "linux")] -#[inline] -const fn next_page_aligned_addr(address: u64) -> u64 { - (address + PAGE_SIZE as u64 - 1) & !(PAGE_SIZE as u64 - 1) -} - -#[cfg(target_os = "linux")] -#[inline] -fn smp_rmb() { - // SAFETY: just a memory barrier - unsafe { - core::arch::asm!("lfence", options(nostack, preserves_flags)); - } -} - -#[cfg(target_os = "linux")] -#[inline] -const fn wrap_aux_pointer(ptr: u64, perf_aux_buffer_size: usize) -> u64 { - ptr & (perf_aux_buffer_size as u64 - 1) -} - -#[cfg(target_os = "linux")] -#[inline] -fn current_cpu() -> Option { - let cpuid = CpuId::new(); - cpuid - .get_feature_info() - .map(|fi| Cpu::intel(fi.family_id().into(), fi.model_id(), fi.stepping_id())) -} - #[cfg(test)] mod test { - #[cfg(target_os = "linux")] - use arbitrary_int::Number; use static_assertions::assert_eq_size; use super::*; @@ -1072,65 +136,4 @@ mod test { Err(e) => println!("❌\tReasons: {e}"), } } - - #[test] - #[cfg(target_os = "linux")] - fn intel_pt_builder_default_values_are_valid() { - let default = IntelPT::builder(); - IntelPT::builder() - .perf_buffer_size(default.perf_buffer_size) - .unwrap(); - IntelPT::builder() - .perf_aux_buffer_size(default.perf_aux_buffer_size) - .unwrap(); - } - - #[test] - #[cfg(target_os = "linux")] - fn intel_pt_pt_config_noretcomp_format() { - let ptconfig_noretcomp = PtConfig::DEFAULT.with_noretcomp(true).raw_value; - let path = format!("{PT_EVENT_PATH}/format/noretcomp"); - let s = fs::read_to_string(&path).expect("Failed to read Intel PT config noretcomp format"); - assert!( - s.starts_with("config:"), - "Unexpected Intel PT config noretcomp format" - ); - let bit = s["config:".len()..] - .trim() - .parse::() - .expect("Failed to parse Intel PT config noretcomp format"); - assert_eq!( - ptconfig_noretcomp, - 0b1 << bit, - "Unexpected Intel PT config noretcomp format" - ); - } - - #[test] - #[cfg(target_os = "linux")] - fn intel_pt_pt_config_psb_period_format() { - let ptconfig_psb_period = PtConfig::DEFAULT.with_psb_period(u4::MAX).raw_value; - let path = format!("{PT_EVENT_PATH}/format/psb_period"); - let s = - fs::read_to_string(&path).expect("Failed to read Intel PT config psb_period format"); - assert!( - s.starts_with("config:"), - "Unexpected Intel PT config psb_period format" - ); - let from = s["config:".len().."config:".len() + 2] - .parse::() - .expect("Failed to parse Intel PT config psb_period format"); - let to = s["config:".len() + 3..] - .trim() - .parse::() - .expect("Failed to parse Intel PT config psb_period format"); - let mut format = 0; - for bit in from..=to { - format |= 0b1 << bit; - } - assert_eq!( - ptconfig_psb_period, format, - "Unexpected Intel PT config psb_period format" - ); - } } diff --git a/libafl_intelpt/src/linux.rs b/libafl_intelpt/src/linux.rs new file mode 100644 index 00000000000..02f9eb70769 --- /dev/null +++ b/libafl_intelpt/src/linux.rs @@ -0,0 +1,980 @@ +use std::{ + borrow::ToOwned, + boxed::Box, + ffi::{CStr, CString}, + fmt::Debug, + format, fs, + ops::RangeInclusive, + os::{ + fd::{AsRawFd, FromRawFd, OwnedFd}, + raw::c_void, + }, + path::Path, + ptr, + string::{String, ToString}, + sync::LazyLock, + vec::Vec, +}; + +use arbitrary_int::u4; +use bitbybit::bitfield; +use caps::{CapSet, Capability}; +use libafl_bolts::{hash_64_fast, Error}; +pub use libipt::{ + asid::Asid, + image::{Image, SectionCache, SectionInfo}, + status::Status, +}; +use libipt::{ + block::BlockDecoder, + enc_dec_builder::{ + AddrFilterRange, AddrFilterType, AddrFilters, AddrFiltersBuilder, Cpu, + EncoderDecoderBuilder, + }, + error::{PtError, PtErrorCode}, +}; +use num_enum::TryFromPrimitive; +use num_traits::{Euclid, SaturatingAdd}; +use perf_event_open_sys::{ + bindings::{perf_event_attr, perf_event_mmap_page, PERF_FLAG_FD_CLOEXEC}, + ioctls::{DISABLE, ENABLE, SET_FILTER}, + perf_event_open, +}; +use raw_cpuid::CpuId; + +use super::{availability, PAGE_SIZE}; + +const PT_EVENT_PATH: &str = "/sys/bus/event_source/devices/intel_pt"; + +static NR_ADDR_FILTERS: LazyLock> = LazyLock::new(|| { + // This info is available in two different files, use the second path as fail-over + let path = format!("{PT_EVENT_PATH}/nr_addr_filters"); + let path2 = format!("{PT_EVENT_PATH}/caps/num_address_ranges"); + let err = format!("Failed to read Intel PT number of address filters from {path} and {path2}"); + + let s = fs::read_to_string(&path); + if let Ok(s) = s { + let n = s.trim().parse::(); + if let Ok(n) = n { + return Ok(n); + } + } + + let s2 = fs::read_to_string(&path2).map_err(|_| err.clone())?; + s2.trim().parse::().map_err(|_| err) +}); + +static PERF_EVENT_TYPE: LazyLock> = LazyLock::new(|| { + let path = format!("{PT_EVENT_PATH}/type"); + let s = fs::read_to_string(&path) + .map_err(|_| format!("Failed to read Intel PT perf event type from {path}"))?; + s.trim() + .parse::() + .map_err(|_| format!("Failed to parse Intel PT perf event type in {path}")) +}); + +/// Intel PT mode of operation with KVM +/// +/// Check out +/// for more details +#[derive(TryFromPrimitive, Debug)] +#[repr(i32)] +pub enum KvmPTMode { + /// trace both host/guest and output to host buffer + System = 0, + /// trace host and guest simultaneously and output to their respective buffer + HostGuest = 1, +} + +/// Intel Processor Trace (PT) +#[derive(Debug)] +pub struct IntelPT { + fd: OwnedFd, + perf_buffer: *mut c_void, + perf_aux_buffer: *mut c_void, + perf_buffer_size: usize, + perf_aux_buffer_size: usize, + aux_head: *mut u64, + aux_tail: *mut u64, + previous_decode_head: u64, + ip_filters: Vec>, + // The lifetime of BlockDecoder<'a> is irrelevant during the building phase. + decoder_builder: EncoderDecoderBuilder>, + #[cfg(feature = "export_raw")] + last_decode_trace: Vec, +} + +impl IntelPT { + /// Create a default builder + /// + /// Checkout [`IntelPTBuilder::default()`] for more details + #[must_use] + pub fn builder() -> IntelPTBuilder { + IntelPTBuilder::default() + } + + /// Set filters based on Instruction Pointer (IP) + /// + /// Only instructions in `filters` ranges will be traced. + pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { + let str_filter = filters + .iter() + .map(|filter| { + let size = filter.end() - filter.start(); + format!("filter {:#016x}/{:#016x} ", filter.start(), size) + }) + .reduce(|acc, s| acc + &s) + .unwrap_or_default(); + + // SAFETY: CString::from_vec_unchecked is safe because no null bytes are added to str_filter + let c_str_filter = unsafe { CString::from_vec_unchecked(str_filter.into_bytes()) }; + match unsafe { SET_FILTER(self.fd.as_raw_fd(), c_str_filter.into_raw()) } { + -1 => { + let availability = match availability() { + Ok(()) => String::new(), + Err(reasons) => format!(" Possible reasons: {reasons}"), + }; + Err(Error::last_os_error(format!( + "Failed to set IP filters.{availability}" + ))) + } + 0 => { + self.ip_filters = filters.to_vec(); + Ok(()) + } + ret => Err(Error::unsupported(format!( + "Failed to set IP filter, ioctl returned unexpected value {ret}" + ))), + } + } + + /// Get the current IP filters configuration + #[must_use] + pub fn ip_filters(&self) -> Vec> { + self.ip_filters.clone() + } + + fn ip_filters_to_addr_filter(&self) -> AddrFilters { + let mut builder = AddrFiltersBuilder::new(); + let mut iter = self.ip_filters.iter().map(|f| { + AddrFilterRange::new(*f.start() as u64, *f.end() as u64, AddrFilterType::FILTER) + }); + if let Some(f) = iter.next() { + builder.addr0(f); + if let Some(f) = iter.next() { + builder.addr1(f); + if let Some(f) = iter.next() { + builder.addr2(f); + if let Some(f) = iter.next() { + builder.addr3(f); + } + } + } + } + builder.build() + } + + /// Start tracing + /// + /// Be aware that the tracing is not started on [`IntelPT`] construction. + pub fn enable_tracing(&mut self) -> Result<(), Error> { + match unsafe { ENABLE(self.fd.as_raw_fd(), 0) } { + -1 => { + let availability = match availability() { + Ok(()) => String::new(), + Err(reasons) => format!(" Possible reasons: {reasons}"), + }; + Err(Error::last_os_error(format!( + "Failed to enable tracing.{availability}" + ))) + } + 0 => Ok(()), + ret => Err(Error::unsupported(format!( + "Failed to enable tracing, ioctl returned unexpected value {ret}" + ))), + } + } + + /// Stop tracing + /// + /// This doesn't drop [`IntelPT`], the configuration will be preserved. + pub fn disable_tracing(&mut self) -> Result<(), Error> { + match unsafe { DISABLE(self.fd.as_raw_fd(), 0) } { + -1 => Err(Error::last_os_error("Failed to disable tracing")), + 0 => Ok(()), + ret => Err(Error::unsupported(format!( + "Failed to disable tracing, ioctl returned unexpected value {ret}" + ))), + } + } + + // /// Fill the coverage map by decoding the PT traces and reading target memory through `read_mem` + // /// + // /// This function consumes the traces. + // /// + // /// # Example + // /// + // /// An example `read_mem` callback function for the (inprocess) `intel_pt_babyfuzzer` could be: + // /// ``` + // /// let read_mem = |buf: &mut [u8], addr: u64| { + // /// let src = addr as *const u8; + // /// let dst = buf.as_mut_ptr(); + // /// let size = buf.len(); + // /// unsafe { + // /// core::ptr::copy_nonoverlapping(src, dst, size); + // /// } + // /// }; + // /// ``` + // #[allow(clippy::cast_possible_wrap)] + // pub fn decode_with_callback(&mut self, read_memory: F, map: &mut [T]) -> Result<(), Error> + // where + // F: Fn(&mut [u8], u64), + // T: SaturatingAdd + From + Debug, + // { + // self.decode_traces_into_map_common( + // None, + // Some(|buff: &mut [u8], addr: u64, _: Asid| { + // debug_assert!(i32::try_from(buff.len()).is_ok()); + // read_memory(buff, addr); + // buff.len() as i32 + // }), + // map, + // ) + // } + + /// Fill the coverage map by decoding the PT traces + /// + /// This function consumes the traces. + pub fn decode_traces_into_map( + &mut self, + image: &mut Image, + map_ptr: *mut T, + map_len: usize, + ) -> Result<(), Error> + where + T: SaturatingAdd + From + Debug, + { + self.decode_traces_into_map_common( + Some(image), + None:: i32>, + map_ptr, + map_len, + ) + } + + fn decode_traces_into_map_common( + &mut self, + image: Option<&mut Image>, + read_memory: Option, + map_ptr: *mut T, + map_len: usize, + ) -> Result<(), Error> + where + F: Fn(&mut [u8], u64, Asid) -> i32, + T: SaturatingAdd + From + Debug, + { + let head = unsafe { self.aux_head.read_volatile() }; + let tail = unsafe { self.aux_tail.read_volatile() }; + if head < tail { + return Err(Error::unknown( + "Intel PT: aux buffer head is behind aux tail.", + )); + }; + if self.previous_decode_head < tail { + return Err(Error::unknown( + "Intel PT: aux previous head is behind aux tail.", + )); + }; + let len = (head - tail) as usize; + if len >= self.perf_aux_buffer_size { + log::warn!( + "The fuzzer run filled the entire PT buffer. Consider increasing the aux buffer \ + size or refining the IP filters." + ); + } + let skip = self.previous_decode_head - tail; + + let head_wrap = wrap_aux_pointer(head, self.perf_aux_buffer_size); + let tail_wrap = wrap_aux_pointer(tail, self.perf_aux_buffer_size); + + // after reading the data_head value, user space should issue an rmb() + // https://manpages.debian.org/bookworm/manpages-dev/perf_event_open.2.en.html#data_head + smp_rmb(); + + let (data_ptr, _owned_data) = unsafe { + if head_wrap >= tail_wrap { + let ptr = self.perf_aux_buffer.add(tail_wrap as usize).cast::(); + (ptr, None) + } else { + // Head pointer wrapped, the trace is split + let mut owned_data = self.join_split_trace(head_wrap, tail_wrap); + (owned_data.as_mut_ptr(), Some(owned_data)) + } + }; + #[cfg(feature = "export_raw")] + { + self.last_decode_trace = Vec::with_capacity(len); + unsafe { + ptr::copy_nonoverlapping(data_ptr, self.last_decode_trace.as_mut_ptr(), len); + self.last_decode_trace.set_len(len); + } + } + let builder = unsafe { self.decoder_builder.clone().buffer_from_raw(data_ptr, len) } + .filter(self.ip_filters_to_addr_filter()); + + let mut decoder = builder.build().map_err(error_from_pt_error)?; + decoder.set_image(image).map_err(error_from_pt_error)?; + if let Some(rm) = read_memory { + decoder.image().set_callback(Some(rm)); + } + + let mut previous_block_end_ip = 0; + let mut status; + 'sync: loop { + match decoder.sync_forward() { + Ok(s) => { + status = s; + Self::decode_blocks( + &mut decoder, + &mut status, + &mut previous_block_end_ip, + skip, + map_ptr, + map_len, + )?; + } + Err(e) => { + if e.code() != PtErrorCode::Eos { + log::trace!("PT error in sync forward {e:?}"); + } + break 'sync; + } + }; + } + + // Advance the trace pointer up to the latest sync point, otherwise next execution's trace + // might not contain a PSB packet. + decoder.sync_backward().map_err(error_from_pt_error)?; + let offset = decoder.sync_offset().map_err(error_from_pt_error)?; + unsafe { self.aux_tail.write_volatile(tail + offset) }; + self.previous_decode_head = head; + Ok(()) + } + + /// # Safety: + /// + /// The caller must ensure that `head_wrap` and `tail_wrap` have been wrapped properly, in other + /// words, this ensures that `head_wrap` and `tail_wrap` are < `self.perf_aux_buffer_size`. + #[inline] + #[must_use] + unsafe fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> Box<[u8]> { + // this function is unsafe, but let's make it safe when compiling in debug mode + debug_assert!(head_wrap < self.perf_aux_buffer_size as u64); + debug_assert!(tail_wrap < self.perf_aux_buffer_size as u64); + + // SAFETY: tail_wrap is guaranteed to be < `self.perf_aux_buffer_size` from the fn safety + // preconditions + let first_ptr = unsafe { self.perf_aux_buffer.add(tail_wrap as usize) }.cast::(); + let first_len = self.perf_aux_buffer_size - tail_wrap as usize; + + let second_ptr = self.perf_aux_buffer.cast::(); + let second_len = head_wrap as usize; + + let mut data = Box::<[u8]>::new_uninit_slice(first_len + second_len); + unsafe { + ptr::copy_nonoverlapping(first_ptr, data.as_mut_ptr().cast(), first_len); + ptr::copy_nonoverlapping( + second_ptr, + data.as_mut_ptr().add(first_len).cast(), + second_len, + ); + data.assume_init() + } + } + + #[inline] + fn decode_blocks( + decoder: &mut BlockDecoder, + status: &mut Status, + previous_block_end_ip: &mut u64, + skip: u64, + map_ptr: *mut T, + map_len: usize, + ) -> Result<(), Error> + where + T: SaturatingAdd + From + Debug, + { + 'block: loop { + while status.event_pending() { + match decoder.event() { + Ok((_, s)) => { + *status = s; + } + Err(e) => { + log::trace!("PT error in event {e:?}"); + break 'block; + } + }; + } + + match decoder.decode_next() { + Ok((b, s)) => { + *status = s; + let offset = decoder.offset().map_err(error_from_pt_error)?; + + if b.ninsn() > 0 && skip < offset { + let id = hash_64_fast(*previous_block_end_ip) ^ hash_64_fast(b.ip()); + // SAFETY: the index is < map_len since the modulo operation is applied + unsafe { + let map_loc = map_ptr.add(id as usize % map_len); + *map_loc = (*map_loc).saturating_add(&1u8.into()); + } + *previous_block_end_ip = b.end_ip(); + } + + if status.eos() { + break 'block; + } + } + Err(e) => { + if e.code() != PtErrorCode::Eos { + let offset = decoder.offset().map_err(error_from_pt_error)?; + log::trace!( + "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", + previous_block_end_ip + ); + } + break 'block; + } + } + } + Ok(()) + } + + /// Get the raw trace used in the last decoding + #[cfg(feature = "export_raw")] + pub fn last_decode_trace(&self) -> Vec { + self.last_decode_trace.clone() + } + + /// Dump the raw trace used in the last decoding to the file + /// /// `./traces/trace_` + #[cfg(feature = "export_raw")] + pub fn dump_last_trace_to_file(&self) -> Result<(), Error> { + use std::{fs, io::Write, path::Path, time}; + + let traces_dir = Path::new("traces"); + fs::create_dir_all(traces_dir)?; + let timestamp = time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .map_err(|e| Error::unknown(e.to_string()))? + .as_micros(); + let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?; + file.write_all(&self.last_decode_trace())?; + Ok(()) + } +} + +impl Drop for IntelPT { + fn drop(&mut self) { + unsafe { + let ret = libc::munmap(self.perf_aux_buffer, self.perf_aux_buffer_size); + assert_eq!(ret, 0, "Intel PT: Failed to unmap perf aux buffer"); + let ret = libc::munmap(self.perf_buffer, self.perf_buffer_size); + assert_eq!(ret, 0, "Intel PT: Failed to unmap perf buffer"); + } + } +} + +/// Builder for [`IntelPT`] +#[derive(Debug, Clone, PartialEq)] +pub struct IntelPTBuilder { + pid: Option, + cpu: i32, + exclude_kernel: bool, + exclude_hv: bool, + inherit: bool, + perf_buffer_size: usize, + perf_aux_buffer_size: usize, + ip_filters: Vec>, +} + +impl Default for IntelPTBuilder { + /// Create a default builder for [`IntelPT`] + /// + /// The default configuration corresponds to: + /// ```rust + /// use libafl_intelpt::{IntelPTBuilder, PAGE_SIZE}; + /// let builder = IntelPTBuilder::default() + /// .pid(None) + /// .all_cpus() + /// .exclude_kernel(true) + /// .exclude_hv(true) + /// .inherit(false) + /// .perf_buffer_size(128 * PAGE_SIZE + PAGE_SIZE).unwrap() + /// .perf_aux_buffer_size(2 * 1024 * 1024).unwrap() + /// .ip_filters(&[]); + /// assert_eq!(builder, IntelPTBuilder::default()); + /// ``` + fn default() -> Self { + Self { + pid: None, + cpu: -1, + exclude_kernel: true, + exclude_hv: true, + inherit: false, + perf_buffer_size: 128 * PAGE_SIZE + PAGE_SIZE, + perf_aux_buffer_size: 2 * 1024 * 1024, + ip_filters: Vec::new(), + } + } +} + +impl IntelPTBuilder { + /// Build the [`IntelPT`] struct + pub fn build(&self) -> Result { + self.check_config(); + let mut perf_event_attr = new_perf_event_attr_intel_pt()?; + perf_event_attr.set_exclude_kernel(self.exclude_kernel.into()); + perf_event_attr.set_exclude_hv(self.exclude_hv.into()); + perf_event_attr.set_inherit(self.inherit.into()); + + // SAFETY: perf_event_attr is properly initialized + let fd = match unsafe { + perf_event_open( + ptr::from_mut(&mut perf_event_attr), + self.pid.unwrap_or(0), + self.cpu, + -1, + PERF_FLAG_FD_CLOEXEC.into(), + ) + } { + -1 => { + let availability = match availability() { + Ok(()) => String::new(), + Err(reasons) => format!(" Possible reasons: {reasons}"), + }; + return Err(Error::last_os_error(format!( + "Failed to open Intel PT perf event.{availability}" + ))); + } + fd => { + // SAFETY: On success, perf_event_open() returns a new file descriptor. + // On error, -1 is returned, and it is checked above + unsafe { OwnedFd::from_raw_fd(fd) } + } + }; + + let perf_buffer = setup_perf_buffer(&fd, self.perf_buffer_size)?; + + // the first perf_buff page is a metadata page + let buff_metadata = perf_buffer.cast::(); + let aux_offset = unsafe { &raw mut (*buff_metadata).aux_offset }; + let aux_size = unsafe { &raw mut (*buff_metadata).aux_size }; + let data_offset = unsafe { &raw mut (*buff_metadata).data_offset }; + let data_size = unsafe { &raw mut (*buff_metadata).data_size }; + + unsafe { + aux_offset.write_volatile(next_page_aligned_addr( + data_offset.read_volatile() + data_size.read_volatile(), + )); + aux_size.write_volatile(self.perf_aux_buffer_size as u64); + } + + let perf_aux_buffer = unsafe { + setup_perf_aux_buffer(&fd, aux_size.read_volatile(), aux_offset.read_volatile())? + }; + + let aux_head = unsafe { &raw mut (*buff_metadata).aux_head }; + let aux_tail = unsafe { &raw mut (*buff_metadata).aux_tail }; + + let mut decoder_builder = EncoderDecoderBuilder::new() + .set_end_on_call(true) + .set_end_on_jump(true); + if let Some(cpu) = current_cpu() { + decoder_builder = decoder_builder.cpu(cpu); + } + + let mut intel_pt = IntelPT { + fd, + perf_buffer, + perf_aux_buffer, + perf_buffer_size: self.perf_buffer_size, + perf_aux_buffer_size: self.perf_aux_buffer_size, + aux_head, + aux_tail, + previous_decode_head: 0, + ip_filters: Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize), + decoder_builder, + #[cfg(feature = "export_raw")] + last_decode_trace: Vec::new(), + }; + if !self.ip_filters.is_empty() { + intel_pt.set_ip_filters(&self.ip_filters)?; + } + Ok(intel_pt) + } + + /// Warn if the configuration is not recommended + #[inline] + fn check_config(&self) { + if self.inherit && self.cpu == -1 { + log::warn!( + "IntelPT set up on all CPUs with process inheritance enabled. This configuration \ + is not recommended and might not work as expected" + ); + } + } + + #[must_use] + /// Set the process to be traced via its `PID`. Set to `None` to trace the current process. + pub fn pid(mut self, pid: Option) -> Self { + self.pid = pid; + self + } + + #[must_use] + /// Set the CPU to be traced + /// + /// # Panics + /// + /// The function will panic if `cpu` is greater than `i32::MAX` + pub fn cpu(mut self, cpu: usize) -> Self { + self.cpu = cpu.try_into().unwrap(); + self + } + + #[must_use] + /// Trace all the CPUs + pub fn all_cpus(mut self) -> Self { + self.cpu = -1; + self + } + + #[must_use] + /// Do not trace kernel code + pub fn exclude_kernel(mut self, exclude_kernel: bool) -> Self { + self.exclude_kernel = exclude_kernel; + self + } + + #[must_use] + /// Do not trace Hypervisor code + pub fn exclude_hv(mut self, exclude_hv: bool) -> Self { + self.exclude_hv = exclude_hv; + self + } + + #[must_use] + /// Child processes are traced + pub fn inherit(mut self, inherit: bool) -> Self { + self.inherit = inherit; + self + } + + /// Set the size of the perf buffer + pub fn perf_buffer_size(mut self, perf_buffer_size: usize) -> Result { + let err = Err(Error::illegal_argument( + "IntelPT perf_buffer_size should be 1+2^n pages", + )); + if perf_buffer_size < PAGE_SIZE { + return err; + } + let (q, r) = (perf_buffer_size - PAGE_SIZE).div_rem_euclid(&PAGE_SIZE); + if !q.is_power_of_two() || r != 0 { + return err; + } + + self.perf_buffer_size = perf_buffer_size; + Ok(self) + } + + /// Set the size of the perf aux buffer (actual PT traces buffer) + pub fn perf_aux_buffer_size(mut self, perf_aux_buffer_size: usize) -> Result { + // todo:replace with is_multiple_of once stable + if perf_aux_buffer_size % PAGE_SIZE != 0 { + return Err(Error::illegal_argument( + "IntelPT perf_aux_buffer must be page aligned", + )); + } + if !perf_aux_buffer_size.is_power_of_two() { + return Err(Error::illegal_argument( + "IntelPT perf_aux_buffer must be a power of two", + )); + } + + self.perf_aux_buffer_size = perf_aux_buffer_size; + Ok(self) + } + + #[must_use] + /// Set filters based on Instruction Pointer (IP) + /// + /// Only instructions in `filters` ranges will be traced. + pub fn ip_filters(mut self, filters: &[RangeInclusive]) -> Self { + self.ip_filters = filters.to_vec(); + self + } +} + +/// Perf event config for `IntelPT` +/// +/// (This is almost mapped to `IA32_RTIT_CTL MSR` by perf) +#[bitfield(u64, default = 0)] +struct PtConfig { + /// Disable call return address compression. AKA DisRETC in Intel SDM. + #[bit(11, rw)] + noretcomp: bool, + /// Indicates the frequency of PSB packets. AKA PSBFreq in Intel SDM. + #[bits(24..=27, rw)] + psb_period: u4, +} + +/// Number of address filters available on the running CPU +pub fn nr_addr_filters() -> Result { + NR_ADDR_FILTERS.clone() +} + +/// Convert [`PtError`] into [`Error`] +#[inline] +#[must_use] +pub fn error_from_pt_error(err: PtError) -> Error { + Error::unknown(err.to_string()) +} + +pub(crate) fn availability_in_linux() -> Result<(), String> { + let mut reasons = Vec::new(); + match linux_version() { + // https://docs.rs/perf-event-open-sys/4.0.0/perf_event_open_sys/#kernel-versions + Ok(ver) if ver >= (5, 19, 4) => {} + Ok((major, minor, patch)) => reasons.push(format!( + "Kernel version {major}.{minor}.{patch} is older than 5.19.4 and might not work." + )), + Err(()) => reasons.push("Failed to retrieve kernel version".to_owned()), + } + + if let Err(e) = &*PERF_EVENT_TYPE { + reasons.push(e.clone()); + } + + if let Err(e) = &*NR_ADDR_FILTERS { + reasons.push(e.clone()); + } + + // official way of knowing if perf_event_open() support is enabled + // https://man7.org/linux/man-pages/man2/perf_event_open.2.html + let perf_event_support_path = "/proc/sys/kernel/perf_event_paranoid"; + if !Path::new(perf_event_support_path).exists() { + reasons.push(format!( + "perf_event_open() support is not enabled: {perf_event_support_path} not found" + )); + } + + // TODO check also the value of perf_event_paranoid, check which values are required by pt + // https://www.kernel.org/doc/Documentation/sysctl/kernel.txt + // also, looks like it is distribution dependent + // https://askubuntu.com/questions/1400874/what-does-perf-paranoia-level-four-do + // CAP_SYS_ADMIN might make this check useless + + match caps::read(None, CapSet::Permitted) { + Ok(current_capabilities) => { + let required_caps = [ + Capability::CAP_IPC_LOCK, + Capability::CAP_SYS_PTRACE, + Capability::CAP_SYS_ADMIN, // TODO: CAP_PERFMON doesn't look to be enough!? + Capability::CAP_SYSLOG, + ]; + + for rc in required_caps { + if !current_capabilities.contains(&rc) { + reasons.push(format!("Required capability {rc} missing")); + } + } + } + Err(e) => reasons.push(format!("Failed to read linux capabilities: {e}")), + }; + + if reasons.is_empty() { + Ok(()) + } else { + Err(reasons.join("; ")) + } +} + +fn new_perf_event_attr_intel_pt() -> Result { + let type_ = match &*PERF_EVENT_TYPE { + Ok(t) => Ok(*t), + Err(e) => Err(Error::unsupported(e.clone())), + }?; + let config = PtConfig::builder() + .with_noretcomp(true) + .with_psb_period(u4::new(0)) + .build() + .raw_value; + + let mut attr = perf_event_attr { + size: size_of::() as u32, + type_, + config, + ..Default::default() + }; + + // Do not enable tracing as soon as the perf_event_open syscall is issued + attr.set_disabled(true.into()); + + Ok(attr) +} + +fn setup_perf_buffer(fd: &OwnedFd, perf_buffer_size: usize) -> Result<*mut c_void, Error> { + match unsafe { + libc::mmap( + ptr::null_mut(), + perf_buffer_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED, + fd.as_raw_fd(), + 0, + ) + } { + libc::MAP_FAILED => Err(Error::last_os_error("IntelPT: Failed to mmap perf buffer")), + mmap_addr => Ok(mmap_addr), + } +} + +fn setup_perf_aux_buffer(fd: &OwnedFd, size: u64, offset: u64) -> Result<*mut c_void, Error> { + match unsafe { + libc::mmap( + ptr::null_mut(), + size as usize, + // PROT_WRITE sets PT to stop when the buffer is full + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED, + fd.as_raw_fd(), + i64::try_from(offset)?, + ) + } { + libc::MAP_FAILED => Err(Error::last_os_error( + "IntelPT: Failed to mmap perf aux buffer", + )), + mmap_addr => Ok(mmap_addr), + } +} + +fn linux_version() -> Result<(usize, usize, usize), ()> { + let mut uname_data = libc::utsname { + sysname: [0; 65], + nodename: [0; 65], + release: [0; 65], + version: [0; 65], + machine: [0; 65], + domainname: [0; 65], + }; + + if unsafe { libc::uname(&mut uname_data) } != 0 { + return Err(()); + } + + let release = unsafe { CStr::from_ptr(uname_data.release.as_ptr()) }; + let mut parts = release + .to_bytes() + .split(|&c| c == b'.' || c == b'-') + .take(3) + .map(|s| String::from_utf8_lossy(s).parse::()); + if let (Some(Ok(major)), Some(Ok(minor)), Some(Ok(patch))) = + (parts.next(), parts.next(), parts.next()) + { + Ok((major, minor, patch)) + } else { + Err(()) + } +} + +#[inline] +const fn next_page_aligned_addr(address: u64) -> u64 { + (address + PAGE_SIZE as u64 - 1) & !(PAGE_SIZE as u64 - 1) +} + +#[inline] +fn smp_rmb() { + // SAFETY: just a memory barrier + unsafe { + core::arch::asm!("lfence", options(nostack, preserves_flags)); + } +} + +#[inline] +const fn wrap_aux_pointer(ptr: u64, perf_aux_buffer_size: usize) -> u64 { + ptr & (perf_aux_buffer_size as u64 - 1) +} + +#[inline] +fn current_cpu() -> Option { + let cpuid = CpuId::new(); + cpuid + .get_feature_info() + .map(|fi| Cpu::intel(fi.family_id().into(), fi.model_id(), fi.stepping_id())) +} + +#[cfg(test)] +mod test { + use arbitrary_int::Number; + + use super::*; + #[test] + fn intel_pt_builder_default_values_are_valid() { + let default = IntelPT::builder(); + IntelPT::builder() + .perf_buffer_size(default.perf_buffer_size) + .unwrap(); + IntelPT::builder() + .perf_aux_buffer_size(default.perf_aux_buffer_size) + .unwrap(); + } + + #[test] + fn intel_pt_pt_config_noretcomp_format() { + let ptconfig_noretcomp = PtConfig::DEFAULT.with_noretcomp(true).raw_value; + let path = format!("{PT_EVENT_PATH}/format/noretcomp"); + let s = fs::read_to_string(&path).expect("Failed to read Intel PT config noretcomp format"); + assert!( + s.starts_with("config:"), + "Unexpected Intel PT config noretcomp format" + ); + let bit = s["config:".len()..] + .trim() + .parse::() + .expect("Failed to parse Intel PT config noretcomp format"); + assert_eq!( + ptconfig_noretcomp, + 0b1 << bit, + "Unexpected Intel PT config noretcomp format" + ); + } + + #[test] + fn intel_pt_pt_config_psb_period_format() { + let ptconfig_psb_period = PtConfig::DEFAULT.with_psb_period(u4::MAX).raw_value; + let path = format!("{PT_EVENT_PATH}/format/psb_period"); + let s = + fs::read_to_string(&path).expect("Failed to read Intel PT config psb_period format"); + assert!( + s.starts_with("config:"), + "Unexpected Intel PT config psb_period format" + ); + let from = s["config:".len().."config:".len() + 2] + .parse::() + .expect("Failed to parse Intel PT config psb_period format"); + let to = s["config:".len() + 3..] + .trim() + .parse::() + .expect("Failed to parse Intel PT config psb_period format"); + let mut format = 0; + for bit in from..=to { + format |= 0b1 << bit; + } + assert_eq!( + ptconfig_psb_period, format, + "Unexpected Intel PT config psb_period format" + ); + } +} From 23579be46ba50798557ff2718ec017717dff242b Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:31:02 +0100 Subject: [PATCH 81/93] Add qemu initrd config --- Cargo.toml | 2 +- libafl_qemu/src/qemu/config.rs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1aa35cd153d..19bd832f200 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ document-features = "0.2.10" fastbloom = { version = "0.8.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible libc = "0.2.159" # For (*nix) libc -libipt = { git = "https://github.com/Marcondiro/libipt-rs" } +libipt = "0.3.0" log = "0.4.22" meminterval = "0.4.1" mimalloc = { version = "0.1.43", default-features = false } diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index da782017d7c..2531939b592 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -174,6 +174,28 @@ impl> From for AppendKernelCmd { } } +#[cfg(feature = "systemmode")] +#[derive(Debug, Clone)] +pub struct InitRD { + path: PathBuf, +} + +#[cfg(feature = "systemmode")] +impl Display for InitRD { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "-initrd {}", self.path.to_str().unwrap()) + } +} + +#[cfg(feature = "systemmode")] +impl> From for InitRD { + fn from(path: R) -> Self { + Self { + path: path.as_ref().to_path_buf(), + } + } +} + #[derive(Debug, Clone)] pub struct LoadVM { path: PathBuf, @@ -413,6 +435,9 @@ pub struct QemuConfig { #[cfg(feature = "systemmode")] #[builder(default, setter(strip_option, into))] append_kernel_cmd: Option, + #[cfg(feature = "systemmode")] + #[builder(default, setter(strip_option, into))] + initrd: Option, #[cfg(feature = "usermode")] #[builder(setter(into))] program: Program, From 45708a8f9f3869ba1617dd3e760b915af71e0069 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:01:29 +0100 Subject: [PATCH 82/93] Add qemu monitor tcp --- libafl_qemu/src/qemu/config.rs | 76 ++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 2531939b592..8a56589cde4 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -90,20 +90,88 @@ impl Display for Drive { } } -#[derive(Debug, strum_macros::Display, Clone)] -#[strum(prefix = "-serial ", serialize_all = "lowercase")] +#[derive(Debug, Clone, TypedBuilder)] +pub struct Tcp { + #[builder(default, setter(strip_option))] + host: Option, + port: u16, + #[builder(default, setter(strip_option))] + server: Option, + #[builder(default, setter(strip_option))] + wait: Option, + #[builder(default, setter(strip_option))] + nodelay: Option, + #[builder(default, setter(strip_option))] + reconnect_ms: Option, +} + +impl Display for Tcp { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "tcp:{}", self.host.as_ref().unwrap_or(&"".to_owned()))?; + write!(f, ":{}", self.port)?; + let server = match self.server { + Some(true) => ",server=on", + Some(false) => ",server=off", + None => "", + }; + write!(f, "{server}")?; + let wait = match self.wait { + Some(true) => ",wait=on", + Some(false) => ",wait=off", + None => "", + }; + write!(f, "{wait}")?; + let nodelay = match self.nodelay { + Some(true) => ",nodelay=on", + Some(false) => ",nodelay=off", + None => "", + }; + write!(f, "{nodelay}")?; + if let Some(ms) = self.reconnect_ms { + write!(f, "{ms}")?; + } + Ok(()) + } +} + +#[derive(Debug, Clone)] pub enum Serial { None, Null, Stdio, + Tcp(Tcp), } -#[derive(Debug, strum_macros::Display, Clone)] -#[strum(prefix = "-monitor ", serialize_all = "lowercase")] +impl Display for Serial { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "-serial ")?; + match self { + Serial::None => write!(f, "none"), + Serial::Null => write!(f, "null"), + Serial::Stdio => write!(f, "stdio"), + Serial::Tcp(tcp) => write!(f, "{tcp}"), + } + } +} + +#[derive(Debug, Clone)] pub enum Monitor { None, Null, Stdio, + Tcp(Tcp), +} + +impl Display for Monitor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "-monitor ")?; + match self { + Monitor::None => write!(f, "none"), + Monitor::Null => write!(f, "null"), + Monitor::Stdio => write!(f, "stdio"), + Monitor::Tcp(tcp) => write!(f, "{tcp}"), + } + } } /// Set the directory for the BIOS, VGA BIOS and keymaps. From 4c5855db23a13dc6ae6b3f9161f0d726e26bde8c Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:25:38 +0100 Subject: [PATCH 83/93] Add not enough ip filters message --- libafl_intelpt/src/linux.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libafl_intelpt/src/linux.rs b/libafl_intelpt/src/linux.rs index 54342e80b25..61d7535ad6c 100644 --- a/libafl_intelpt/src/linux.rs +++ b/libafl_intelpt/src/linux.rs @@ -134,8 +134,18 @@ impl IntelPT { Ok(()) => String::new(), Err(reasons) => format!(" Possible reasons: {reasons}"), }; + let not_enough_filters = if filters.len() > nr_addr_filters().unwrap_or(0) as usize + { + format!( + " Not enough filters, trying to set {} filters while {} available.", + filters.len(), + nr_addr_filters().unwrap_or(0) + ) + } else { + String::new() + }; Err(Error::last_os_error(format!( - "Failed to set IP filters.{availability}" + "Failed to set IP filters.{not_enough_filters}{availability}" ))) } 0 => { From 2e21039cf74fe50397f6db62568df0b66c757550 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:50:09 +0100 Subject: [PATCH 84/93] Fix wrong must_use --- libafl_intelpt/src/linux.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libafl_intelpt/src/linux.rs b/libafl_intelpt/src/linux.rs index 61d7535ad6c..eeecfaa00ed 100644 --- a/libafl_intelpt/src/linux.rs +++ b/libafl_intelpt/src/linux.rs @@ -322,7 +322,6 @@ impl IntelPT { } }; #[cfg(feature = "export_raw")] - #[must_use] { self.last_decode_trace = Vec::with_capacity(len); unsafe { @@ -464,6 +463,7 @@ impl IntelPT { /// Get the raw trace used in the last decoding #[cfg(feature = "export_raw")] + #[must_use] pub fn last_decode_trace(&self) -> Vec { self.last_decode_trace.clone() } From c20183ea9e395cc40d761f58df7c702dd18e4611 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:17:51 +0100 Subject: [PATCH 85/93] Prevent possible infinite loop in block decoding in debug mode --- libafl_intelpt/src/linux.rs | 24 +++++++++++++++---- .../run_integration_tests_linux_with_caps.sh | 10 ++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/libafl_intelpt/src/linux.rs b/libafl_intelpt/src/linux.rs index eeecfaa00ed..5dd823e90d0 100644 --- a/libafl_intelpt/src/linux.rs +++ b/libafl_intelpt/src/linux.rs @@ -355,7 +355,7 @@ impl IntelPT { } Err(e) => { if e.code() != PtErrorCode::Eos { - log::trace!("PT error in sync forward {e:?}"); + log::info!("PT error in sync forward {e:?}"); } break 'sync; } @@ -414,14 +414,31 @@ impl IntelPT { where T: SaturatingAdd + From + Debug, { + #[cfg(debug_assertions)] + let mut trace_entry_iters: (u64, u64) = (0, 0); + 'block: loop { + let offset = decoder.offset().map_err(error_from_pt_error)?; + #[cfg(debug_assertions)] + { + if trace_entry_iters.0 == offset { + trace_entry_iters.1 += 1; + if trace_entry_iters.1 > 1000 { + log::warn!("Decoder got stuck at trace offset {offset:x}. Make sure the decoder Image has the right content and offsets."); + break 'block; + } + } else { + trace_entry_iters = (offset, 0); + } + } + while status.event_pending() { match decoder.event() { Ok((_, s)) => { *status = s; } Err(e) => { - log::trace!("PT error in event {e:?}"); + log::info!("PT error in event {e:?}"); break 'block; } } @@ -430,7 +447,6 @@ impl IntelPT { match decoder.decode_next() { Ok((b, s)) => { *status = s; - let offset = decoder.offset().map_err(error_from_pt_error)?; if b.ninsn() > 0 && skip < offset { let id = hash_64_fast(*previous_block_end_ip) ^ hash_64_fast(b.ip()); @@ -449,7 +465,7 @@ impl IntelPT { Err(e) => { if e.code() != PtErrorCode::Eos { let offset = decoder.offset().map_err(error_from_pt_error)?; - log::trace!( + log::info!( "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", previous_block_end_ip ); diff --git a/libafl_intelpt/tests/run_integration_tests_linux_with_caps.sh b/libafl_intelpt/tests/run_integration_tests_linux_with_caps.sh index 78a1f41bee2..86bfee83d06 100755 --- a/libafl_intelpt/tests/run_integration_tests_linux_with_caps.sh +++ b/libafl_intelpt/tests/run_integration_tests_linux_with_caps.sh @@ -9,3 +9,13 @@ for test_bin in ../target/debug/deps/integration_tests_linux-*; do done cargo test intel_pt_trace_fork -- --show-output + +cargo test --release intel_pt_trace_fork --no-run + +for test_bin in ../target/release/deps/integration_tests_linux-*; do + if file "$test_bin" | grep -q "ELF"; then + sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep "$test_bin" + fi +done + +cargo test --release intel_pt_trace_fork -- --show-output From 4f3e18ded029f977c6f9c6e27f94b4340648ac65 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:21:38 +0100 Subject: [PATCH 86/93] Clippy --- libafl_qemu/src/qemu/config.rs | 2 +- libafl_qemu/src/qemu/mod.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 8a56589cde4..1e27f37dc42 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -107,7 +107,7 @@ pub struct Tcp { impl Display for Tcp { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "tcp:{}", self.host.as_ref().unwrap_or(&"".to_owned()))?; + write!(f, "tcp:{}", self.host.as_deref().unwrap_or(""))?; write!(f, ":{}", self.port)?; let server = match self.server { Some(true) => ",server=on", diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index e8f9e8fc2bc..db0ef7d2330 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -11,7 +11,7 @@ use std::{ ffi::{c_void, CString}, fmt::{Display, Formatter, Write}, mem::{transmute, MaybeUninit}, - ops::Range, + ops::{Deref, Range}, pin::Pin, ptr::copy_nonoverlapping, sync::OnceLock, @@ -114,7 +114,8 @@ pub struct Qemu { #[derive(Clone, Debug)] pub enum QemuParams { - Config(QemuConfig), + // QemuConfig is quite big, at least 240 bytes so we use a Box + Config(Box), Cli(Vec), } @@ -182,7 +183,7 @@ impl Display for QemuExitReason { impl From for QemuParams { fn from(config: QemuConfig) -> Self { - QemuParams::Config(config) + QemuParams::Config(Box::new(config)) } } @@ -543,7 +544,7 @@ impl Qemu { match ¶ms { QemuParams::Config(cfg) => { QEMU_CONFIG - .set(cfg.clone()) + .set(cfg.deref().clone()) .map_err(|_| unreachable!("QEMU_CONFIG was already set but Qemu was not init!")) .expect("Could not set QEMU Config."); } From 064755e3ca0940372464336bee464f64dfc8592d Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:37:35 +0100 Subject: [PATCH 87/93] fix CI? --- fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index a5f62799224..fec85d9d891 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -1,6 +1,5 @@ [env] TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" -USER = { script = ["logname"] } [env.development] PROFILE_DIR = "debug" @@ -19,7 +18,7 @@ mkdir -p ${TARGET_DIR} script = """ sudo mkdir -p /mnt/libafl_qemu_tmpfs || echo "/mnt/libafl_qemu_tmpfs creation failed"; sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs || echo "tmpfs mount failed"; -sudo chown "${USER}:${USER}" "/mnt/libafl_qemu_tmpfs"; +sudo chown $(id -u):$(id -g) "/mnt/libafl_qemu_tmpfs"; """ [tasks.remove_ram_disk] From f23cf9352d67cf0b1115df2a990db4b07bd7996b Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:59:38 +0100 Subject: [PATCH 88/93] Bump qemu-libafl-bridge --- libafl_qemu/libafl_qemu_build/src/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 0262d1db5e9..a1af0f6c90c 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -11,7 +11,7 @@ use crate::cargo_add_rpath; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -pub const QEMU_REVISION: &str = "06c738f64a4a92d5fc8184c9b5a9fe9340f4a63f"; +pub const QEMU_REVISION: &str = "59ce9daf5456d08b3908ac4a1151f7cfd961aa5d"; pub struct BuildResult { pub qemu_path: PathBuf, From 1cad4d249e28fe614e96e2d022ea7f697835cf42 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:55:27 +0100 Subject: [PATCH 89/93] Remove ram disk after run --- .../qemu_intel_pt_bootloader/Makefile.toml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml index fec85d9d891..8e1bbc18a31 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml @@ -21,13 +21,6 @@ sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs || echo "tmpfs moun sudo chown $(id -u):$(id -g) "/mnt/libafl_qemu_tmpfs"; """ -[tasks.remove_ram_disk] -condition = { files_exist = ["/mnt/libafl_qemu_tmpfs"] } -script = """ -sudo umount /mnt/libafl_qemu_tmpfs; -sudo rm -r /mnt/libafl_qemu_tmpfs -""" - [tasks.build_target] dependencies = ["target_dir"] command = "nasm" @@ -60,8 +53,12 @@ script = "sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep ${ dependencies = ["build_fuzzer"] [tasks.run] -command = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" dependencies = ["build", "setcap", "convert_target_image"] +script = """ +${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME} +sudo umount /mnt/libafl_qemu_tmpfs; +sudo rm -r /mnt/libafl_qemu_tmpfs +""" [tasks.default] alias = "run" From f9ebb476d5c15477f6ac02e7e24ba2136e827171 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 5 Mar 2025 09:16:47 +0100 Subject: [PATCH 90/93] Post merge fix --- fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs | 3 +-- libafl_qemu/src/modules/systemmode/intel_pt.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs index 2ca98844c0e..eb64c7b8264 100644 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs @@ -72,7 +72,7 @@ fn main() { .accelerator(Accelerator::Kvm) //.snapshot(true) todo: doesnt work .default_devices(false) - .bios("/home/marco/code/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") + .bios("target/debug/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") .start_cpu(false) .build(); @@ -103,7 +103,6 @@ fn main() { qemu.set_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr) .unwrap(); - // todo: there is smth broken somewhere, QemuExitReason::Breakpoint reports a wrong address unsafe { match qemu.run() { Ok(QemuExitReason::Breakpoint(ba)) => { diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index 74a438f10ea..b6865ef5148 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -127,7 +127,7 @@ impl AddressFilter for IntelPTModule where T: Debug + 'static, { - fn register(&mut self, address_range: Range) { + fn register(&mut self, address_range: &Range) { let pt = self.pt.as_mut().unwrap(); let mut filters = pt.ip_filters(); let range_inclusive = From f34ca424e4818317949e47cc7c287edf62ca75ae Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 5 Mar 2025 09:45:15 +0100 Subject: [PATCH 91/93] Cleanup --- .../src/modules/systemmode/intel_pt.rs | 35 +++---------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/libafl_qemu/src/modules/systemmode/intel_pt.rs b/libafl_qemu/src/modules/systemmode/intel_pt.rs index b6865ef5148..c9b25c416ee 100644 --- a/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -43,9 +43,6 @@ where S: Unpin + HasMetadata, T: SaturatingAdd + From + Debug + 'static, { - // type ModuleAddressFilter = Self; - // type ModulePageFilter = NopPageFilter; - fn pre_qemu_init( &mut self, emulator_modules: &mut EmulatorModules, @@ -56,7 +53,7 @@ where emulator_modules .thread_creation(NewThreadHook::Function(intel_pt_new_thread::)) .unwrap(); - // TODO emulator_modules.thread_teradown + // fixme: consider implementing a clean emulator_modules.thread_teradown } fn pre_exec( @@ -86,17 +83,10 @@ where { let pt = self.pt.as_mut().expect("Intel PT module not initialized."); pt.disable_tracing().unwrap(); - // TODO handle self modifying code - - // TODO log errors or panic or smth - // let _ = pt.decode_with_callback( - // |addr, out_buff| { - // let _ = qemu.read_mem(out_buff, addr.into()); - // }, - // unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) }, - // ); - let _ = pt.decode_traces_into_map(&mut self.image, self.map_ptr, self.map_len); + let _ = pt + .decode_traces_into_map(&mut self.image, self.map_ptr, self.map_len) + .inspect_err(|e| log::warn!("Intel PT trace decode failed: {e}")); #[cfg(feature = "intel_pt_export_raw")] { @@ -105,22 +95,6 @@ where .inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}")); } } - - // fn address_filter(&self) -> &Self::ModuleAddressFilter { - // self - // } - // - // fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter { - // self - // } - // - // fn page_filter(&self) -> &Self::ModulePageFilter { - // unimplemented!() - // } - // - // fn page_filter_mut(&mut self) -> &mut Self::ModulePageFilter { - // unimplemented!() - // } } impl AddressFilter for IntelPTModule @@ -177,6 +151,5 @@ where intel_pt_module.pt = Some(pt); - // What does this bool mean? ignore for the moment true } From 6a19d87484f4cd99f547c9c9e3907a7d81a8f166 Mon Sep 17 00:00:00 2001 From: Marcondiro <46560192+Marcondiro@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:04:44 +0100 Subject: [PATCH 92/93] Reduce the PR's scope --- .github/workflows/build_and_test.yml | 1 - .../qemu_intel_pt_bootloader/Cargo.toml | 13 -- .../qemu_intel_pt_bootloader/Makefile.toml | 64 ------ .../qemu_intel_pt_bootloader/src/boot.s | 74 ------ .../qemu_intel_pt_bootloader/src/main.rs | 214 ------------------ 5 files changed, 366 deletions(-) delete mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml delete mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml delete mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s delete mode 100644 fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 3b4abeee7d0..5f559a61682 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -277,7 +277,6 @@ jobs: - ./fuzzers/forkserver/fuzzbench_forkserver_cmplog - ./fuzzers/forkserver/libafl-fuzz - ./fuzzers/forkserver/baby_fuzzer_with_forkexecutor - - ./fuzzers/full_system/qemu_intel_pt_bootloader # Full-system - ./fuzzers/full_system/nyx_launcher diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml deleted file mode 100644 index e498abc7100..00000000000 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "qemu_intel_pt_bootloader" -version = "0.1.0" -authors = ["Marco Cavenati "] -edition = "2021" - -[dependencies] -libafl = { path = "../../../libafl", default-features = false } -libafl_bolts = { path = "../../../libafl_bolts" } -libafl_qemu = { path = "../../../libafl_qemu", features = [ - "intel_pt", -], default-features = false } -env_logger = "0.11.5" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml b/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml deleted file mode 100644 index 8e1bbc18a31..00000000000 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/Makefile.toml +++ /dev/null @@ -1,64 +0,0 @@ -[env] -TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" - -[env.development] -PROFILE_DIR = "debug" - -[env.release] -PROFILE_DIR = "release" - -[tasks.target_dir] -condition = { files_not_exist = ["${TARGET_DIR}"] } -script_runner = "@shell" -script = ''' -mkdir -p ${TARGET_DIR} -''' - -[tasks.ram_disk] -script = """ -sudo mkdir -p /mnt/libafl_qemu_tmpfs || echo "/mnt/libafl_qemu_tmpfs creation failed"; -sudo mount -o size=128M -t tmpfs none /mnt/libafl_qemu_tmpfs || echo "tmpfs mount failed"; -sudo chown $(id -u):$(id -g) "/mnt/libafl_qemu_tmpfs"; -""" - -[tasks.build_target] -dependencies = ["target_dir"] -command = "nasm" -args = [ - "-o", - "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.bin", - "${CARGO_MAKE_WORKING_DIRECTORY}/src/boot.s", -] - -[tasks.build_fuzzer] -command = "cargo" -args = ["build", "--profile", "${CARGO_MAKE_CARGO_PROFILE}"] - -[tasks.build] -dependencies = ["build_fuzzer", "build_target"] - -[tasks.convert_target_image] -command = "qemu-img" -args = [ - "convert", - "-O", - "qcow2", - "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/boot.bin", - "/mnt/libafl_qemu_tmpfs/boot.qcow2", -] -dependencies = ["build_target", "ram_disk"] - -[tasks.setcap] -script = "sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME}" -dependencies = ["build_fuzzer"] - -[tasks.run] -dependencies = ["build", "setcap", "convert_target_image"] -script = """ -${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${PROFILE_DIR}/${CARGO_MAKE_CRATE_NAME} -sudo umount /mnt/libafl_qemu_tmpfs; -sudo rm -r /mnt/libafl_qemu_tmpfs -""" - -[tasks.default] -alias = "run" diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s b/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s deleted file mode 100644 index 397d3310207..00000000000 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/boot.s +++ /dev/null @@ -1,74 +0,0 @@ -[bits 16] ; use 16 bits -[org 0x7c00] ; sets the start address - -%macro print_string 1 ; %1: Pointer to the string (null-terminated) - mov si, %1 ; Load the pointer to the string -.print_char: - lodsb ; Load the next byte from [SI] into AL - or al, al ; Check if it's the null terminator - jz .done ; If zero, we are done - mov ah, 0x0E ; BIOS teletype function - int 0x10 ; Call BIOS interrupt - jmp .print_char ; Repeat for the next character -.done: - mov al, 0x0d ; CR - int 0x10 - mov al, 0x0a ; LF - int 0x10 -%endmacro - -start: - mov ah, 0xc0 - int 0x15 ; ask for the system configuration parameters - jc fail ; carry must be 0 - cmp ah, 0 ; ah must be 0 - jne fail - - mov ax, [es:bx] ; byte count of the system configuration parameters - cmp ax, 8 - jl fail - - mov ch, [es:bx + 2] ; Model - mov cl, [es:bx + 3] ; Submodel - mov dh, [es:bx + 4] ; BIOS revision - - cmp ch, 'a' - jne fail - cmp cl, 'b' - jne fail - cmp dh, 'c' - jne fail - -shutdown: - print_string bye - - ; sleep a bit to make sure output is printed - xor cx, cx - mov dx, 0xffff - mov ah, 0x86 - int 0x15 - - ; actual shutdown - mov ax, 0x1000 - mov ax, ss - mov sp, 0xf000 - mov ax, 0x5307 - mov bx, 0x0001 - mov cx, 0x0003 - int 0x15 - -fail: - print_string fail_msg -sleep_forever: - mov cx, 0xffff - mov dx, 0xffff - mov ah, 0x86 - int 0x15 - jmp sleep_forever - -fail_msg db "I don't like your BIOS. :(", 0 -bye db "Artificial bug triggered =)", 0 - -times 510-($-$$) db 0 ; fill the output file with zeroes until 510 bytes are full - -dw 0xaa55 ; magic bytes that tell BIOS that this is bootable diff --git a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs b/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs deleted file mode 100644 index eb64c7b8264..00000000000 --- a/fuzzers/full_system/qemu_intel_pt_bootloader/src/main.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! A fuzzer using qemu in systemmode with intel PT - -use core::time::Duration; -use std::{ - env, - num::NonZero, - ops::RangeInclusive, - path::{Path, PathBuf}, -}; - -use libafl::{ - corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, - events::{ProgressReporter, SimpleEventManager}, - executors::ExitKind, - feedback_or, feedback_or_fast, - feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, - fuzzer::{Fuzzer, StdFuzzer}, - generators::RandPrintablesGenerator, - inputs::{BytesInput, HasTargetBytes}, - monitors::SimpleMonitor, - mutators::{havoc_mutations::havoc_mutations, scheduled::StdScheduledMutator}, - observers::{StdMapObserver, TimeObserver}, - schedulers::QueueScheduler, - stages::StdMutationalStage, - state::{HasSolutions, StdState}, -}; -use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice}; -use libafl_qemu::{ - config, - config::{Accelerator, DriveCache, QemuConfig}, - executor::QemuExecutor, - modules::intel_pt::{IntelPTModule, SectionInfo}, - Emulator, EmulatorBuilder, GuestAddr, QemuExitReason, QemuShutdownCause, -}; - -// Coverage map -const MAP_SIZE: usize = 256; -static mut MAP: [u16; MAP_SIZE] = [0; MAP_SIZE]; - -// Bootloader code section and sleep fn address can be retrieved with `ndisasm target/boot.bin` -const BOOTLOADER_CODE: RangeInclusive = 0x7c00..=0x7c80; -const BOOTLOADER_SLEEP_FN_ADDR: GuestAddr = 0x7c60; - -fn main() { - // Initialize the logger (use env variable RUST_LOG=trace for maximum logging) - env_logger::init(); - - // Hardcoded parameters - let timeout = Duration::from_secs(5); - let objective_dir = PathBuf::from("./crashes"); - - let mon = SimpleMonitor::new(|s| println!("{s}")); - - // The event manager handle the various events generated during the fuzzing loop - // such as the notification of the addition of a new item to the corpus - let mut mgr = SimpleEventManager::new(mon); - - let target_dir = env::var("TARGET_DIR").unwrap_or("target".to_string()); - - // Configure QEMU - let qemu_config = QemuConfig::builder() - .no_graphic(true) - .monitor(config::Monitor::Null) - .serial(config::Serial::Null) - .cpu("host") - .ram_size(config::RamSize::MB(2)) - .drives([config::Drive::builder() - .format(config::DiskImageFileFormat::Qcow2) - .file("/mnt/libafl_qemu_tmpfs/boot.qcow2") - .cache(DriveCache::None) - .build()]) - .accelerator(Accelerator::Kvm) - //.snapshot(true) todo: doesnt work - .default_devices(false) - .bios("target/debug/qemu-libafl-bridge/build/qemu-bundle/usr/local/share/qemu/") - .start_cpu(false) - .build(); - - let filename = Path::new(&target_dir) - .join("boot.bin") - .to_string_lossy() - .to_string(); - let image = [SectionInfo { - filename, - offset: 0, - size: (BOOTLOADER_CODE.end() - BOOTLOADER_CODE.start()) as u64 + 1, - virtual_address: *BOOTLOADER_CODE.start() as u64, - }]; - let intel_pt_builder = IntelPTModule::default_pt_builder().ip_filters(&[BOOTLOADER_CODE]); - let emulator_modules = tuple_list!(IntelPTModule::builder() - .map_ptr(unsafe { MAP.as_mut_ptr() }) - .map_len(MAP_SIZE) - .intel_pt_builder(intel_pt_builder) - .image(&image) - .build()); - - let emulator = EmulatorBuilder::empty() - .qemu_parameters(qemu_config) - .modules(emulator_modules) - .build() - .unwrap(); - let qemu = emulator.qemu(); - qemu.set_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr) - .unwrap(); - - unsafe { - match qemu.run() { - Ok(QemuExitReason::Breakpoint(ba)) => { - println!("break1 at {ba:x}") - } - _ => panic!("Pre-harness Unexpected QEMU exit."), - } - } - qemu.remove_hw_breakpoint(*BOOTLOADER_CODE.start() as GuestAddr) - .unwrap(); - - qemu.set_hw_breakpoint(BOOTLOADER_SLEEP_FN_ADDR).unwrap(); - - qemu.save_snapshot("bootloader_start", true); - - let mut harness = |emulator: &mut Emulator<_, _, _, _, _, _, _>, - _: &mut StdState<_, _, _, _>, - input: &BytesInput| unsafe { - let mut fixed_len_input = input.target_bytes().as_slice().to_vec(); - fixed_len_input.resize(3, 0); - - qemu.load_snapshot("bootloader_start", true); - qemu.write_phys_mem(0xfe6f7, &fixed_len_input); - match emulator.qemu().run() { - Ok(QemuExitReason::End(QemuShutdownCause::GuestShutdown)) => { - println!( - "crashing input: {}", - String::from_utf8_lossy(&fixed_len_input) - ); - ExitKind::Crash - } - Ok(QemuExitReason::Breakpoint(_)) => ExitKind::Ok, - e => panic!("Harness Unexpected QEMU exit. {e:?}"), - } - }; - - // Create an observation channel using the map - let observer = unsafe { StdMapObserver::from_mut_ptr("signals", MAP.as_mut_ptr(), MAP_SIZE) }; - - // Create an observation channel to keep track of the execution time - let time_observer = TimeObserver::new("time"); - - // Feedback to rate the interestingness of an input - // This one is composed by two Feedbacks in OR - let mut feedback = feedback_or!( - // New maximization map feedback linked to the edges observer and the feedback state - MaxMapFeedback::new(&observer), - // Time feedback, this one does not need a feedback state - TimeFeedback::new(&time_observer) - ); - - // A feedback to choose if an input is a solution or not - let mut objective = feedback_or_fast!(CrashFeedback::new()); - - // If not restarting, create a State from scratch - let mut state = StdState::new( - // RNG - StdRand::with_seed(current_nanos()), - // Corpus that will be evolved, we keep it in memory for performance - InMemoryCorpus::new(), - // Corpus in which we store solutions (crashes in this example), - // on disk so the user can get them after stopping the fuzzer - OnDiskCorpus::new(objective_dir.clone()).unwrap(), - // States of the feedbacks. - // The feedbacks can report the data that should persist in the State. - &mut feedback, - // Same for objective feedbacks - &mut objective, - ) - .unwrap(); - - // A queue policy to get testcases from the corpus - let scheduler = QueueScheduler::new(); - - // A fuzzer with feedbacks and a corpus scheduler - let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - - // Create a QEMU in-process executor - let mut executor = QemuExecutor::new( - emulator, - &mut harness, - tuple_list!(observer, time_observer), - &mut fuzzer, - &mut state, - &mut mgr, - timeout, - ) - .expect("Failed to create QemuExecutor"); - - // Generator of printable bytearrays of max size 3 - let mut generator = RandPrintablesGenerator::new(NonZero::new(3).unwrap()); - - state - .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 4) - .expect("Failed to generate the initial corpus"); - - // Setup an havoc mutator with a mutational stage - let mutator = StdScheduledMutator::new(havoc_mutations()); - let mut stages = tuple_list!(StdMutationalStage::new(mutator)); - - while state.solutions().is_empty() { - mgr.maybe_report_progress(&mut state, Duration::from_secs(5)) - .unwrap(); - - fuzzer - .fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr) - .expect("Error in the fuzzing loop"); - } -} From 270b94ef4fdf235478fa86dd5a71d15d78f3701d Mon Sep 17 00:00:00 2001 From: Marco Cavenati Date: Tue, 1 Apr 2025 16:48:02 +0200 Subject: [PATCH 93/93] Do not install cmake from apt Looks like it is already included --- .github/workflows/ubuntu-prepare/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-prepare/action.yml b/.github/workflows/ubuntu-prepare/action.yml index cfabece5334..9558a84e335 100644 --- a/.github/workflows/ubuntu-prepare/action.yml +++ b/.github/workflows/ubuntu-prepare/action.yml @@ -5,7 +5,7 @@ runs: steps: - name: Install and cache deps shell: bash - run: sudo apt-get update && sudo apt-get install -y curl lsb-release wget software-properties-common gnupg ninja-build shellcheck pax-utils nasm libsqlite3-dev libc6-dev libgtk-3-dev gcc g++ gcc-arm-none-eabi gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev libz3-dev build-essential cmake + run: sudo apt-get update && sudo apt-get install -y curl lsb-release wget software-properties-common gnupg ninja-build shellcheck pax-utils nasm libsqlite3-dev libc6-dev libgtk-3-dev gcc g++ gcc-arm-none-eabi gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev libz3-dev build-essential - uses: dtolnay/rust-toolchain@stable - name: install just uses: extractions/setup-just@v2