Add builder and tests for QASAN (#2898)

* Add tests for QASAN from aflplusplus

* refactor asan module to use the builder pattern

* move injection tests to the new tests directory
This commit is contained in:
Romain Malmain 2025-01-31 15:43:50 +01:00 committed by GitHub
parent 37fc43f53c
commit 75feedd1a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 574 additions and 245 deletions

View File

@ -1,3 +1,14 @@
env_scripts = ['''
#!@duckscript
profile = get_env PROFILE
if eq ${profile} "dev"
set_env PROFILE_DIR debug
else
set_env PROFILE_DIR ${profile}
end
''']
[env]
PROFILE = { value = "release", condition = { env_not_set = ["PROFILE"] } }
PROFILE_DIR = { source = "${PROFILE}", default_value = "release", mapping = { "release" = "release", "dev" = "debug" }, condition = { env_not_set = [
@ -360,21 +371,11 @@ windows_alias = "unsupported"
script_runner = "@shell"
script = '''
echo "Profile: ${PROFILE}"
cd injection_test || exit 1
make
mkdir in || true
echo aaaaaaaaaa > in/a
timeout 10s "$(find ${TARGET_DIR} -name 'qemu_launcher')" -o out -i in -j ../injections.toml -v -- ./static >/dev/null 2>fuzz.log || true
if [ -z "$(grep -Ei "found.*injection" fuzz.log)" ]; then
echo "Fuzzer does not generate any testcases or any crashes"
echo "Logs:"
cat fuzz.log
exit 1
else
echo "Fuzzer is working"
fi
make clean
#rm -rf in out fuzz.log || true
export QEMU_LAUNCHER=${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher
./tests/injection/test.sh || exit 1
./tests/qasan/test.sh || exit 1
'''
dependencies = ["build_unix"]

View File

@ -94,6 +94,8 @@ impl Client<'_> {
let is_cmplog = self.options.is_cmplog_core(core_id);
let is_drcov = self.options.drcov.is_some();
let extra_tokens = if cfg!(feature = "injections") {
injection_module
.as_ref()
@ -109,24 +111,47 @@ impl Client<'_> {
.client_description(client_description)
.extra_tokens(extra_tokens);
if self.options.rerun_input.is_some() && self.options.drcov.is_some() {
// Special code path for re-running inputs with DrCov.
// TODO: Add ASan support, injection support
if self.options.rerun_input.is_some() {
if is_drcov {
// Special code path for re-running inputs with DrCov and Asan.
// TODO: Add injection support
let drcov = self.options.drcov.as_ref().unwrap();
let drcov = DrCovModule::builder()
if is_asan {
let modules = tuple_list!(
DrCovModule::builder()
.filename(drcov.clone())
.full_trace(true)
.build();
instance_builder
.build()
.run(args, tuple_list!(drcov), state)
.build(),
unsafe { AsanModule::builder().env(&env).asan_report().build() }
);
instance_builder.build().run(args, modules, state)
} else {
let modules = tuple_list!(DrCovModule::builder()
.filename(drcov.clone())
.full_trace(true)
.build(),);
instance_builder.build().run(args, modules, state)
}
} else if is_asan {
let modules =
tuple_list!(unsafe { AsanModule::builder().env(&env).asan_report().build() });
instance_builder.build().run(args, modules, state)
} else {
let modules = tuple_list!();
instance_builder.build().run(args, modules, state)
}
} else if is_asan && is_cmplog {
if let Some(injection_module) = injection_module {
instance_builder.build().run(
args,
tuple_list!(
CmpLogModule::default(),
AsanModule::default(&env),
AsanModule::builder().env(&env).build(),
injection_module,
),
state,
@ -134,7 +159,10 @@ impl Client<'_> {
} else {
instance_builder.build().run(
args,
tuple_list!(CmpLogModule::default(), AsanModule::default(&env),),
tuple_list!(
CmpLogModule::default(),
AsanModule::builder().env(&env).build()
),
state,
)
}
@ -160,13 +188,15 @@ impl Client<'_> {
if let Some(injection_module) = injection_module {
instance_builder.build().run(
args,
tuple_list!(AsanModule::default(&env), injection_module),
tuple_list!(AsanModule::builder().env(&env).build(), injection_module),
state,
)
} else {
instance_builder
.build()
.run(args, tuple_list!(AsanModule::default(&env),), state)
instance_builder.build().run(
args,
tuple_list!(AsanModule::builder().env(&env).build()),
state,
)
}
} else if is_asan_guest {
instance_builder

View File

@ -0,0 +1,30 @@
#!/bin/bash
set -e
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
if [[ ! -x "$QEMU_LAUNCHER" ]]; then
echo "env variable QEMU_LAUNCHER does not point to a valid executable"
echo "QEMU_LAUNCHER should point to qemu_launcher location, but points to ${QEMU_LAUNCHER} instead."
exit 1
fi
cd "$SCRIPT_DIR"
make
mkdir in || true
echo aaaaaaaaaa > in/a
timeout 10s "$QEMU_LAUNCHER" -o out -i in -j ../../injections.toml -v -- ./static >/dev/null 2>fuzz.log || true
if ! grep -Ei "found.*injection" fuzz.log; then
echo "Fuzzer does not generate any testcases or any crashes"
echo "Logs:"
cat fuzz.log
exit 1
else
echo "Fuzzer is working"
fi
make clean

View File

@ -0,0 +1,7 @@
all: qasan
qasan: qasan.c
gcc qasan.c -o qasan
clean:
rm -rf qasan out stats.txt

View File

@ -0,0 +1 @@
M

View File

@ -0,0 +1 @@
A

View File

@ -0,0 +1,85 @@
// taken from
// https://github.com/AFLplusplus/AFLplusplus/blob/da2d4d8258d725f79c2daa22bf3b1a59c593e472/frida_mode/test/fasan/test.c
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#define UNUSED_PARAMETER(x) (void)(x)
#define LOG(x) \
do { \
char buf[] = x; \
write(STDOUT_FILENO, buf, sizeof(buf)); \
\
} while (false);
void LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *buf = malloc(10);
if (buf == NULL) return;
switch (*data) {
/* Underflow */
case 'U':
LOG("Underflow\n");
buf[-1] = '\0';
free(buf);
break;
/* Overflow */
case 'O':
LOG("Overflow\n");
buf[10] = '\0';
free(buf);
break;
/* Double free */
case 'D':
LOG("Double free\n");
free(buf);
free(buf);
break;
/* Use after free */
case 'A':
LOG("Use after free\n");
free(buf);
buf[0] = '\0';
break;
/* Test Limits (OK) */
case 'T':
LOG("Test-Limits - No Error\n");
buf[0] = 'A';
buf[9] = 'I';
free(buf);
break;
case 'M':
LOG("Memset too many\n");
memset(buf, '\0', 11);
free(buf);
break;
default:
LOG("Nop - No Error\n");
break;
}
}
int main(int argc, char **argv) {
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
char input = '\0';
// if (read(STDIN_FILENO, &input, 1) < 0) {
// LOG("Failed to read stdin\n");
// return 1;
// }
LLVMFuzzerTestOneInput(&input, 1);
LOG("DONE\n");
return 0;
}

View File

@ -0,0 +1,69 @@
#!/bin/bash
set -e
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
if [[ ! -x "$QEMU_LAUNCHER" ]]; then
echo "env variable QEMU_LAUNCHER does not point to a valid executable"
echo "QEMU_LAUNCHER should point to qemu_launcher"
exit 1
fi
cd "$SCRIPT_DIR"
make
tests=(
"overflow"
"underflow"
"double_free"
"memset"
"uaf"
"test_limits"
)
tests_expected=(
"is 0 bytes to the right of the 10-byte chunk"
"is 1 bytes to the left of the 10-byte chunk"
"is 0 bytes inside the 10-byte chunk"
"is 0 bytes to the right of the 10-byte chunk"
"is 0 bytes inside the 10-byte chunk"
"Test-Limits - No Error"
)
tests_not_expected=(
"dummy"
"dummy"
"dummy"
"dummy"
"dummy"
"Context:"
)
for i in "${!tests[@]}"
do
test="${tests[i]}"
expected="${tests_expected[i]}"
not_expected="${tests_not_expected[i]}"
echo "Running $test detection test..."
OUT=$("$QEMU_LAUNCHER" \
-r "inputs/$test.txt" \
--input dummy \
--output out \
--asan-cores 0 \
-- qasan 2>&1 | tr -d '\0')
if ! echo "$OUT" | grep -q "$expected"; then
echo "ERROR: Expected: $expected."
echo "Output is:"
echo "$OUT"
exit 1
elif echo "$OUT" | grep -q "$not_expected"; then
echo "ERROR: Did not expect: $not_expected."
echo "Output is:"
echo "$OUT"
exit 1
else
echo "OK."
fi
done

View File

@ -572,16 +572,17 @@ where
static mut CALLSTACKS: Option<ThreadLocal<UnsafeCell<Vec<GuestAddr>>>> = None;
#[derive(Debug)]
pub struct FullBacktraceCollector {}
pub struct FullBacktraceCollector;
impl FullBacktraceCollector {
/// # Safety
///
/// This accesses the global [`CALLSTACKS`] variable and may not be called concurrently.
#[expect(rustdoc::private_intra_doc_links)]
pub unsafe fn new() -> Self {
let callstacks_ptr = &raw mut CALLSTACKS;
unsafe { (*callstacks_ptr) = Some(ThreadLocal::new()) };
Self {}
Self
}
pub fn reset(&mut self) {

View File

@ -1,22 +1,38 @@
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::needless_pass_by_value)] // default compiler complains about Option<&mut T> otherwise, and this is used extensively.
use std::{borrow::Cow, env, fs, path::PathBuf, sync::Mutex};
use core::{fmt, slice};
use std::{
borrow::Cow,
env,
fmt::{Debug, Display},
fs,
path::PathBuf,
pin::Pin,
process,
sync::Mutex,
};
use hashbrown::{HashMap, HashSet};
use libafl::{executors::ExitKind, observers::ObserversTuple};
use libafl_qemu_sys::GuestAddr;
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 object::{Object, ObjectSection};
use rangemap::RangeMap;
use crate::{
emu::EmulatorModules,
modules::{
calls::FullBacktraceCollector, snapshot::SnapshotModule, utils::filters::HasAddressFilter,
EmulatorModule, EmulatorModuleTuple,
calls::FullBacktraceCollector,
snapshot::SnapshotModule,
utils::filters::{HasAddressFilter, StdAddressFilter},
AddressFilter, EmulatorModule, EmulatorModuleTuple,
},
qemu::MemAccessInfo,
qemu::{Hook, MemAccessInfo, QemuHooks, SyscallHookResult},
sys::TCGTemp,
Qemu, QemuParams, Regs,
};
@ -40,6 +56,25 @@ pub const SHADOW_PAGE_MASK: GuestAddr = !(SHADOW_PAGE_SIZE as GuestAddr - 1);
pub const DEFAULT_REDZONE_SIZE: usize = 128;
#[derive(Debug)]
pub struct AsanModule {
env: Vec<(String, String)>,
enabled: bool,
detect_leaks: bool,
empty: bool,
rt: Pin<Box<AsanGiovese>>,
filter: StdAddressFilter,
}
pub struct AsanGiovese {
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>>,
pub snapshot_shadow: bool,
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)]
#[repr(u64)]
pub enum QasanAction {
@ -56,14 +91,6 @@ pub enum QasanAction {
SwapState,
}
impl TryFrom<u32> for QasanAction {
type Error = num_enum::TryFromPrimitiveError<QasanAction>;
fn try_from(value: u32) -> Result<Self, Self::Error> {
QasanAction::try_from(u64::from(value))
}
}
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, PartialEq)]
#[repr(i8)]
pub enum PoisonKind {
@ -105,8 +132,65 @@ pub enum AsanError {
Signal(i32),
}
impl core::fmt::Display for AsanError {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
pub struct AsanModuleBuilder {
env: Option<Vec<(String, String)>>,
detect_leaks: bool,
snapshot: bool,
filter: StdAddressFilter,
error_callback: Option<AsanErrorCallback>,
}
#[derive(Debug, Clone)]
pub struct AllocTreeItem {
backtrace: Vec<GuestAddr>,
free_backtrace: Vec<GuestAddr>,
allocated: bool,
}
type AsanErrorFn = Box<dyn FnMut(&AsanGiovese, Qemu, GuestAddr, AsanError)>;
pub struct AsanErrorCallback(AsanErrorFn);
impl AsanErrorCallback {
/// Initialize a new [`AsanErrorCallback`]
#[must_use]
pub fn new(error_callback: AsanErrorFn) -> Self {
Self(error_callback)
}
/// Special [`AsanErrorCallback`] providing a full report in case of QASAN trigger.
///
/// # Safety
///
/// The `ASan` error report accesses [`FullBacktraceCollector`]
#[must_use]
pub unsafe fn report() -> Self {
Self::new(Box::new(|rt, qemu, pc, err| {
asan_report(rt, qemu, pc, &err);
}))
}
pub fn call(
&mut self,
asan_giovese: &AsanGiovese,
qemu: Qemu,
pc: GuestAddr,
error: AsanError,
) {
self.0(asan_giovese, qemu, pc, error);
}
}
impl TryFrom<u32> for QasanAction {
type Error = num_enum::TryFromPrimitiveError<QasanAction>;
fn try_from(value: u32) -> Result<Self, Self::Error> {
QasanAction::try_from(u64::from(value))
}
}
impl Display for AsanError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
AsanError::Read(addr, len) => write!(fmt, "Invalid {len} bytes read at {addr:#x}"),
AsanError::Write(addr, len) => {
@ -122,15 +206,6 @@ impl core::fmt::Display for AsanError {
}
}
pub type AsanErrorCallback = Box<dyn FnMut(&AsanGiovese, Qemu, 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 {
@ -146,27 +221,8 @@ impl AllocTreeItem {
self.allocated = false;
}
}
use std::pin::Pin;
use libafl_qemu_sys::GuestAddr;
use object::{Object, ObjectSection};
use crate::{
emu::EmulatorModules,
modules::{utils::filters::StdAddressFilter, AddressFilter},
qemu::{Hook, QemuHooks, SyscallHookResult},
};
pub struct AsanGiovese {
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>>,
pub snapshot_shadow: bool,
}
impl core::fmt::Debug for AsanGiovese {
impl Debug for AsanGiovese {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AsanGiovese")
.field("alloc_tree", &self.alloc_tree)
@ -175,6 +231,211 @@ impl core::fmt::Debug for AsanGiovese {
}
}
impl AsanModuleBuilder {
#[must_use]
pub fn new(
env: Option<Vec<(String, String)>>,
detect_leaks: bool,
snapshot: bool,
filter: StdAddressFilter,
error_callback: Option<AsanErrorCallback>,
) -> Self {
Self {
env,
detect_leaks,
snapshot,
filter,
error_callback,
}
}
#[must_use]
pub fn env(self, env: &[(String, String)]) -> Self {
Self::new(
Some(env.to_vec()),
self.detect_leaks,
self.snapshot,
self.filter,
self.error_callback,
)
}
#[must_use]
pub fn detect_leaks(self, detect_leaks: bool) -> Self {
Self::new(
self.env,
detect_leaks,
self.snapshot,
self.filter,
self.error_callback,
)
}
#[must_use]
pub fn snapshot(self, snapshot: bool) -> Self {
Self::new(
self.env,
self.detect_leaks,
snapshot,
self.filter,
self.error_callback,
)
}
#[must_use]
pub fn filter(self, filter: StdAddressFilter) -> Self {
Self::new(
self.env,
self.detect_leaks,
self.snapshot,
filter,
self.error_callback,
)
}
#[must_use]
pub fn error_callback(self, callback: AsanErrorCallback) -> Self {
Self::new(
self.env,
self.detect_leaks,
self.snapshot,
self.filter,
Some(callback),
)
}
/// Get an ASAN report in case of problem.
///
/// # Safety
///
/// The `ASan` error report accesses [`FullBacktraceCollector`].
/// Check its safety note for more details.
#[must_use]
pub unsafe fn asan_report(self) -> Self {
Self::new(
self.env,
self.detect_leaks,
self.snapshot,
self.filter,
Some(AsanErrorCallback::report()),
)
}
#[must_use]
pub fn build(self) -> AsanModule {
AsanModule::new(
self.env.unwrap().as_ref(),
self.detect_leaks,
self.snapshot,
self.filter,
self.error_callback,
)
}
}
impl Default for AsanModuleBuilder {
fn default() -> Self {
Self::new(None, false, true, StdAddressFilter::default(), None)
}
}
impl AsanModule {
#[must_use]
pub fn builder() -> AsanModuleBuilder {
AsanModuleBuilder::default()
}
#[must_use]
pub fn new(
env: &[(String, String)],
detect_leaks: bool,
snapshot: bool,
filter: StdAddressFilter,
error_callback: Option<AsanErrorCallback>,
) -> Self {
let mut rt = AsanGiovese::new();
rt.set_snapshot_shadow(snapshot);
if let Some(cb) = error_callback {
rt.set_error_callback(cb);
}
Self {
env: env.to_vec(),
enabled: true,
detect_leaks,
empty: true,
rt,
filter,
}
}
#[must_use]
pub fn must_instrument(&self, addr: GuestAddr) -> 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, pc: GuestAddr, start: GuestAddr, end: GuestAddr) {
self.rt.allocation(pc, start, end);
}
pub fn dealloc(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) {
self.rt.deallocation(qemu, pc, addr);
}
#[must_use]
pub fn is_poisoned(&self, qemu: Qemu, addr: GuestAddr, size: usize) -> bool {
AsanGiovese::is_invalid_access_n(qemu, addr, size)
}
pub fn read<const N: usize>(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access::<N>(qemu, addr) {
self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, N));
}
}
pub fn read_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access_n(qemu, addr, size) {
self.rt
.report_or_crash(qemu, pc, AsanError::Read(addr, size));
}
}
pub fn write<const N: usize>(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access::<N>(qemu, addr) {
self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, N));
}
}
pub fn write_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access_n(qemu, addr, size) {
self.rt
.report_or_crash(qemu, pc, AsanError::Write(addr, size));
}
}
pub fn poison(&mut self, qemu: Qemu, addr: GuestAddr, size: usize, poison: PoisonKind) {
self.rt.poison(qemu, addr, size, poison.into());
}
pub fn unpoison(&mut self, qemu: Qemu, addr: GuestAddr, size: usize) {
AsanGiovese::unpoison(qemu, addr, size);
}
pub fn reset(&mut self, qemu: Qemu) -> AsanRollback {
self.rt.rollback(qemu, self.detect_leaks)
}
}
impl AsanGiovese {
unsafe fn init(self: &mut Pin<Box<Self>>, qemu_hooks: QemuHooks) {
assert_ne!(
@ -435,22 +696,22 @@ impl AsanGiovese {
unsafe {
let h = qemu.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)
slice::from_raw_parts_mut(shadow_addr, SHADOW_PAGE_SIZE)
}
}
pub fn report_or_crash(&mut self, qemu: Qemu, pc: GuestAddr, error: AsanError) {
if let Some(mut cb) = self.error_callback.take() {
cb(self, qemu, pc, error);
cb.call(self, qemu, pc, error);
self.error_callback = Some(cb);
} else {
std::process::abort();
process::abort();
}
}
pub fn report(&mut self, qemu: Qemu, pc: GuestAddr, error: AsanError) {
if let Some(mut cb) = self.error_callback.take() {
cb(self, qemu, pc, error);
cb.call(self, qemu, pc, error);
self.error_callback = Some(cb);
}
}
@ -649,167 +910,6 @@ impl AsanGiovese {
}
}
pub enum QemuAsanOptions {
None,
Snapshot,
DetectLeaks,
SnapshotDetectLeaks,
}
pub type AsanChildModule = AsanModule;
#[derive(Debug)]
pub struct AsanModule {
env: Vec<(String, String)>,
enabled: bool,
detect_leaks: bool,
empty: bool,
rt: Pin<Box<AsanGiovese>>,
filter: StdAddressFilter,
}
impl AsanModule {
#[must_use]
pub fn default(env: &[(String, String)]) -> Self {
Self::new(StdAddressFilter::default(), &QemuAsanOptions::Snapshot, env)
}
#[must_use]
pub fn new(
filter: StdAddressFilter,
options: &QemuAsanOptions,
env: &[(String, String)],
) -> Self {
let (snapshot, detect_leaks) = match options {
QemuAsanOptions::None => (false, false),
QemuAsanOptions::Snapshot => (true, false),
QemuAsanOptions::DetectLeaks => (false, true),
QemuAsanOptions::SnapshotDetectLeaks => (true, true),
};
let mut rt = AsanGiovese::new();
rt.set_snapshot_shadow(snapshot);
Self {
env: env.to_vec(),
enabled: true,
detect_leaks,
empty: true,
rt,
filter,
}
}
#[must_use]
pub fn with_error_callback(
filter: StdAddressFilter,
error_callback: AsanErrorCallback,
options: &QemuAsanOptions,
env: &[(String, String)],
) -> Self {
let (snapshot, detect_leaks) = match options {
QemuAsanOptions::None => (false, false),
QemuAsanOptions::Snapshot => (true, false),
QemuAsanOptions::DetectLeaks => (false, true),
QemuAsanOptions::SnapshotDetectLeaks => (true, true),
};
let mut rt = AsanGiovese::new();
rt.set_snapshot_shadow(snapshot);
rt.set_error_callback(error_callback);
Self {
env: env.to_vec(),
enabled: true,
detect_leaks,
empty: true,
rt,
filter,
}
}
/// # Safety
/// The `ASan` error report accesses [`FullBacktraceCollector`]
#[must_use]
pub unsafe fn with_asan_report(
filter: StdAddressFilter,
options: &QemuAsanOptions,
env: &[(String, String)],
) -> Self {
Self::with_error_callback(
filter,
Box::new(|rt, qemu, pc, err| unsafe { asan_report(rt, qemu, pc, &err) }),
options,
env,
)
}
#[must_use]
pub fn must_instrument(&self, addr: GuestAddr) -> 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, pc: GuestAddr, start: GuestAddr, end: GuestAddr) {
self.rt.allocation(pc, start, end);
}
pub fn dealloc(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) {
self.rt.deallocation(qemu, pc, addr);
}
#[must_use]
pub fn is_poisoned(&self, qemu: Qemu, addr: GuestAddr, size: usize) -> bool {
AsanGiovese::is_invalid_access_n(qemu, addr, size)
}
pub fn read<const N: usize>(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access::<N>(qemu, addr) {
self.rt.report_or_crash(qemu, pc, AsanError::Read(addr, N));
}
}
pub fn read_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access_n(qemu, addr, size) {
self.rt
.report_or_crash(qemu, pc, AsanError::Read(addr, size));
}
}
pub fn write<const N: usize>(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr) {
if self.enabled() && AsanGiovese::is_invalid_access::<N>(qemu, addr) {
self.rt.report_or_crash(qemu, pc, AsanError::Write(addr, N));
}
}
pub fn write_n(&mut self, qemu: Qemu, pc: GuestAddr, addr: GuestAddr, size: usize) {
if self.enabled() && AsanGiovese::is_invalid_access_n(qemu, addr, size) {
self.rt
.report_or_crash(qemu, pc, AsanError::Write(addr, size));
}
}
pub fn poison(&mut self, qemu: Qemu, addr: GuestAddr, size: usize, poison: PoisonKind) {
self.rt.poison(qemu, addr, size, poison.into());
}
pub fn unpoison(&mut self, qemu: Qemu, addr: GuestAddr, size: usize) {
AsanGiovese::unpoison(qemu, addr, size);
}
pub fn reset(&mut self, qemu: Qemu) -> AsanRollback {
self.rt.rollback(qemu, self.detect_leaks)
}
}
impl<I, S> EmulatorModule<I, S> for AsanModule
where
I: Unpin,
@ -1203,7 +1303,7 @@ fn load_file_section<'input, 'arena, Endian: addr2line::gimli::Endianity>(
/// has been removed in version v0.23 for some reason.
/// TODO: find another cleaner solution.
mod addr2line_legacy {
use std::{borrow::Cow, ffi::OsString, fs::File, path::PathBuf, sync::Arc};
use std::{borrow::Cow, env, ffi::OsString, fs::File, path::PathBuf, sync::Arc};
use addr2line::{gimli, LookupContinuation, LookupResult};
use object::Object;
@ -1223,7 +1323,7 @@ mod addr2line_legacy {
r: &R,
) -> Result<PathBuf, gimli::Error> {
let bytes = r.to_slice()?;
let s = std::str::from_utf8(&bytes).map_err(|_| gimli::Error::BadUtf8)?;
let s = str::from_utf8(&bytes).map_err(|_| gimli::Error::BadUtf8)?;
Ok(PathBuf::from(s))
}
@ -1269,7 +1369,7 @@ mod addr2line_legacy {
loader: &mut F,
path: Option<PathBuf>,
) -> Option<gimli::DwarfPackage<R>> {
let mut path = path.map_or_else(std::env::current_exe, Ok).ok()?;
let mut path = path.map_or_else(env::current_exe, Ok).ok()?;
let dwp_extension = path.extension().map_or_else(
|| OsString::from("dwp"),
|previous_extension| {