diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index bc97002274..b72859f1b6 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -60,7 +60,7 @@ num_enum = { version = "0.5.4", default-features = false } typed-builder = "0.9.1" # Implement the builder pattern at compiletime ahash = { version = "0.7", default-features=false, features=["compile-time-rng"] } # The hash function already used in hashbrown intervaltree = { version = "0.2.7", default-features = false, features = ["serde"] } -backtrace = {version = "0.3.62", optional = true} # Used to get the stacktrace in StacktraceObserver +backtrace = {version = "0.3", optional = true} # Used to get the stacktrace in StacktraceObserver serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } miniz_oxide = { version = "0.5", optional = true} diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 1229c1a323..93be32e45a 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -36,7 +36,7 @@ capstone = "0.10.0" color-backtrace ={ version = "0.5", features = [ "resolve-modules" ] } termcolor = "1.1.2" serde = "1.0" -backtrace = { version = "0.3.58", default-features = false, features = ["std", "serde"] } +backtrace = { version = "0.3", default-features = false, features = ["std", "serde"] } num-traits = "0.2.14" ahash = "0.7" paste = "1.0" diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index d53e98f3b0..488dd4c253 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -34,7 +34,9 @@ goblin = "0.4.2" libc = "0.2" strum = "0.21" strum_macros = "0.21" -syscall-numbers = "2.0.0" +syscall-numbers = "2.0" +bio = "0.39" +thread_local = "1.1.3" #pyo3 = { version = "0.15", features = ["extension-module"], optional = true } pyo3 = { version = "0.15", optional = true } diff --git a/libafl_qemu/build.rs b/libafl_qemu/build.rs index 87ae41bcd8..98da94e10a 100644 --- a/libafl_qemu/build.rs +++ b/libafl_qemu/build.rs @@ -3,7 +3,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 = "fa2b9c4a25f548f15b3d1b1afcfdb75cc7165f9a"; +const QEMU_REVISION: &str = "152fdbe024493f31e60060714caee3b90fdf3d9e"; fn build_dep_check(tools: &[&str]) { for tool in tools { @@ -221,7 +221,6 @@ fn main() { "--disable-vvfat", "--disable-xen", "--disable-xen-pci-passthrough", - "--disable-xfsctl", ]) .status() .expect("Configure failed"); @@ -248,7 +247,7 @@ fn main() { for dir in &[ build_dir.join("libcommon.fa.p"), build_dir.join(&format!("libqemu-{}-linux-user.fa.p", cpu_target)), - build_dir.join("libcommon-user.fa.p"), + //build_dir.join("libcommon-user.fa.p"), //build_dir.join("libqemuutil.a.p"), //build_dir.join("libqom.fa.p"), //build_dir.join("libhwcore.fa.p"), diff --git a/libafl_qemu/src/aarch64.rs b/libafl_qemu/src/aarch64.rs index c51473a560..75cbbc8395 100644 --- a/libafl_qemu/src/aarch64.rs +++ b/libafl_qemu/src/aarch64.rs @@ -1,5 +1,5 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; -use strum_macros::EnumIter; +pub use strum_macros::EnumIter; #[cfg(feature = "python")] use pyo3::prelude::*; diff --git a/libafl_qemu/src/arm.rs b/libafl_qemu/src/arm.rs index 2223303c8a..548c299bdd 100644 --- a/libafl_qemu/src/arm.rs +++ b/libafl_qemu/src/arm.rs @@ -1,5 +1,5 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; -use strum_macros::EnumIter; +pub use strum_macros::EnumIter; #[cfg(feature = "python")] use pyo3::prelude::*; diff --git a/libafl_qemu/src/asan.rs b/libafl_qemu/src/asan.rs index a592360f5a..cf9cd9a95b 100644 --- a/libafl_qemu/src/asan.rs +++ b/libafl_qemu/src/asan.rs @@ -162,8 +162,9 @@ pub fn init_with_asan(args: &mut Vec, env: &mut [(String, String)]) -> E Emulator::new(args, env) } +pub type QemuAsanChildHelper = QemuAsanHelper; + #[derive(Debug)] -// TODO intrumentation filter pub struct QemuAsanHelper { enabled: bool, filter: QemuInstrumentationFilter, @@ -418,6 +419,8 @@ where I: Input, S: HasMetadata, { + const HOOKS_DO_SIDE_EFFECTS: bool = false; + fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) where H: FnMut(&I) -> ExitKind, diff --git a/libafl_qemu/src/cmplog.rs b/libafl_qemu/src/cmplog.rs index c238380a3f..4dbf518f71 100644 --- a/libafl_qemu/src/cmplog.rs +++ b/libafl_qemu/src/cmplog.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ emu::Emulator, executor::QemuExecutor, - helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, }; #[derive(Debug, Default, Serialize, Deserialize)] @@ -78,6 +78,57 @@ where } } +#[derive(Debug)] +pub struct QemuCmpLogChildHelper { + filter: QemuInstrumentationFilter, +} + +impl QemuCmpLogChildHelper { + #[must_use] + pub fn new() -> Self { + Self { + filter: QemuInstrumentationFilter::None, + } + } + + #[must_use] + pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { + Self { filter } + } + + #[must_use] + pub fn must_instrument(&self, addr: u64) -> bool { + self.filter.allowed(addr) + } +} + +impl Default for QemuCmpLogChildHelper { + fn default() -> Self { + Self::new() + } +} + +impl QemuHelper for QemuCmpLogChildHelper +where + I: Input, + S: HasMetadata, +{ + const HOOKS_DO_SIDE_EFFECTS: bool = false; + + fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + executor.hook_cmp_generation(gen_hashed_cmp_ids::); + executor.emulator().set_exec_cmp8_hook(trace_cmp8_cmplog); + executor.emulator().set_exec_cmp4_hook(trace_cmp4_cmplog); + executor.emulator().set_exec_cmp2_hook(trace_cmp2_cmplog); + executor.emulator().set_exec_cmp1_hook(trace_cmp1_cmplog); + } +} + pub fn gen_unique_cmp_ids( _emulator: &Emulator, helpers: &mut QT, @@ -110,6 +161,26 @@ where })) } +pub fn gen_hashed_cmp_ids( + _emulator: &Emulator, + helpers: &mut QT, + _state: &mut S, + pc: u64, + _size: usize, +) -> Option +where + S: HasMetadata, + I: Input, + QT: QemuHelperTuple, +{ + if let Some(h) = helpers.match_first_type::() { + if !h.must_instrument(pc) { + return None; + } + } + Some(hash_me(pc)) +} + pub extern "C" fn trace_cmp1_cmplog(id: u64, v0: u8, v1: u8) { unsafe { __libafl_targets_cmplog_instructions(id as usize, 1, u64::from(v0), u64::from(v1)); diff --git a/libafl_qemu/src/edges.rs b/libafl_qemu/src/edges.rs index 7da9b452f8..9bc8f43d00 100644 --- a/libafl_qemu/src/edges.rs +++ b/libafl_qemu/src/edges.rs @@ -1,13 +1,15 @@ use hashbrown::{hash_map::Entry, HashMap}; use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; -pub use libafl_targets::{EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM}; +pub use libafl_targets::{ + edges_max_num, EDGES_MAP, EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE, EDGES_MAP_SIZE, MAX_EDGES_NUM, +}; use serde::{Deserialize, Serialize}; use std::{cell::UnsafeCell, cmp::max}; use crate::{ emu::Emulator, executor::QemuExecutor, - helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, }; #[derive(Debug, Default, Serialize, Deserialize)] @@ -74,15 +76,58 @@ where } } -thread_local!(static PREV_LOC : UnsafeCell = UnsafeCell::new(0)); +pub type QemuEdgeCoverageWithBlocksHelper = QemuEdgeCoverageChildHelper; -fn hash_me(mut x: u64) -> u64 { - x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0; - x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0; - x = (x.overflowing_shr(16).0 ^ x) ^ x; - x +#[derive(Debug)] +pub struct QemuEdgeCoverageChildHelper { + filter: QemuInstrumentationFilter, } +impl QemuEdgeCoverageChildHelper { + #[must_use] + pub fn new() -> Self { + Self { + filter: QemuInstrumentationFilter::None, + } + } + + #[must_use] + pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { + Self { filter } + } + + #[must_use] + pub fn must_instrument(&self, addr: u64) -> bool { + self.filter.allowed(addr) + } +} + +impl Default for QemuEdgeCoverageChildHelper { + fn default() -> Self { + Self::new() + } +} + +impl QemuHelper for QemuEdgeCoverageChildHelper +where + I: Input, + S: HasMetadata, +{ + const HOOKS_DO_SIDE_EFFECTS: bool = false; + + fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + executor.hook_edge_generation(gen_unique_edge_ids::); + executor.emulator().set_exec_edge_hook(trace_edge_hitcount); + } +} + +thread_local!(static PREV_LOC : UnsafeCell = UnsafeCell::new(0)); + pub fn gen_unique_edge_ids( _emulator: &Emulator, helpers: &mut QT, @@ -140,7 +185,7 @@ where I: Input, QT: QemuHelperTuple, { - if let Some(h) = helpers.match_first_type::() { + if let Some(h) = helpers.match_first_type::() { if !h.must_instrument(src) && !h.must_instrument(dest) { return None; } @@ -181,8 +226,9 @@ pub fn gen_hashed_block_ids( pub extern "C" fn trace_block_transition_hitcount(id: u64) { unsafe { PREV_LOC.with(|prev_loc| { - let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_SIZE - 1); - EDGES_MAP[x] = EDGES_MAP[x].wrapping_add(1); + let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_PTR_SIZE - 1); + let entry = EDGES_MAP_PTR.add(x); + *entry = (*entry).wrapping_add(1); *prev_loc.get() = id.overflowing_shr(1).0; }); } @@ -191,8 +237,9 @@ pub extern "C" fn trace_block_transition_hitcount(id: u64) { pub extern "C" fn trace_block_transition_single(id: u64) { unsafe { PREV_LOC.with(|prev_loc| { - let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_SIZE - 1); - EDGES_MAP[x] = 1; + let x = ((*prev_loc.get() ^ id) as usize) & (EDGES_MAP_PTR_SIZE - 1); + let entry = EDGES_MAP_PTR.add(x); + *entry = 1; *prev_loc.get() = id.overflowing_shr(1).0; }); } diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index a43fc4ba22..c076a58422 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -27,7 +27,7 @@ use pyo3::{prelude::*, PyIterProtocol}; pub const SKIP_EXEC_HOOK: u64 = u64::MAX; -#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] +#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter, PartialEq)] #[repr(i32)] pub enum MmapPerms { None = 0, @@ -201,7 +201,7 @@ extern "C" { fn target_mmap(start: u64, len: u64, target_prot: i32, flags: i32, fd: i32, offset: u64) -> u64; - /// int target_mprotect(abi_ulong start, abi_ulong len, int prot); + /// int target_mprotect(abi_ulong start, abi_ulong len, int prot) fn target_mprotect(start: u64, len: u64, target_prot: i32) -> i32; /// int target_munmap(abi_ulong start, abi_ulong len) diff --git a/libafl_qemu/src/helper.rs b/libafl_qemu/src/helper.rs index d4b383409d..49596ff672 100644 --- a/libafl_qemu/src/helper.rs +++ b/libafl_qemu/src/helper.rs @@ -11,6 +11,8 @@ pub trait QemuHelper: 'static + Debug where I: Input, { + const HOOKS_DO_SIDE_EFFECTS: bool = true; + fn init<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) where H: FnMut(&I) -> ExitKind, @@ -114,3 +116,11 @@ impl QemuInstrumentationFilter { } } } + +#[must_use] +pub fn hash_me(mut x: u64) -> u64 { + x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0; + x = (x.overflowing_shr(16).0 ^ x).overflowing_mul(0x45d9f3b).0; + x = (x.overflowing_shr(16).0 ^ x) ^ x; + x +} diff --git a/libafl_qemu/src/i386.rs b/libafl_qemu/src/i386.rs index 5f3cea5d4a..477ea22d37 100644 --- a/libafl_qemu/src/i386.rs +++ b/libafl_qemu/src/i386.rs @@ -1,5 +1,5 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; -use strum_macros::EnumIter; +pub use strum_macros::EnumIter; #[cfg(feature = "python")] use pyo3::prelude::*; diff --git a/libafl_qemu/src/snapshot.rs b/libafl_qemu/src/snapshot.rs index 4de98a8c27..8c15b66df2 100644 --- a/libafl_qemu/src/snapshot.rs +++ b/libafl_qemu/src/snapshot.rs @@ -1,11 +1,17 @@ +use bio::data_structures::interval_tree::IntervalTree; use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; -use std::collections::HashMap; +use std::{ + cell::UnsafeCell, + collections::{HashMap, HashSet}, + sync::Mutex, +}; +use thread_local::ThreadLocal; use crate::{ - emu::Emulator, + emu::{Emulator, MmapPerms}, executor::QemuExecutor, helper::{QemuHelper, QemuHelperTuple}, - GuestAddr, SYS_mmap, SYS_mremap, + GuestAddr, SYS_mmap, SYS_mprotect, SYS_mremap, }; pub const SNAPSHOT_PAGE_SIZE: usize = 4096; @@ -13,19 +19,32 @@ pub const SNAPSHOT_PAGE_SIZE: usize = 4096; #[derive(Debug)] pub struct SnapshotPageInfo { pub addr: GuestAddr, - pub dirty: bool, - pub data: [u8; SNAPSHOT_PAGE_SIZE], + pub perms: MmapPerms, + pub private: bool, + pub data: Option>, +} + +#[derive(Default, Debug)] +pub struct SnapshotAccessInfo { + pub access_cache: [GuestAddr; 4], + pub access_cache_idx: usize, + pub dirty: HashSet, +} + +impl SnapshotAccessInfo { + pub fn clear(&mut self) { + self.access_cache_idx = 0; + self.access_cache = [GuestAddr::MAX; 4]; + self.dirty.clear(); + } } #[derive(Debug)] -// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html pub struct QemuSnapshotHelper { - pub access_cache: [GuestAddr; 4], - pub access_cache_idx: usize, + pub accesses: ThreadLocal>, + pub new_maps: Mutex>>, pub pages: HashMap, - pub dirty: Vec, pub brk: GuestAddr, - pub new_maps: Vec<(GuestAddr, usize)>, pub empty: bool, } @@ -33,32 +52,33 @@ impl QemuSnapshotHelper { #[must_use] pub fn new() -> Self { Self { - access_cache: [GuestAddr::MAX; 4], - access_cache_idx: 0, + accesses: ThreadLocal::new(), + new_maps: Mutex::new(IntervalTree::new()), pages: HashMap::default(), - dirty: vec![], brk: 0, - new_maps: vec![], empty: true, } } + #[allow(clippy::uninit_assumed_init)] pub fn snapshot(&mut self, emulator: &Emulator) { self.brk = emulator.get_brk(); self.pages.clear(); for map in emulator.mappings() { - // TODO track all the pages OR track mproctect - if !map.flags().is_w() { - continue; - } let mut addr = map.start(); while addr < map.end() { let mut info = SnapshotPageInfo { addr, - dirty: false, - data: [0; SNAPSHOT_PAGE_SIZE], + perms: map.flags(), + private: map.is_priv(), + data: None, }; - unsafe { emulator.read_mem(addr, &mut info.data) }; + if map.flags().is_w() { + unsafe { + info.data = Some(Box::new(core::mem::MaybeUninit::uninit().assume_init())); + emulator.read_mem(addr, &mut info.data.as_mut().unwrap()[..]); + } + } self.pages.insert(addr, info); addr += SNAPSHOT_PAGE_SIZE as GuestAddr; } @@ -67,22 +87,20 @@ impl QemuSnapshotHelper { } pub fn page_access(&mut self, page: GuestAddr) { - if self.access_cache[0] == page - || self.access_cache[1] == page - || self.access_cache[2] == page - || self.access_cache[3] == page - { - return; - } - self.access_cache[self.access_cache_idx] = page; - self.access_cache_idx = (self.access_cache_idx + 1) & 3; - if let Some(info) = self.pages.get_mut(&page) { - if info.dirty { + unsafe { + let acc = self.accesses.get_or_default().get(); + if (*acc).access_cache[0] == page + || (*acc).access_cache[1] == page + || (*acc).access_cache[2] == page + || (*acc).access_cache[3] == page + { return; } - info.dirty = true; + let idx = (*acc).access_cache_idx; + (*acc).access_cache[idx] = page; + (*acc).access_cache_idx = (idx + 1) & 3; + (*acc).dirty.insert(page); } - self.dirty.push(page); } pub fn access(&mut self, addr: GuestAddr, size: usize) { @@ -96,27 +114,62 @@ impl QemuSnapshotHelper { } pub fn reset(&mut self, emulator: &Emulator) { - self.access_cache = [GuestAddr::MAX; 4]; - self.access_cache_idx = 0; - while let Some(page) = self.dirty.pop() { - if let Some(info) = self.pages.get_mut(&page) { - unsafe { emulator.write_mem(page, &info.data) }; - info.dirty = false; + self.reset_maps(emulator); + for acc in self.accesses.iter_mut() { + for page in unsafe { &(*acc.get()).dirty } { + if let Some(info) = self.pages.get_mut(page) { + // TODO avoid duplicated memcpy + if let Some(data) = info.data.as_ref() { + unsafe { emulator.write_mem(*page, &data[..]) }; + } + } } + unsafe { (*acc.get()).clear() }; } emulator.set_brk(self.brk); - self.reset_maps(emulator); } - pub fn add_mapped(&mut self, start: GuestAddr, size: usize) { - self.new_maps.push((start, size)); + pub fn add_mapped(&mut self, start: GuestAddr, mut size: usize, perms: Option) { + if size % SNAPSHOT_PAGE_SIZE != 0 { + size = size + (SNAPSHOT_PAGE_SIZE - size % SNAPSHOT_PAGE_SIZE); + } + self.new_maps + .lock() + .unwrap() + .insert(start..start + (size as GuestAddr), perms); } pub fn reset_maps(&mut self, emulator: &Emulator) { - for (addr, size) in &self.new_maps { - drop(emulator.unmap(*addr, *size)); + let new_maps = self.new_maps.get_mut().unwrap(); + for r in new_maps.find(0..GuestAddr::MAX) { + let addr = r.interval().start; + let end = r.interval().end; + let perms = r.data(); + let mut page = addr & (SNAPSHOT_PAGE_SIZE as GuestAddr - 1); + let mut prev = None; + while page < end { + if let Some(info) = self.pages.get(&page) { + if let Some((addr, size)) = prev { + drop(emulator.unmap(addr, size)); + } + prev = None; + if let Some(p) = perms { + if info.perms != *p { + drop(emulator.mprotect(page, SNAPSHOT_PAGE_SIZE, info.perms)); + } + } + } else if let Some((_, size)) = &mut prev { + *size += SNAPSHOT_PAGE_SIZE; + } else { + prev = Some((page, SNAPSHOT_PAGE_SIZE)); + } + page += SNAPSHOT_PAGE_SIZE as GuestAddr; + } + if let Some((addr, size)) = prev { + drop(emulator.unmap(addr, size)); + } } - self.new_maps.clear(); + *new_maps = IntervalTree::new(); } } @@ -262,15 +315,24 @@ where return result; } if i64::from(sys_num) == SYS_mmap { - let h = helpers - .match_first_type_mut::() - .unwrap(); - h.add_mapped(result as GuestAddr, a1 as usize); + if let Ok(prot) = MmapPerms::try_from(a2 as i32) { + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.add_mapped(result as GuestAddr, a1 as usize, Some(prot)); + } } else if i64::from(sys_num) == SYS_mremap { let h = helpers .match_first_type_mut::() .unwrap(); - h.add_mapped(a0 as GuestAddr, a2 as usize); + h.add_mapped(result as GuestAddr, a2 as usize, None); + } else if i64::from(sys_num) == SYS_mprotect { + if let Ok(prot) = MmapPerms::try_from(a2 as i32) { + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.add_mapped(a0 as GuestAddr, a2 as usize, Some(prot)); + } } result } diff --git a/libafl_qemu/src/x86_64.rs b/libafl_qemu/src/x86_64.rs index ba20a88b85..5d5fc0f5f1 100644 --- a/libafl_qemu/src/x86_64.rs +++ b/libafl_qemu/src/x86_64.rs @@ -1,5 +1,5 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; -use strum_macros::EnumIter; +pub use strum_macros::EnumIter; #[cfg(feature = "python")] use pyo3::prelude::*;