
* build and sys qemu crates * working libafl_qemu_build * libafl_qemu_sys * switch libafl_qemu to use libafl_qemu_sys * fix * use sys * fmt * mmu lookup * fix * autofix * clippy * fix * allow * cl * docker * docker * fix * mem access info in mem hooks * fmt * fix * kill libafl_page_size * fix * clippy * default bindings for docs.rs * macos * fix arm build * fix * plugins * fix * fix fuzzer * Correct PC on breakpoint Co-authored-by: Dominik Maier <domenukk@gmail.com>
950 lines
28 KiB
Rust
950 lines
28 KiB
Rust
#![allow(clippy::cast_possible_wrap)]
|
|
|
|
use std::{
|
|
collections::{HashMap, HashSet},
|
|
env, fs,
|
|
sync::Mutex,
|
|
};
|
|
|
|
use libafl::{inputs::UsesInput, state::HasMetadata};
|
|
use libc::{
|
|
c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE,
|
|
};
|
|
use meminterval::{Interval, IntervalTree};
|
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|
|
|
use crate::{
|
|
emu::{Emulator, MemAccessInfo, SyscallHookResult},
|
|
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
|
|
hooks::QemuHooks,
|
|
GuestAddr,
|
|
};
|
|
|
|
// TODO at some point, merge parts with libafl_frida
|
|
|
|
pub const HIGH_SHADOW_ADDR: *mut c_void = 0x02008fff7000 as *mut c_void;
|
|
pub const LOW_SHADOW_ADDR: *mut c_void = 0x00007fff8000 as *mut c_void;
|
|
pub const GAP_SHADOW_ADDR: *mut c_void = 0x00008fff7000 as *mut c_void;
|
|
|
|
pub const HIGH_SHADOW_SIZE: usize = 0xdfff0000fff;
|
|
pub const LOW_SHADOW_SIZE: usize = 0xfffefff;
|
|
pub const GAP_SHADOW_SIZE: usize = 0x1ffffffffff;
|
|
|
|
pub const SHADOW_OFFSET: isize = 0x7fff8000;
|
|
|
|
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);
|
|
|
|
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)]
|
|
#[repr(u64)]
|
|
pub enum QasanAction {
|
|
CheckLoad,
|
|
CheckStore,
|
|
Poison,
|
|
UserPoison,
|
|
UnPoison,
|
|
IsPoison,
|
|
Alloc,
|
|
Dealloc,
|
|
Enable,
|
|
Disable,
|
|
SwapState,
|
|
}
|
|
|
|
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)]
|
|
#[repr(i8)]
|
|
pub enum PoisonKind {
|
|
Valid = 0,
|
|
Partial1 = 1,
|
|
Partial2 = 2,
|
|
Partial3 = 3,
|
|
Partial4 = 4,
|
|
Partial5 = 5,
|
|
Partial6 = 6,
|
|
Partial7 = 7,
|
|
ArrayCookie = -84, // 0xac
|
|
StackRz = -16, // 0xf0
|
|
StackLeftRz = -15, // 0xf1
|
|
StackMidRz = -14, // 0xf2
|
|
StackRightRz = -13, // 0xf3
|
|
StacKFreed = -11, // 0xf5
|
|
StackOOScope = -8, // 0xf8
|
|
GlobalRz = -7, // 0xf9
|
|
HeapRz = -23, // 0xe9
|
|
User = -9, // 0xf7
|
|
HeapLeftRz = -6, // 0xfa
|
|
HeapRightRz = -5, // 0xfb
|
|
HeapFreed = -3, // 0xfd
|
|
}
|
|
|
|
pub enum AsanError {
|
|
Read(GuestAddr, usize),
|
|
Write(GuestAddr, usize),
|
|
BadFree(GuestAddr, Option<Interval<GuestAddr>>),
|
|
MemLeak(Interval<GuestAddr>),
|
|
}
|
|
|
|
pub type AsanErrorCallback = Box<dyn FnMut(&Emulator, AsanError)>;
|
|
|
|
pub struct AsanGiovese {
|
|
pub alloc_tree: Mutex<IntervalTree<GuestAddr, ()>>,
|
|
pub saved_tree: IntervalTree<GuestAddr, ()>,
|
|
pub error_callback: Option<AsanErrorCallback>,
|
|
pub dirty_shadow: Mutex<HashSet<GuestAddr>>,
|
|
pub saved_shadow: HashMap<GuestAddr, Vec<i8>>,
|
|
pub snapshot_shadow: bool,
|
|
}
|
|
|
|
impl core::fmt::Debug for AsanGiovese {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_struct("AsanGiovese")
|
|
.field("alloc_tree", &self.alloc_tree)
|
|
.field("dirty_shadow", &self.dirty_shadow)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl AsanGiovese {
|
|
pub unsafe fn map_shadow() {
|
|
assert!(
|
|
libc::mmap(
|
|
HIGH_SHADOW_ADDR,
|
|
HIGH_SHADOW_SIZE,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON,
|
|
-1,
|
|
0
|
|
) != MAP_FAILED
|
|
);
|
|
assert!(
|
|
libc::mmap(
|
|
LOW_SHADOW_ADDR,
|
|
LOW_SHADOW_SIZE,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON,
|
|
-1,
|
|
0
|
|
) != MAP_FAILED
|
|
);
|
|
assert!(
|
|
libc::mmap(
|
|
GAP_SHADOW_ADDR,
|
|
GAP_SHADOW_SIZE,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANON,
|
|
-1,
|
|
0
|
|
) != MAP_FAILED
|
|
);
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn is_invalid_access_1(emu: &Emulator, addr: GuestAddr) -> bool {
|
|
unsafe {
|
|
let h = emu.g2h::<*const c_void>(addr) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
let k = *shadow_addr as isize;
|
|
k != 0 && (h & 7).wrapping_add(1) > k
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn is_invalid_access_2(emu: &Emulator, addr: GuestAddr) -> bool {
|
|
unsafe {
|
|
let h = emu.g2h::<*const c_void>(addr) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
let k = *shadow_addr as isize;
|
|
k != 0 && (h & 7).wrapping_add(2) > k
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn is_invalid_access_4(emu: &Emulator, addr: GuestAddr) -> bool {
|
|
unsafe {
|
|
let h = emu.g2h::<*const c_void>(addr) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
let k = *shadow_addr as isize;
|
|
k != 0 && (h & 7).wrapping_add(4) > k
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn is_invalid_access_8(emu: &Emulator, addr: GuestAddr) -> bool {
|
|
unsafe {
|
|
let h = emu.g2h::<*const c_void>(addr) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
*shadow_addr != 0
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
#[allow(clippy::cast_sign_loss)]
|
|
pub fn is_invalid_access(emu: &Emulator, addr: GuestAddr, n: usize) -> bool {
|
|
unsafe {
|
|
if n == 0 {
|
|
return false;
|
|
}
|
|
|
|
let n = n as isize;
|
|
let mut start = addr;
|
|
let end = start.wrapping_add(n as GuestAddr);
|
|
let last_8 = end & !7;
|
|
|
|
if start & 0x7 != 0 {
|
|
let next_8 = (start & !7).wrapping_add(8);
|
|
let first_size = next_8.wrapping_sub(start) as isize;
|
|
if n <= first_size {
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
let k = *shadow_addr as isize;
|
|
return k != 0 && (h & 7).wrapping_add(n) > k;
|
|
}
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
let k = *shadow_addr as isize;
|
|
if k != 0 && (h & 7).wrapping_add(first_size) > k {
|
|
return true;
|
|
}
|
|
start = next_8;
|
|
}
|
|
|
|
while start < last_8 {
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
if *shadow_addr != 0 {
|
|
return true;
|
|
}
|
|
start = (start).wrapping_add(8);
|
|
}
|
|
|
|
if last_8 != end {
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let last_size = end.wrapping_sub(last_8) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
let k = *shadow_addr as isize;
|
|
return k != 0 && (h & 7).wrapping_add(last_size) > k;
|
|
}
|
|
|
|
false
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(clippy::cast_sign_loss)]
|
|
pub fn poison(&mut self, emu: &Emulator, addr: GuestAddr, n: usize, poison_byte: i8) -> bool {
|
|
unsafe {
|
|
if n == 0 {
|
|
return false;
|
|
}
|
|
|
|
if self.snapshot_shadow {
|
|
let mut page = addr & SHADOW_PAGE_MASK;
|
|
let mut set = self.dirty_shadow.lock().unwrap();
|
|
while page < addr + n as GuestAddr {
|
|
set.insert(page);
|
|
page += SHADOW_PAGE_SIZE as GuestAddr;
|
|
}
|
|
}
|
|
|
|
let n = n as isize;
|
|
let mut start = addr;
|
|
let end = start.wrapping_add(n as GuestAddr);
|
|
let last_8 = end & !7;
|
|
|
|
if start & 0x7 != 0 {
|
|
let next_8 = (start & !7).wrapping_add(8);
|
|
let first_size = next_8.wrapping_sub(start) as isize;
|
|
if n < first_size {
|
|
return false;
|
|
}
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
*shadow_addr = (8isize).wrapping_sub(first_size) as i8;
|
|
start = next_8;
|
|
}
|
|
|
|
while start < last_8 {
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
*shadow_addr = poison_byte;
|
|
start = (start).wrapping_add(8);
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(clippy::must_use_candidate)]
|
|
#[allow(clippy::cast_sign_loss)]
|
|
pub fn unpoison(emu: &Emulator, addr: GuestAddr, n: usize) -> bool {
|
|
unsafe {
|
|
let n = n as isize;
|
|
let mut start = addr;
|
|
let end = start.wrapping_add(n as GuestAddr);
|
|
|
|
while start < end {
|
|
let h = emu.g2h::<*const c_void>(start) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
*shadow_addr = 0;
|
|
start = (start).wrapping_add(8);
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn unpoison_page(emu: &Emulator, page: GuestAddr) {
|
|
unsafe {
|
|
let h = emu.g2h::<*const c_void>(page) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
shadow_addr.write_bytes(0, SHADOW_PAGE_SIZE);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[allow(clippy::mut_from_ref)]
|
|
fn get_shadow_page(emu: &Emulator, page: GuestAddr) -> &mut [i8] {
|
|
unsafe {
|
|
let h = emu.g2h::<*const c_void>(page) as isize;
|
|
let shadow_addr = ((h >> 3) as *mut i8).offset(SHADOW_OFFSET);
|
|
std::slice::from_raw_parts_mut(shadow_addr, SHADOW_PAGE_SIZE)
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn new(snapshot_shadow: bool) -> Self {
|
|
Self {
|
|
alloc_tree: Mutex::new(IntervalTree::new()),
|
|
saved_tree: IntervalTree::new(),
|
|
error_callback: None,
|
|
dirty_shadow: Mutex::new(HashSet::default()),
|
|
saved_shadow: HashMap::default(),
|
|
snapshot_shadow,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_error_callback(snapshot_shadow: bool, error_callback: AsanErrorCallback) -> Self {
|
|
Self {
|
|
alloc_tree: Mutex::new(IntervalTree::new()),
|
|
saved_tree: IntervalTree::new(),
|
|
error_callback: Some(error_callback),
|
|
dirty_shadow: Mutex::new(HashSet::default()),
|
|
saved_shadow: HashMap::default(),
|
|
snapshot_shadow,
|
|
}
|
|
}
|
|
|
|
pub fn report_and_crash(&mut self, emu: &Emulator, error: AsanError) {
|
|
if let Some(cb) = self.error_callback.as_mut() {
|
|
(cb)(emu, error);
|
|
} else {
|
|
std::process::abort();
|
|
}
|
|
}
|
|
|
|
pub fn alloc_insert(&mut self, start: GuestAddr, end: GuestAddr) {
|
|
self.alloc_tree.lock().unwrap().insert(start..end, ());
|
|
}
|
|
|
|
pub fn alloc_remove(&mut self, start: GuestAddr, end: GuestAddr) {
|
|
let mut tree = self.alloc_tree.lock().unwrap();
|
|
let mut found = vec![];
|
|
for entry in tree.query(start..end) {
|
|
found.push(*entry.interval);
|
|
}
|
|
for interval in found {
|
|
tree.delete(interval);
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn alloc_search(&mut self, query: GuestAddr) -> Option<Interval<GuestAddr>> {
|
|
self.alloc_tree
|
|
.lock()
|
|
.unwrap()
|
|
.query(query..=query)
|
|
.next()
|
|
.map(|entry| *entry.interval)
|
|
}
|
|
|
|
pub fn snapshot(&mut self, emu: &Emulator) {
|
|
if self.snapshot_shadow {
|
|
let set = self.dirty_shadow.lock().unwrap();
|
|
|
|
for &page in set.iter() {
|
|
let data = Self::get_shadow_page(emu, page).to_vec();
|
|
self.saved_shadow.insert(page, data);
|
|
}
|
|
|
|
let tree = self.alloc_tree.lock().unwrap();
|
|
self.saved_tree = tree.clone();
|
|
}
|
|
}
|
|
|
|
pub fn rollback(&mut self, emu: &Emulator, detect_leaks: bool) {
|
|
let mut leaks = vec![];
|
|
|
|
{
|
|
let mut tree = self.alloc_tree.lock().unwrap();
|
|
|
|
if detect_leaks {
|
|
for entry in tree.query(0..GuestAddr::MAX) {
|
|
leaks.push(*entry.interval);
|
|
}
|
|
}
|
|
|
|
if self.snapshot_shadow {
|
|
tree.clear();
|
|
}
|
|
}
|
|
|
|
if self.snapshot_shadow {
|
|
let mut set = self.dirty_shadow.lock().unwrap();
|
|
|
|
for &page in set.iter() {
|
|
let original = self.saved_shadow.get(&page);
|
|
if let Some(data) = original {
|
|
let cur = Self::get_shadow_page(emu, page);
|
|
cur.copy_from_slice(data);
|
|
} else {
|
|
Self::unpoison_page(emu, page);
|
|
}
|
|
}
|
|
|
|
set.clear();
|
|
}
|
|
|
|
for interval in leaks {
|
|
self.report_and_crash(emu, AsanError::MemLeak(interval));
|
|
}
|
|
}
|
|
}
|
|
|
|
static mut ASAN_INITED: bool = false;
|
|
|
|
pub fn init_with_asan(args: &mut Vec<String>, env: &mut [(String, String)]) -> Emulator {
|
|
assert!(!args.is_empty());
|
|
let current = env::current_exe().unwrap();
|
|
let asan_lib = fs::canonicalize(current)
|
|
.unwrap()
|
|
.parent()
|
|
.unwrap()
|
|
.join("libqasan.so");
|
|
let asan_lib = asan_lib
|
|
.to_str()
|
|
.expect("The path to the asan lib is invalid")
|
|
.to_string();
|
|
let add_asan =
|
|
|e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..];
|
|
|
|
let mut added = false;
|
|
for (k, v) in env.iter_mut() {
|
|
if k == "QEMU_SET_ENV" {
|
|
let mut new_v = vec![];
|
|
for e in v.split(',') {
|
|
if e.starts_with("LD_PRELOAD=") {
|
|
added = true;
|
|
new_v.push(add_asan(e));
|
|
} else {
|
|
new_v.push(e.to_string());
|
|
}
|
|
}
|
|
*v = new_v.join(",");
|
|
}
|
|
}
|
|
for i in 0..args.len() {
|
|
if args[i] == "-E" && i + 1 < args.len() && args[i + 1].starts_with("LD_PRELOAD=") {
|
|
added = true;
|
|
args[i + 1] = add_asan(&args[i + 1]);
|
|
}
|
|
}
|
|
|
|
if !added {
|
|
args.insert(1, "LD_PRELOAD=".to_string() + &asan_lib);
|
|
args.insert(1, "-E".into());
|
|
}
|
|
|
|
unsafe {
|
|
AsanGiovese::map_shadow();
|
|
ASAN_INITED = true;
|
|
}
|
|
Emulator::new(args, env)
|
|
}
|
|
|
|
pub enum QemuAsanOptions {
|
|
None,
|
|
Snapshot,
|
|
DetectLeaks,
|
|
SnapshotDetectLeaks,
|
|
}
|
|
|
|
pub type QemuAsanChildHelper = QemuAsanHelper;
|
|
|
|
#[derive(Debug)]
|
|
pub struct QemuAsanHelper {
|
|
enabled: bool,
|
|
detect_leaks: bool,
|
|
empty: bool,
|
|
rt: AsanGiovese,
|
|
filter: QemuInstrumentationFilter,
|
|
}
|
|
|
|
impl QemuAsanHelper {
|
|
#[must_use]
|
|
pub fn new(filter: QemuInstrumentationFilter, options: QemuAsanOptions) -> Self {
|
|
assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_with_asan(...) instead of just Emulator::new(...)");
|
|
let (snapshot, detect_leaks) = match options {
|
|
QemuAsanOptions::None => (false, false),
|
|
QemuAsanOptions::Snapshot => (true, false),
|
|
QemuAsanOptions::DetectLeaks => (false, true),
|
|
QemuAsanOptions::SnapshotDetectLeaks => (true, true),
|
|
};
|
|
Self {
|
|
enabled: true,
|
|
detect_leaks,
|
|
empty: true,
|
|
rt: AsanGiovese::new(snapshot),
|
|
filter,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_error_callback(
|
|
filter: QemuInstrumentationFilter,
|
|
error_callback: AsanErrorCallback,
|
|
options: QemuAsanOptions,
|
|
) -> Self {
|
|
assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_with_asan(...) instead of just Emulator::new(...)");
|
|
let (snapshot, detect_leaks) = match options {
|
|
QemuAsanOptions::None => (false, false),
|
|
QemuAsanOptions::Snapshot => (true, false),
|
|
QemuAsanOptions::DetectLeaks => (false, true),
|
|
QemuAsanOptions::SnapshotDetectLeaks => (true, true),
|
|
};
|
|
Self {
|
|
enabled: true,
|
|
detect_leaks,
|
|
empty: true,
|
|
rt: AsanGiovese::with_error_callback(snapshot, error_callback),
|
|
filter,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn must_instrument(&self, addr: u64) -> bool {
|
|
self.filter.allowed(addr)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn enabled(&self) -> bool {
|
|
self.enabled
|
|
}
|
|
|
|
pub fn set_enabled(&mut self, enabled: bool) {
|
|
self.enabled = enabled;
|
|
}
|
|
|
|
pub fn alloc(&mut self, _emulator: &Emulator, start: GuestAddr, end: GuestAddr) {
|
|
self.rt.alloc_insert(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_and_crash(emulator, AsanError::BadFree(addr, Some(ck)));
|
|
}
|
|
} else {
|
|
// Free of wild ptr
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::BadFree(addr, None));
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::unused_self)]
|
|
#[must_use]
|
|
pub fn is_poisoned(&self, emulator: &Emulator, addr: GuestAddr, size: usize) -> bool {
|
|
AsanGiovese::is_invalid_access(emulator, addr, size)
|
|
}
|
|
|
|
pub fn read_1(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) {
|
|
self.rt.report_and_crash(emulator, AsanError::Read(addr, 1));
|
|
}
|
|
}
|
|
|
|
pub fn read_2(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) {
|
|
self.rt.report_and_crash(emulator, AsanError::Read(addr, 2));
|
|
}
|
|
}
|
|
|
|
pub fn read_4(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) {
|
|
self.rt.report_and_crash(emulator, AsanError::Read(addr, 4));
|
|
}
|
|
}
|
|
|
|
pub fn read_8(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) {
|
|
self.rt.report_and_crash(emulator, AsanError::Read(addr, 8));
|
|
}
|
|
}
|
|
|
|
pub fn read_n(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) {
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::Read(addr, size));
|
|
}
|
|
}
|
|
|
|
pub fn write_1(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_1(emulator, addr) {
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::Write(addr, 1));
|
|
}
|
|
}
|
|
|
|
pub fn write_2(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_2(emulator, addr) {
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::Write(addr, 2));
|
|
}
|
|
}
|
|
|
|
pub fn write_4(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_4(emulator, addr) {
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::Write(addr, 4));
|
|
}
|
|
}
|
|
|
|
pub fn write_8(&mut self, emulator: &Emulator, addr: GuestAddr) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access_8(emulator, addr) {
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::Write(addr, 8));
|
|
}
|
|
}
|
|
|
|
pub fn write_n(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) {
|
|
if self.enabled() && AsanGiovese::is_invalid_access(emulator, addr, size) {
|
|
self.rt
|
|
.report_and_crash(emulator, AsanError::Write(addr, size));
|
|
}
|
|
}
|
|
|
|
pub fn poison(
|
|
&mut self,
|
|
emulator: &Emulator,
|
|
addr: GuestAddr,
|
|
size: usize,
|
|
poison: PoisonKind,
|
|
) {
|
|
self.rt.poison(emulator, addr, size, poison.into());
|
|
}
|
|
|
|
#[allow(clippy::unused_self)]
|
|
pub fn unpoison(&mut self, emulator: &Emulator, addr: GuestAddr, size: usize) {
|
|
AsanGiovese::unpoison(emulator, addr, size);
|
|
}
|
|
|
|
pub fn reset(&mut self, emulator: &Emulator) {
|
|
self.rt.rollback(emulator, self.detect_leaks);
|
|
}
|
|
}
|
|
|
|
impl Default for QemuAsanHelper {
|
|
fn default() -> Self {
|
|
Self::new(QemuInstrumentationFilter::None, QemuAsanOptions::Snapshot)
|
|
}
|
|
}
|
|
|
|
impl<S> QemuHelper<S> for QemuAsanHelper
|
|
where
|
|
S: UsesInput + HasMetadata,
|
|
{
|
|
const HOOKS_DO_SIDE_EFFECTS: bool = false;
|
|
|
|
fn init_hooks<QT>(&self, hooks: &QemuHooks<'_, QT, S>)
|
|
where
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
hooks.syscalls(qasan_fake_syscall::<QT, S>);
|
|
}
|
|
|
|
fn first_exec<QT>(&self, hooks: &QemuHooks<'_, QT, S>)
|
|
where
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
hooks.reads(
|
|
Some(gen_readwrite_asan::<QT, S>),
|
|
Some(trace_read1_asan::<QT, S>),
|
|
Some(trace_read2_asan::<QT, S>),
|
|
Some(trace_read4_asan::<QT, S>),
|
|
Some(trace_read8_asan::<QT, S>),
|
|
Some(trace_read_n_asan::<QT, S>),
|
|
);
|
|
|
|
hooks.writes(
|
|
Some(gen_readwrite_asan::<QT, S>),
|
|
Some(trace_write1_asan::<QT, S>),
|
|
Some(trace_write2_asan::<QT, S>),
|
|
Some(trace_write4_asan::<QT, S>),
|
|
Some(trace_write8_asan::<QT, S>),
|
|
Some(trace_write_n_asan::<QT, S>),
|
|
);
|
|
}
|
|
|
|
fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) {
|
|
if self.empty {
|
|
self.rt.snapshot(emulator);
|
|
self.empty = false;
|
|
}
|
|
}
|
|
|
|
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input) {
|
|
self.reset(emulator);
|
|
}
|
|
}
|
|
|
|
pub fn gen_readwrite_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
pc: GuestAddr,
|
|
_info: MemAccessInfo,
|
|
) -> Option<u64>
|
|
where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
if h.must_instrument(pc.into()) {
|
|
Some(pc.into())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn trace_read1_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.read_1(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_read2_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.read_2(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_read4_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.read_4(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_read8_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.read_8(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_read_n_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
size: usize,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.read_n(&emulator, addr, size);
|
|
}
|
|
|
|
pub fn trace_write1_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.write_1(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_write2_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.write_2(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_write4_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.write_4(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_write8_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.write_8(&emulator, addr);
|
|
}
|
|
|
|
pub fn trace_write_n_asan<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
_id: u64,
|
|
addr: GuestAddr,
|
|
size: usize,
|
|
) where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
h.read_n(&emulator, addr, size);
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn qasan_fake_syscall<QT, S>(
|
|
hooks: &mut QemuHooks<'_, QT, S>,
|
|
_state: Option<&mut S>,
|
|
sys_num: i32,
|
|
a0: u64,
|
|
a1: u64,
|
|
a2: u64,
|
|
a3: u64,
|
|
_a4: u64,
|
|
_a5: u64,
|
|
_a6: u64,
|
|
_a7: u64,
|
|
) -> SyscallHookResult
|
|
where
|
|
S: UsesInput,
|
|
QT: QemuHelperTuple<S>,
|
|
{
|
|
if sys_num == QASAN_FAKESYS_NR {
|
|
let emulator = hooks.emulator().clone();
|
|
let h = hooks.match_helper_mut::<QemuAsanHelper>().unwrap();
|
|
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);
|
|
}
|
|
QasanAction::CheckStore => {
|
|
h.write_n(&emulator, a1 as GuestAddr, a2 as usize);
|
|
}
|
|
QasanAction::Poison => {
|
|
h.poison(
|
|
&emulator,
|
|
a1 as GuestAddr,
|
|
a2 as usize,
|
|
PoisonKind::try_from(a3 as i8).unwrap(),
|
|
);
|
|
}
|
|
QasanAction::UserPoison => {
|
|
h.poison(&emulator, a1 as GuestAddr, a2 as usize, PoisonKind::User);
|
|
}
|
|
QasanAction::UnPoison => {
|
|
h.unpoison(&emulator, a1 as GuestAddr, a2 as usize);
|
|
}
|
|
QasanAction::IsPoison => {
|
|
if h.is_poisoned(&emulator, a1 as GuestAddr, a2 as usize) {
|
|
r = 1;
|
|
}
|
|
}
|
|
QasanAction::Alloc => {
|
|
h.alloc(&emulator, a1 as GuestAddr, a2 as GuestAddr);
|
|
}
|
|
QasanAction::Dealloc => {
|
|
h.dealloc(&emulator, a1 as GuestAddr);
|
|
}
|
|
QasanAction::Enable => {
|
|
h.set_enabled(true);
|
|
}
|
|
QasanAction::Disable => {
|
|
h.set_enabled(false);
|
|
}
|
|
QasanAction::SwapState => {
|
|
h.set_enabled(!h.enabled());
|
|
}
|
|
}
|
|
SyscallHookResult::new(Some(r))
|
|
} else {
|
|
SyscallHookResult::new(None)
|
|
}
|
|
}
|