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:
parent
37fc43f53c
commit
75feedd1a0
@ -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"]
|
||||
|
||||
|
@ -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
|
||||
|
30
fuzzers/binary_only/qemu_launcher/tests/injection/test.sh
Executable file
30
fuzzers/binary_only/qemu_launcher/tests/injection/test.sh
Executable 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
|
7
fuzzers/binary_only/qemu_launcher/tests/qasan/Makefile
Normal file
7
fuzzers/binary_only/qemu_launcher/tests/qasan/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
all: qasan
|
||||
|
||||
qasan: qasan.c
|
||||
gcc qasan.c -o qasan
|
||||
|
||||
clean:
|
||||
rm -rf qasan out stats.txt
|
@ -0,0 +1 @@
|
||||
D
|
@ -0,0 +1 @@
|
||||
M
|
@ -0,0 +1 @@
|
||||
O
|
@ -0,0 +1 @@
|
||||
T
|
@ -0,0 +1 @@
|
||||
A
|
@ -0,0 +1 @@
|
||||
U
|
85
fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c
Normal file
85
fuzzers/binary_only/qemu_launcher/tests/qasan/qasan.c
Normal 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;
|
||||
}
|
69
fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh
Executable file
69
fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh
Executable 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
|
@ -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) {
|
||||
|
@ -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| {
|
||||
|
Loading…
x
Reference in New Issue
Block a user