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:
parent
02cd260af0
commit
406e77faa9
@ -829,8 +829,10 @@ pub mod unix_signal_handler {
|
||||
Z: HasObjective<Objective = OF, State = E::State>,
|
||||
{
|
||||
#[cfg(all(target_os = "android", target_arch = "aarch64"))]
|
||||
let _context = &mut *(((_context as *mut _ as *mut libc::c_void as usize) + 128)
|
||||
as *mut libc::c_void as *mut ucontext_t);
|
||||
let _context = _context.map(|p| {
|
||||
&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}");
|
||||
if data.is_valid() {
|
||||
|
@ -13,10 +13,8 @@ use std::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use libafl_bolts::rands::StdRand;
|
||||
use libafl_bolts::{
|
||||
rands::Rand,
|
||||
rands::{Rand, StdRand},
|
||||
serdeany::{NamedSerdeAnyMap, SerdeAny, SerdeAnyMap},
|
||||
};
|
||||
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.
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct NopState<I> {
|
||||
@ -891,7 +888,6 @@ pub struct NopState<I> {
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> NopState<I> {
|
||||
/// Create a new State that does nothing (for tests)
|
||||
#[must_use]
|
||||
@ -905,7 +901,6 @@ impl<I> NopState<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> UsesInput for NopState<I>
|
||||
where
|
||||
I: Input,
|
||||
@ -913,7 +908,6 @@ where
|
||||
type Input = I;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> HasExecutions for NopState<I> {
|
||||
fn executions(&self) -> &usize {
|
||||
&self.execution
|
||||
@ -924,7 +918,6 @@ impl<I> HasExecutions for NopState<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> HasLastReportTime for NopState<I> {
|
||||
fn last_report_time(&self) -> &Option<Duration> {
|
||||
unimplemented!();
|
||||
@ -935,7 +928,6 @@ impl<I> HasLastReportTime for NopState<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> HasMetadata for NopState<I> {
|
||||
fn metadata_map(&self) -> &SerdeAnyMap {
|
||||
&self.metadata
|
||||
@ -946,7 +938,6 @@ impl<I> HasMetadata for NopState<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> HasRand for NopState<I> {
|
||||
type Rand = StdRand;
|
||||
|
||||
@ -959,7 +950,6 @@ impl<I> HasRand for NopState<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> HasClientPerfMonitor for NopState<I> {
|
||||
fn introspection_monitor(&self) -> &ClientPerfMonitor {
|
||||
unimplemented!()
|
||||
@ -970,7 +960,6 @@ impl<I> HasClientPerfMonitor for NopState<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<I> State for NopState<I> where I: Input {}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
|
@ -49,6 +49,45 @@ pub fn type_eq<T: ?Sized, U: ?Sized>() -> bool {
|
||||
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
|
||||
pub trait HasConstLen {
|
||||
/// 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
|
||||
pub trait MatchType {
|
||||
/// 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
|
||||
pub trait NamedTuple: HasConstLen {
|
||||
/// Gets the name of this tuple
|
||||
|
@ -60,9 +60,12 @@ syscall-numbers = "3.0"
|
||||
meminterval = "0.4"
|
||||
thread_local = "1.1.4"
|
||||
capstone = "0.11.0"
|
||||
pyo3 = { version = "0.18", optional = true }
|
||||
rangemap = "1.3"
|
||||
log = "0.4.20"
|
||||
addr2line = "0.21"
|
||||
typed-arena = "2.0"
|
||||
|
||||
pyo3 = { version = "0.18", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { version = "0.18", optional = true }
|
||||
|
@ -8,7 +8,7 @@ use which::which;
|
||||
|
||||
const QEMU_URL: &str = "https://github.com/AFLplusplus/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]) {
|
||||
for tool in tools {
|
||||
|
@ -124,8 +124,8 @@ fn qemu_bindgen_clang_args(
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"/softmmu/main.c",
|
||||
format!("qemu-system-{cpu_target}.p/softmmu_main.c.o"),
|
||||
"/system/main.c",
|
||||
format!("libqemu-system-{cpu_target}.so.p/system_main.c.o"),
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
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 }
|
||||
}
|
||||
|
@ -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
|
||||
@ -53,13 +53,15 @@ struct chunk_begin {
|
||||
struct chunk_begin *next;
|
||||
struct chunk_begin *prev;
|
||||
char redzone[REDZONE_SIZE];
|
||||
};
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
struct chunk_struct {
|
||||
struct chunk_begin begin;
|
||||
char redzone[REDZONE_SIZE];
|
||||
size_t prev_size_padding;
|
||||
};
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
#ifdef __GLIBC__
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
@ -62,7 +63,9 @@ impl IntoPy<PyObject> for Regs {
|
||||
|
||||
/// Return an 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;
|
||||
|
@ -1,11 +1,13 @@
|
||||
#![allow(clippy::cast_possible_wrap)]
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet},
|
||||
env, fs,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use addr2line::object::{Object, ObjectSection};
|
||||
use libafl::{
|
||||
executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata,
|
||||
};
|
||||
@ -14,12 +16,14 @@ use libc::{
|
||||
};
|
||||
use meminterval::{Interval, IntervalTree};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use rangemap::RangeMap;
|
||||
|
||||
use crate::{
|
||||
calls::FullBacktraceCollector,
|
||||
emu::{EmuError, Emulator, MemAccessInfo, SyscallHookResult},
|
||||
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
|
||||
hooks::QemuHooks,
|
||||
GuestAddr,
|
||||
GuestAddr, Regs,
|
||||
};
|
||||
|
||||
// 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_MASK: GuestAddr = !(SHADOW_PAGE_SIZE as GuestAddr - 1);
|
||||
|
||||
pub const DEFAULT_REDZONE_SIZE: usize = 128;
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)]
|
||||
#[repr(u64)]
|
||||
pub enum QasanAction {
|
||||
@ -93,13 +99,54 @@ pub enum AsanError {
|
||||
Write(GuestAddr, usize),
|
||||
BadFree(GuestAddr, Option<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 alloc_tree: Mutex<IntervalTree<GuestAddr, ()>>,
|
||||
pub saved_tree: IntervalTree<GuestAddr, ()>,
|
||||
pub alloc_tree: Mutex<IntervalTree<GuestAddr, AllocTreeItem>>,
|
||||
pub saved_tree: IntervalTree<GuestAddr, AllocTreeItem>,
|
||||
pub error_callback: Option<AsanErrorCallback>,
|
||||
pub dirty_shadow: Mutex<HashSet<GuestAddr>>,
|
||||
pub saved_shadow: HashMap<GuestAddr, Vec<i8>>,
|
||||
@ -352,22 +399,34 @@ impl AsanGiovese {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_or_crash(&mut self, emu: &Emulator, error: AsanError) {
|
||||
if let Some(cb) = self.error_callback.as_mut() {
|
||||
(cb)(emu, error);
|
||||
pub fn report_or_crash(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) {
|
||||
if let Some(mut cb) = self.error_callback.take() {
|
||||
(cb)(self, emu, pc, error);
|
||||
self.error_callback = Some(cb);
|
||||
} else {
|
||||
std::process::abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report(&mut self, emu: &Emulator, error: AsanError) {
|
||||
if let Some(cb) = self.error_callback.as_mut() {
|
||||
(cb)(emu, error);
|
||||
pub fn report(&mut self, emu: &Emulator, pc: GuestAddr, error: AsanError) {
|
||||
if let Some(mut cb) = self.error_callback.take() {
|
||||
(cb)(self, emu, pc, error);
|
||||
self.error_callback = Some(cb);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc_insert(&mut self, start: GuestAddr, end: GuestAddr) {
|
||||
self.alloc_tree.lock().unwrap().insert(start..end, ());
|
||||
pub fn alloc_insert(&mut self, pc: GuestAddr, start: GuestAddr, end: GuestAddr) {
|
||||
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) {
|
||||
@ -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]
|
||||
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
|
||||
.lock()
|
||||
.unwrap()
|
||||
@ -391,6 +487,48 @@ impl AsanGiovese {
|
||||
.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) {
|
||||
if self.snapshot_shadow {
|
||||
let set = self.dirty_shadow.lock().unwrap();
|
||||
@ -445,7 +583,11 @@ impl AsanGiovese {
|
||||
};
|
||||
|
||||
for interval in leaks {
|
||||
self.report(emu, AsanError::MemLeak(interval));
|
||||
self.report(
|
||||
emu,
|
||||
emu.read_reg(Regs::Pc).unwrap(),
|
||||
AsanError::MemLeak(interval),
|
||||
);
|
||||
}
|
||||
|
||||
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]
|
||||
pub fn must_instrument(&self, addr: GuestAddr) -> bool {
|
||||
self.filter.allowed(addr)
|
||||
@ -578,23 +725,13 @@ impl QemuAsanHelper {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn alloc(&mut self, _emulator: &Emulator, start: GuestAddr, end: GuestAddr) {
|
||||
self.rt.alloc_insert(start, end);
|
||||
pub fn alloc(&mut self, pc: GuestAddr, start: GuestAddr, end: GuestAddr) {
|
||||
self.rt.alloc_remove(start, end);
|
||||
self.rt.alloc_insert(pc, start, end);
|
||||
}
|
||||
|
||||
pub fn dealloc(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
||||
let chunk = self.rt.alloc_search(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));
|
||||
}
|
||||
pub fn dealloc(&mut self, emulator: &Emulator, pc: GuestAddr, addr: GuestAddr) {
|
||||
self.rt.alloc_free(emulator, pc, addr);
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
@ -603,65 +740,73 @@ impl QemuAsanHelper {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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_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) {
|
||||
@ -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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
@ -770,7 +930,7 @@ where
|
||||
pub fn trace_read1_asan<QT, S>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -778,13 +938,13 @@ pub fn trace_read1_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -792,13 +952,13 @@ pub fn trace_read2_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -806,13 +966,13 @@ pub fn trace_read4_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -820,13 +980,13 @@ pub fn trace_read8_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
size: usize,
|
||||
) where
|
||||
@ -835,13 +995,13 @@ pub fn trace_read_n_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -849,13 +1009,13 @@ pub fn trace_write1_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -863,13 +1023,13 @@ pub fn trace_write2_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -877,13 +1037,13 @@ pub fn trace_write4_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
) where
|
||||
S: UsesInput,
|
||||
@ -891,13 +1051,13 @@ pub fn trace_write8_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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>(
|
||||
hooks: &mut QemuHooks<'_, QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
_id: u64,
|
||||
id: u64,
|
||||
addr: GuestAddr,
|
||||
size: usize,
|
||||
) where
|
||||
@ -906,7 +1066,7 @@ pub fn trace_write_n_asan<QT, S>(
|
||||
{
|
||||
let emulator = hooks.emulator().clone();
|
||||
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)]
|
||||
@ -933,10 +1093,12 @@ where
|
||||
let mut r = 0;
|
||||
match QasanAction::try_from(a0).expect("Invalid QASan action number") {
|
||||
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 => {
|
||||
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 => {
|
||||
h.poison(
|
||||
@ -958,10 +1120,12 @@ where
|
||||
}
|
||||
}
|
||||
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 => {
|
||||
h.dealloc(&emulator, a1 as GuestAddr);
|
||||
let pc: GuestAddr = emulator.read_reg(Regs::Pc).unwrap();
|
||||
h.dealloc(&emulator, pc, a1 as GuestAddr);
|
||||
}
|
||||
QasanAction::Enable => {
|
||||
h.set_enabled(true);
|
||||
@ -978,3 +1142,230 @@ where
|
||||
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());
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use core::fmt::Debug;
|
||||
use core::{cell::UnsafeCell, fmt::Debug};
|
||||
|
||||
use capstone::prelude::*;
|
||||
use libafl::{
|
||||
@ -7,6 +7,7 @@ use libafl::{
|
||||
observers::{stacktrace::BacktraceObserver, ObserversTuple},
|
||||
};
|
||||
use libafl_bolts::{tuples::MatchFirstType, Named};
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
use crate::{
|
||||
capstone,
|
||||
@ -410,6 +411,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// TODO support multiple threads with thread local callstack
|
||||
#[derive(Debug)]
|
||||
pub struct OnCrashBacktraceCollector {
|
||||
callstack_hash: u64,
|
||||
@ -495,3 +497,91 @@ impl CallTraceCollector for OnCrashBacktraceCollector {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ use std::{slice::from_raw_parts, str::from_utf8_unchecked};
|
||||
use libc::c_int;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use num_traits::Num;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::GuestReg;
|
||||
use crate::{GuestReg, Regs};
|
||||
|
||||
pub type GuestAddr = 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;
|
||||
static mut libafl_post_syscall_hook:
|
||||
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")]
|
||||
@ -729,14 +732,22 @@ impl CPU {
|
||||
pub fn save_state(&self) -> CPUArchState {
|
||||
unsafe {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_state(&self, saved: &CPUArchState) {
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
#[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;
|
||||
@ -1302,6 +1334,14 @@ impl Emulator {
|
||||
pub fn gdb_reply(&self, output: &str) {
|
||||
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 {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use core::{fmt::Debug, ops::Range};
|
||||
|
||||
use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple};
|
||||
use libafl_bolts::tuples::MatchFirstType;
|
||||
use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType};
|
||||
|
||||
use crate::{
|
||||
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
|
||||
S: UsesInput,
|
||||
{
|
||||
|
@ -9,7 +9,11 @@ use core::{
|
||||
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;
|
||||
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;
|
||||
|
||||
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>
|
||||
where
|
||||
QT: QemuHelperTuple<S>,
|
||||
@ -1515,4 +1571,20 @@ where
|
||||
self.emulator
|
||||
.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user