QEMU Asan backtrace and report (#1628)

* wip

* ExtractFirstRefMutType

* Asan report with backtrace

* Print asan reports and fix backtraces in libafl qemu

* print context

* enlarge redzone

* nopstate

* fix

* reproducer

* clippy

* clippy

* Fix android

* Crash hook
This commit is contained in:
Andrea Fioraldi 2023-10-25 15:58:32 +02:00 committed by GitHub
parent 02cd260af0
commit 406e77faa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 852 additions and 141 deletions

View File

@ -829,8 +829,10 @@ pub mod unix_signal_handler {
Z: HasObjective<Objective = OF, State = E::State>, Z: HasObjective<Objective = OF, State = E::State>,
{ {
#[cfg(all(target_os = "android", target_arch = "aarch64"))] #[cfg(all(target_os = "android", target_arch = "aarch64"))]
let _context = &mut *(((_context as *mut _ as *mut libc::c_void as usize) + 128) let _context = _context.map(|p| {
as *mut libc::c_void as *mut ucontext_t); &mut *(((p as *mut _ as *mut libc::c_void as usize) + 128) as *mut libc::c_void
as *mut ucontext_t)
});
log::error!("Crashed with {signal}"); log::error!("Crashed with {signal}");
if data.is_valid() { if data.is_valid() {

View File

@ -13,10 +13,8 @@ use std::{
vec::Vec, vec::Vec,
}; };
#[cfg(test)]
use libafl_bolts::rands::StdRand;
use libafl_bolts::{ use libafl_bolts::{
rands::Rand, rands::{Rand, StdRand},
serdeany::{NamedSerdeAnyMap, SerdeAny, SerdeAnyMap}, serdeany::{NamedSerdeAnyMap, SerdeAny, SerdeAnyMap},
}; };
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -881,7 +879,6 @@ impl<I, C, R, SC> HasClientPerfMonitor for StdState<I, C, R, SC> {
} }
} }
#[cfg(test)]
/// A very simple state without any bells or whistles, for testing. /// A very simple state without any bells or whistles, for testing.
#[derive(Debug, Serialize, Deserialize, Default)] #[derive(Debug, Serialize, Deserialize, Default)]
pub struct NopState<I> { pub struct NopState<I> {
@ -891,7 +888,6 @@ pub struct NopState<I> {
phantom: PhantomData<I>, phantom: PhantomData<I>,
} }
#[cfg(test)]
impl<I> NopState<I> { impl<I> NopState<I> {
/// Create a new State that does nothing (for tests) /// Create a new State that does nothing (for tests)
#[must_use] #[must_use]
@ -905,7 +901,6 @@ impl<I> NopState<I> {
} }
} }
#[cfg(test)]
impl<I> UsesInput for NopState<I> impl<I> UsesInput for NopState<I>
where where
I: Input, I: Input,
@ -913,7 +908,6 @@ where
type Input = I; type Input = I;
} }
#[cfg(test)]
impl<I> HasExecutions for NopState<I> { impl<I> HasExecutions for NopState<I> {
fn executions(&self) -> &usize { fn executions(&self) -> &usize {
&self.execution &self.execution
@ -924,7 +918,6 @@ impl<I> HasExecutions for NopState<I> {
} }
} }
#[cfg(test)]
impl<I> HasLastReportTime for NopState<I> { impl<I> HasLastReportTime for NopState<I> {
fn last_report_time(&self) -> &Option<Duration> { fn last_report_time(&self) -> &Option<Duration> {
unimplemented!(); unimplemented!();
@ -935,7 +928,6 @@ impl<I> HasLastReportTime for NopState<I> {
} }
} }
#[cfg(test)]
impl<I> HasMetadata for NopState<I> { impl<I> HasMetadata for NopState<I> {
fn metadata_map(&self) -> &SerdeAnyMap { fn metadata_map(&self) -> &SerdeAnyMap {
&self.metadata &self.metadata
@ -946,7 +938,6 @@ impl<I> HasMetadata for NopState<I> {
} }
} }
#[cfg(test)]
impl<I> HasRand for NopState<I> { impl<I> HasRand for NopState<I> {
type Rand = StdRand; type Rand = StdRand;
@ -959,7 +950,6 @@ impl<I> HasRand for NopState<I> {
} }
} }
#[cfg(test)]
impl<I> HasClientPerfMonitor for NopState<I> { impl<I> HasClientPerfMonitor for NopState<I> {
fn introspection_monitor(&self) -> &ClientPerfMonitor { fn introspection_monitor(&self) -> &ClientPerfMonitor {
unimplemented!() unimplemented!()
@ -970,7 +960,6 @@ impl<I> HasClientPerfMonitor for NopState<I> {
} }
} }
#[cfg(test)]
impl<I> State for NopState<I> where I: Input {} impl<I> State for NopState<I> where I: Input {}
#[cfg(feature = "python")] #[cfg(feature = "python")]

View File

@ -49,6 +49,45 @@ pub fn type_eq<T: ?Sized, U: ?Sized>() -> bool {
type_name::<T>() == type_name::<U>() type_name::<T>() == type_name::<U>()
} }
/// Borrow each member of the tuple
pub trait SplitBorrow<'a> {
/// The Resulting [`TupleList`], of an [`SplitBorrow::borrow()`] call
type SplitBorrowResult;
/// The Resulting [`TupleList`], of an [`SplitBorrow::borrow_mut()`] call
type SplitBorrowMutResult;
/// Return a tuple of borrowed references
fn borrow(&'a self) -> Self::SplitBorrowResult;
/// Return a tuple of borrowed mutable references
fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult;
}
impl<'a> SplitBorrow<'a> for () {
type SplitBorrowResult = ();
type SplitBorrowMutResult = ();
fn borrow(&'a self) -> Self::SplitBorrowResult {}
fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {}
}
impl<'a, Head, Tail> SplitBorrow<'a> for (Head, Tail)
where
Head: 'a,
Tail: SplitBorrow<'a>,
{
type SplitBorrowResult = (Option<&'a Head>, Tail::SplitBorrowResult);
type SplitBorrowMutResult = (Option<&'a mut Head>, Tail::SplitBorrowMutResult);
fn borrow(&'a self) -> Self::SplitBorrowResult {
(Some(&self.0), self.1.borrow())
}
fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {
(Some(&mut self.0), self.1.borrow_mut())
}
}
/// Gets the length of the element /// Gets the length of the element
pub trait HasConstLen { pub trait HasConstLen {
/// The length as constant `usize` /// The length as constant `usize`
@ -172,6 +211,117 @@ where
} }
} }
/// Returns the first element with the given type (dereference mut version)
pub trait ExtractFirstRefType {
/// Returns the first element with the given type as borrow, or [`Option::None`]
fn take<'a, T: 'static>(self) -> (Option<&'a T>, Self);
}
impl ExtractFirstRefType for () {
fn take<'a, T: 'static>(self) -> (Option<&'a T>, Self) {
(None, ())
}
}
impl<Head, Tail> ExtractFirstRefType for (Option<&Head>, Tail)
where
Head: 'static,
Tail: ExtractFirstRefType,
{
fn take<'a, T: 'static>(mut self) -> (Option<&'a T>, Self) {
if TypeId::of::<T>() == TypeId::of::<Head>() {
let r = self.0.take();
(unsafe { core::mem::transmute(r) }, self)
} else {
let (r, tail) = self.1.take::<T>();
(r, (self.0, tail))
}
}
}
impl<Head, Tail> ExtractFirstRefType for (Option<&mut Head>, Tail)
where
Head: 'static,
Tail: ExtractFirstRefType,
{
fn take<'a, T: 'static>(mut self) -> (Option<&'a T>, Self) {
if TypeId::of::<T>() == TypeId::of::<Head>() {
let r = self.0.take();
(unsafe { core::mem::transmute(r) }, self)
} else {
let (r, tail) = self.1.take::<T>();
(r, (self.0, tail))
}
}
}
/// Returns the first element with the given type (dereference mut version)
pub trait ExtractFirstRefMutType {
/// Returns the first element with the given type as borrow, or [`Option::None`]
fn take<'a, T: 'static>(self) -> (Option<&'a mut T>, Self);
}
impl ExtractFirstRefMutType for () {
fn take<'a, T: 'static>(self) -> (Option<&'a mut T>, Self) {
(None, ())
}
}
impl<Head, Tail> ExtractFirstRefMutType for (Option<&mut Head>, Tail)
where
Head: 'static,
Tail: ExtractFirstRefMutType,
{
fn take<'a, T: 'static>(mut self) -> (Option<&'a mut T>, Self) {
if TypeId::of::<T>() == TypeId::of::<Head>() {
let r = self.0.take();
(unsafe { core::mem::transmute(r) }, self)
} else {
let (r, tail) = self.1.take::<T>();
(r, (self.0, tail))
}
}
}
/// Borrow each member of the tuple
pub trait SplitBorrowExtractFirstType<'a> {
/// The Resulting [`TupleList`], of an [`SplitBorrow::borrow()`] call
type SplitBorrowResult: ExtractFirstRefType;
/// The Resulting [`TupleList`], of an [`SplitBorrow::borrow_mut()`] call
type SplitBorrowMutResult: ExtractFirstRefType + ExtractFirstRefMutType;
/// Return a tuple of borrowed references
fn borrow(&'a self) -> Self::SplitBorrowResult;
/// Return a tuple of borrowed mutable references
fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult;
}
impl<'a> SplitBorrowExtractFirstType<'a> for () {
type SplitBorrowResult = ();
type SplitBorrowMutResult = ();
fn borrow(&'a self) -> Self::SplitBorrowResult {}
fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {}
}
impl<'a, Head, Tail> SplitBorrowExtractFirstType<'a> for (Head, Tail)
where
Head: 'static,
Tail: SplitBorrowExtractFirstType<'a>,
{
type SplitBorrowResult = (Option<&'a Head>, Tail::SplitBorrowResult);
type SplitBorrowMutResult = (Option<&'a mut Head>, Tail::SplitBorrowMutResult);
fn borrow(&'a self) -> Self::SplitBorrowResult {
(Some(&self.0), self.1.borrow())
}
fn borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {
(Some(&mut self.0), self.1.borrow_mut())
}
}
/// Match by type /// Match by type
pub trait MatchType { pub trait MatchType {
/// Match by type and call the passed `f` function with a borrow, if found /// Match by type and call the passed `f` function with a borrow, if found
@ -209,45 +359,6 @@ where
} }
} }
/// Borrow each member of the tuple
pub trait SplitBorrow<'a> {
/// The Resulting [`TupleList`], of an [`SplitBorrow::split_borrow()`] call
type SplitBorrowResult;
/// The Resulting [`TupleList`], of an [`SplitBorrow::split_borrow_mut()`] call
type SplitBorrowMutResult;
/// Return a tuple of borrowed references
fn split_borrow(&'a self) -> Self::SplitBorrowResult;
/// Return a tuple of borrowed mutable references
fn split_borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult;
}
impl<'a> SplitBorrow<'a> for () {
type SplitBorrowResult = ();
type SplitBorrowMutResult = ();
fn split_borrow(&'a self) -> Self::SplitBorrowResult {}
fn split_borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {}
}
impl<'a, Head, Tail> SplitBorrow<'a> for (Head, Tail)
where
Head: 'a,
Tail: SplitBorrow<'a>,
{
type SplitBorrowResult = (&'a Head, Tail::SplitBorrowResult);
type SplitBorrowMutResult = (&'a mut Head, Tail::SplitBorrowMutResult);
fn split_borrow(&'a self) -> Self::SplitBorrowResult {
(&self.0, self.1.split_borrow())
}
fn split_borrow_mut(&'a mut self) -> Self::SplitBorrowMutResult {
(&mut self.0, self.1.split_borrow_mut())
}
}
/// A named tuple /// A named tuple
pub trait NamedTuple: HasConstLen { pub trait NamedTuple: HasConstLen {
/// Gets the name of this tuple /// Gets the name of this tuple

View File

@ -60,9 +60,12 @@ syscall-numbers = "3.0"
meminterval = "0.4" meminterval = "0.4"
thread_local = "1.1.4" thread_local = "1.1.4"
capstone = "0.11.0" capstone = "0.11.0"
pyo3 = { version = "0.18", optional = true }
rangemap = "1.3" rangemap = "1.3"
log = "0.4.20" log = "0.4.20"
addr2line = "0.21"
typed-arena = "2.0"
pyo3 = { version = "0.18", optional = true }
[build-dependencies] [build-dependencies]
pyo3-build-config = { version = "0.18", optional = true } pyo3-build-config = { version = "0.18", optional = true }

View File

@ -8,7 +8,7 @@ use which::which;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "ead06288fd597e72cbf50db1c89386f952592860"; const QEMU_REVISION: &str = "e42124c0c8363184ef286fde43dce1d5c607699b";
fn build_dep_check(tools: &[&str]) { fn build_dep_check(tools: &[&str]) {
for tool in tools { for tool in tools {

View File

@ -124,8 +124,8 @@ fn qemu_bindgen_clang_args(
) )
} else { } else {
( (
"/softmmu/main.c", "/system/main.c",
format!("qemu-system-{cpu_target}.p/softmmu_main.c.o"), format!("libqemu-system-{cpu_target}.so.p/system_main.c.o"),
) )
}; };

View File

@ -36,3 +36,10 @@ pub fn memop_big_endian(op: MemOp) -> bool {
pub fn make_plugin_meminfo(oi: MemOpIdx, rw: qemu_plugin_mem_rw) -> qemu_plugin_meminfo_t { pub fn make_plugin_meminfo(oi: MemOpIdx, rw: qemu_plugin_mem_rw) -> qemu_plugin_meminfo_t {
oi | (rw.0 << 16) oi | (rw.0 << 16)
} }
// from include/hw/core/cpu.h
#[cfg(target_os = "linux")]
pub fn cpu_env(cpu: *mut CPUState) -> *mut CPUArchState {
unsafe { cpu.add(1) as *mut CPUArchState }
}

View File

@ -1,5 +1,5 @@
/******************************************************************************* /*******************************************************************************
Copyright (c) 2019-2020, Andrea Fioraldi Copyright (c) 2019-2023, Andrea Fioraldi
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -53,13 +53,15 @@ struct chunk_begin {
struct chunk_begin *next; struct chunk_begin *next;
struct chunk_begin *prev; struct chunk_begin *prev;
char redzone[REDZONE_SIZE]; char redzone[REDZONE_SIZE];
};
} __attribute__((packed));
struct chunk_struct { struct chunk_struct {
struct chunk_begin begin; struct chunk_begin begin;
char redzone[REDZONE_SIZE]; char redzone[REDZONE_SIZE];
size_t prev_size_padding; size_t prev_size_padding;
};
} __attribute__((packed));
#ifdef __GLIBC__ #ifdef __GLIBC__

View File

@ -1,3 +1,4 @@
use capstone::arch::BuildsCapstone;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "python")] #[cfg(feature = "python")]
use pyo3::prelude::*; use pyo3::prelude::*;
@ -62,7 +63,9 @@ impl IntoPy<PyObject> for Regs {
/// Return an ARM64 ArchCapstoneBuilder /// Return an ARM64 ArchCapstoneBuilder
pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder { pub fn capstone() -> capstone::arch::arm64::ArchCapstoneBuilder {
capstone::Capstone::new().arm64() capstone::Capstone::new()
.arm64()
.mode(capstone::arch::arm64::ArchMode::Arm)
} }
pub type GuestReg = u64; pub type GuestReg = u64;

View File

@ -1,11 +1,13 @@
#![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_possible_wrap)]
use std::{ use std::{
borrow::Cow,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
env, fs, env, fs,
sync::Mutex, sync::Mutex,
}; };
use addr2line::object::{Object, ObjectSection};
use libafl::{ use libafl::{
executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata, executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata,
}; };
@ -14,12 +16,14 @@ use libc::{
}; };
use meminterval::{Interval, IntervalTree}; use meminterval::{Interval, IntervalTree};
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use rangemap::RangeMap;
use crate::{ use crate::{
calls::FullBacktraceCollector,
emu::{EmuError, Emulator, MemAccessInfo, SyscallHookResult}, emu::{EmuError, Emulator, MemAccessInfo, SyscallHookResult},
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
hooks::QemuHooks, hooks::QemuHooks,
GuestAddr, GuestAddr, Regs,
}; };
// TODO at some point, merge parts with libafl_frida // TODO at some point, merge parts with libafl_frida
@ -39,6 +43,8 @@ pub const QASAN_FAKESYS_NR: i32 = 0xa2a4;
pub const SHADOW_PAGE_SIZE: usize = 4096; pub const SHADOW_PAGE_SIZE: usize = 4096;
pub const SHADOW_PAGE_MASK: GuestAddr = !(SHADOW_PAGE_SIZE as GuestAddr - 1); pub const SHADOW_PAGE_MASK: GuestAddr = !(SHADOW_PAGE_SIZE as GuestAddr - 1);
pub const DEFAULT_REDZONE_SIZE: usize = 128;
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)] #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)]
#[repr(u64)] #[repr(u64)]
pub enum QasanAction { pub enum QasanAction {
@ -93,13 +99,54 @@ pub enum AsanError {
Write(GuestAddr, usize), Write(GuestAddr, usize),
BadFree(GuestAddr, Option<Interval<GuestAddr>>), BadFree(GuestAddr, Option<Interval<GuestAddr>>),
MemLeak(Interval<GuestAddr>), MemLeak(Interval<GuestAddr>),
Signal(i32),
} }
pub type AsanErrorCallback = Box<dyn FnMut(&Emulator, AsanError)>; impl core::fmt::Display for AsanError {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
AsanError::Read(addr, len) => write!(fmt, "Invalid {len} bytes read at {addr:#x}"),
AsanError::Write(addr, len) => {
write!(fmt, "Invalid {len} bytes write at {addr:#x}")
}
AsanError::BadFree(addr, interval) => match interval {
Some(chunk) => write!(fmt, "Bad free at {addr:#x} in the allocated chunk {chunk}",),
None => write!(fmt, "Bad free at {addr:#x} (wild pointer)"),
},
AsanError::MemLeak(interval) => write!(fmt, "Memory leak of chunk {interval}"),
AsanError::Signal(sig) => write!(fmt, "Signal {sig} received"),
}
}
}
pub type AsanErrorCallback = Box<dyn FnMut(&AsanGiovese, &Emulator, GuestAddr, AsanError)>;
#[derive(Debug, Clone)]
pub struct AllocTreeItem {
backtrace: Vec<GuestAddr>,
free_backtrace: Vec<GuestAddr>,
allocated: bool,
}
impl AllocTreeItem {
#[must_use]
pub fn alloc(backtrace: Vec<GuestAddr>) -> Self {
AllocTreeItem {
backtrace,
free_backtrace: vec![],
allocated: true,
}
}
pub fn free(&mut self, backtrace: Vec<GuestAddr>) {
self.free_backtrace = backtrace;
self.allocated = false;
}
}
pub struct AsanGiovese { pub struct AsanGiovese {
pub alloc_tree: Mutex<IntervalTree<GuestAddr, ()>>, pub alloc_tree: Mutex<IntervalTree<GuestAddr, AllocTreeItem>>,
pub saved_tree: IntervalTree<GuestAddr, ()>, pub saved_tree: IntervalTree<GuestAddr, AllocTreeItem>,
pub error_callback: Option<AsanErrorCallback>, pub error_callback: Option<AsanErrorCallback>,
pub dirty_shadow: Mutex<HashSet<GuestAddr>>, pub dirty_shadow: Mutex<HashSet<GuestAddr>>,
pub saved_shadow: HashMap<GuestAddr, Vec<i8>>, pub saved_shadow: HashMap<GuestAddr, Vec<i8>>,
@ -352,22 +399,34 @@ impl AsanGiovese {
} }
} }
pub fn report_or_crash(&mut self, emu: &Emulator, error: AsanError) { pub fn report_or_crash(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) {
if let Some(cb) = self.error_callback.as_mut() { if let Some(mut cb) = self.error_callback.take() {
(cb)(emu, error); (cb)(self, emu, pc, error);
self.error_callback = Some(cb);
} else { } else {
std::process::abort(); std::process::abort();
} }
} }
pub fn report(&mut self, emu: &Emulator, error: AsanError) { pub fn report(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) {
if let Some(cb) = self.error_callback.as_mut() { if let Some(mut cb) = self.error_callback.take() {
(cb)(emu, error); (cb)(self, emu, pc, error);
self.error_callback = Some(cb);
} }
} }
pub fn alloc_insert(&mut self, start: GuestAddr, end: GuestAddr) { pub fn alloc_insert(&mut self, pc: GuestAddr, start: GuestAddr, end: GuestAddr) {
self.alloc_tree.lock().unwrap().insert(start..end, ()); let backtrace = FullBacktraceCollector::backtrace()
.map(|r| {
let mut v = r.to_vec();
v.push(pc);
v
})
.unwrap_or_default();
self.alloc_tree
.lock()
.unwrap()
.insert(start..end, AllocTreeItem::alloc(backtrace));
} }
pub fn alloc_remove(&mut self, start: GuestAddr, end: GuestAddr) { pub fn alloc_remove(&mut self, start: GuestAddr, end: GuestAddr) {
@ -381,8 +440,45 @@ impl AsanGiovese {
} }
} }
pub fn alloc_free(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
let mut chunk = None;
self.alloc_map_mut(addr, |interval, item| {
chunk = Some(*interval);
let backtrace = FullBacktraceCollector::backtrace()
.map(|r| {
let mut v = r.to_vec();
v.push(pc);
v
})
.unwrap_or_default();
item.free(backtrace);
});
if let Some(ck) = chunk {
if ck.start != addr {
// Free not the start of the chunk
self.report_or_crash(emulator, pc, AsanError::BadFree(addr, Some(ck)));
}
} else {
// Free of wild ptr
self.report_or_crash(emulator, pc, AsanError::BadFree(addr, None));
}
}
#[must_use] #[must_use]
pub fn alloc_search(&mut self, query: GuestAddr) -> Option<Interval<GuestAddr>> { pub fn alloc_get_clone(
&self,
query: GuestAddr,
) -> Option<(Interval<GuestAddr>, AllocTreeItem)> {
self.alloc_tree
.lock()
.unwrap()
.query(query..=query)
.next()
.map(|entry| (*entry.interval, entry.value.clone()))
}
#[must_use]
pub fn alloc_get_interval(&self, query: GuestAddr) -> Option<Interval<GuestAddr>> {
self.alloc_tree self.alloc_tree
.lock() .lock()
.unwrap() .unwrap()
@ -391,6 +487,48 @@ impl AsanGiovese {
.map(|entry| *entry.interval) .map(|entry| *entry.interval)
} }
pub fn alloc_map<F>(&self, query: GuestAddr, mut func: F)
where
F: FnMut(&Interval<GuestAddr>, &AllocTreeItem),
{
if let Some(entry) = self.alloc_tree.lock().unwrap().query(query..=query).next() {
func(entry.interval, entry.value);
}
}
pub fn alloc_map_mut<F>(&mut self, query: GuestAddr, mut func: F)
where
F: FnMut(&Interval<GuestAddr>, &mut AllocTreeItem),
{
if let Some(entry) = self
.alloc_tree
.lock()
.unwrap()
.query_mut(query..=query)
.next()
{
func(entry.interval, entry.value);
}
}
pub fn alloc_map_interval<F>(&self, query: Interval<GuestAddr>, mut func: F)
where
F: FnMut(&Interval<GuestAddr>, &AllocTreeItem),
{
if let Some(entry) = self.alloc_tree.lock().unwrap().query(query).next() {
func(entry.interval, entry.value);
}
}
pub fn alloc_map_interval_mut<F>(&mut self, query: Interval<GuestAddr>, mut func: F)
where
F: FnMut(&Interval<GuestAddr>, &mut AllocTreeItem),
{
if let Some(entry) = self.alloc_tree.lock().unwrap().query_mut(query).next() {
func(entry.interval, entry.value);
}
}
pub fn snapshot(&mut self, emu: &Emulator) { pub fn snapshot(&mut self, emu: &Emulator) {
if self.snapshot_shadow { if self.snapshot_shadow {
let set = self.dirty_shadow.lock().unwrap(); let set = self.dirty_shadow.lock().unwrap();
@ -445,7 +583,11 @@ impl AsanGiovese {
}; };
for interval in leaks { for interval in leaks {
self.report(emu, AsanError::MemLeak(interval)); self.report(
emu,
emu.read_reg(Regs::Pc).unwrap(),
AsanError::MemLeak(interval),
);
} }
ret ret
@ -564,6 +706,11 @@ impl QemuAsanHelper {
} }
} }
#[must_use]
pub fn with_asan_report(filter: QemuInstrumentationFilter, options: QemuAsanOptions) -> Self {
Self::with_error_callback(filter, Box::new(asan_report), options)
}
#[must_use] #[must_use]
pub fn must_instrument(&self, addr: GuestAddr) -> bool { pub fn must_instrument(&self, addr: GuestAddr) -> bool {
self.filter.allowed(addr) self.filter.allowed(addr)
@ -578,23 +725,13 @@ impl QemuAsanHelper {
self.enabled = enabled; self.enabled = enabled;
} }
pub fn alloc(&mut self, _emulator: &Emulator, start: GuestAddr, end: GuestAddr) { pub fn alloc(&mut self, pc: GuestAddr, start: GuestAddr, end: GuestAddr) {
self.rt.alloc_insert(start, end); self.rt.alloc_remove(start, end);
self.rt.alloc_insert(pc, start, end);
} }
pub fn dealloc(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn dealloc(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
let chunk = self.rt.alloc_search(addr); self.rt.alloc_free(emulator, pc, addr);
if let Some(ck) = chunk {
if ck.start != addr {
// Free not the start of the chunk
self.rt
.report_or_crash(emulator, AsanError::BadFree(addr, Some(ck)));
}
} else {
// Free of wild ptr
self.rt
.report_or_crash(emulator, AsanError::BadFree(addr, None));
}
} }
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
@ -603,65 +740,73 @@ impl QemuAsanHelper {
AsanGiovese::is_invalid_access(emulator, addr, size) AsanGiovese::is_invalid_access(emulator, addr, size)
} }
pub fn read_1(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn read_1(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Read(addr, 1)); self.rt
.report_or_crash(emulator, pc, AsanError::Read(addr, 1));
} }
} }
pub fn read_2(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn read_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Read(addr, 2)); self.rt
.report_or_crash(emulator, pc, AsanError::Read(addr, 2));
} }
} }
pub fn read_4(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn read_4(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Read(addr, 4)); self.rt
.report_or_crash(emulator, pc, AsanError::Read(addr, 4));
} }
} }
pub fn read_8(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn read_8(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Read(addr, 8)); self.rt
.report_or_crash(emulator, pc, AsanError::Read(addr, 8));
} }
} }
pub fn read_n(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) { pub fn read_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) { if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) {
self.rt self.rt
.report_or_crash(emulator, AsanError::Read(addr, size)); .report_or_crash(emulator, pc, AsanError::Read(addr, size));
} }
} }
pub fn write_1(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn write_1(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Write(addr, 1)); self.rt
.report_or_crash(emulator, pc, AsanError::Write(addr, 1));
} }
} }
pub fn write_2(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn write_2(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Write(addr, 2)); self.rt
.report_or_crash(emulator, pc, AsanError::Write(addr, 2));
} }
} }
pub fn write_4(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn write_4(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Write(addr, 4)); self.rt
.report_or_crash(emulator, pc, AsanError::Write(addr, 4));
} }
} }
pub fn write_8(&mut self, emulator: &Emulator, addr: GuestAddr) { pub fn write_8(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) { if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) {
self.rt.report_or_crash(emulator, AsanError::Write(addr, 8)); self.rt
.report_or_crash(emulator, pc, AsanError::Write(addr, 8));
} }
} }
pub fn write_n(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) { pub fn write_n(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) { if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) {
self.rt self.rt
.report_or_crash(emulator, AsanError::Write(addr, size)); .report_or_crash(emulator, pc, AsanError::Write(addr, size));
} }
} }
@ -725,6 +870,10 @@ where
Some(trace_write8_asan::<QT, S>), Some(trace_write8_asan::<QT, S>),
Some(trace_write_n_asan::<QT, S>), Some(trace_write_n_asan::<QT, S>),
); );
if self.rt.error_callback.is_some() {
hooks.crash(oncrash_asan::<QT, S>);
}
} }
fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) { fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) {
@ -749,6 +898,17 @@ where
} }
} }
pub fn oncrash_asan<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, target_sig: i32)
where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emu = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
let pc: GuestAddr = emu.read_reg(Regs::Pc).unwrap();
h.rt.report(&emu, pc, AsanError::Signal(target_sig));
}
pub fn gen_readwrite_asan<QT, S>( pub fn gen_readwrite_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
@ -770,7 +930,7 @@ where
pub fn trace_read1_asan<QT, S>( pub fn trace_read1_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -778,13 +938,13 @@ pub fn trace_read1_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_1(&emulator, addr); h.read_1(&emulator, id as GuestAddr, addr);
} }
pub fn trace_read2_asan<QT, S>( pub fn trace_read2_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -792,13 +952,13 @@ pub fn trace_read2_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_2(&emulator, addr); h.read_2(&emulator, id as GuestAddr, addr);
} }
pub fn trace_read4_asan<QT, S>( pub fn trace_read4_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -806,13 +966,13 @@ pub fn trace_read4_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_4(&emulator, addr); h.read_4(&emulator, id as GuestAddr, addr);
} }
pub fn trace_read8_asan<QT, S>( pub fn trace_read8_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -820,13 +980,13 @@ pub fn trace_read8_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_8(&emulator, addr); h.read_8(&emulator, id as GuestAddr, addr);
} }
pub fn trace_read_n_asan<QT, S>( pub fn trace_read_n_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
size: usize, size: usize,
) where ) where
@ -835,13 +995,13 @@ pub fn trace_read_n_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_n(&emulator, addr, size); h.read_n(&emulator, id as GuestAddr, addr, size);
} }
pub fn trace_write1_asan<QT, S>( pub fn trace_write1_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -849,13 +1009,13 @@ pub fn trace_write1_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_1(&emulator, addr); h.write_1(&emulator, id as GuestAddr, addr);
} }
pub fn trace_write2_asan<QT, S>( pub fn trace_write2_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -863,13 +1023,13 @@ pub fn trace_write2_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_2(&emulator, addr); h.write_2(&emulator, id as GuestAddr, addr);
} }
pub fn trace_write4_asan<QT, S>( pub fn trace_write4_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -877,13 +1037,13 @@ pub fn trace_write4_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_4(&emulator, addr); h.write_4(&emulator, id as GuestAddr, addr);
} }
pub fn trace_write8_asan<QT, S>( pub fn trace_write8_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
) where ) where
S: UsesInput, S: UsesInput,
@ -891,13 +1051,13 @@ pub fn trace_write8_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.write_8(&emulator, addr); h.write_8(&emulator, id as GuestAddr, addr);
} }
pub fn trace_write_n_asan<QT, S>( pub fn trace_write_n_asan<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>, hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
_id: u64, id: u64,
addr: GuestAddr, addr: GuestAddr,
size: usize, size: usize,
) where ) where
@ -906,7 +1066,7 @@ pub fn trace_write_n_asan<QT, S>(
{ {
let emulator = hooks.emulator().clone(); let emulator = hooks.emulator().clone();
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
h.read_n(&emulator, addr, size); h.read_n(&emulator, id as GuestAddr, addr, size);
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -933,10 +1093,12 @@ where
let mut r = 0; let mut r = 0;
match QasanAction::try_from(a0).expect("Invalid QASan action number") { match QasanAction::try_from(a0).expect("Invalid QASan action number") {
QasanAction::CheckLoad => { QasanAction::CheckLoad => {
h.read_n(&emulator, a1 as GuestAddr, a2 as usize); let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap();
h.read_n(&emulator, pc, a1 as GuestAddr, a2 as usize);
} }
QasanAction::CheckStore => { QasanAction::CheckStore => {
h.write_n(&emulator, a1 as GuestAddr, a2 as usize); let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap();
h.write_n(&emulator, pc, a1 as GuestAddr, a2 as usize);
} }
QasanAction::Poison => { QasanAction::Poison => {
h.poison( h.poison(
@ -958,10 +1120,12 @@ where
} }
} }
QasanAction::Alloc => { QasanAction::Alloc => {
h.alloc(&emulator, a1 as GuestAddr, a2 as GuestAddr); let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap();
h.alloc(pc, a1 as GuestAddr, a2 as GuestAddr);
} }
QasanAction::Dealloc => { QasanAction::Dealloc => {
h.dealloc(&emulator, a1 as GuestAddr); let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap();
h.dealloc(&emulator, pc, a1 as GuestAddr);
} }
QasanAction::Enable => { QasanAction::Enable => {
h.set_enabled(true); h.set_enabled(true);
@ -978,3 +1142,230 @@ where
SyscallHookResult::new(None) SyscallHookResult::new(None)
} }
} }
fn load_file_section<'input, 'arena, Endian: addr2line::gimli::Endianity>(
id: addr2line::gimli::SectionId,
file: &addr2line::object::File<'input>,
endian: Endian,
arena_data: &'arena typed_arena::Arena<Cow<'input, [u8]>>,
) -> Result<addr2line::gimli::EndianSlice<'arena, Endian>, addr2line::object::Error> {
// TODO: Unify with dwarfdump.rs in gimli.
let name = id.name();
match file.section_by_name(name) {
Some(section) => match section.uncompressed_data()? {
Cow::Borrowed(b) => Ok(addr2line::gimli::EndianSlice::new(b, endian)),
Cow::Owned(b) => Ok(addr2line::gimli::EndianSlice::new(
arena_data.alloc(b.into()),
endian,
)),
},
None => Ok(addr2line::gimli::EndianSlice::new(&[][..], endian)),
}
}
#[allow(clippy::unnecessary_cast)]
#[allow(clippy::too_many_lines)]
pub fn asan_report(rt: &AsanGiovese, emu: &Emulator, pc: GuestAddr, err: AsanError) {
let mut regions = std::collections::HashMap::new();
for region in emu.mappings() {
if let Some(path) = region.path() {
let start = region.start();
let end = region.end();
let entry = regions.entry(path.to_owned()).or_insert(start..end);
if start < entry.start {
*entry = start..entry.end;
}
if end > entry.end {
*entry = entry.start..end;
}
}
}
let mut resolvers = vec![];
let mut images = vec![];
let mut ranges = RangeMap::new();
for (path, rng) in regions {
let data = std::fs::read(&path);
if data.is_err() {
continue;
}
let data = data.unwrap();
let idx = images.len();
images.push((path, data));
ranges.insert(rng, idx);
}
let arena_data = typed_arena::Arena::new();
for img in &images {
if let Ok(obj) = addr2line::object::read::File::parse(&*img.1) {
let endian = if obj.is_little_endian() {
addr2line::gimli::RunTimeEndian::Little
} else {
addr2line::gimli::RunTimeEndian::Big
};
let mut load_section = |id: addr2line::gimli::SectionId| -> Result<_, _> {
load_file_section(id, &obj, endian, &arena_data)
};
let dwarf = addr2line::gimli::Dwarf::load(&mut load_section).unwrap();
let ctx = addr2line::Context::from_dwarf(dwarf)
.expect("Failed to create an addr2line context");
//let ctx = addr2line::Context::new(&obj).expect("Failed to create an addr2line context");
resolvers.push(Some((obj, ctx)));
} else {
resolvers.push(None);
}
}
let resolve_addr = |addr: GuestAddr| -> String {
let mut info = String::new();
if let Some((rng, idx)) = ranges.get_key_value(&addr) {
let raddr = (addr - rng.start) as u64;
if let Some((obj, ctx)) = resolvers[*idx].as_ref() {
let symbols = obj.symbol_map();
let mut func = symbols.get(raddr).map(|x| x.name().to_string());
if func.is_none() {
let pathname = std::path::PathBuf::from(images[*idx].0.clone());
let mut split_dwarf_loader =
addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new(
|data, endian| {
addr2line::gimli::EndianSlice::new(
arena_data.alloc(Cow::Owned(data.into_owned())),
endian,
)
},
Some(pathname),
);
let frames = ctx.find_frames(raddr);
if let Ok(mut frames) = split_dwarf_loader.run(frames) {
if let Some(frame) = frames.next().unwrap_or(None) {
if let Some(function) = frame.function {
if let Ok(name) = function.raw_name() {
let demangled =
addr2line::demangle_auto(name, function.language);
func = Some(demangled.to_string());
}
}
}
}
}
if let Some(name) = func {
info += " in ";
info += &name;
}
if let Some(loc) = ctx.find_location(raddr).unwrap_or(None) {
if info.is_empty() {
info += " in";
}
info += " ";
if let Some(file) = loc.file {
info += file;
}
if let Some(line) = loc.line {
info += ":";
info += &line.to_string();
}
} else {
info += &format!(" ({}+{raddr:#x})", images[*idx].0);
}
}
if info.is_empty() {
info += &format!(" ({}+{raddr:#x})", images[*idx].0);
}
}
info
};
let backtrace = FullBacktraceCollector::backtrace()
.map(|r| {
let mut v = r.to_vec();
v.push(pc);
v
})
.unwrap_or(vec![pc]);
eprintln!("AddressSanitizer Error: {err}");
for (i, addr) in backtrace.iter().rev().enumerate() {
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
}
let addr = match err {
AsanError::Read(addr, _) | AsanError::Write(addr, _) | AsanError::BadFree(addr, _) => {
Some(addr)
}
AsanError::MemLeak(_) | AsanError::Signal(_) => None,
};
if let Some(addr) = addr {
let print_bts = |item: &AllocTreeItem| {
if item.allocated {
eprintln!("Allocated at:");
} else {
eprintln!("Freed at:");
for (i, addr) in item.free_backtrace.iter().rev().enumerate() {
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
}
eprintln!("And previously allocated at:");
}
for (i, addr) in item.backtrace.iter().rev().enumerate() {
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
}
};
if let Some((chunk, item)) = rt.alloc_get_clone(addr) {
eprintln!(
"Address {addr:#x} is {} bytes inside the chunk [{:#x},{:#x})",
addr - chunk.start,
chunk.start,
chunk.end
);
print_bts(&item);
} else {
let mut found = false;
rt.alloc_map_interval(
(addr..=(addr + DEFAULT_REDZONE_SIZE as GuestAddr)).into(),
|chunk, item| {
if found {
return;
}
found = true;
eprintln!(
"Address {addr:#x} is {} bytes to the left of the chunk [{:#x},{:#x})",
chunk.start - addr,
chunk.start,
chunk.end
);
print_bts(item);
},
);
if !found {
rt.alloc_map_interval(
((addr - DEFAULT_REDZONE_SIZE as GuestAddr)..addr).into(),
|chunk, item| {
if found {
return;
}
found = true;
eprintln!(
"Address {addr:#x} is {} bytes to the right of the chunk [{:#x},{:#x})",
addr - chunk.end,
chunk.start,
chunk.end
);
print_bts(item);
},
);
}
}
}
// fix pc in case it is not synced (in hooks)
emu.write_reg(Regs::Pc, pc).unwrap();
eprint!("Context:\n{}", emu.current_cpu().unwrap().display_context());
}

View File

@ -1,4 +1,4 @@
use core::fmt::Debug; use core::{cell::UnsafeCell, fmt::Debug};
use capstone::prelude::*; use capstone::prelude::*;
use libafl::{ use libafl::{
@ -7,6 +7,7 @@ use libafl::{
observers::{stacktrace::BacktraceObserver, ObserversTuple}, observers::{stacktrace::BacktraceObserver, ObserversTuple},
}; };
use libafl_bolts::{tuples::MatchFirstType, Named}; use libafl_bolts::{tuples::MatchFirstType, Named};
use thread_local::ThreadLocal;
use crate::{ use crate::{
capstone, capstone,
@ -410,6 +411,7 @@ where
} }
} }
// TODO support multiple threads with thread local callstack
#[derive(Debug)] #[derive(Debug)]
pub struct OnCrashBacktraceCollector { pub struct OnCrashBacktraceCollector {
callstack_hash: u64, callstack_hash: u64,
@ -495,3 +497,91 @@ impl CallTraceCollector for OnCrashBacktraceCollector {
observer.fill_external(self.callstack_hash, exit_kind); observer.fill_external(self.callstack_hash, exit_kind);
} }
} }
static mut CALLSTACKS: Option<ThreadLocal<UnsafeCell<Vec<GuestAddr>>>> = None;
#[derive(Debug)]
pub struct FullBacktraceCollector {}
impl Default for FullBacktraceCollector {
fn default() -> Self {
Self::new()
}
}
impl FullBacktraceCollector {
pub fn new() -> Self {
unsafe { CALLSTACKS = Some(ThreadLocal::new()) };
Self {}
}
pub fn reset(&mut self) {
unsafe {
for tls in CALLSTACKS.as_mut().unwrap().iter_mut() {
(*tls.get()).clear();
}
}
}
pub fn backtrace() -> Option<&'static [GuestAddr]> {
unsafe {
if let Some(c) = CALLSTACKS.as_mut() {
Some(&*c.get_or_default().get())
} else {
None
}
}
}
}
impl CallTraceCollector for FullBacktraceCollector {
#[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>,
{
// TODO handle Thumb
unsafe {
(*CALLSTACKS.as_mut().unwrap().get_or_default().get()).push(pc + call_len as GuestAddr);
}
}
#[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>,
{
unsafe {
let v = &mut *CALLSTACKS.as_mut().unwrap().get_or_default().get();
if !v.is_empty() {
// if *v.last().unwrap() == ret_addr {
// v.pop();
// }
while let Some(p) = v.pop() {
if p == ret_addr {
break;
}
}
}
}
}
fn pre_exec<I>(&mut self, _emulator: &Emulator, _input: &I)
where
I: Input,
{
self.reset();
}
}

View File

@ -20,9 +20,10 @@ use std::{slice::from_raw_parts, str::from_utf8_unchecked};
use libc::c_int; use libc::c_int;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
use num_traits::Num; use num_traits::Num;
use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
use crate::GuestReg; use crate::{GuestReg, Regs};
pub type GuestAddr = libafl_qemu_sys::target_ulong; pub type GuestAddr = libafl_qemu_sys::target_ulong;
pub type GuestUsize = libafl_qemu_sys::target_ulong; pub type GuestUsize = libafl_qemu_sys::target_ulong;
@ -316,6 +317,8 @@ extern "C" {
unsafe extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult; unsafe extern "C" fn(i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult;
static mut libafl_post_syscall_hook: static mut libafl_post_syscall_hook:
unsafe extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64; unsafe extern "C" fn(u64, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> u64;
static mut libafl_dump_core_hook: unsafe extern "C" fn(i32);
} }
#[cfg(emulation_mode = "systemmode")] #[cfg(emulation_mode = "systemmode")]
@ -729,14 +732,22 @@ impl CPU {
pub fn save_state(&self) -> CPUArchState { pub fn save_state(&self) -> CPUArchState {
unsafe { unsafe {
let mut saved = MaybeUninit::<CPUArchState>::uninit(); let mut saved = MaybeUninit::<CPUArchState>::uninit();
copy_nonoverlapping(self.ptr.as_mut().unwrap().env_ptr, saved.as_mut_ptr(), 1); copy_nonoverlapping(
libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()),
saved.as_mut_ptr(),
1,
);
saved.assume_init() saved.assume_init()
} }
} }
pub fn restore_state(&self, saved: &CPUArchState) { pub fn restore_state(&self, saved: &CPUArchState) {
unsafe { unsafe {
copy_nonoverlapping(saved, self.ptr.as_mut().unwrap().env_ptr, 1); copy_nonoverlapping(
saved,
libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()),
1,
);
} }
} }
@ -766,6 +777,27 @@ impl CPU {
unsafe { libafl_qemu_sys::qemu_target_page_size() } unsafe { libafl_qemu_sys::qemu_target_page_size() }
} }
} }
#[must_use]
pub fn display_context(&self) -> String {
let mut display = String::new();
let mut maxl = 0;
for r in Regs::iter() {
maxl = std::cmp::max(format!("{r:#?}").len(), maxl);
}
for (i, r) in Regs::iter().enumerate() {
let v: GuestAddr = self.read_reg(r).unwrap();
let sr = format!("{r:#?}");
display += &format!("{sr:>maxl$}: {v:#016x} ");
if (i + 1) % 4 == 0 {
display += "\n";
}
}
if !display.ends_with('\n') {
display += "\n";
}
display
}
} }
static mut EMULATOR_IS_INITIALIZED: bool = false; static mut EMULATOR_IS_INITIALIZED: bool = false;
@ -1302,6 +1334,14 @@ impl Emulator {
pub fn gdb_reply(&self, output: &str) { pub fn gdb_reply(&self, output: &str) {
unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) }; unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) };
} }
#[cfg(emulation_mode = "usermode")]
#[allow(clippy::type_complexity)]
pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) {
unsafe {
libafl_dump_core_hook = callback;
}
}
} }
impl ArchExtras for Emulator { impl ArchExtras for Emulator {

View File

@ -1,7 +1,7 @@
use core::{fmt::Debug, ops::Range}; use core::{fmt::Debug, ops::Range};
use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple};
use libafl_bolts::tuples::MatchFirstType; use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType};
use crate::{ use crate::{
emu::{Emulator, GuestAddr}, emu::{Emulator, GuestAddr},
@ -42,7 +42,8 @@ where
} }
} }
pub trait QemuHelperTuple<S>: MatchFirstType + Debug pub trait QemuHelperTuple<S>:
MatchFirstType + for<'a> SplitBorrowExtractFirstType<'a> + Debug
where where
S: UsesInput, S: UsesInput,
{ {

View File

@ -9,7 +9,11 @@ use core::{
ptr::{self, addr_of}, ptr::{self, addr_of},
}; };
use libafl::{executors::inprocess::inprocess_get_state, inputs::UsesInput}; use libafl::{
executors::{inprocess::inprocess_get_state, ExitKind},
inputs::UsesInput,
state::NopState,
};
pub use crate::emu::SyscallHookResult; pub use crate::emu::SyscallHookResult;
use crate::{ use crate::{
@ -683,6 +687,33 @@ where
} }
} }
#[cfg(emulation_mode = "usermode")]
static mut CRASH_HOOKS: Vec<Hook> = vec![];
#[cfg(emulation_mode = "usermode")]
extern "C" fn crash_hook_wrapper<QT, S>(target_sig: i32)
where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
unsafe {
let hooks = get_qemu_hooks::<QT, S>();
for hook in &mut CRASH_HOOKS {
match hook {
Hook::Function(ptr) => {
let func: fn(&mut QemuHooks<'_, QT, S>, i32) = transmute(*ptr);
func(hooks, target_sig);
}
Hook::Closure(ptr) => {
let func: &mut Box<dyn FnMut(&mut QemuHooks<'_, QT, S>, i32)> = transmute(ptr);
func(hooks, target_sig);
}
_ => (),
}
}
}
}
static mut HOOKS_IS_INITIALIZED: bool = false; static mut HOOKS_IS_INITIALIZED: bool = false;
pub struct QemuHooks<'a, QT, S> pub struct QemuHooks<'a, QT, S>
@ -708,6 +739,31 @@ where
} }
} }
impl<'a, I, QT> QemuHooks<'a, QT, NopState<I>>
where
QT: QemuHelperTuple<NopState<I>>,
NopState<I>: UsesInput<Input = I>,
{
pub fn reproducer(emulator: &'a Emulator, helpers: QT) -> Box<Self> {
Self::new(emulator, helpers)
}
pub fn repro_run<H>(&mut self, harness: &mut H, input: &I) -> ExitKind
where
H: FnMut(&I) -> ExitKind,
{
self.helpers.first_exec_all(self);
self.helpers.pre_exec_all(self.emulator, input);
let mut exit_kind = harness(input);
self.helpers
.post_exec_all(self.emulator, input, &mut (), &mut exit_kind);
exit_kind
}
}
impl<'a, QT, S> QemuHooks<'a, QT, S> impl<'a, QT, S> QemuHooks<'a, QT, S>
where where
QT: QemuHelperTuple<S>, QT: QemuHelperTuple<S>,
@ -1515,4 +1571,20 @@ where
self.emulator self.emulator
.set_post_syscall_hook(syscall_after_hooks_wrapper::<QT, S>); .set_post_syscall_hook(syscall_after_hooks_wrapper::<QT, S>);
} }
#[cfg(emulation_mode = "usermode")]
pub fn crash_closure(&self, hook: Box<dyn FnMut(&mut Self, i32)>) {
unsafe {
self.emulator.set_crash_hook(crash_hook_wrapper::<QT, S>);
CRASH_HOOKS.push(Hook::Closure(transmute(hook)));
}
}
#[cfg(emulation_mode = "usermode")]
pub fn crash(&self, hook: fn(&mut Self, target_signal: i32)) {
unsafe {
self.emulator.set_crash_hook(crash_hook_wrapper::<QT, S>);
CRASH_HOOKS.push(Hook::Function(hook as *const libc::c_void));
}
}
} }