diff --git a/fuzzers/qemu_arm_launcher/.gitignore b/fuzzers/qemu_arm_launcher/.gitignore index d69c2ab133..11fb5666bc 100644 --- a/fuzzers/qemu_arm_launcher/.gitignore +++ b/fuzzers/qemu_arm_launcher/.gitignore @@ -4,3 +4,4 @@ libpng_harness_crashing zlib-* crashes target +drcov.log diff --git a/fuzzers/qemu_arm_launcher/Cargo.toml b/fuzzers/qemu_arm_launcher/Cargo.toml index c1fe49b378..7e9d7dcb08 100644 --- a/fuzzers/qemu_arm_launcher/Cargo.toml +++ b/fuzzers/qemu_arm_launcher/Cargo.toml @@ -17,3 +17,4 @@ debug = true [dependencies] libafl = { path = "../../libafl/" } libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "usermode"] } +rangemap = "1.0.3" diff --git a/fuzzers/qemu_arm_launcher/src/fuzzer.rs b/fuzzers/qemu_arm_launcher/src/fuzzer.rs index 845a8e8be9..780cf2db1c 100644 --- a/fuzzers/qemu_arm_launcher/src/fuzzer.rs +++ b/fuzzers/qemu_arm_launcher/src/fuzzer.rs @@ -29,6 +29,7 @@ use libafl::{ Error, }; use libafl_qemu::{ + drcov::QemuDrCovHelper, //asan::QemuAsanHelper, edges, edges::QemuEdgeCoverageHelper, @@ -37,8 +38,10 @@ use libafl_qemu::{ MmapPerms, QemuExecutor, QemuHooks, + QemuInstrumentationFilter, Regs, }; +use rangemap::RangeMap; pub fn fuzz() { // Hardcoded parameters @@ -151,7 +154,30 @@ pub fn fuzz() { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let mut hooks = QemuHooks::new(&emu, tuple_list!(QemuEdgeCoverageHelper::default())); + let mut rangemap = RangeMap::::new(); + let mappings = emu.mappings(); + let mut idx = 0; + for map in mappings { + if map.path().unwrap() != "" { + rangemap.insert( + (map.start() as usize)..(map.end() as usize), + (idx, map.path().unwrap().to_string()), + ); + idx += 1; + } + } + let mut hooks = QemuHooks::new( + &emu, + tuple_list!( + QemuEdgeCoverageHelper::default(), + QemuDrCovHelper::new( + QemuInstrumentationFilter::None, + rangemap, + PathBuf::from("drcov.log"), + false, + ) + ), + ); // Create a QEMU in-process executor let executor = QemuExecutor::new( diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 126700cae4..f539397a75 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -46,6 +46,7 @@ thread_local = "1.1.4" capstone = "0.11.0" #pyo3 = { version = "0.15", features = ["extension-module"], optional = true } pyo3 = { version = "0.17", features = ["pyproto"], optional = true } +rangemap = "1.0" [build-dependencies] cc = { version = "1.0" } diff --git a/libafl_qemu/src/blocks.rs b/libafl_qemu/src/blocks.rs new file mode 100644 index 0000000000..97e4c150a9 --- /dev/null +++ b/libafl_qemu/src/blocks.rs @@ -0,0 +1,113 @@ +use capstone::prelude::*; + +use crate::{Emulator, GuestAddr}; + +pub struct Instruction { + pub start_addr: GuestAddr, + pub mnemonic: String, + pub operands: String, + pub insn_len: usize, +} + +/* + * Generating the basic block from it's starting address (pc) + * Basic block: + * - Starting at pc + * - Ending at the first branch/jump/interrupt/call instruction + * Output: + * - Vector of instructions + * - Start address + * - mnemonic string + * - operand string + * - instruction length + */ +pub fn pc2basicblock(pc: GuestAddr, emu: &Emulator) -> Result, String> { + #[allow(unused_mut)] + let mut code = { + #[cfg(emulation_mode = "usermode")] + unsafe { + std::slice::from_raw_parts(emu.g2h(pc), 512) + } + #[cfg(emulation_mode = "systemmode")] + &mut [0; 512] + }; + #[cfg(emulation_mode = "systemmode")] + unsafe { + emu.read_mem(pc, code) + }; + // TODO better fault handling + if code.iter().all(|&x| x == 0) { + return Err("Memory region is empty".to_string()); + } + + let mut iaddr = pc; + let mut block = Vec::::new(); + + #[cfg(cpu_target = "x86_64")] + let cs = Capstone::new() + .x86() + .mode(capstone::arch::x86::ArchMode::Mode64) + .detail(true) + .build() + .unwrap(); + #[cfg(cpu_target = "arm")] + let cs = Capstone::new() + .arm() + .mode(capstone::arch::arm::ArchMode::Arm) + .detail(true) + .build() + .unwrap(); + #[cfg(cpu_target = "aarch64")] + let cs = Capstone::new() + .arm64() + .mode(capstone::arch::arm64::ArchMode::Arm) + .detail(true) + .build() + .unwrap(); + + 'disasm: while let Ok(insns) = cs.disasm_count(code, iaddr.into(), 1) { + if insns.is_empty() { + break; + } + let insn = insns.first().unwrap(); + let insn_detail: InsnDetail = cs.insn_detail(insn).unwrap(); + block.push(Instruction { + start_addr: insn.address() as GuestAddr, + mnemonic: insn.mnemonic().unwrap().to_string(), + operands: insn.op_str().unwrap().to_string(), + insn_len: insn.len(), + }); + for detail in insn_detail.groups() { + match u32::from(detail.0) { + capstone::InsnGroupType::CS_GRP_BRANCH_RELATIVE + | capstone::InsnGroupType::CS_GRP_CALL + | capstone::InsnGroupType::CS_GRP_INT + | capstone::InsnGroupType::CS_GRP_INVALID + | capstone::InsnGroupType::CS_GRP_IRET + | capstone::InsnGroupType::CS_GRP_JUMP + | capstone::InsnGroupType::CS_GRP_PRIVILEGE + | capstone::InsnGroupType::CS_GRP_RET => { + break 'disasm; + } + _ => {} + } + } + + iaddr += insn.bytes().len() as GuestAddr; + + #[cfg(emulation_mode = "usermode")] + unsafe { + code = std::slice::from_raw_parts(emu.g2h(iaddr), 512); + } + #[cfg(emulation_mode = "systemmode")] + unsafe { + emu.read_mem(iaddr, code); + } + // TODO better fault handling + if code.iter().all(|&x| x == 0) { + return Err("Memory region is empty".to_string()); + } + } + + Ok(block) +} diff --git a/libafl_qemu/src/drcov.rs b/libafl_qemu/src/drcov.rs new file mode 100644 index 0000000000..760baf1307 --- /dev/null +++ b/libafl_qemu/src/drcov.rs @@ -0,0 +1,233 @@ +use std::{path::PathBuf, sync::Mutex}; + +use hashbrown::{hash_map::Entry, HashMap}; +use libafl::{inputs::UsesInput, state::HasMetadata}; +use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter}; +use rangemap::RangeMap; +use serde::{Deserialize, Serialize}; + +use crate::{ + blocks::pc2basicblock, + emu::GuestAddr, + helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + hooks::QemuHooks, + Emulator, +}; + +static DRCOV_IDS: Mutex>> = Mutex::new(None); +static DRCOV_MAP: Mutex>> = Mutex::new(None); + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct QemuDrCovMetadata { + pub current_id: u64, +} + +impl QemuDrCovMetadata { + #[must_use] + pub fn new() -> Self { + Self { current_id: 0 } + } +} + +libafl::impl_serdeany!(QemuDrCovMetadata); + +#[derive(Debug)] +pub struct QemuDrCovHelper { + filter: QemuInstrumentationFilter, + module_mapping: RangeMap, + filename: PathBuf, + full_trace: bool, + drcov_len: usize, +} + +impl QemuDrCovHelper { + #[must_use] + pub fn new( + filter: QemuInstrumentationFilter, + module_mapping: RangeMap, + filename: PathBuf, + full_trace: bool, + ) -> Self { + if full_trace { + let _ = DRCOV_IDS.lock().unwrap().insert(vec![]); + } + let _ = DRCOV_MAP.lock().unwrap().insert(HashMap::new()); + Self { + filter, + module_mapping, + filename, + full_trace, + drcov_len: 0, + } + } + + #[must_use] + pub fn must_instrument(&self, addr: u64) -> bool { + self.filter.allowed(addr) + } +} + +impl QemuHelper for QemuDrCovHelper +where + S: UsesInput + HasMetadata, +{ + fn init_hooks(&self, hooks: &QemuHooks<'_, QT, S>) + where + QT: QemuHelperTuple, + { + hooks.blocks( + Some(gen_unique_block_ids::), + Some(exec_trace_block::), + ); + } + + fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {} + + fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input) { + if self.full_trace { + if DRCOV_IDS.lock().unwrap().as_ref().unwrap().len() > self.drcov_len { + let mut drcov_vec = Vec::::new(); + for id in DRCOV_IDS.lock().unwrap().as_ref().unwrap().iter() { + 'pcs_full: for (pc, idm) in DRCOV_MAP.lock().unwrap().as_ref().unwrap().iter() { + let mut module_found = false; + for module in self.module_mapping.iter() { + let (range, (_, _)) = module; + if *pc >= range.start.try_into().unwrap() + && *pc <= range.end.try_into().unwrap() + { + module_found = true; + break; + } + } + if !module_found { + continue 'pcs_full; + } + if *idm == *id { + match pc2basicblock(*pc, emulator) { + Ok(block) => { + let mut block_len = 0; + for instr in &block { + block_len += instr.insn_len; + } + drcov_vec.push(DrCovBasicBlock::new( + *pc as usize, + *pc as usize + block_len, + )); + } + Err(r) => println!("{r:#?}"), + } + } + } + } + + DrCovWriter::new(&self.module_mapping) + .write(&self.filename, &drcov_vec) + .expect("Failed to write coverage file"); + } + self.drcov_len = DRCOV_IDS.lock().unwrap().as_ref().unwrap().len(); + } else { + if DRCOV_MAP.lock().unwrap().as_ref().unwrap().len() > self.drcov_len { + let mut drcov_vec = Vec::::new(); + 'pcs: for (pc, _) in DRCOV_MAP.lock().unwrap().as_ref().unwrap().iter() { + let mut module_found = false; + for module in self.module_mapping.iter() { + let (range, (_, _)) = module; + if *pc >= range.start.try_into().unwrap() + && *pc <= range.end.try_into().unwrap() + { + module_found = true; + break; + } + } + if !module_found { + continue 'pcs; + } + match pc2basicblock(*pc, emulator) { + Ok(block) => { + let mut block_len = 0; + for instr in &block { + block_len += instr.insn_len; + } + drcov_vec + .push(DrCovBasicBlock::new(*pc as usize, *pc as usize + block_len)); + } + Err(r) => println!("{r:#?}"), + } + } + + DrCovWriter::new(&self.module_mapping) + .write(&self.filename, &drcov_vec) + .expect("Failed to write coverage file"); + } + self.drcov_len = DRCOV_MAP.lock().unwrap().as_ref().unwrap().len(); + } + } +} + +pub fn gen_unique_block_ids( + hooks: &mut QemuHooks<'_, QT, S>, + state: Option<&mut S>, + pc: GuestAddr, +) -> Option +where + S: HasMetadata, + S: UsesInput, + QT: QemuHelperTuple, +{ + let drcov_helper = hooks + .helpers() + .match_first_type::() + .unwrap(); + if !drcov_helper.must_instrument(pc.into()) { + return None; + } + + let state = state.expect("The gen_unique_block_ids hook works only for in-process fuzzing"); + if state + .metadata_mut() + .get_mut::() + .is_none() + { + state.add_metadata(QemuDrCovMetadata::new()); + } + let meta = state.metadata_mut().get_mut::().unwrap(); + + match DRCOV_MAP.lock().unwrap().as_mut().unwrap().entry(pc) { + Entry::Occupied(e) => { + let id = *e.get(); + if drcov_helper.full_trace { + Some(id) + } else { + None + } + } + Entry::Vacant(e) => { + let id = meta.current_id; + e.insert(id); + meta.current_id = id + 1; + if drcov_helper.full_trace { + // GuestAddress is u32 for 32 bit guests + #[allow(clippy::unnecessary_cast)] + Some(id as u64) + } else { + None + } + } + } +} + +pub fn exec_trace_block(hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, id: u64) +where + S: HasMetadata, + S: UsesInput, + QT: QemuHelperTuple, +{ + if hooks + .helpers() + .match_first_type::() + .unwrap() + .full_trace + { + DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id); + } +} diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 58f3c107c9..5583f89a1f 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -17,7 +17,6 @@ use libafl::{ Error, }; -pub use crate::emu::SyscallHookResult; use crate::{emu::Emulator, helper::QemuHelperTuple, hooks::QemuHooks}; pub struct QemuExecutor<'a, H, OT, QT, S> diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index f5c4fac701..34f472f588 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -49,6 +49,7 @@ pub use hooks::*; pub mod edges; pub use edges::QemuEdgeCoverageHelper; pub mod cmplog; +pub mod drcov; pub use cmplog::QemuCmpLogHelper; #[cfg(emulation_mode = "usermode")] pub mod snapshot; @@ -59,6 +60,7 @@ pub mod asan; #[cfg(emulation_mode = "usermode")] pub use asan::{init_with_asan, QemuAsanHelper}; +pub mod blocks; pub mod calls; pub mod executor;