From 42b7c6d7e74f77810c527dc1c3410bb24be48c04 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Tue, 13 Jul 2021 16:02:53 +0200 Subject: [PATCH] Qemu CmpLog (#223) * empty libafl_qemu crate * fuzzbench qemu fuzzer skeleton * emu.run() works without bp * working emu loop * resolve elf symbols * running Qemu fuzzer without coverage * qemu fuzzer with edge coverage * merge into inprocess::GLOBAL_STATE * create QemuExecutor and remove QemuEmulator * qemu hooks and persist edges mapping storing them in State * windows fix * add libafl_qemu to workspace * windows fix * some clippy * clippy * fix fuzzbench_qemu * fix fuzzbench_qemu makefile * fuck you macos * resolve PIC symbols * cmp hooks * cmplog hooks * qemu cmplog * clippy --- fuzzers/fuzzbench_qemu/Makefile | 1 + fuzzers/fuzzbench_qemu/fuzz.c | 2 +- fuzzers/fuzzbench_qemu/src/fuzzer.rs | 38 +++++++++---- libafl/src/corpus/ondisk.rs | 4 +- libafl/src/lib.rs | 3 - libafl_qemu/src/elf.rs | 8 +-- libafl_qemu/src/emu.rs | 34 ++++++++++-- libafl_qemu/src/executor.rs | 51 +++++++++++++++-- libafl_qemu/src/hooks.rs | 82 ++++++++++++++++++++++++---- libafl_qemu/src/weaks.c | 6 ++ libafl_targets/src/cmplog.rs | 5 ++ 11 files changed, 195 insertions(+), 39 deletions(-) diff --git a/fuzzers/fuzzbench_qemu/Makefile b/fuzzers/fuzzbench_qemu/Makefile index edbae46d36..679c6d61f9 100644 --- a/fuzzers/fuzzbench_qemu/Makefile +++ b/fuzzers/fuzzbench_qemu/Makefile @@ -33,6 +33,7 @@ target/$(BUILD_TARGET)/lib$(FUZZER_NAME).a: src/* qemu-libafl-bridge: git clone git@github.com:AFLplusplus/qemu-libafl-bridge.git + cd qemu-libafl-bridge && git checkout f71558480c336b51360e8d382b95c2bb753b785b build/config.status: qemu-libafl-bridge qemu-libafl-bridge/configure mkdir -p build diff --git a/fuzzers/fuzzbench_qemu/fuzz.c b/fuzzers/fuzzbench_qemu/fuzz.c index 5f2460d84e..70afd693f1 100644 --- a/fuzzers/fuzzbench_qemu/fuzz.c +++ b/fuzzers/fuzzbench_qemu/fuzz.c @@ -4,7 +4,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { // printf("Got %ld bytes.\n", Size); - if (Size >= 4 && *(uint16_t*)Data == 0xaabb && *(uint16_t*)&Data[2] == 0xccab) + if (Size >= 4 && *(uint32_t*)Data == 0xaabbccdd) abort(); } diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index 4eae473f6d..9a2ff1918c 100644 --- a/fuzzers/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -24,23 +24,24 @@ use libafl::{ }, corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler}, events::SimpleRestartingEventManager, - executors::{ExitKind, TimeoutExecutor}, + executors::{ExitKind, ShadowExecutor, TimeoutExecutor}, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes}, mutators::{ scheduled::{havoc_mutations, StdScheduledMutator}, - tokens_mutations, Tokens, + tokens_mutations, I2SRandReplace, Tokens, }, observers::{HitcountsMapObserver, TimeObserver, VariableMapObserver}, - stages::StdMutationalStage, + stages::{ShadowTracingStage, StdMutationalStage}, state::{HasCorpus, HasMetadata, StdState}, stats::SimpleStats, Error, }; use libafl_qemu::{ - amd64::Amd64Regs, elf::EasyElf, emu, filter_qemu_args, hooks, MmapPerms, QemuExecutor, + amd64::Amd64Regs, elf::EasyElf, emu, filter_qemu_args, hooks, hooks::CmpLogObserver, MmapPerms, + QemuExecutor, }; /// The fuzzer main (as `no_mangle` C function) @@ -228,6 +229,9 @@ fn fuzz( // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); + // Create an observation channel using cmplog map + let cmplog_observer = CmpLogObserver::new("cmplog", unsafe { &mut hooks::CMPLOG_MAP }, true); + // The state of the edges feedback. let feedback_state = MapFeedbackState::with_observer(&edges_observer); @@ -269,8 +273,8 @@ fn fuzz( let mut harness = |input: &BytesInput| { let target = input.target_bytes(); let mut buf = target.as_slice(); - if buf.len() > 32 { - buf = &buf[0..32]; + if buf.len() > 4096 { + buf = &buf[0..4096]; } emu::write_mem(input_addr, buf); @@ -293,11 +297,19 @@ fn fuzz( &mut mgr, )?; - executor.hook_edge_generation(hooks::gen_unique_edges_id); - executor.hook_edge_execution(hooks::exec_log_hitcount); + // Track edge coverage + executor.hook_edge_generation(hooks::gen_unique_edge_ids); + executor.hook_edge_execution(hooks::trace_edge_hitcount); + // Track 2-4-8 cmps with CmpLog + executor.hook_cmp_generation(hooks::gen_unique_cmp_ids); + executor.hook_cmp8_execution(hooks::trace_cmp8_cmplog); + executor.hook_cmp4_execution(hooks::trace_cmp4_cmplog); + executor.hook_cmp2_execution(hooks::trace_cmp2_cmplog); // Create the executor for an in-process function with one observer for edge coverage and one for the execution time - let mut executor = TimeoutExecutor::new(executor, timeout); + let executor = TimeoutExecutor::new(executor, timeout); + // Show the cmplog observer + let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer)); // Read tokens if let Some(tokenfile) = tokenfile { @@ -316,9 +328,15 @@ fn fuzz( println!("We imported {} inputs from disk.", state.corpus().count()); } + let tracing = ShadowTracingStage::new(&mut executor); + + // Setup a randomic Input2State stage + let i2s = StdMutationalStage::new(I2SRandReplace::new()); + // Setup a mutational stage with a basic bytes mutator let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); - let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + let mut stages = tuple_list!(tracing, i2s, StdMutationalStage::new(mutator)); // Remove target ouput (logs still survive) #[cfg(unix)] diff --git a/libafl/src/corpus/ondisk.rs b/libafl/src/corpus/ondisk.rs index 5f42d6f192..e538a65bdf 100644 --- a/libafl/src/corpus/ondisk.rs +++ b/libafl/src/corpus/ondisk.rs @@ -129,7 +129,7 @@ where Ok(Self { entries: vec![], current: None, - dir_path: dir_path, + dir_path, meta_format: None, }) } @@ -144,7 +144,7 @@ where Ok(Self { entries: vec![], current: None, - dir_path: dir_path, + dir_path, meta_format, }) } diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index f865bdea03..73469ec0e2 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -45,9 +45,6 @@ use core::fmt; #[cfg(feature = "std")] use std::{env::VarError, io, num::ParseIntError, num::TryFromIntError, string::FromUtf8Error}; -#[cfg(all(unix, feature = "std"))] -use nix; - /// Main error struct for AFL #[derive(Debug)] pub enum Error { diff --git a/libafl_qemu/src/elf.rs b/libafl_qemu/src/elf.rs index 456d3b8001..5ee8fbb36c 100644 --- a/libafl_qemu/src/elf.rs +++ b/libafl_qemu/src/elf.rs @@ -44,12 +44,10 @@ impl<'a> EasyElf<'a> { if sym_name == name { return if sym.st_value == 0 { None + } else if self.is_pic() { + Some(sym.st_value + load_addr) } else { - if self.is_pic() { - Some(sym.st_value + load_addr) - } else { - Some(sym.st_value) - } + Some(sym.st_value) }; } } diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index 37ae48cf22..1ef7e51604 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -32,6 +32,12 @@ extern "C" { static mut libafl_gen_edge_hook: unsafe extern "C" fn(u64, u64) -> u32; static mut libafl_exec_block_hook: unsafe extern "C" fn(u64); static mut libafl_gen_block_hook: unsafe extern "C" fn(u64) -> u32; + + static mut libafl_exec_cmp_hook1: unsafe extern "C" fn(u32, u8, u8); + static mut libafl_exec_cmp_hook2: unsafe extern "C" fn(u32, u16, u16); + static mut libafl_exec_cmp_hook4: unsafe extern "C" fn(u32, u32, u32); + static mut libafl_exec_cmp_hook8: unsafe extern "C" fn(u32, u64, u64); + static mut libafl_gen_cmp_hook: unsafe extern "C" fn(u64, u32) -> u32; } #[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy)] @@ -161,18 +167,38 @@ pub fn unmap(addr: u64, size: usize) -> Result<(), String> { } } -pub fn set_exec_edge_hook(hook: extern "C" fn(u32)) { +pub fn set_exec_edge_hook(hook: extern "C" fn(id: u32)) { unsafe { libafl_exec_edge_hook = hook }; } -pub fn set_gen_edge_hook(hook: extern "C" fn(u64, u64) -> u32) { +pub fn set_gen_edge_hook(hook: extern "C" fn(src: u64, dest: u64) -> u32) { unsafe { libafl_gen_edge_hook = hook }; } -pub fn set_exec_block_hook(hook: extern "C" fn(u64)) { +pub fn set_exec_block_hook(hook: extern "C" fn(addr: u64)) { unsafe { libafl_exec_block_hook = hook }; } -pub fn set_gen_block_hook(hook: extern "C" fn(u64) -> u32) { +pub fn set_gen_block_hook(hook: extern "C" fn(addr: u64) -> u32) { unsafe { libafl_gen_block_hook = hook }; } + +pub fn set_exec_cmp1_hook(hook: extern "C" fn(id: u32, v0: u8, v1: u8)) { + unsafe { libafl_exec_cmp_hook1 = hook }; +} + +pub fn set_exec_cmp2_hook(hook: extern "C" fn(id: u32, v0: u16, v1: u16)) { + unsafe { libafl_exec_cmp_hook2 = hook }; +} + +pub fn set_exec_cmp4_hook(hook: extern "C" fn(id: u32, v0: u32, v1: u32)) { + unsafe { libafl_exec_cmp_hook4 = hook }; +} + +pub fn set_exec_cmp8_hook(hook: extern "C" fn(id: u32, v0: u64, v1: u64)) { + unsafe { libafl_exec_cmp_hook8 = hook }; +} + +pub fn set_gen_cmp_hook(hook: extern "C" fn(addr: u64, size: u32) -> u32) { + unsafe { libafl_gen_cmp_hook = hook }; +} diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 1ecfedf507..8d3e40da88 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -16,6 +16,7 @@ use crate::{emu, emu::SKIP_EXEC_HOOK}; static mut GEN_EDGE_HOOK_PTR: *const c_void = ptr::null(); static mut GEN_BLOCK_HOOK_PTR: *const c_void = ptr::null(); +static mut GEN_CMP_HOOK_PTR: *const c_void = ptr::null(); extern "C" fn gen_edge_hook_wrapper(src: u64, dst: u64) -> u32 { unsafe { @@ -33,6 +34,14 @@ extern "C" fn gen_block_hook_wrapper(addr: u64) -> u32 { } } +extern "C" fn gen_cmp_hook_wrapper(addr: u64, size: u32) -> u32 { + unsafe { + let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); + let func: fn(&mut S, u64, usize) -> Option = transmute(GEN_CMP_HOOK_PTR); + (func)(state, addr, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) + } +} + pub struct QemuExecutor<'a, H, I, OT, S> where H: FnMut(&I) -> ExitKind, @@ -67,27 +76,61 @@ where }) } + pub fn inner(&self) -> &InProcessExecutor<'a, H, I, OT, S> { + &self.inner + } + + pub fn inner_mut(&mut self) -> &mut InProcessExecutor<'a, H, I, OT, S> { + &mut self.inner + } + #[allow(clippy::unused_self)] - pub fn hook_edge_generation(&self, hook: fn(&mut S, u64, u64) -> Option) { + pub fn hook_edge_generation(&self, hook: fn(&mut S, src: u64, dest: u64) -> Option) { unsafe { GEN_EDGE_HOOK_PTR = hook as *const _ }; emu::set_gen_edge_hook(gen_edge_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_edge_execution(&self, hook: extern "C" fn(u32)) { + pub fn hook_edge_execution(&self, hook: extern "C" fn(id: u32)) { emu::set_exec_edge_hook(hook); } #[allow(clippy::unused_self)] - pub fn hook_block_generation(&self, hook: fn(&mut S, u64) -> Option) { + pub fn hook_block_generation(&self, hook: fn(&mut S, addr: u64) -> Option) { unsafe { GEN_BLOCK_HOOK_PTR = hook as *const _ }; emu::set_gen_block_hook(gen_block_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_block_execution(&self, hook: extern "C" fn(u64)) { + pub fn hook_block_execution(&self, hook: extern "C" fn(addr: u64)) { emu::set_exec_block_hook(hook); } + + #[allow(clippy::unused_self)] + pub fn hook_cmp_generation(&self, hook: fn(&mut S, addr: u64, size: usize) -> Option) { + unsafe { GEN_CMP_HOOK_PTR = hook as *const _ }; + emu::set_gen_cmp_hook(gen_cmp_hook_wrapper::); + } + + #[allow(clippy::unused_self)] + pub fn hook_cmp1_execution(&self, hook: extern "C" fn(id: u32, v0: u8, v1: u8)) { + emu::set_exec_cmp1_hook(hook); + } + + #[allow(clippy::unused_self)] + pub fn hook_cmp2_execution(&self, hook: extern "C" fn(id: u32, v0: u16, v1: u16)) { + emu::set_exec_cmp2_hook(hook); + } + + #[allow(clippy::unused_self)] + pub fn hook_cmp4_execution(&self, hook: extern "C" fn(id: u32, v0: u32, v1: u32)) { + emu::set_exec_cmp4_hook(hook); + } + + #[allow(clippy::unused_self)] + pub fn hook_cmp8_execution(&self, hook: extern "C" fn(id: u32, v0: u64, v1: u64)) { + emu::set_exec_cmp8_hook(hook); + } } impl<'a, EM, H, I, OT, S, Z> Executor for QemuExecutor<'a, H, I, OT, S> diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 8f578f5d1c..f113c7ccd9 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -1,13 +1,17 @@ +use core::cmp::max; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use libafl::state::HasMetadata; -pub use libafl_targets::{EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM}; +pub use libafl_targets::{ + cmplog::__libafl_targets_cmplog_instructions, CmpLogObserver, CMPLOG_MAP, CMPLOG_MAP_W, + EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM, +}; -/// A testcase metadata saying if a testcase is favored #[derive(Default, Serialize, Deserialize)] pub struct QemuEdgesMapMetadata { pub map: HashMap<(u64, u64), u32>, + pub current_id: u32, } impl QemuEdgesMapMetadata { @@ -15,13 +19,32 @@ impl QemuEdgesMapMetadata { pub fn new() -> Self { Self { map: HashMap::new(), + current_id: 0, } } } libafl::impl_serdeany!(QemuEdgesMapMetadata); -pub fn gen_unique_edges_id(state: &mut S, src: u64, dest: u64) -> Option +#[derive(Default, Serialize, Deserialize)] +pub struct QemuCmpsMapMetadata { + pub map: HashMap, + pub current_id: u32, +} + +impl QemuCmpsMapMetadata { + #[must_use] + pub fn new() -> Self { + Self { + map: HashMap::new(), + current_id: 0, + } + } +} + +libafl::impl_serdeany!(QemuCmpsMapMetadata); + +pub fn gen_unique_edge_ids(state: &mut S, src: u64, dest: u64) -> Option where S: HasMetadata, { @@ -32,17 +55,56 @@ where .metadata_mut() .get_mut::() .unwrap(); - Some(*meta.map.entry((src, dest)).or_insert_with(|| unsafe { - let id = MAX_EDGES_NUM; - MAX_EDGES_NUM = (MAX_EDGES_NUM + 1) & (EDGES_MAP_SIZE - 1); - id as u32 - })) + let id = max(meta.current_id as usize, unsafe { MAX_EDGES_NUM }); + if meta.map.contains_key(&(src, dest)) { + Some(*meta.map.get(&(src, dest)).unwrap()) + } else { + meta.current_id = ((id + 1) & (EDGES_MAP_SIZE - 1)) as u32; + unsafe { MAX_EDGES_NUM = meta.current_id as usize }; + Some(id as u32) + } } -pub extern "C" fn exec_log_hitcount(id: u32) { +pub extern "C" fn trace_edge_hitcount(id: u32) { unsafe { EDGES_MAP[id as usize] += 1 }; } -pub extern "C" fn exec_log_single(id: u32) { +pub extern "C" fn trace_edge_single(id: u32) { unsafe { EDGES_MAP[id as usize] = 1 }; } + +pub fn gen_unique_cmp_ids(state: &mut S, addr: u64, _size: usize) -> Option +where + S: HasMetadata, +{ + if state.metadata().get::().is_none() { + state.add_metadata(QemuCmpsMapMetadata::new()); + } + let meta = state + .metadata_mut() + .get_mut::() + .unwrap(); + let id = meta.current_id as usize; + if meta.map.contains_key(&addr) { + Some(*meta.map.get(&addr).unwrap()) + } else { + meta.current_id = ((id + 1) & (CMPLOG_MAP_W - 1)) as u32; + Some(id as u32) + } +} + +pub extern "C" fn trace_cmp1_cmplog(id: u32, v0: u8, v1: u8) { + unsafe { __libafl_targets_cmplog_instructions(id as usize, 1, u64::from(v0), u64::from(v1)) } +} + +pub extern "C" fn trace_cmp2_cmplog(id: u32, v0: u16, v1: u16) { + unsafe { __libafl_targets_cmplog_instructions(id as usize, 2, u64::from(v0), u64::from(v1)) } +} + +pub extern "C" fn trace_cmp4_cmplog(id: u32, v0: u32, v1: u32) { + unsafe { __libafl_targets_cmplog_instructions(id as usize, 4, u64::from(v0), u64::from(v1)) } +} + +pub extern "C" fn trace_cmp8_cmplog(id: u32, v0: u64, v1: u64) { + unsafe { __libafl_targets_cmplog_instructions(id as usize, 8, v0, v1) } +} diff --git a/libafl_qemu/src/weaks.c b/libafl_qemu/src/weaks.c index 822f655ea0..beff45be65 100644 --- a/libafl_qemu/src/weaks.c +++ b/libafl_qemu/src/weaks.c @@ -64,3 +64,9 @@ __attribute__((weak)) void (*libafl_exec_edge_hook)(uint32_t); __attribute__((weak)) uint32_t (*libafl_gen_edge_hook)(uint64_t, uint64_t); __attribute__((weak)) void (*libafl_exec_block_hook)(uint64_t); __attribute__((weak)) uint32_t (*libafl_gen_block_hook)(uint64_t); + +__attribute__((weak)) void (*libafl_exec_cmp_hook1)(uint32_t, uint8_t, uint8_t); +__attribute__((weak)) void (*libafl_exec_cmp_hook2)(uint32_t, uint16_t, uint16_t); +__attribute__((weak)) void (*libafl_exec_cmp_hook4)(uint32_t, uint32_t, uint32_t); +__attribute__((weak)) void (*libafl_exec_cmp_hook8)(uint32_t, uint64_t, uint64_t); +__attribute__((weak)) uint32_t (*libafl_gen_cmp_hook)(uint64_t, uint32_t); diff --git a/libafl_targets/src/cmplog.rs b/libafl_targets/src/cmplog.rs index 569aea3dea..65d4d4ec40 100644 --- a/libafl_targets/src/cmplog.rs +++ b/libafl_targets/src/cmplog.rs @@ -24,6 +24,11 @@ pub const CMPLOG_KIND_INS: u8 = 0; /// `CmpLog` return kind pub const CMPLOG_KIND_RTN: u8 = 1; +// void __libafl_targets_cmplog_instructions(uintptr_t k, uint8_t shape, uint64_t arg1, uint64_t arg2) +extern "C" { + pub fn __libafl_targets_cmplog_instructions(k: usize, shape: u8, arg1: u64, arg2: u64); +} + /// The header for `CmpLog` hits. #[repr(C)] #[derive(Default, Debug, Clone, Copy)]