diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 3dad667167..a3e5f3f8b3 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -829,8 +829,10 @@ pub mod unix_signal_handler { Z: HasObjective, { #[cfg(all(target_os = "android", target_arch = "aarch64"))] - let _context = &mut *(((_context as *mut _ as *mut libc::c_void as usize) + 128) - as *mut libc::c_void as *mut ucontext_t); + let _context = _context.map(|p| { + &mut *(((p as *mut _ as *mut libc::c_void as usize) + 128) as *mut libc::c_void + as *mut ucontext_t) + }); log::error!("Crashed with {signal}"); if data.is_valid() { diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 1e8f73e438..57cb3599f2 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -13,10 +13,8 @@ use std::{ vec::Vec, }; -#[cfg(test)] -use libafl_bolts::rands::StdRand; use libafl_bolts::{ - rands::Rand, + rands::{Rand, StdRand}, serdeany::{NamedSerdeAnyMap, SerdeAny, SerdeAnyMap}, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -881,7 +879,6 @@ impl HasClientPerfMonitor for StdState { } } -#[cfg(test)] /// A very simple state without any bells or whistles, for testing. #[derive(Debug, Serialize, Deserialize, Default)] pub struct NopState { @@ -891,7 +888,6 @@ pub struct NopState { phantom: PhantomData, } -#[cfg(test)] impl NopState { /// Create a new State that does nothing (for tests) #[must_use] @@ -905,7 +901,6 @@ impl NopState { } } -#[cfg(test)] impl UsesInput for NopState where I: Input, @@ -913,7 +908,6 @@ where type Input = I; } -#[cfg(test)] impl HasExecutions for NopState { fn executions(&self) -> &usize { &self.execution @@ -924,7 +918,6 @@ impl HasExecutions for NopState { } } -#[cfg(test)] impl HasLastReportTime for NopState { fn last_report_time(&self) -> &Option { unimplemented!(); @@ -935,7 +928,6 @@ impl HasLastReportTime for NopState { } } -#[cfg(test)] impl HasMetadata for NopState { fn metadata_map(&self) -> &SerdeAnyMap { &self.metadata @@ -946,7 +938,6 @@ impl HasMetadata for NopState { } } -#[cfg(test)] impl HasRand for NopState { type Rand = StdRand; @@ -959,7 +950,6 @@ impl HasRand for NopState { } } -#[cfg(test)] impl HasClientPerfMonitor for NopState { fn introspection_monitor(&self) -> &ClientPerfMonitor { unimplemented!() @@ -970,7 +960,6 @@ impl HasClientPerfMonitor for NopState { } } -#[cfg(test)] impl State for NopState where I: Input {} #[cfg(feature = "python")] diff --git a/libafl_bolts/src/tuples.rs b/libafl_bolts/src/tuples.rs index 3a7f8ecc46..254fb888bd 100644 --- a/libafl_bolts/src/tuples.rs +++ b/libafl_bolts/src/tuples.rs @@ -49,6 +49,45 @@ pub fn type_eq() -> bool { type_name::() == type_name::() } +/// Borrow each member of the tuple +pub trait SplitBorrow<'a> { + /// The Resulting [`TupleList`], of an [`SplitBorrow::borrow()`] call + type SplitBorrowResult; + /// The Resulting [`TupleList`], of an [`SplitBorrow::borrow_mut()`] call + type SplitBorrowMutResult; + + /// Return a tuple of borrowed references + fn borrow(&'a self) -> Self::SplitBorrowResult; + /// Return a tuple of borrowed mutable references + fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult; +} + +impl<'a> SplitBorrow<'a> for () { + type SplitBorrowResult = (); + type SplitBorrowMutResult = (); + + fn borrow(&'a self) -> Self::SplitBorrowResult {} + + fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {} +} + +impl<'a, Head, Tail> SplitBorrow<'a> for (Head, Tail) +where + Head: 'a, + Tail: SplitBorrow<'a>, +{ + type SplitBorrowResult = (Option<&'a Head>, Tail::SplitBorrowResult); + type SplitBorrowMutResult = (Option<&'a mut Head>, Tail::SplitBorrowMutResult); + + fn borrow(&'a self) -> Self::SplitBorrowResult { + (Some(&self.0), self.1.borrow()) + } + + fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult { + (Some(&mut self.0), self.1.borrow_mut()) + } +} + /// Gets the length of the element pub trait HasConstLen { /// The length as constant `usize` @@ -172,6 +211,117 @@ where } } +/// Returns the first element with the given type (dereference mut version) +pub trait ExtractFirstRefType { + /// Returns the first element with the given type as borrow, or [`Option::None`] + fn take<'a, T: 'static>(self) -> (Option<&'a T>, Self); +} + +impl ExtractFirstRefType for () { + fn take<'a, T: 'static>(self) -> (Option<&'a T>, Self) { + (None, ()) + } +} + +impl ExtractFirstRefType for (Option<&Head>, Tail) +where + Head: 'static, + Tail: ExtractFirstRefType, +{ + fn take<'a, T: 'static>(mut self) -> (Option<&'a T>, Self) { + if TypeId::of::() == TypeId::of::() { + let r = self.0.take(); + (unsafe { core::mem::transmute(r) }, self) + } else { + let (r, tail) = self.1.take::(); + (r, (self.0, tail)) + } + } +} + +impl ExtractFirstRefType for (Option<&mut Head>, Tail) +where + Head: 'static, + Tail: ExtractFirstRefType, +{ + fn take<'a, T: 'static>(mut self) -> (Option<&'a T>, Self) { + if TypeId::of::() == TypeId::of::() { + let r = self.0.take(); + (unsafe { core::mem::transmute(r) }, self) + } else { + let (r, tail) = self.1.take::(); + (r, (self.0, tail)) + } + } +} + +/// Returns the first element with the given type (dereference mut version) +pub trait ExtractFirstRefMutType { + /// Returns the first element with the given type as borrow, or [`Option::None`] + fn take<'a, T: 'static>(self) -> (Option<&'a mut T>, Self); +} + +impl ExtractFirstRefMutType for () { + fn take<'a, T: 'static>(self) -> (Option<&'a mut T>, Self) { + (None, ()) + } +} + +impl ExtractFirstRefMutType for (Option<&mut Head>, Tail) +where + Head: 'static, + Tail: ExtractFirstRefMutType, +{ + fn take<'a, T: 'static>(mut self) -> (Option<&'a mut T>, Self) { + if TypeId::of::() == TypeId::of::() { + let r = self.0.take(); + (unsafe { core::mem::transmute(r) }, self) + } else { + let (r, tail) = self.1.take::(); + (r, (self.0, tail)) + } + } +} + +/// Borrow each member of the tuple +pub trait SplitBorrowExtractFirstType<'a> { + /// The Resulting [`TupleList`], of an [`SplitBorrow::borrow()`] call + type SplitBorrowResult: ExtractFirstRefType; + /// The Resulting [`TupleList`], of an [`SplitBorrow::borrow_mut()`] call + type SplitBorrowMutResult: ExtractFirstRefType + ExtractFirstRefMutType; + + /// Return a tuple of borrowed references + fn borrow(&'a self) -> Self::SplitBorrowResult; + /// Return a tuple of borrowed mutable references + fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult; +} + +impl<'a> SplitBorrowExtractFirstType<'a> for () { + type SplitBorrowResult = (); + type SplitBorrowMutResult = (); + + fn borrow(&'a self) -> Self::SplitBorrowResult {} + + fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {} +} + +impl<'a, Head, Tail> SplitBorrowExtractFirstType<'a> for (Head, Tail) +where + Head: 'static, + Tail: SplitBorrowExtractFirstType<'a>, +{ + type SplitBorrowResult = (Option<&'a Head>, Tail::SplitBorrowResult); + type SplitBorrowMutResult = (Option<&'a mut Head>, Tail::SplitBorrowMutResult); + + fn borrow(&'a self) -> Self::SplitBorrowResult { + (Some(&self.0), self.1.borrow()) + } + + fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult { + (Some(&mut self.0), self.1.borrow_mut()) + } +} + /// Match by type pub trait MatchType { /// Match by type and call the passed `f` function with a borrow, if found @@ -209,45 +359,6 @@ where } } -/// Borrow each member of the tuple -pub trait SplitBorrow<'a> { - /// The Resulting [`TupleList`], of an [`SplitBorrow::split_borrow()`] call - type SplitBorrowResult; - /// The Resulting [`TupleList`], of an [`SplitBorrow::split_borrow_mut()`] call - type SplitBorrowMutResult; - - /// Return a tuple of borrowed references - fn split_borrow(&'a self) -> Self::SplitBorrowResult; - /// Return a tuple of borrowed mutable references - fn split_borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult; -} - -impl<'a> SplitBorrow<'a> for () { - type SplitBorrowResult = (); - type SplitBorrowMutResult = (); - - fn split_borrow(&'a self) -> Self::SplitBorrowResult {} - - fn split_borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {} -} - -impl<'a, Head, Tail> SplitBorrow<'a> for (Head, Tail) -where - Head: 'a, - Tail: SplitBorrow<'a>, -{ - type SplitBorrowResult = (&'a Head, Tail::SplitBorrowResult); - type SplitBorrowMutResult = (&'a mut Head, Tail::SplitBorrowMutResult); - - fn split_borrow(&'a self) -> Self::SplitBorrowResult { - (&self.0, self.1.split_borrow()) - } - - fn split_borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult { - (&mut self.0, self.1.split_borrow_mut()) - } -} - /// A named tuple pub trait NamedTuple: HasConstLen { /// Gets the name of this tuple diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index c5138ca899..e93d4cd9f5 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -60,9 +60,12 @@ syscall-numbers = "3.0" meminterval = "0.4" thread_local = "1.1.4" capstone = "0.11.0" -pyo3 = { version = "0.18", optional = true } rangemap = "1.3" log = "0.4.20" +addr2line = "0.21" +typed-arena = "2.0" + +pyo3 = { version = "0.18", optional = true } [build-dependencies] pyo3-build-config = { version = "0.18", optional = true } diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 8ffdad7261..b31bbaf4cf 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -8,7 +8,7 @@ use which::which; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -const QEMU_REVISION: &str = "ead06288fd597e72cbf50db1c89386f952592860"; +const QEMU_REVISION: &str = "e42124c0c8363184ef286fde43dce1d5c607699b"; fn build_dep_check(tools: &[&str]) { for tool in tools { diff --git a/libafl_qemu/libafl_qemu_build/src/lib.rs b/libafl_qemu/libafl_qemu_build/src/lib.rs index ed2c432094..5f19cb3997 100644 --- a/libafl_qemu/libafl_qemu_build/src/lib.rs +++ b/libafl_qemu/libafl_qemu_build/src/lib.rs @@ -124,8 +124,8 @@ fn qemu_bindgen_clang_args( ) } else { ( - "/softmmu/main.c", - format!("qemu-system-{cpu_target}.p/softmmu_main.c.o"), + "/system/main.c", + format!("libqemu-system-{cpu_target}.so.p/system_main.c.o"), ) }; diff --git a/libafl_qemu/libafl_qemu_sys/src/lib.rs b/libafl_qemu/libafl_qemu_sys/src/lib.rs index ff077b3057..5af24d4479 100644 --- a/libafl_qemu/libafl_qemu_sys/src/lib.rs +++ b/libafl_qemu/libafl_qemu_sys/src/lib.rs @@ -36,3 +36,10 @@ pub fn memop_big_endian(op: MemOp) -> bool { pub fn make_plugin_meminfo(oi: MemOpIdx, rw: qemu_plugin_mem_rw) -> qemu_plugin_meminfo_t { oi | (rw.0 << 16) } + +// from include/hw/core/cpu.h + +#[cfg(target_os = "linux")] +pub fn cpu_env(cpu: *mut CPUState) -> *mut CPUArchState { + unsafe { cpu.add(1) as *mut CPUArchState } +} diff --git a/libafl_qemu/libqasan/malloc.c b/libafl_qemu/libqasan/malloc.c index b6a6d38466..0a03e71b42 100644 --- a/libafl_qemu/libqasan/malloc.c +++ b/libafl_qemu/libqasan/malloc.c @@ -1,5 +1,5 @@ /******************************************************************************* -Copyright (c) 2019-2020, Andrea Fioraldi +Copyright (c) 2019-2023, Andrea Fioraldi Redistribution and use in source and binary forms, with or without @@ -53,13 +53,15 @@ struct chunk_begin { struct chunk_begin *next; struct chunk_begin *prev; char redzone[REDZONE_SIZE]; -}; + +} __attribute__((packed)); struct chunk_struct { struct chunk_begin begin; char redzone[REDZONE_SIZE]; size_t prev_size_padding; -}; + +} __attribute__((packed)); #ifdef __GLIBC__ diff --git a/libafl_qemu/src/aarch64.rs b/libafl_qemu/src/aarch64.rs index 0e783b7a09..108b633a86 100644 --- a/libafl_qemu/src/aarch64.rs +++ b/libafl_qemu/src/aarch64.rs @@ -1,3 +1,4 @@ +use capstone::arch::BuildsCapstone; use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "python")] use pyo3::prelude::*; @@ -62,7 +63,9 @@ impl IntoPy for Regs { /// Return an ARM64 ArchCapstoneBuilder pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder { - capstone::Capstone::new().arm64() + capstone::Capstone::new() + .arm64() + .mode(capstone::arch::arm64::ArchMode::Arm) } pub type GuestReg = u64; diff --git a/libafl_qemu/src/asan.rs b/libafl_qemu/src/asan.rs index 9d133a542a..8f76318e77 100644 --- a/libafl_qemu/src/asan.rs +++ b/libafl_qemu/src/asan.rs @@ -1,11 +1,13 @@ #![allow(clippy::cast_possible_wrap)] use std::{ + borrow::Cow, collections::{HashMap, HashSet}, env, fs, sync::Mutex, }; +use addr2line::object::{Object, ObjectSection}; use libafl::{ executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata, }; @@ -14,12 +16,14 @@ use libc::{ }; use meminterval::{Interval, IntervalTree}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use rangemap::RangeMap; use crate::{ + calls::FullBacktraceCollector, emu::{EmuError, Emulator, MemAccessInfo, SyscallHookResult}, helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, hooks::QemuHooks, - GuestAddr, + GuestAddr, Regs, }; // TODO at some point, merge parts with libafl_frida @@ -39,6 +43,8 @@ pub const QASAN_FAKESYS_NR: i32 = 0xa2a4; pub const SHADOW_PAGE_SIZE: usize = 4096; pub const SHADOW_PAGE_MASK: GuestAddr = !(SHADOW_PAGE_SIZE as GuestAddr - 1); +pub const DEFAULT_REDZONE_SIZE: usize = 128; + #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)] #[repr(u64)] pub enum QasanAction { @@ -93,13 +99,54 @@ pub enum AsanError { Write(GuestAddr, usize), BadFree(GuestAddr, Option>), MemLeak(Interval), + Signal(i32), } -pub type AsanErrorCallback = Box; +impl core::fmt::Display for AsanError { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + AsanError::Read(addr, len) => write!(fmt, "Invalid {len} bytes read at {addr:#x}"), + AsanError::Write(addr, len) => { + write!(fmt, "Invalid {len} bytes write at {addr:#x}") + } + AsanError::BadFree(addr, interval) => match interval { + Some(chunk) => write!(fmt, "Bad free at {addr:#x} in the allocated chunk {chunk}",), + None => write!(fmt, "Bad free at {addr:#x} (wild pointer)"), + }, + AsanError::MemLeak(interval) => write!(fmt, "Memory leak of chunk {interval}"), + AsanError::Signal(sig) => write!(fmt, "Signal {sig} received"), + } + } +} + +pub type AsanErrorCallback = Box; + +#[derive(Debug, Clone)] +pub struct AllocTreeItem { + backtrace: Vec, + free_backtrace: Vec, + allocated: bool, +} + +impl AllocTreeItem { + #[must_use] + pub fn alloc(backtrace: Vec) -> Self { + AllocTreeItem { + backtrace, + free_backtrace: vec![], + allocated: true, + } + } + + pub fn free(&mut self, backtrace: Vec) { + self.free_backtrace = backtrace; + self.allocated = false; + } +} pub struct AsanGiovese { - pub alloc_tree: Mutex>, - pub saved_tree: IntervalTree, + pub alloc_tree: Mutex>, + pub saved_tree: IntervalTree, pub error_callback: Option, pub dirty_shadow: Mutex>, pub saved_shadow: HashMap>, @@ -352,22 +399,34 @@ impl AsanGiovese { } } - pub fn report_or_crash(&mut self, emu: &Emulator, error: AsanError) { - if let Some(cb) = self.error_callback.as_mut() { - (cb)(emu, error); + pub fn report_or_crash(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) { + if let Some(mut cb) = self.error_callback.take() { + (cb)(self, emu, pc, error); + self.error_callback = Some(cb); } else { std::process::abort(); } } - pub fn report(&mut self, emu: &Emulator, error: AsanError) { - if let Some(cb) = self.error_callback.as_mut() { - (cb)(emu, error); + pub fn report(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) { + if let Some(mut cb) = self.error_callback.take() { + (cb)(self, emu, pc, error); + self.error_callback = Some(cb); } } - pub fn alloc_insert(&mut self, start: GuestAddr, end: GuestAddr) { - self.alloc_tree.lock().unwrap().insert(start..end, ()); + pub fn alloc_insert(&mut self, pc: GuestAddr, start: GuestAddr, end: GuestAddr) { + let backtrace = FullBacktraceCollector::backtrace() + .map(|r| { + let mut v = r.to_vec(); + v.push(pc); + v + }) + .unwrap_or_default(); + self.alloc_tree + .lock() + .unwrap() + .insert(start..end, AllocTreeItem::alloc(backtrace)); } pub fn alloc_remove(&mut self, start: GuestAddr, end: GuestAddr) { @@ -381,8 +440,45 @@ impl AsanGiovese { } } + pub fn alloc_free(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { + let mut chunk = None; + self.alloc_map_mut(addr, |interval, item| { + chunk = Some(*interval); + let backtrace = FullBacktraceCollector::backtrace() + .map(|r| { + let mut v = r.to_vec(); + v.push(pc); + v + }) + .unwrap_or_default(); + item.free(backtrace); + }); + if let Some(ck) = chunk { + if ck.start != addr { + // Free not the start of the chunk + self.report_or_crash(emulator, pc, AsanError::BadFree(addr, Some(ck))); + } + } else { + // Free of wild ptr + self.report_or_crash(emulator, pc, AsanError::BadFree(addr, None)); + } + } + #[must_use] - pub fn alloc_search(&mut self, query: GuestAddr) -> Option> { + pub fn alloc_get_clone( + &self, + query: GuestAddr, + ) -> Option<(Interval, AllocTreeItem)> { + self.alloc_tree + .lock() + .unwrap() + .query(query..=query) + .next() + .map(|entry| (*entry.interval, entry.value.clone())) + } + + #[must_use] + pub fn alloc_get_interval(&self, query: GuestAddr) -> Option> { self.alloc_tree .lock() .unwrap() @@ -391,6 +487,48 @@ impl AsanGiovese { .map(|entry| *entry.interval) } + pub fn alloc_map(&self, query: GuestAddr, mut func: F) + where + F: FnMut(&Interval, &AllocTreeItem), + { + if let Some(entry) = self.alloc_tree.lock().unwrap().query(query..=query).next() { + func(entry.interval, entry.value); + } + } + + pub fn alloc_map_mut(&mut self, query: GuestAddr, mut func: F) + where + F: FnMut(&Interval, &mut AllocTreeItem), + { + if let Some(entry) = self + .alloc_tree + .lock() + .unwrap() + .query_mut(query..=query) + .next() + { + func(entry.interval, entry.value); + } + } + + pub fn alloc_map_interval(&self, query: Interval, mut func: F) + where + F: FnMut(&Interval, &AllocTreeItem), + { + if let Some(entry) = self.alloc_tree.lock().unwrap().query(query).next() { + func(entry.interval, entry.value); + } + } + + pub fn alloc_map_interval_mut(&mut self, query: Interval, mut func: F) + where + F: FnMut(&Interval, &mut AllocTreeItem), + { + if let Some(entry) = self.alloc_tree.lock().unwrap().query_mut(query).next() { + func(entry.interval, entry.value); + } + } + pub fn snapshot(&mut self, emu: &Emulator) { if self.snapshot_shadow { let set = self.dirty_shadow.lock().unwrap(); @@ -445,7 +583,11 @@ impl AsanGiovese { }; for interval in leaks { - self.report(emu, AsanError::MemLeak(interval)); + self.report( + emu, + emu.read_reg(Regs::Pc).unwrap(), + AsanError::MemLeak(interval), + ); } ret @@ -564,6 +706,11 @@ impl QemuAsanHelper { } } + #[must_use] + pub fn with_asan_report(filter: QemuInstrumentationFilter, options: QemuAsanOptions) -> Self { + Self::with_error_callback(filter, Box::new(asan_report), options) + } + #[must_use] pub fn must_instrument(&self, addr: GuestAddr) -> bool { self.filter.allowed(addr) @@ -578,23 +725,13 @@ impl QemuAsanHelper { self.enabled = enabled; } - pub fn alloc(&mut self, _emulator: &Emulator, start: GuestAddr, end: GuestAddr) { - self.rt.alloc_insert(start, end); + pub fn alloc(&mut self, pc: GuestAddr, start: GuestAddr, end: GuestAddr) { + self.rt.alloc_remove(start, end); + self.rt.alloc_insert(pc, start, end); } - pub fn dealloc(&mut self, emulator: &Emulator, addr: GuestAddr) { - let chunk = self.rt.alloc_search(addr); - if let Some(ck) = chunk { - if ck.start != addr { - // Free not the start of the chunk - self.rt - .report_or_crash(emulator, AsanError::BadFree(addr, Some(ck))); - } - } else { - // Free of wild ptr - self.rt - .report_or_crash(emulator, AsanError::BadFree(addr, None)); - } + pub fn dealloc(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { + self.rt.alloc_free(emulator, pc, addr); } #[allow(clippy::unused_self)] @@ -603,65 +740,73 @@ impl QemuAsanHelper { AsanGiovese::is_invalid_access(emulator, addr, size) } - pub fn read_1(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn read_1(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Read(addr, 1)); + self.rt + .report_or_crash(emulator, pc, AsanError::Read(addr, 1)); } } - pub fn read_2(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn read_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Read(addr, 2)); + self.rt + .report_or_crash(emulator, pc, AsanError::Read(addr, 2)); } } - pub fn read_4(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn read_4(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Read(addr, 4)); + self.rt + .report_or_crash(emulator, pc, AsanError::Read(addr, 4)); } } - pub fn read_8(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn read_8(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Read(addr, 8)); + self.rt + .report_or_crash(emulator, pc, AsanError::Read(addr, 8)); } } - pub fn read_n(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) { + pub fn read_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) { if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) { self.rt - .report_or_crash(emulator, AsanError::Read(addr, size)); + .report_or_crash(emulator, pc, AsanError::Read(addr, size)); } } - pub fn write_1(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn write_1(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Write(addr, 1)); + self.rt + .report_or_crash(emulator, pc, AsanError::Write(addr, 1)); } } - pub fn write_2(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn write_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Write(addr, 2)); + self.rt + .report_or_crash(emulator, pc, AsanError::Write(addr, 2)); } } - pub fn write_4(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn write_4(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Write(addr, 4)); + self.rt + .report_or_crash(emulator, pc, AsanError::Write(addr, 4)); } } - pub fn write_8(&mut self, emulator: &Emulator, addr: GuestAddr) { + pub fn write_8(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) { if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) { - self.rt.report_or_crash(emulator, AsanError::Write(addr, 8)); + self.rt + .report_or_crash(emulator, pc, AsanError::Write(addr, 8)); } } - pub fn write_n(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) { + pub fn write_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) { if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) { self.rt - .report_or_crash(emulator, AsanError::Write(addr, size)); + .report_or_crash(emulator, pc, AsanError::Write(addr, size)); } } @@ -725,6 +870,10 @@ where Some(trace_write8_asan::), Some(trace_write_n_asan::), ); + + if self.rt.error_callback.is_some() { + hooks.crash(oncrash_asan::); + } } fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) { @@ -749,6 +898,17 @@ where } } +pub fn oncrash_asan(hooks: &mut QemuHooks<'_, QT, S>, target_sig: i32) +where + S: UsesInput, + QT: QemuHelperTuple, +{ + let emu = hooks.emulator().clone(); + let h = hooks.match_helper_mut::().unwrap(); + let pc: GuestAddr = emu.read_reg(Regs::Pc).unwrap(); + h.rt.report(&emu, pc, AsanError::Signal(target_sig)); +} + pub fn gen_readwrite_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, @@ -770,7 +930,7 @@ where pub fn trace_read1_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -778,13 +938,13 @@ pub fn trace_read1_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.read_1(&emulator, addr); + h.read_1(&emulator, id as GuestAddr, addr); } pub fn trace_read2_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -792,13 +952,13 @@ pub fn trace_read2_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.read_2(&emulator, addr); + h.read_2(&emulator, id as GuestAddr, addr); } pub fn trace_read4_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -806,13 +966,13 @@ pub fn trace_read4_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.read_4(&emulator, addr); + h.read_4(&emulator, id as GuestAddr, addr); } pub fn trace_read8_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -820,13 +980,13 @@ pub fn trace_read8_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.read_8(&emulator, addr); + h.read_8(&emulator, id as GuestAddr, addr); } pub fn trace_read_n_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, size: usize, ) where @@ -835,13 +995,13 @@ pub fn trace_read_n_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.read_n(&emulator, addr, size); + h.read_n(&emulator, id as GuestAddr, addr, size); } pub fn trace_write1_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -849,13 +1009,13 @@ pub fn trace_write1_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.write_1(&emulator, addr); + h.write_1(&emulator, id as GuestAddr, addr); } pub fn trace_write2_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -863,13 +1023,13 @@ pub fn trace_write2_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.write_2(&emulator, addr); + h.write_2(&emulator, id as GuestAddr, addr); } pub fn trace_write4_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -877,13 +1037,13 @@ pub fn trace_write4_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.write_4(&emulator, addr); + h.write_4(&emulator, id as GuestAddr, addr); } pub fn trace_write8_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, ) where S: UsesInput, @@ -891,13 +1051,13 @@ pub fn trace_write8_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.write_8(&emulator, addr); + h.write_8(&emulator, id as GuestAddr, addr); } pub fn trace_write_n_asan( hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, - _id: u64, + id: u64, addr: GuestAddr, size: usize, ) where @@ -906,7 +1066,7 @@ pub fn trace_write_n_asan( { let emulator = hooks.emulator().clone(); let h = hooks.match_helper_mut::().unwrap(); - h.read_n(&emulator, addr, size); + h.read_n(&emulator, id as GuestAddr, addr, size); } #[allow(clippy::too_many_arguments)] @@ -933,10 +1093,12 @@ where let mut r = 0; match QasanAction::try_from(a0).expect("Invalid QASan action number") { QasanAction::CheckLoad => { - h.read_n(&emulator, a1 as GuestAddr, a2 as usize); + let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); + h.read_n(&emulator, pc, a1 as GuestAddr, a2 as usize); } QasanAction::CheckStore => { - h.write_n(&emulator, a1 as GuestAddr, a2 as usize); + let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); + h.write_n(&emulator, pc, a1 as GuestAddr, a2 as usize); } QasanAction::Poison => { h.poison( @@ -958,10 +1120,12 @@ where } } QasanAction::Alloc => { - h.alloc(&emulator, a1 as GuestAddr, a2 as GuestAddr); + let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); + h.alloc(pc, a1 as GuestAddr, a2 as GuestAddr); } QasanAction::Dealloc => { - h.dealloc(&emulator, a1 as GuestAddr); + let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap(); + h.dealloc(&emulator, pc, a1 as GuestAddr); } QasanAction::Enable => { h.set_enabled(true); @@ -978,3 +1142,230 @@ where SyscallHookResult::new(None) } } + +fn load_file_section<'input, 'arena, Endian: addr2line::gimli::Endianity>( + id: addr2line::gimli::SectionId, + file: &addr2line::object::File<'input>, + endian: Endian, + arena_data: &'arena typed_arena::Arena>, +) -> Result, addr2line::object::Error> { + // TODO: Unify with dwarfdump.rs in gimli. + let name = id.name(); + match file.section_by_name(name) { + Some(section) => match section.uncompressed_data()? { + Cow::Borrowed(b) => Ok(addr2line::gimli::EndianSlice::new(b, endian)), + Cow::Owned(b) => Ok(addr2line::gimli::EndianSlice::new( + arena_data.alloc(b.into()), + endian, + )), + }, + None => Ok(addr2line::gimli::EndianSlice::new(&[][..], endian)), + } +} + +#[allow(clippy::unnecessary_cast)] +#[allow(clippy::too_many_lines)] +pub fn asan_report(rt: &AsanGiovese, emu: &Emulator, pc: GuestAddr, err: AsanError) { + let mut regions = std::collections::HashMap::new(); + for region in emu.mappings() { + if let Some(path) = region.path() { + let start = region.start(); + let end = region.end(); + let entry = regions.entry(path.to_owned()).or_insert(start..end); + if start < entry.start { + *entry = start..entry.end; + } + if end > entry.end { + *entry = entry.start..end; + } + } + } + + let mut resolvers = vec![]; + let mut images = vec![]; + let mut ranges = RangeMap::new(); + + for (path, rng) in regions { + let data = std::fs::read(&path); + if data.is_err() { + continue; + } + let data = data.unwrap(); + let idx = images.len(); + images.push((path, data)); + ranges.insert(rng, idx); + } + + let arena_data = typed_arena::Arena::new(); + + for img in &images { + if let Ok(obj) = addr2line::object::read::File::parse(&*img.1) { + let endian = if obj.is_little_endian() { + addr2line::gimli::RunTimeEndian::Little + } else { + addr2line::gimli::RunTimeEndian::Big + }; + + let mut load_section = |id: addr2line::gimli::SectionId| -> Result<_, _> { + load_file_section(id, &obj, endian, &arena_data) + }; + + let dwarf = addr2line::gimli::Dwarf::load(&mut load_section).unwrap(); + let ctx = addr2line::Context::from_dwarf(dwarf) + .expect("Failed to create an addr2line context"); + + //let ctx = addr2line::Context::new(&obj).expect("Failed to create an addr2line context"); + resolvers.push(Some((obj, ctx))); + } else { + resolvers.push(None); + } + } + + let resolve_addr = |addr: GuestAddr| -> String { + let mut info = String::new(); + if let Some((rng, idx)) = ranges.get_key_value(&addr) { + let raddr = (addr - rng.start) as u64; + if let Some((obj, ctx)) = resolvers[*idx].as_ref() { + let symbols = obj.symbol_map(); + let mut func = symbols.get(raddr).map(|x| x.name().to_string()); + + if func.is_none() { + let pathname = std::path::PathBuf::from(images[*idx].0.clone()); + let mut split_dwarf_loader = + addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new( + |data, endian| { + addr2line::gimli::EndianSlice::new( + arena_data.alloc(Cow::Owned(data.into_owned())), + endian, + ) + }, + Some(pathname), + ); + + let frames = ctx.find_frames(raddr); + if let Ok(mut frames) = split_dwarf_loader.run(frames) { + if let Some(frame) = frames.next().unwrap_or(None) { + if let Some(function) = frame.function { + if let Ok(name) = function.raw_name() { + let demangled = + addr2line::demangle_auto(name, function.language); + func = Some(demangled.to_string()); + } + } + } + } + } + + if let Some(name) = func { + info += " in "; + info += &name; + } + + if let Some(loc) = ctx.find_location(raddr).unwrap_or(None) { + if info.is_empty() { + info += " in"; + } + info += " "; + if let Some(file) = loc.file { + info += file; + } + if let Some(line) = loc.line { + info += ":"; + info += &line.to_string(); + } + } else { + info += &format!(" ({}+{raddr:#x})", images[*idx].0); + } + } + if info.is_empty() { + info += &format!(" ({}+{raddr:#x})", images[*idx].0); + } + } + info + }; + + let backtrace = FullBacktraceCollector::backtrace() + .map(|r| { + let mut v = r.to_vec(); + v.push(pc); + v + }) + .unwrap_or(vec![pc]); + eprintln!("AddressSanitizer Error: {err}"); + for (i, addr) in backtrace.iter().rev().enumerate() { + eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr)); + } + let addr = match err { + AsanError::Read(addr, _) | AsanError::Write(addr, _) | AsanError::BadFree(addr, _) => { + Some(addr) + } + AsanError::MemLeak(_) | AsanError::Signal(_) => None, + }; + if let Some(addr) = addr { + let print_bts = |item: &AllocTreeItem| { + if item.allocated { + eprintln!("Allocated at:"); + } else { + eprintln!("Freed at:"); + for (i, addr) in item.free_backtrace.iter().rev().enumerate() { + eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr)); + } + eprintln!("And previously allocated at:"); + } + + for (i, addr) in item.backtrace.iter().rev().enumerate() { + eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr)); + } + }; + + if let Some((chunk, item)) = rt.alloc_get_clone(addr) { + eprintln!( + "Address {addr:#x} is {} bytes inside the chunk [{:#x},{:#x})", + addr - chunk.start, + chunk.start, + chunk.end + ); + print_bts(&item); + } else { + let mut found = false; + rt.alloc_map_interval( + (addr..=(addr + DEFAULT_REDZONE_SIZE as GuestAddr)).into(), + |chunk, item| { + if found { + return; + } + found = true; + eprintln!( + "Address {addr:#x} is {} bytes to the left of the chunk [{:#x},{:#x})", + chunk.start - addr, + chunk.start, + chunk.end + ); + print_bts(item); + }, + ); + if !found { + rt.alloc_map_interval( + ((addr - DEFAULT_REDZONE_SIZE as GuestAddr)..addr).into(), + |chunk, item| { + if found { + return; + } + found = true; + eprintln!( + "Address {addr:#x} is {} bytes to the right of the chunk [{:#x},{:#x})", + addr - chunk.end, + chunk.start, + chunk.end + ); + print_bts(item); + }, + ); + } + } + } + + // fix pc in case it is not synced (in hooks) + emu.write_reg(Regs::Pc, pc).unwrap(); + eprint!("Context:\n{}", emu.current_cpu().unwrap().display_context()); +} diff --git a/libafl_qemu/src/calls.rs b/libafl_qemu/src/calls.rs index f22c9bc77a..88fce7f0ed 100644 --- a/libafl_qemu/src/calls.rs +++ b/libafl_qemu/src/calls.rs @@ -1,4 +1,4 @@ -use core::fmt::Debug; +use core::{cell::UnsafeCell, fmt::Debug}; use capstone::prelude::*; use libafl::{ @@ -7,6 +7,7 @@ use libafl::{ observers::{stacktrace::BacktraceObserver, ObserversTuple}, }; use libafl_bolts::{tuples::MatchFirstType, Named}; +use thread_local::ThreadLocal; use crate::{ capstone, @@ -410,6 +411,7 @@ where } } +// TODO support multiple threads with thread local callstack #[derive(Debug)] pub struct OnCrashBacktraceCollector { callstack_hash: u64, @@ -495,3 +497,91 @@ impl CallTraceCollector for OnCrashBacktraceCollector { observer.fill_external(self.callstack_hash, exit_kind); } } + +static mut CALLSTACKS: Option>>> = None; + +#[derive(Debug)] +pub struct FullBacktraceCollector {} + +impl Default for FullBacktraceCollector { + fn default() -> Self { + Self::new() + } +} + +impl FullBacktraceCollector { + pub fn new() -> Self { + unsafe { CALLSTACKS = Some(ThreadLocal::new()) }; + Self {} + } + + pub fn reset(&mut self) { + unsafe { + for tls in CALLSTACKS.as_mut().unwrap().iter_mut() { + (*tls.get()).clear(); + } + } + } + + pub fn backtrace() -> Option<&'static [GuestAddr]> { + unsafe { + if let Some(c) = CALLSTACKS.as_mut() { + Some(&*c.get_or_default().get()) + } else { + None + } + } + } +} + +impl CallTraceCollector for FullBacktraceCollector { + #[allow(clippy::unnecessary_cast)] + fn on_call( + &mut self, + _hooks: &mut QemuHooks<'_, QT, S>, + _state: Option<&mut S>, + pc: GuestAddr, + call_len: usize, + ) where + S: UsesInput, + QT: QemuHelperTuple, + { + // TODO handle Thumb + unsafe { + (*CALLSTACKS.as_mut().unwrap().get_or_default().get()).push(pc + call_len as GuestAddr); + } + } + + #[allow(clippy::unnecessary_cast)] + fn on_ret( + &mut self, + _hooks: &mut QemuHooks<'_, QT, S>, + _state: Option<&mut S>, + _pc: GuestAddr, + ret_addr: GuestAddr, + ) where + S: UsesInput, + QT: QemuHelperTuple, + { + unsafe { + let v = &mut *CALLSTACKS.as_mut().unwrap().get_or_default().get(); + if !v.is_empty() { + // if *v.last().unwrap() == ret_addr { + // v.pop(); + // } + while let Some(p) = v.pop() { + if p == ret_addr { + break; + } + } + } + } + } + + fn pre_exec(&mut self, _emulator: &Emulator, _input: &I) + where + I: Input, + { + self.reset(); + } +} diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index 3c9181a3eb..69a5e750c9 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -20,9 +20,10 @@ use std::{slice::from_raw_parts, str::from_utf8_unchecked}; use libc::c_int; use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_traits::Num; +use strum::IntoEnumIterator; use strum_macros::EnumIter; -use crate::GuestReg; +use crate::{GuestReg, Regs}; pub type GuestAddr = libafl_qemu_sys::target_ulong; pub type GuestUsize = libafl_qemu_sys::target_ulong; @@ -316,6 +317,8 @@ extern "C" { unsafe extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult; static mut libafl_post_syscall_hook: unsafe extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64; + + static mut libafl_dump_core_hook: unsafe extern "C" fn(i32); } #[cfg(emulation_mode = "systemmode")] @@ -729,14 +732,22 @@ impl CPU { pub fn save_state(&self) -> CPUArchState { unsafe { let mut saved = MaybeUninit::::uninit(); - copy_nonoverlapping(self.ptr.as_mut().unwrap().env_ptr, saved.as_mut_ptr(), 1); + copy_nonoverlapping( + libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()), + saved.as_mut_ptr(), + 1, + ); saved.assume_init() } } pub fn restore_state(&self, saved: &CPUArchState) { unsafe { - copy_nonoverlapping(saved, self.ptr.as_mut().unwrap().env_ptr, 1); + copy_nonoverlapping( + saved, + libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()), + 1, + ); } } @@ -766,6 +777,27 @@ impl CPU { unsafe { libafl_qemu_sys::qemu_target_page_size() } } } + + #[must_use] + pub fn display_context(&self) -> String { + let mut display = String::new(); + let mut maxl = 0; + for r in Regs::iter() { + maxl = std::cmp::max(format!("{r:#?}").len(), maxl); + } + for (i, r) in Regs::iter().enumerate() { + let v: GuestAddr = self.read_reg(r).unwrap(); + let sr = format!("{r:#?}"); + display += &format!("{sr:>maxl$}: {v:#016x} "); + if (i + 1) % 4 == 0 { + display += "\n"; + } + } + if !display.ends_with('\n') { + display += "\n"; + } + display + } } static mut EMULATOR_IS_INITIALIZED: bool = false; @@ -1302,6 +1334,14 @@ impl Emulator { pub fn gdb_reply(&self, output: &str) { unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) }; } + + #[cfg(emulation_mode = "usermode")] + #[allow(clippy::type_complexity)] + pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) { + unsafe { + libafl_dump_core_hook = callback; + } + } } impl ArchExtras for Emulator { diff --git a/libafl_qemu/src/helper.rs b/libafl_qemu/src/helper.rs index 5e110712b3..259af872d2 100644 --- a/libafl_qemu/src/helper.rs +++ b/libafl_qemu/src/helper.rs @@ -1,7 +1,7 @@ use core::{fmt::Debug, ops::Range}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple}; -use libafl_bolts::tuples::MatchFirstType; +use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType}; use crate::{ emu::{Emulator, GuestAddr}, @@ -42,7 +42,8 @@ where } } -pub trait QemuHelperTuple: MatchFirstType + Debug +pub trait QemuHelperTuple: + MatchFirstType + for<'a> SplitBorrowExtractFirstType<'a> + Debug where S: UsesInput, { diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 9204fecb55..850c15bae7 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -9,7 +9,11 @@ use core::{ ptr::{self, addr_of}, }; -use libafl::{executors::inprocess::inprocess_get_state, inputs::UsesInput}; +use libafl::{ + executors::{inprocess::inprocess_get_state, ExitKind}, + inputs::UsesInput, + state::NopState, +}; pub use crate::emu::SyscallHookResult; use crate::{ @@ -683,6 +687,33 @@ where } } +#[cfg(emulation_mode = "usermode")] +static mut CRASH_HOOKS: Vec = vec![]; + +#[cfg(emulation_mode = "usermode")] +extern "C" fn crash_hook_wrapper(target_sig: i32) +where + S: UsesInput, + QT: QemuHelperTuple, +{ + unsafe { + let hooks = get_qemu_hooks::(); + for hook in &mut CRASH_HOOKS { + match hook { + Hook::Function(ptr) => { + let func: fn(&mut QemuHooks<'_, QT, S>, i32) = transmute(*ptr); + func(hooks, target_sig); + } + Hook::Closure(ptr) => { + let func: &mut Box, i32)> = transmute(ptr); + func(hooks, target_sig); + } + _ => (), + } + } + } +} + static mut HOOKS_IS_INITIALIZED: bool = false; pub struct QemuHooks<'a, QT, S> @@ -708,6 +739,31 @@ where } } +impl<'a, I, QT> QemuHooks<'a, QT, NopState> +where + QT: QemuHelperTuple>, + NopState: UsesInput, +{ + pub fn reproducer(emulator: &'a Emulator, helpers: QT) -> Box { + Self::new(emulator, helpers) + } + + pub fn repro_run(&mut self, harness: &mut H, input: &I) -> ExitKind + where + H: FnMut(&I) -> ExitKind, + { + self.helpers.first_exec_all(self); + self.helpers.pre_exec_all(self.emulator, input); + + let mut exit_kind = harness(input); + + self.helpers + .post_exec_all(self.emulator, input, &mut (), &mut exit_kind); + + exit_kind + } +} + impl<'a, QT, S> QemuHooks<'a, QT, S> where QT: QemuHelperTuple, @@ -1515,4 +1571,20 @@ where self.emulator .set_post_syscall_hook(syscall_after_hooks_wrapper::); } + + #[cfg(emulation_mode = "usermode")] + pub fn crash_closure(&self, hook: Box) { + unsafe { + self.emulator.set_crash_hook(crash_hook_wrapper::); + CRASH_HOOKS.push(Hook::Closure(transmute(hook))); + } + } + + #[cfg(emulation_mode = "usermode")] + pub fn crash(&self, hook: fn(&mut Self, target_signal: i32)) { + unsafe { + self.emulator.set_crash_hook(crash_hook_wrapper::); + CRASH_HOOKS.push(Hook::Function(hook as *const libc::c_void)); + } + } }