Define custom collectors for QemuCallTracerHelper (#1099)

* Define custom collectors for QemuCallTracerHelper and create OnCrashBacktraceCollector

* fmt

* clippy
This commit is contained in:
Andrea Fioraldi 2023-03-07 13:16:51 +01:00 committed by GitHub
parent 3ffec79a17
commit 20c32316eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 567 additions and 192 deletions

View File

@ -52,6 +52,8 @@ pub enum HarnessType {
InProcess,
/// Harness type when the target is a child process
Child,
/// Harness type with an external component filling the backtrace hash (e.g. `CrashBacktraceCollector` in `libafl_qemu`)
External,
}
/// An observer looking at the backtrace after the harness crashes
@ -87,6 +89,17 @@ impl<'a> BacktraceObserver<'a> {
fn clear_hash(&mut self) {
*self.hash.as_mut() = None;
}
/// Fill the hash value if the harness type is external
pub fn fill_external(&mut self, hash: u64, exit_kind: &ExitKind) {
if self.harness_type == HarnessType::External {
if *exit_kind == ExitKind::Crash {
self.update_hash(hash);
} else {
self.clear_hash();
}
}
}
}
impl<'a> ObserverWithHashField for BacktraceObserver<'a> {
@ -108,7 +121,7 @@ where
exit_kind: &ExitKind,
) -> Result<(), Error> {
if self.harness_type == HarnessType::InProcess {
if exit_kind == &ExitKind::Crash {
if *exit_kind == ExitKind::Crash {
self.update_hash(collect_backtrace());
} else {
self.clear_hash();
@ -124,7 +137,7 @@ where
exit_kind: &ExitKind,
) -> Result<(), Error> {
if self.harness_type == HarnessType::Child {
if exit_kind == &ExitKind::Crash {
if *exit_kind == ExitKind::Crash {
self.update_hash(collect_backtrace());
} else {
self.clear_hash();

View File

@ -6,7 +6,9 @@ use std::{
sync::Mutex,
};
use libafl::{executors::ExitKind, inputs::UsesInput, state::HasMetadata};
use libafl::{
executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata,
};
use libc::{
c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE,
};
@ -730,7 +732,15 @@ where
}
}
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input, exit_kind: &mut ExitKind) {
fn post_exec<OT>(
&mut self,
emulator: &Emulator,
_input: &S::Input,
_observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
if self.reset(emulator) == AsanRollback::HasLeaks {
*exit_kind = ExitKind::Crash;
}

View File

@ -1,27 +1,234 @@
use core::fmt::Debug;
use capstone::prelude::*;
use libafl::inputs::UsesInput;
use libafl::{
bolts::tuples::{MatchFirstType, Named},
executors::ExitKind,
inputs::{Input, UsesInput},
observers::{stacktrace::BacktraceObserver, ObserversTuple},
};
use crate::{
capstone,
emu::Emulator,
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
hooks::QemuHooks,
Emulator, GuestAddr, Regs,
GuestAddr, Regs,
};
#[derive(Debug)]
pub struct QemuCallTracerHelper {
filter: QemuInstrumentationFilter,
cs: Capstone,
callstack: Vec<GuestAddr>,
pub trait CallTraceCollector: 'static + Debug {
fn on_call<QT, S>(
&mut self,
hooks: &mut QemuHooks<'_, QT, S>,
state: Option<&mut S>,
pc: GuestAddr,
call_len: usize,
) where
S: UsesInput,
QT: QemuHelperTuple<S>;
fn on_ret<QT, S>(
&mut self,
hooks: &mut QemuHooks<'_, QT, S>,
state: Option<&mut S>,
pc: GuestAddr,
ret_addr: GuestAddr,
) where
S: UsesInput,
QT: QemuHelperTuple<S>;
// Frowarded from the `QemuCallTracerHelper`
fn pre_exec<I>(&mut self, _emulator: &Emulator, _input: &I)
where
I: Input,
{
}
impl QemuCallTracerHelper {
fn post_exec<OT, S>(
&mut self,
_emulator: &Emulator,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
S: UsesInput,
{
}
}
pub trait CallTraceCollectorTuple: 'static + MatchFirstType + Debug {
fn on_call_all<QT, S>(
&mut self,
hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
pc: GuestAddr,
call_len: usize,
) where
S: UsesInput,
QT: QemuHelperTuple<S>;
fn on_ret_all<QT, S>(
&mut self,
hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
_pc: GuestAddr,
ret_addr: GuestAddr,
) where
S: UsesInput,
QT: QemuHelperTuple<S>;
fn pre_exec_all<I>(&mut self, _emulator: &Emulator, input: &I)
where
I: Input;
fn post_exec_all<OT, S>(
&mut self,
_emulator: &Emulator,
input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
S: UsesInput;
}
impl CallTraceCollectorTuple for () {
fn on_call_all<QT, S>(
&mut self,
_hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
_pc: GuestAddr,
_call_len: usize,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
}
fn on_ret_all<QT, S>(
&mut self,
_hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
_pc: GuestAddr,
_ret_addr: GuestAddr,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
}
fn pre_exec_all<I>(&mut self, _emulator: &Emulator, _input: &I)
where
I: Input,
{
}
fn post_exec_all<OT, S>(
&mut self,
_emulator: &Emulator,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
S: UsesInput,
{
}
}
impl<Head, Tail> CallTraceCollectorTuple for (Head, Tail)
where
Head: CallTraceCollector,
Tail: CallTraceCollectorTuple,
{
fn on_call_all<QT, S>(
&mut self,
hooks: &mut QemuHooks<'_, QT, S>,
mut state: Option<&mut S>,
pc: GuestAddr,
call_len: usize,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
self.0.on_call(
hooks,
match state.as_mut() {
Some(s) => Some(*s),
None => None,
},
pc,
call_len,
);
self.1.on_call_all(hooks, state, pc, call_len);
}
fn on_ret_all<QT, S>(
&mut self,
hooks: &mut QemuHooks<'_, QT, S>,
mut state: Option<&mut S>,
pc: GuestAddr,
ret_addr: GuestAddr,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
self.0.on_ret(
hooks,
match state.as_mut() {
Some(s) => Some(*s),
None => None,
},
pc,
ret_addr,
);
self.1.on_ret_all(hooks, state, pc, ret_addr);
}
fn pre_exec_all<I>(&mut self, emulator: &Emulator, input: &I)
where
I: Input,
{
self.0.pre_exec(emulator, input);
self.1.pre_exec_all(emulator, input);
}
fn post_exec_all<OT, S>(
&mut self,
emulator: &Emulator,
input: &S::Input,
observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
S: UsesInput,
{
self.0.post_exec(emulator, input, observers, exit_kind);
self.1.post_exec_all(emulator, input, observers, exit_kind);
}
}
#[derive(Debug)]
pub struct QemuCallTracerHelper<T>
where
T: CallTraceCollectorTuple,
{
filter: QemuInstrumentationFilter,
cs: Capstone,
collectors: Option<T>,
}
impl<T> QemuCallTracerHelper<T>
where
T: CallTraceCollectorTuple,
{
#[must_use]
pub fn new(filter: QemuInstrumentationFilter) -> Self {
pub fn new(filter: QemuInstrumentationFilter, collectors: T) -> Self {
Self {
filter,
cs: capstone().detail(true).build().unwrap(),
callstack: vec![],
collectors: Some(collectors),
}
}
@ -30,46 +237,7 @@ impl QemuCallTracerHelper {
self.filter.allowed(addr)
}
#[must_use]
pub fn callstack(&self) -> &[GuestAddr] {
&self.callstack
}
pub fn reset(&mut self) {
self.callstack.clear();
}
}
impl Default for QemuCallTracerHelper {
fn default() -> Self {
Self::new(QemuInstrumentationFilter::None)
}
}
impl<S> QemuHelper<S> for QemuCallTracerHelper
where
S: UsesInput,
{
fn init_hooks<QT>(&self, hooks: &QemuHooks<'_, QT, S>)
where
QT: QemuHelperTuple<S>,
{
hooks.blocks(Some(gen_blocks_calls::<QT, S>), None);
}
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {
self.reset();
}
}
/*pub fn on_call<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, pc: GuestAddr)
where
QT: QemuHelperTuple<S>,
{
}*/
pub fn on_ret<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, _pc: GuestAddr)
fn on_ret<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, state: Option<&mut S>, pc: GuestAddr)
where
S: UsesInput,
QT: QemuHelperTuple<S>,
@ -108,19 +276,23 @@ where
// log::info!("RET @ 0x{:#x}", ret_addr);
if let Some(h) = hooks
let mut collectors = if let Some(h) = hooks.helpers_mut().match_first_type_mut::<Self>() {
h.collectors.take()
} else {
return;
};
collectors
.as_mut()
.unwrap()
.on_ret_all(hooks, state, pc, ret_addr);
hooks
.helpers_mut()
.match_first_type_mut::<QemuCallTracerHelper>()
{
while let Some(addr) = h.callstack.pop() {
if addr == ret_addr {
break;
}
}
}
.match_first_type_mut::<Self>()
.unwrap()
.collectors = collectors;
}
pub fn gen_blocks_calls<QT, S>(
fn gen_blocks_calls<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
pc: GuestAddr,
@ -130,7 +302,7 @@ where
QT: QemuHelperTuple<S>,
{
let emu = hooks.emulator();
if let Some(h) = hooks.helpers().match_first_type::<QemuCallTracerHelper>() {
if let Some(h) = hooks.helpers().match_first_type::<Self>() {
if !h.must_instrument(pc) {
return None;
}
@ -160,16 +332,28 @@ where
for detail in insn_detail.groups() {
match u32::from(detail.0) {
capstone::InsnGroupType::CS_GRP_CALL => {
// hooks.instruction_closure(insn.address() as GuestAddr, on_call, false);
let call_len = insn.bytes().len() as GuestAddr;
let call_cb = move |hooks: &mut QemuHooks<'_, QT, S>, _, pc| {
let call_len = insn.bytes().len();
// TODO do not use a closure, find a more efficient way to pass call_len
let call_cb = move |hooks: &mut QemuHooks<'_, QT, S>,
state: Option<&mut S>,
pc| {
// eprintln!("CALL @ 0x{:#x}", pc + call_len);
if let Some(h) = hooks
.helpers_mut()
.match_first_type_mut::<QemuCallTracerHelper>()
let mut collectors = if let Some(h) =
hooks.helpers_mut().match_first_type_mut::<Self>()
{
h.callstack.push(pc + call_len);
}
h.collectors.take()
} else {
return;
};
collectors
.as_mut()
.unwrap()
.on_call_all(hooks, state, pc, call_len);
hooks
.helpers_mut()
.match_first_type_mut::<Self>()
.unwrap()
.collectors = collectors;
};
unsafe {
hooks.instruction_closure(
@ -180,7 +364,7 @@ where
}
}
capstone::InsnGroupType::CS_GRP_RET => {
hooks.instruction(insn.address() as GuestAddr, on_ret, false);
hooks.instruction(insn.address() as GuestAddr, Self::on_ret, false);
break 'disasm;
}
capstone::InsnGroupType::CS_GRP_INVALID
@ -208,3 +392,125 @@ where
None
}
}
impl<S, T> QemuHelper<S> for QemuCallTracerHelper<T>
where
S: UsesInput,
T: CallTraceCollectorTuple,
{
fn first_exec<QT>(&self, hooks: &QemuHooks<'_, QT, S>)
where
QT: QemuHelperTuple<S>,
{
hooks.blocks(Some(Self::gen_blocks_calls::<QT, S>), None);
}
fn pre_exec(&mut self, emulator: &Emulator, input: &S::Input) {
self.collectors
.as_mut()
.unwrap()
.pre_exec_all(emulator, input);
}
fn post_exec<OT>(
&mut self,
emulator: &Emulator,
input: &S::Input,
observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
self.collectors
.as_mut()
.unwrap()
.post_exec_all(emulator, input, observers, exit_kind);
}
}
#[derive(Debug)]
pub struct OnCrashBacktraceCollector {
callstack_hash: u64,
observer_name: String,
}
impl OnCrashBacktraceCollector {
#[must_use]
pub fn new(observer: &BacktraceObserver<'_>) -> Self {
Self {
callstack_hash: 0,
observer_name: observer.name().to_string(),
}
}
#[must_use]
pub fn with_name(observer_name: String) -> Self {
Self {
callstack_hash: 0,
observer_name,
}
}
#[must_use]
pub fn callstack_hash(&self) -> u64 {
self.callstack_hash
}
pub fn reset(&mut self) {
self.callstack_hash = 0;
}
}
impl CallTraceCollector for OnCrashBacktraceCollector {
#[allow(clippy::unnecessary_cast)]
fn on_call<QT, S>(
&mut self,
_hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
pc: GuestAddr,
call_len: usize,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
self.callstack_hash ^= pc as u64 + call_len as u64;
}
#[allow(clippy::unnecessary_cast)]
fn on_ret<QT, S>(
&mut self,
_hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
_pc: GuestAddr,
ret_addr: GuestAddr,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
self.callstack_hash ^= ret_addr as u64;
}
fn pre_exec<I>(&mut self, _emulator: &Emulator, _input: &I)
where
I: Input,
{
self.reset();
}
fn post_exec<OT, S>(
&mut self,
_emulator: &Emulator,
_input: &S::Input,
observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
S: UsesInput,
{
let observer = observers
.match_name_mut::<BacktraceObserver<'_>>(&self.observer_name)
.expect("A OnCrashBacktraceCollector needs a BacktraceObserver");
observer.fill_external(self.callstack_hash, exit_kind);
}
}

View File

@ -1,7 +1,9 @@
use std::{path::PathBuf, sync::Mutex};
use hashbrown::{hash_map::Entry, HashMap};
use libafl::{executors::ExitKind, inputs::UsesInput, state::HasMetadata};
use libafl::{
executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata,
};
use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter};
use rangemap::RangeMap;
use serde::{Deserialize, Serialize};
@ -84,7 +86,15 @@ where
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {}
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input, _exit_kind: &mut ExitKind) {
fn post_exec<OT>(
&mut self,
emulator: &Emulator,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
if self.full_trace {
if DRCOV_IDS.lock().unwrap().as_ref().unwrap().len() > self.drcov_len {
let mut drcov_vec = Vec::<DrCovBasicBlock>::new();

View File

@ -463,7 +463,7 @@ impl Drop for GuestMaps {
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FatPtr(pub *const c_void, pub *const c_void);
static mut GDB_COMMANDS: Vec<FatPtr> = vec![];

View File

@ -126,9 +126,12 @@ where
}
self.hooks.helpers_mut().pre_exec_all(&emu, input);
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
self.hooks
.helpers_mut()
.post_exec_all(&emu, input, &mut exit_kind);
self.hooks.helpers_mut().post_exec_all(
&emu,
input,
self.inner.observers_mut(),
&mut exit_kind,
);
Ok(exit_kind)
}
}
@ -288,9 +291,12 @@ where
}
self.hooks.helpers_mut().pre_exec_all(&emu, input);
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
self.hooks
.helpers_mut()
.post_exec_all(&emu, input, &mut exit_kind);
self.hooks.helpers_mut().post_exec_all(
&emu,
input,
self.inner.observers_mut(),
&mut exit_kind,
);
Ok(exit_kind)
}
}

View File

@ -1,6 +1,9 @@
use core::{fmt::Debug, ops::Range};
use libafl::{bolts::tuples::MatchFirstType, executors::ExitKind, inputs::UsesInput};
use libafl::{
bolts::tuples::MatchFirstType, executors::ExitKind, inputs::UsesInput,
observers::ObserversTuple,
};
use crate::{
emu::{Emulator, GuestAddr},
@ -29,7 +32,16 @@ where
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {}
fn post_exec(&mut self, _emulator: &Emulator, _input: &S::Input, _exit_kind: &mut ExitKind) {}
fn post_exec<OT>(
&mut self,
_emulator: &Emulator,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
}
}
pub trait QemuHelperTuple<S>: MatchFirstType + Debug
@ -48,7 +60,14 @@ where
fn pre_exec_all(&mut self, _emulator: &Emulator, input: &S::Input);
fn post_exec_all(&mut self, _emulator: &Emulator, input: &S::Input, _exit_kind: &mut ExitKind);
fn post_exec_all<OT>(
&mut self,
_emulator: &Emulator,
input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>;
}
impl<S> QemuHelperTuple<S> for ()
@ -71,12 +90,15 @@ where
fn pre_exec_all(&mut self, _emulator: &Emulator, _input: &S::Input) {}
fn post_exec_all(
fn post_exec_all<OT>(
&mut self,
_emulator: &Emulator,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) {
) where
OT: ObserversTuple<S>,
{
}
}
@ -109,9 +131,17 @@ where
self.1.pre_exec_all(emulator, input);
}
fn post_exec_all(&mut self, emulator: &Emulator, input: &S::Input, exit_kind: &mut ExitKind) {
self.0.post_exec(emulator, input, exit_kind);
self.1.post_exec_all(emulator, input, exit_kind);
fn post_exec_all<OT>(
&mut self,
emulator: &Emulator,
input: &S::Input,
observers: &mut OT,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
{
self.0.post_exec(emulator, input, observers, exit_kind);
self.1.post_exec_all(emulator, input, observers, exit_kind);
}
}

View File

@ -19,8 +19,8 @@ use crate::{
};
// all kinds of hooks
#[derive(Clone, Copy, PartialEq, Eq)]
enum Hook {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Hook {
Function(*const c_void),
Closure(FatPtr),
#[cfg(emulation_mode = "usermode")]