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
This commit is contained in:
parent
712c5daeb9
commit
42b7c6d7e7
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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<S>(src: u64, dst: u64) -> u32 {
|
||||
unsafe {
|
||||
@ -33,6 +34,14 @@ extern "C" fn gen_block_hook_wrapper<S>(addr: u64) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gen_cmp_hook_wrapper<S>(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<u32> = 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<u32>) {
|
||||
pub fn hook_edge_generation(&self, hook: fn(&mut S, src: u64, dest: u64) -> Option<u32>) {
|
||||
unsafe { GEN_EDGE_HOOK_PTR = hook as *const _ };
|
||||
emu::set_gen_edge_hook(gen_edge_hook_wrapper::<S>);
|
||||
}
|
||||
|
||||
#[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<u32>) {
|
||||
pub fn hook_block_generation(&self, hook: fn(&mut S, addr: u64) -> Option<u32>) {
|
||||
unsafe { GEN_BLOCK_HOOK_PTR = hook as *const _ };
|
||||
emu::set_gen_block_hook(gen_block_hook_wrapper::<S>);
|
||||
}
|
||||
|
||||
#[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<u32>) {
|
||||
unsafe { GEN_CMP_HOOK_PTR = hook as *const _ };
|
||||
emu::set_gen_cmp_hook(gen_cmp_hook_wrapper::<S>);
|
||||
}
|
||||
|
||||
#[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<EM, I, S, Z> for QemuExecutor<'a, H, I, OT, S>
|
||||
|
@ -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<S>(state: &mut S, src: u64, dest: u64) -> Option<u32>
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct QemuCmpsMapMetadata {
|
||||
pub map: HashMap<u64, u32>,
|
||||
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<S>(state: &mut S, src: u64, dest: u64) -> Option<u32>
|
||||
where
|
||||
S: HasMetadata,
|
||||
{
|
||||
@ -32,17 +55,56 @@ where
|
||||
.metadata_mut()
|
||||
.get_mut::<QemuEdgesMapMetadata>()
|
||||
.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<S>(state: &mut S, addr: u64, _size: usize) -> Option<u32>
|
||||
where
|
||||
S: HasMetadata,
|
||||
{
|
||||
if state.metadata().get::<QemuCmpsMapMetadata>().is_none() {
|
||||
state.add_metadata(QemuCmpsMapMetadata::new());
|
||||
}
|
||||
let meta = state
|
||||
.metadata_mut()
|
||||
.get_mut::<QemuCmpsMapMetadata>()
|
||||
.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) }
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
Loading…
x
Reference in New Issue
Block a user