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:
Andrea Fioraldi 2021-07-13 16:02:53 +02:00 committed by GitHub
parent 712c5daeb9
commit 42b7c6d7e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 195 additions and 39 deletions

View File

@ -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

View File

@ -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();
}

View File

@ -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)]

View File

@ -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,
})
}

View File

@ -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 {

View File

@ -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)
};
}
}

View File

@ -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 };
}

View File

@ -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>

View File

@ -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) }
}

View File

@ -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);

View File

@ -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)]