Implement user-space QEMU ASAN (#1806)

* Implement user-space QEMU ASAN

* Fix wrong cfgs

* fmt

* merge conflicts in libafl qemu

* A few more fixes to qemu_launcher

* Change commit of qemu-libafl-bridge

* Fix clippy in qemu_launcher

* Fix commit id again

* Empty commit to trigger CI

* Fix path to fuzzer for test in qemu_launcher?

* Revert location of target binary and show the full error log from qemu_launcher test

* Appease the clippy gods

* Empty

* Fix format

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>
This commit is contained in:
WorksButNotTested 2024-04-10 00:11:28 +01:00 committed by GitHub
parent 47c41c2925
commit 374f8735fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 841 additions and 105 deletions

View File

@ -216,8 +216,7 @@ ${CROSS_CXX} \
-I"${TARGET_DIR}/build-zlib/zlib/include" \ -I"${TARGET_DIR}/build-zlib/zlib/include" \
-L"${TARGET_DIR}/build-zlib/zlib/lib" \ -L"${TARGET_DIR}/build-zlib/zlib/lib" \
-o"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}" \ -o"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}" \
-lm \ -lm
-static
''' '''
dependencies = [ "libpng" ] dependencies = [ "libpng" ]
@ -280,6 +279,42 @@ args = [
] ]
dependencies = [ "harness", "fuzzer" ] dependencies = [ "harness", "fuzzer" ]
[tasks.asan]
linux_alias = "asan_unix"
mac_alias = "unsupported"
windows_alias = "unsupported"
[tasks.asan_unix]
command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher-${CARGO_MAKE_PROFILE}"
args = [
"--input", "./corpus",
"--output", "${TARGET_DIR}/output/",
"--log", "${TARGET_DIR}/output/log.txt",
"--cores", "0",
"--asan-cores", "0",
"--",
"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}",
]
dependencies = [ "harness", "fuzzer" ]
[tasks.asan_guest]
linux_alias = "asan_guest_unix"
mac_alias = "unsupported"
windows_alias = "unsupported"
[tasks.asan_guest_unix]
command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher-${CARGO_MAKE_PROFILE}"
args = [
"--input", "./corpus",
"--output", "${TARGET_DIR}/output/",
"--log", "${TARGET_DIR}/output/log.txt",
"--cores", "0",
"--asan-guest-cores", "0",
"--",
"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}",
]
dependencies = [ "harness", "fuzzer" ]
[tasks.test] [tasks.test]
linux_alias = "test_unix" linux_alias = "test_unix"
mac_alias = "unsupported" mac_alias = "unsupported"
@ -297,7 +332,7 @@ timeout 10s "$(find ${TARGET_DIR} -name 'qemu_launcher')" -o out -i in -j ../inj
if [ -z "$(grep -Ei "found.*injection" fuzz.log)" ]; then if [ -z "$(grep -Ei "found.*injection" fuzz.log)" ]; then
echo "Fuzzer does not generate any testcases or any crashes" echo "Fuzzer does not generate any testcases or any crashes"
echo "Logs:" echo "Logs:"
tail fuzz.log cat fuzz.log
exit 1 exit 1
else else
echo "Fuzzer is working" echo "Fuzzer is working"

View File

@ -12,6 +12,7 @@ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list};
use libafl_qemu::injections::QemuInjectionHelper; use libafl_qemu::injections::QemuInjectionHelper;
use libafl_qemu::{ use libafl_qemu::{
asan::{init_qemu_with_asan, QemuAsanHelper}, asan::{init_qemu_with_asan, QemuAsanHelper},
asan_guest::{init_qemu_with_asan_guest, QemuAsanGuestHelper},
cmplog::QemuCmpLogHelper, cmplog::QemuCmpLogHelper,
edges::QemuEdgeCoverageHelper, edges::QemuEdgeCoverageHelper,
elf::EasyElf, elf::EasyElf,
@ -110,12 +111,22 @@ impl<'a> Client<'a> {
let mut env = self.env(); let mut env = self.env();
log::debug!("ENV: {:#?}", env); log::debug!("ENV: {:#?}", env);
let (qemu, mut asan) = { let is_asan = self.options.is_asan_core(core_id);
if self.options.is_asan_core(core_id) { let is_asan_guest = self.options.is_asan_guest_core(core_id);
if is_asan && is_asan_guest {
Err(Error::empty_optional("Multiple ASAN modes configured"))?;
}
let (qemu, mut asan, mut asan_lib) = {
if is_asan {
let (emu, asan) = init_qemu_with_asan(&mut args, &mut env)?; let (emu, asan) = init_qemu_with_asan(&mut args, &mut env)?;
(emu, Some(asan)) (emu, Some(asan), None)
} else if is_asan_guest {
let (emu, asan_lib) = init_qemu_with_asan_guest(&mut args, &mut env)?;
(emu, None, Some(asan_lib))
} else { } else {
(Qemu::init(&args, &env)?, None) (Qemu::init(&args, &env)?, None, None)
} }
}; };
@ -151,7 +162,6 @@ impl<'a> Client<'a> {
log::debug!("ret_addr = {ret_addr:#x}"); log::debug!("ret_addr = {ret_addr:#x}");
qemu.set_breakpoint(ret_addr); qemu.set_breakpoint(ret_addr);
let is_asan = self.options.is_asan_core(core_id);
let is_cmplog = self.options.is_cmplog_core(core_id); let is_cmplog = self.options.is_cmplog_core(core_id);
let edge_coverage_helper = QemuEdgeCoverageHelper::new(self.coverage_filter(&qemu)?); let edge_coverage_helper = QemuEdgeCoverageHelper::new(self.coverage_filter(&qemu)?);
@ -184,6 +194,27 @@ impl<'a> Client<'a> {
state, state,
) )
} }
} else if is_asan_guest && is_cmplog {
if let Some(injection_helper) = injection_helper {
instance.build().run(
tuple_list!(
edge_coverage_helper,
QemuCmpLogHelper::default(),
QemuAsanGuestHelper::default(&qemu, asan_lib.take().unwrap()),
injection_helper
),
state,
)
} else {
instance.build().run(
tuple_list!(
edge_coverage_helper,
QemuCmpLogHelper::default(),
QemuAsanGuestHelper::default(&qemu, asan_lib.take().unwrap()),
),
state,
)
}
} else if is_asan { } else if is_asan {
if let Some(injection_helper) = injection_helper { if let Some(injection_helper) = injection_helper {
instance.build().run( instance.build().run(
@ -203,6 +234,12 @@ impl<'a> Client<'a> {
state, state,
) )
} }
} else if is_asan_guest {
let helpers = tuple_list!(
edge_coverage_helper,
QemuAsanGuestHelper::default(&qemu, asan_lib.take().unwrap())
);
instance.build().run(helpers, state)
} else if is_cmplog { } else if is_cmplog {
if let Some(injection_helper) = injection_helper { if let Some(injection_helper) = injection_helper {
instance.build().run( instance.build().run(

View File

@ -51,6 +51,9 @@ pub struct FuzzerOptions {
#[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)] #[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)]
pub asan_cores: Option<Cores>, pub asan_cores: Option<Cores>,
#[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)]
pub asan_guest_cores: Option<Cores>,
#[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)] #[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)]
pub cmplog_cores: Option<Cores>, pub cmplog_cores: Option<Cores>,
@ -113,6 +116,12 @@ impl FuzzerOptions {
.map_or(false, |c| c.contains(core_id)) .map_or(false, |c| c.contains(core_id))
} }
pub fn is_asan_guest_core(&self, core_id: CoreId) -> bool {
self.asan_guest_cores
.as_ref()
.map_or(false, |c| c.contains(core_id))
}
pub fn is_cmplog_core(&self, core_id: CoreId) -> bool { pub fn is_cmplog_core(&self, core_id: CoreId) -> bool {
self.cmplog_cores self.cmplog_cores
.as_ref() .as_ref()

View File

@ -16,7 +16,7 @@ features = ["document-features", "default", "python", "x86_64", "usermode"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
default = ["fork", "build_libqasan", "serdeany_autoreg", "injections"] default = ["fork", "build_libgasan", "build_libqasan", "serdeany_autoreg", "injections"]
clippy = [] # special feature for clippy, don't use in normal projects§ clippy = [] # special feature for clippy, don't use in normal projects§
document-features = ["dep:document-features"] document-features = ["dep:document-features"]
@ -29,6 +29,7 @@ python = ["pyo3", "pyo3-build-config", "libafl_qemu_sys/python"]
## Fork support ## Fork support
fork = ["libafl/fork"] fork = ["libafl/fork"]
## Build libqasan for address sanitization ## Build libqasan for address sanitization
build_libgasan = []
build_libqasan = [] build_libqasan = []
#! ## The following architecture features are mutually exclusive. #! ## The following architecture features are mutually exclusive.

View File

@ -14,9 +14,11 @@ pub fn build() {
}) })
}; };
let libafl_qemu_hdr_name = "libafl_qemu.h";
let build_libqasan = cfg!(all(feature = "build_libqasan", not(feature = "hexagon"))); let qemu_asan_guest = cfg!(all(feature = "build_libgasan", not(feature = "hexagon")));
let qemu_asan = cfg!(all(feature = "build_libqasan", not(feature = "hexagon")));
let libafl_qemu_hdr_name = "libafl_qemu.h";
let exit_hdr_dir = PathBuf::from("runtime"); let exit_hdr_dir = PathBuf::from("runtime");
let libafl_qemu_hdr = exit_hdr_dir.join(libafl_qemu_hdr_name); let libafl_qemu_hdr = exit_hdr_dir.join(libafl_qemu_hdr_name);
@ -50,7 +52,7 @@ pub fn build() {
println!("cargo:rerun-if-env-changed=CPU_TARGET"); println!("cargo:rerun-if-env-changed=CPU_TARGET");
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\""); println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
let cross_cc = if (emulation_mode == "usermode") && build_libqasan { let cross_cc = if (emulation_mode == "usermode") && (qemu_asan || qemu_asan_guest) {
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc) // TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)
let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| { let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| {
println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH); println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH);
@ -96,7 +98,7 @@ pub fn build() {
.write_to_file(binding_file) .write_to_file(binding_file)
.expect("Could not write bindings."); .expect("Could not write bindings.");
if (emulation_mode == "usermode") && build_libqasan { if (emulation_mode == "usermode") && (qemu_asan || qemu_asan_guest) {
let qasan_dir = Path::new("libqasan"); let qasan_dir = Path::new("libqasan");
let qasan_dir = fs::canonicalize(qasan_dir).unwrap(); let qasan_dir = fs::canonicalize(qasan_dir).unwrap();
println!("cargo:rerun-if-changed={}", qasan_dir.display()); println!("cargo:rerun-if-changed={}", qasan_dir.display());
@ -114,6 +116,5 @@ pub fn build() {
.status() .status()
.expect("make failed") .expect("make failed")
.success()); .success());
// println!("cargo:rerun-if-changed={}/libqasan.so", target_dir.display());
} }
} }

View File

@ -8,7 +8,7 @@ use which::which;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "821ad471430360c4eed644d07d59f0d603ef23f6"; const QEMU_REVISION: &str = "f1e48d694ca31045169853ca65b1a5a95e8746e9";
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub struct BuildResult { pub struct BuildResult {

View File

@ -32,9 +32,6 @@ pub fn build() {
println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\""); println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\"");
println!("cargo:rerun-if-env-changed=EMULATION_MODE"); println!("cargo:rerun-if-env-changed=EMULATION_MODE");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=build_linux.rs");
// Make sure we have at most one architecutre feature set // Make sure we have at most one architecutre feature set
// Else, we default to `x86_64` - having a default makes CI easier :) // Else, we default to `x86_64` - having a default makes CI easier :)
assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc", "hexagon"); assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc", "hexagon");

View File

@ -120,6 +120,7 @@ pub type GuestVirtAddr = crate::vaddr;
pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr; pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr;
#[derive(Debug)]
#[repr(C)] #[repr(C)]
#[cfg_attr(feature = "python", pyclass(unsendable))] #[cfg_attr(feature = "python", pyclass(unsendable))]
pub struct MapInfo { pub struct MapInfo {

View File

@ -13500,7 +13500,14 @@ extern "C" {
} }
extern "C" { extern "C" {
pub fn libafl_add_read_hook( pub fn libafl_add_read_hook(
gen: ::std::option::Option<extern "C" fn(data: u64, pc: target_ulong, oi: MemOpIdx) -> u64>, gen: ::std::option::Option<
unsafe extern "C" fn(
data: u64,
pc: target_ulong,
addr: *mut TCGTemp,
oi: MemOpIdx,
) -> u64,
>,
exec1: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>, exec1: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec2: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>, exec2: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec4: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>, exec4: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
@ -13513,7 +13520,14 @@ extern "C" {
} }
extern "C" { extern "C" {
pub fn libafl_add_write_hook( pub fn libafl_add_write_hook(
gen: ::std::option::Option<extern "C" fn(data: u64, pc: target_ulong, oi: MemOpIdx) -> u64>, gen: ::std::option::Option<
unsafe extern "C" fn(
data: u64,
pc: target_ulong,
addr: *mut TCGTemp,
oi: MemOpIdx,
) -> u64,
>,
exec1: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>, exec1: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec2: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>, exec2: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,
exec4: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>, exec4: ::std::option::Option<extern "C" fn(data: u64, id: u64, addr: target_ulong)>,

View File

@ -15,13 +15,16 @@
OUT_DIR ?= . OUT_DIR ?= .
override CFLAGS += -Wno-int-to-void-pointer-cast -ggdb -O1 -fno-builtin override CFLAGS += -Wno-int-to-void-pointer-cast -ggdb -O1 -fno-builtin -Wno-unused-result
override LDFLAGS += -ldl -pthread override LDFLAGS += -ldl -pthread
SRC := libqasan.c hooks.c malloc.c string.c uninstrument.c patch.c dlmalloc.c printf/printf.c SRC := libqasan.c hooks.c malloc.c string.c uninstrument.c patch.c dlmalloc.c printf/printf.c
HDR := libqasan.h qasan.h map_macro.h printf/printf.h HDR := libqasan.h qasan.h map_macro.h printf/printf.h
all: $(OUT_DIR)/libqasan.so all: $(OUT_DIR)/libgasan.so $(OUT_DIR)/libqasan.so
$(OUT_DIR)/libgasan.so: $(HDR) $(SRC)
$(CC) $(CFLAGS) -DASAN_GUEST=1 -fPIC -shared $(SRC) -o $@ $(LDFLAGS)
$(OUT_DIR)/libqasan.so: $(HDR) $(SRC) $(OUT_DIR)/libqasan.so: $(HDR) $(SRC)
$(CC) $(CFLAGS) -fPIC -shared $(SRC) -o $@ $(LDFLAGS) $(CC) $(CFLAGS) -fPIC -shared $(SRC) -o $@ $(LDFLAGS)
@ -30,4 +33,5 @@ $(OUT_DIR)/libqasan.so: $(HDR) $(SRC)
clean: clean:
rm -f *.o *.so *~ a.out core core.[1-9][0-9]* rm -f *.o *.so *~ a.out core core.[1-9][0-9]*
rm -f $(OUT_DIR)/libgasan.so
rm -f $(OUT_DIR)/libqasan.so rm -f $(OUT_DIR)/libqasan.so

View File

@ -57,6 +57,47 @@ void __libqasan_print_maps(void) {
int __libqasan_is_initialized = 0; int __libqasan_is_initialized = 0;
__attribute__((always_inline)) inline size_t qasan_align_down(size_t val,
size_t align) {
return (val & ~(align - 1));
}
__attribute__((always_inline)) inline size_t qasan_align_up(size_t val,
size_t align) {
return qasan_align_down(val + align - 1, align);
}
#ifdef ASAN_GUEST
static void __libqasan_map_shadow(void *addr, void *limit) {
size_t size = (limit - addr) + 1;
void *map = mmap(addr, size, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_FIXED_NOREPLACE | MAP_PRIVATE |
MAP_ANONYMOUS | MAP_NORESERVE,
-1, 0);
if (map != addr) {
QASAN_LOG("Failed to map shadow: %p-%p, errno: %d", addr, limit + 1, errno);
abort();
}
if (madvise(addr, size, MADV_HUGEPAGE) != 0) {
QASAN_LOG("Failed to madvise (MADV_HUGEPAGE) shadow: %p-%p, errno: %d",
addr, limit + 1, errno);
abort();
}
}
#endif
#ifdef ASAN_GUEST
const size_t ALLOC_ALIGN_POW = 3;
const size_t ALLOC_ALIGN_SIZE = (1UL << ALLOC_ALIGN_POW);
#if defined(__x86_64__) || defined(__aarch64__)
#define SHADOW_OFFSET (0x7fff8000)
#else
#define SHADOW_OFFSET (0x20000000)
#endif
#endif
__attribute__((constructor)) void __libqasan_init() { __attribute__((constructor)) void __libqasan_init() {
if (__libqasan_is_initialized) { return; } if (__libqasan_is_initialized) { return; }
__libqasan_is_initialized = 1; __libqasan_is_initialized = 1;
@ -77,9 +118,252 @@ __attribute__((constructor)) void __libqasan_init() {
"Copyright (C) 2019-2021 Andrea Fioraldi <andreafioraldi@gmail.com>\n"); "Copyright (C) 2019-2021 Andrea Fioraldi <andreafioraldi@gmail.com>\n");
QASAN_LOG("\n"); QASAN_LOG("\n");
#ifdef ASAN_GUEST
QASAN_DEBUG("QASAN - Debugging is enabled!!!\n");
/* MMap our shadow and madvise to use huge pages */
#if defined(__x86_64__) || defined(__aarch64__)
// [0x10007fff8000, 0x7fffffffffff] HighMem
// [0x02008fff7000, 0x10007fff7fff] HighShadow
// [0x00008fff7000, 0x02008fff6fff] ShadowGap
// [0x00007fff8000, 0x00008fff6fff] LowShadow
// [0x000000000000, 0x00007fff7fff] LowMem
__libqasan_map_shadow((void *)0x02008fff7000, (void *)0x10007fff7fff);
__libqasan_map_shadow((void *)0x00007fff8000, (void *)0x00008fff6fff);
#else
// [0x40000000, 0xffffffff] HighMem
// [0x28000000, 0x3fffffff] HighShadow
// [0x24000000, 0x27ffffff] ShadowGap
// [0x20000000, 0x23ffffff] LowShadow
// [0x00000000, 0x1fffffff] LowMem
__libqasan_map_shadow((void *)0x28000000, (void *)0x3fffffff);
__libqasan_map_shadow((void *)0x20000000, (void *)0x23ffffff);
#endif
#endif
// if (__qasan_log) { __libqasan_print_maps(); } // if (__qasan_log) { __libqasan_print_maps(); }
} }
#ifdef ASAN_GUEST
__attribute__((always_inline)) static inline char *qasan_get_shadow(
const char *start) {
size_t shadow_addr = ((size_t)start >> ALLOC_ALIGN_POW) + SHADOW_OFFSET;
return ((char *)shadow_addr);
}
__attribute__((always_inline)) static inline const char *qasan_align_ptr_down(
const char *start, size_t n) {
return (const char *)qasan_align_down((size_t)start, n);
}
__attribute__((always_inline)) static inline const char *qasan_align_ptr_up(
const char *start, size_t n) {
return qasan_align_ptr_down(&start[n - 1], n);
}
static bool qemu_mem_test(const char *k_start, const char *k_end) {
for (const char *cursor = k_start; cursor < k_end; cursor++) {
char k = *cursor;
if (k != 0) {
QASAN_DEBUG("qemu_mem_test - k_start: %p, k_end: %p, cursor: %p, k: %d\n",
k_start, k_end, cursor, k);
return true;
}
}
return false;
}
static void qemu_mem_set(char *k_start, char *k_end, char val) {
for (char *cursor = (char *)k_start; cursor < k_end; cursor++) {
*cursor = val;
}
}
/* Our end point should be 8-byte aligned */
void qasan_load(const char *start, size_t len) {
QASAN_DEBUG("LOAD: %p-%p\n", start, &start[len]);
if (qasan_is_poison(start, len)) {
QASAN_LOG("Region is poisoned: %p-%p\n", start, &start[len]);
abort();
}
}
void qasan_store(const char *start, size_t len) {
QASAN_DEBUG("STORE: %p-%p\n", start, &start[len]);
if (qasan_is_poison(start, len)) {
QASAN_LOG("Region is poisoned: %p-%p\n", start, &start[len]);
abort();
}
}
void qasan_poison(const char *start, size_t len, char val) {
const char *end = &start[len];
QASAN_DEBUG("POISON: %p-%p, (%zu) 0x%02x\n", start, end, len, val);
const char *start_aligned = qasan_align_ptr_up(start, ALLOC_ALIGN_SIZE);
const char *end_aligned = qasan_align_ptr_down(end, ALLOC_ALIGN_SIZE);
if (len == 0) return;
if (end != end_aligned) {
QASAN_LOG("Region end is unaligned: %p-%p, end_aligned: %p\n", start, end,
end_aligned);
abort();
}
/* k > 0 (first k bytes are UN-poisoned */
size_t first_unpoisoned = ALLOC_ALIGN_SIZE - (start_aligned - start);
QASAN_DEBUG("UNPOIS - first_unpoisoned: %zu\n", first_unpoisoned);
char *k_start = qasan_get_shadow(start);
QASAN_DEBUG("UNPOISON - k_start: %p\n", k_start);
if (first_unpoisoned == 0) {
*k_start = val;
} else {
*k_start = first_unpoisoned;
}
/*
* The end is aligned, so we can round up the start and deal with the
* remaining aligned buffer now
*/
char *k_start_aligned = qasan_get_shadow(start_aligned);
char *k_end_aligned = qasan_get_shadow(end_aligned);
QASAN_DEBUG("POISONk: %p-%p\n", k_start_aligned, k_end_aligned);
qemu_mem_set(k_start_aligned, k_end_aligned, val);
QASAN_DEBUG("POISONED: %p-%p, 0x%02x\n", start, end, val);
}
void qasan_unpoison(const char *start, size_t len) {
const char *end = &start[len];
QASAN_DEBUG("UNPOISON: %p-%p (%zu)\n", start, end, len);
const char *start_aligned = qasan_align_ptr_up(start, ALLOC_ALIGN_SIZE);
const char *end_aligned = qasan_align_ptr_down(end, ALLOC_ALIGN_SIZE);
if (len == 0) return;
if (start_aligned != start) {
QASAN_LOG("Region start is unaligned: %p-%p, start_aligned: %p\n", start,
end, start_aligned);
abort();
}
char *k_start_aligned = qasan_get_shadow(k_start_aligned);
char *k_end_aligned = qasan_get_shadow(k_end_aligned);
QASAN_DEBUG("UNPOISONk: %p-%p\n", k_start_aligned, k_end_aligned);
qemu_mem_set(k_start_aligned, k_end_aligned, 0);
size_t last_unpoisoned = end - end_aligned;
QASAN_DEBUG("UNPOISON - last_unpoisoned: %zu\n", last_unpoisoned);
char *k_end = qasan_get_shadow(end);
QASAN_DEBUG("UNPOISON - k_end: %p\n", k_end);
*k_end = (char)last_unpoisoned;
QASAN_DEBUG("UNPOISONED: %p-%p\n", start, end);
}
bool qasan_is_poison(const char *start, size_t len) {
const char *end = &start[len];
QASAN_DEBUG("IS POISON: %p-%p (%zu)\n", start, end, len);
const char *start_aligned = qasan_align_ptr_up(start, ALLOC_ALIGN_SIZE);
const char *end_aligned = qasan_align_ptr_down(end, ALLOC_ALIGN_SIZE);
if (len == 0) return false;
/* If our start is unaligned */
if (start_aligned != start) {
char *k_start = qasan_get_shadow(start);
QASAN_DEBUG("IS POISON - k_start: %p\n", k_start);
size_t first_k = (size_t)*k_start;
QASAN_DEBUG("IS POISON - first_k: %zu\n", first_k);
/* If our buffer ends within the first shadow byte */
if (end < start_aligned) {
size_t first_len = end - end_aligned;
QASAN_DEBUG("IS POISON - first_len: %zu\n", first_len);
if ((first_k != 0) && (first_len > first_k)) {
QASAN_DEBUG(
"qasan_is_poison #1 - start_aligned: %p, end_aligned: %p, first_k: "
"%d, first_len: %zu\n",
start_aligned, end_aligned, first_k, first_len);
return true;
}
return false;
}
/*
* If our buffer extends beyond the first shadow byte, then it must be
* zero
*/
if (first_k != 0) {
QASAN_DEBUG(
"qasan_is_poison #2 - start_aligned: %p, end_aligned: %p, first_k: "
"%d\n",
start_aligned, end_aligned, first_k);
return true;
}
}
/* If our end is unaligned */
if (end_aligned != end) {
size_t last_len = end - end_aligned;
QASAN_DEBUG("IS POISON - last_len: %zu\n", last_len);
char *k_end = qasan_get_shadow(end);
QASAN_DEBUG("IS POISON - k_end: %p\n", k_end);
char last_k = *k_end;
QASAN_DEBUG("IS POISON - last_k: %zu\n", last_k);
if ((last_k != 0) && (last_len > last_k)) {
QASAN_DEBUG(
"qasan_is_poison #3 - start_aligned: %p, end_aligned: %p, last_k: "
"%d, last_len: %zu\n",
start_aligned, end_aligned, last_k, last_len);
return true;
}
}
const char *k_start_aligned = qasan_get_shadow(start_aligned);
QASAN_DEBUG("IS POISON - k_start_aligned: %p\n", k_start_aligned);
const char *k_end_aligned = qasan_get_shadow(end_aligned);
QASAN_DEBUG("IS POISON - k_end_aligned: %p\n", k_end_aligned);
return qemu_mem_test(k_start_aligned, k_end_aligned);
}
void qasan_alloc(const char *start, const char *end) {
QASAN_DEBUG("ALLOC: %p-%p\n", start, end);
/* Do Nothing - We don't track allocations */
}
void qasan_dealloc(const char *start) {
QASAN_DEBUG("DEALLOC: %p\n", start);
/* Do Nothing - We don't track allocations */
}
int qasan_swap(int state) {
QASAN_DEBUG("SWAP: %d\n", state);
/* Do Nothing */
}
#endif
int __libc_start_main(int (*main)(int, char **, char **), int argc, char **argv, int __libc_start_main(int (*main)(int, char **, char **), int argc, char **argv,
int (*init)(int, char **, char **), void (*fini)(void), int (*init)(int, char **, char **), void (*fini)(void),
void (*rtld_fini)(void), void *stack_end) { void (*rtld_fini)(void), void *stack_end) {

View File

@ -42,6 +42,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "qasan.h" #include "qasan.h"
#include "printf/printf.h" #include "printf/printf.h"
#ifdef ASAN_GUEST
#include <errno.h>
#include <sys/mman.h>
#endif
#define QASAN_LOG(msg...) \ #define QASAN_LOG(msg...) \
do { \ do { \
if (__qasan_log) { \ if (__qasan_log) { \
@ -81,6 +86,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
extern int __qasan_debug; extern int __qasan_debug;
extern int __qasan_log; extern int __qasan_log;
size_t qasan_align_down(size_t val, size_t align);
size_t qasan_align_up(size_t val, size_t align);
void __libqasan_init_hooks(void); void __libqasan_init_hooks(void);
void __libqasan_init_malloc(void); void __libqasan_init_malloc(void);

View File

@ -72,9 +72,9 @@ struct chunk_struct {
#ifdef USE_LIBC_ALLOC #ifdef USE_LIBC_ALLOC
void *(*__lq_libc_malloc)(size_t); void *(*__lq_libc_memalign)(size_t, size_t);
void (*__lq_libc_free)(void *); void (*__lq_libc_free)(void *);
#define backend_malloc __lq_libc_malloc #define backend_memalign __lq_libc_memalign
#define backend_free __lq_libc_free #define backend_free __lq_libc_free
#define TMP_ZONE_SIZE 4096 #define TMP_ZONE_SIZE 4096
@ -84,9 +84,9 @@ static unsigned char __tmp_alloc_zone[TMP_ZONE_SIZE];
#else #else
// From dlmalloc.c // From dlmalloc.c
void *dlmalloc(size_t); void *dlmemalign(size_t, size_t);
void dlfree(void *); void dlfree(void *);
#define backend_malloc dlmalloc #define backend_memalign dlmemalign
#define backend_free dlfree #define backend_free dlfree
#endif #endif
@ -140,7 +140,7 @@ void __libqasan_init_malloc(void) {
if (__libqasan_malloc_initialized) return; if (__libqasan_malloc_initialized) return;
#ifdef USE_LIBC_ALLOC #ifdef USE_LIBC_ALLOC
__lq_libc_malloc = dlsym(RTLD_NEXT, "malloc"); __lq_libc_memalign = dlsym(RTLD_NEXT, "memalign");
__lq_libc_free = dlsym(RTLD_NEXT, "free"); __lq_libc_free = dlsym(RTLD_NEXT, "free");
#endif #endif
@ -168,26 +168,23 @@ void *__libqasan_malloc(size_t size) {
#ifdef USE_LIBC_ALLOC #ifdef USE_LIBC_ALLOC
void *r = &__tmp_alloc_zone[__tmp_alloc_zone_idx]; void *r = &__tmp_alloc_zone[__tmp_alloc_zone_idx];
__tmp_alloc_zone_idx += qasan_align_up(size, ALLOC_ALIGN_SIZE);
if (size & (ALLOC_ALIGN_SIZE - 1))
__tmp_alloc_zone_idx +=
(size & ~(ALLOC_ALIGN_SIZE - 1)) + ALLOC_ALIGN_SIZE;
else
__tmp_alloc_zone_idx += size;
return r; return r;
#endif #endif
} }
int state = QASAN_SWAP(QASAN_DISABLED); // disable qasan for this thread int state = QASAN_SWAP(QASAN_DISABLED); // disable qasan for this thread
struct chunk_begin *p = backend_malloc(sizeof(struct chunk_struct) + size); struct chunk_begin *p = backend_memalign(
ALLOC_ALIGN_SIZE,
sizeof(struct chunk_struct) + qasan_align_up(size, ALLOC_ALIGN_SIZE));
QASAN_SWAP(state); QASAN_SWAP(state);
if (!p) return NULL; if (!p) return NULL;
QASAN_UNPOISON(p, sizeof(struct chunk_struct) + size); QASAN_UNPOISON(
p, sizeof(struct chunk_struct) + qasan_align_up(size, ALLOC_ALIGN_SIZE));
p->requested_size = size; p->requested_size = size;
p->aligned_orig = NULL; p->aligned_orig = NULL;
@ -195,12 +192,9 @@ void *__libqasan_malloc(size_t size) {
QASAN_ALLOC(&p[1], (char *)&p[1] + size); QASAN_ALLOC(&p[1], (char *)&p[1] + size);
QASAN_POISON(p->redzone, REDZONE_SIZE, ASAN_HEAP_LEFT_RZ); QASAN_POISON(p->redzone, REDZONE_SIZE, ASAN_HEAP_LEFT_RZ);
if (size & (ALLOC_ALIGN_SIZE - 1))
QASAN_POISON((char *)&p[1] + size, QASAN_POISON((char *)&p[1] + size,
(size & ~(ALLOC_ALIGN_SIZE - 1)) + 8 - size + REDZONE_SIZE, qasan_align_up(size, ALLOC_ALIGN_SIZE) - size + REDZONE_SIZE,
ASAN_HEAP_RIGHT_RZ); ASAN_HEAP_RIGHT_RZ);
else
QASAN_POISON((char *)&p[1] + size, REDZONE_SIZE, ASAN_HEAP_RIGHT_RZ);
__libqasan_memset(&p[1], 0xff, size); __libqasan_memset(&p[1], 0xff, size);
@ -235,11 +229,7 @@ void __libqasan_free(void *ptr) {
} }
QASAN_SWAP(state); QASAN_SWAP(state);
QASAN_POISON(ptr, qasan_align_up(n, ALLOC_ALIGN_SIZE), ASAN_HEAP_FREED);
if (n & (ALLOC_ALIGN_SIZE - 1))
n = (n & ~(ALLOC_ALIGN_SIZE - 1)) + ALLOC_ALIGN_SIZE;
QASAN_POISON(ptr, n, ASAN_HEAP_FREED);
QASAN_DEALLOC(ptr); QASAN_DEALLOC(ptr);
} }
@ -289,13 +279,16 @@ int __libqasan_posix_memalign(void **ptr, size_t align, size_t len) {
int state = QASAN_SWAP(QASAN_DISABLED); // disable qasan for this thread int state = QASAN_SWAP(QASAN_DISABLED); // disable qasan for this thread
char *orig = backend_malloc(sizeof(struct chunk_struct) + size); char *orig = backend_memalign(
ALLOC_ALIGN_SIZE,
sizeof(struct chunk_struct) + qasan_align_up(size, ALLOC_ALIGN_SIZE));
QASAN_SWAP(state); QASAN_SWAP(state);
if (!orig) return ENOMEM; if (!orig) return ENOMEM;
QASAN_UNPOISON(orig, sizeof(struct chunk_struct) + size); QASAN_UNPOISON(orig, sizeof(struct chunk_struct) +
qasan_align_up(size, ALLOC_ALIGN_SIZE));
char *data = orig + sizeof(struct chunk_begin); char *data = orig + sizeof(struct chunk_begin);
data += align - ((uintptr_t)data % align); data += align - ((uintptr_t)data % align);
@ -307,13 +300,9 @@ int __libqasan_posix_memalign(void **ptr, size_t align, size_t len) {
QASAN_ALLOC(data, data + len); QASAN_ALLOC(data, data + len);
QASAN_POISON(p->redzone, REDZONE_SIZE, ASAN_HEAP_LEFT_RZ); QASAN_POISON(p->redzone, REDZONE_SIZE, ASAN_HEAP_LEFT_RZ);
if (len & (ALLOC_ALIGN_SIZE - 1)) QASAN_POISON(data + len,
QASAN_POISON( qasan_align_up(len, ALLOC_ALIGN_SIZE) - len + REDZONE_SIZE,
data + len,
(len & ~(ALLOC_ALIGN_SIZE - 1)) + ALLOC_ALIGN_SIZE - len + REDZONE_SIZE,
ASAN_HEAP_RIGHT_RZ); ASAN_HEAP_RIGHT_RZ);
else
QASAN_POISON(data + len, REDZONE_SIZE, ASAN_HEAP_RIGHT_RZ);
__libqasan_memset(data, 0xff, len); __libqasan_memset(data, 0xff, len);

View File

@ -76,7 +76,36 @@ enum {
#include <unistd.h> #include <unistd.h>
#define QASAN_CALL0(action) syscall(QASAN_FAKESYS_NR, action, NULL, NULL, NULL) #ifdef ASAN_GUEST
#include <stdbool.h>
void qasan_load(const char *start, size_t len);
void qasan_store(const char *start, size_t len);
void qasan_poison(const char *start, size_t len, char val);
void qasan_unpoison(const char *start, size_t len);
bool qasan_is_poison(const char *start, size_t len);
void qasan_alloc(const char *start, const char *end);
void qasan_dealloc(const char *start);
int qasan_swap(int state);
#define QASAN_LOAD(ptr, len) qasan_load((const char *)(ptr), (size_t)(len))
#define QASAN_STORE(ptr, len) qasan_store((const char *)(ptr), (size_t)(len))
#define QASAN_POISON(ptr, len, poison_byte) \
qasan_poison((const char *)(ptr), (size_t)(len), (char)(poison_byte))
#define QASAN_USER_POISON(ptr, len) QASAN_POISON(ptr, len, ASAN_USER)
#define QASAN_UNPOISON(ptr, len) \
qasan_unpoison((const char *)(ptr), (size_t)(len))
#define QASAN_IS_POISON(ptr, len) \
qasan_is_poison((const char *)(ptr), (size_t)(len))
#define QASAN_ALLOC(start, end) \
qasan_alloc((const char *)(start), (const char *)(end))
#define QASAN_DEALLOC(ptr) qasan_dealloc((const char *)(ptr))
#define QASAN_SWAP(state) qasan_swap((int)(state))
#else
#define QASAN_CALL0(action) \
syscall(QASAN_FAKESYS_NR, action, NULL, NULL, NULL)
#define QASAN_CALL1(action, arg1) \ #define QASAN_CALL1(action, arg1) \
syscall(QASAN_FAKESYS_NR, action, arg1, NULL, NULL) syscall(QASAN_FAKESYS_NR, action, arg1, NULL, NULL)
#define QASAN_CALL2(action, arg1, arg2) \ #define QASAN_CALL2(action, arg1, arg2) \
@ -92,11 +121,13 @@ enum {
#define QASAN_USER_POISON(ptr, len) \ #define QASAN_USER_POISON(ptr, len) \
QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, ASAN_USER) QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, ASAN_USER)
#define QASAN_UNPOISON(ptr, len) QASAN_CALL2(QASAN_ACTION_UNPOISON, ptr, len) #define QASAN_UNPOISON(ptr, len) QASAN_CALL2(QASAN_ACTION_UNPOISON, ptr, len)
#define QASAN_IS_POISON(ptr, len) QASAN_CALL2(QASAN_ACTION_IS_POISON, ptr, len) #define QASAN_IS_POISON(ptr, len) \
QASAN_CALL2(QASAN_ACTION_IS_POISON, ptr, len)
#define QASAN_ALLOC(start, end) QASAN_CALL2(QASAN_ACTION_ALLOC, start, end) #define QASAN_ALLOC(start, end) QASAN_CALL2(QASAN_ACTION_ALLOC, start, end)
#define QASAN_DEALLOC(ptr) QASAN_CALL1(QASAN_ACTION_DEALLOC, ptr) #define QASAN_DEALLOC(ptr) QASAN_CALL1(QASAN_ACTION_DEALLOC, ptr)
#define QASAN_SWAP(state) QASAN_CALL1(QASAN_ACTION_SWAP_STATE, state) #define QASAN_SWAP(state) QASAN_CALL1(QASAN_ACTION_SWAP_STATE, state)
#endif
#endif #endif

View File

@ -9,7 +9,6 @@ use std::{
use addr2line::object::{Object, ObjectSection}; use addr2line::object::{Object, ObjectSection};
use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, HasMetadata}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, HasMetadata};
use libafl_qemu_sys::GuestAddr;
use libc::{ use libc::{
c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE, c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE,
}; };
@ -26,7 +25,8 @@ use crate::{
}, },
hooks::{Hook, QemuHooks}, hooks::{Hook, QemuHooks},
snapshot::QemuSnapshotHelper, snapshot::QemuSnapshotHelper,
Qemu, Regs, sys::TCGTemp,
GuestAddr, Qemu, Regs,
}; };
// TODO at some point, merge parts with libafl_frida // TODO at some point, merge parts with libafl_frida
@ -1011,6 +1011,7 @@ pub fn gen_readwrite_asan<QT, S>(
hooks: &mut QemuHooks<QT, S>, hooks: &mut QemuHooks<QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
pc: GuestAddr, pc: GuestAddr,
_addr: *mut TCGTemp,
_info: MemAccessInfo, _info: MemAccessInfo,
) -> Option<u64> ) -> Option<u64>
where where
@ -1171,6 +1172,7 @@ pub fn gen_write_asan_snapshot<QT, S>(
hooks: &mut QemuHooks<QT, S>, hooks: &mut QemuHooks<QT, S>,
_state: Option<&mut S>, _state: Option<&mut S>,
pc: GuestAddr, pc: GuestAddr,
_addr: *mut TCGTemp,
_info: MemAccessInfo, _info: MemAccessInfo,
) -> Option<u64> ) -> Option<u64>
where where

View File

@ -0,0 +1,300 @@
#![allow(clippy::cast_possible_wrap)]
use std::{
env,
fmt::{self, Debug, Formatter},
fs,
path::PathBuf,
};
use libafl::{inputs::UsesInput, HasMetadata};
#[cfg(not(feature = "clippy"))]
use crate::sys::libafl_tcg_gen_asan;
use crate::{
emu::{EmuError, MemAccessInfo, Qemu},
helper::{
HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple,
QemuInstrumentationAddressRangeFilter,
},
hooks::{Hook, QemuHooks},
sys::TCGTemp,
GuestAddr, MapInfo,
};
static mut ASAN_GUEST_INITED: bool = false;
pub fn init_qemu_with_asan_guest(
args: &mut Vec<String>,
env: &mut [(String, String)],
) -> Result<(Qemu, String), EmuError> {
let current = env::current_exe().unwrap();
let asan_lib = fs::canonicalize(current)
.unwrap()
.parent()
.unwrap()
.join("libgasan.so");
let asan_lib = env::var_os("CUSTOM_ASAN_PATH")
.map_or(asan_lib, |x| PathBuf::from(x.to_string_lossy().to_string()));
assert!(
asan_lib.as_path().exists(),
"The ASAN library doesn't exist: {asan_lib:#?}"
);
let asan_lib = asan_lib
.to_str()
.expect("The path to the asan lib is invalid")
.to_string();
println!("Loading ASAN: {asan_lib:}");
let add_asan =
|e: &str| "LD_PRELOAD=".to_string() + &asan_lib + " " + &e["LD_PRELOAD=".len()..];
let mut added = false;
for (k, v) in &mut *env {
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());
}
if env::var("QASAN_DEBUG").is_ok() {
args.push("-E".into());
args.push("QASAN_DEBUG=1".into());
}
if env::var("QASAN_LOG").is_ok() {
args.push("-E".into());
args.push("QASAN_LOG=1".into());
}
unsafe {
ASAN_GUEST_INITED = true;
}
let emu = Qemu::init(args, env)?;
Ok((emu, asan_lib))
}
#[derive(Clone)]
struct QemuAsanGuestMapping {
start: GuestAddr,
end: GuestAddr,
path: String,
}
impl Debug for QemuAsanGuestMapping {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "0x{:016x}-0x{:016x} {}", self.start, self.end, self.path)
}
}
impl From<&MapInfo> for QemuAsanGuestMapping {
fn from(map: &MapInfo) -> QemuAsanGuestMapping {
let path = map.path().map(ToString::to_string).unwrap_or_default();
let start = map.start();
let end = map.end();
QemuAsanGuestMapping { start, end, path }
}
}
#[derive(Debug)]
pub struct QemuAsanGuestHelper {
filter: QemuInstrumentationAddressRangeFilter,
mappings: Vec<QemuAsanGuestMapping>,
}
#[cfg(any(cpu_target = "aarch64", cpu_target = "x86_64", feature = "clippy"))]
impl QemuAsanGuestHelper {
const HIGH_SHADOW_START: GuestAddr = 0x02008fff7000;
const HIGH_SHADOW_END: GuestAddr = 0x10007fff7fff;
const LOW_SHADOW_START: GuestAddr = 0x00007fff8000;
const LOW_SHADOW_END: GuestAddr = 0x00008fff6fff;
}
#[cfg(any(
cpu_target = "arm",
cpu_target = "i386",
cpu_target = "mips",
cpu_target = "ppc"
))]
impl QemuAsanGuestHelper {
const HIGH_SHADOW_START: GuestAddr = 0x28000000;
const HIGH_SHADOW_END: GuestAddr = 0x3fffffff;
const LOW_SHADOW_START: GuestAddr = 0x20000000;
const LOW_SHADOW_END: GuestAddr = 0x23ffffff;
}
impl QemuAsanGuestHelper {
#[must_use]
pub fn default(emu: &Qemu, asan: String) -> Self {
Self::new(emu, asan, QemuInstrumentationAddressRangeFilter::None)
}
#[must_use]
pub fn new(emu: &Qemu, asan: String, filter: QemuInstrumentationAddressRangeFilter) -> Self {
for mapping in emu.mappings() {
println!("mapping: {mapping:#?}");
}
let mappings = emu
.mappings()
.map(|m| QemuAsanGuestMapping::from(&m))
.collect::<Vec<QemuAsanGuestMapping>>();
for mapping in &mappings {
println!("guest mapping: {mapping:#?}");
}
mappings
.iter()
.find(|m| m.start <= Self::HIGH_SHADOW_START && m.end > Self::HIGH_SHADOW_END)
.expect("HighShadow not found, confirm ASAN DSO is loaded in the guest");
mappings
.iter()
.find(|m| m.start <= Self::LOW_SHADOW_START && m.end > Self::LOW_SHADOW_END)
.expect("LowShadow not found, confirm ASAN DSO is loaded in the guest");
let mappings = mappings
.iter()
.filter(|m| m.path == asan)
.cloned()
.collect::<Vec<QemuAsanGuestMapping>>();
for mapping in &mappings {
println!("asan mapping: {mapping:#?}");
}
Self { filter, mappings }
}
#[must_use]
pub fn must_instrument(&self, addr: GuestAddr) -> bool {
self.filter.allowed(addr)
}
}
impl<S: UsesInput> HasInstrumentationFilter<QemuInstrumentationAddressRangeFilter, S>
for QemuAsanGuestHelper
{
fn filter(&self) -> &QemuInstrumentationAddressRangeFilter {
&self.filter
}
fn filter_mut(&mut self) -> &mut QemuInstrumentationAddressRangeFilter {
&mut self.filter
}
}
fn gen_readwrite_guest_asan<QT, S>(
hooks: &mut QemuHooks<QT, S>,
_state: Option<&mut S>,
pc: GuestAddr,
addr: *mut TCGTemp,
info: MemAccessInfo,
) -> Option<u64>
where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let h = hooks.match_helper_mut::<QemuAsanGuestHelper>().unwrap();
if !h.must_instrument(pc) {
return None;
}
/* Don't sanitize the sanitizer! */
if h.mappings.iter().any(|m| m.start <= pc && pc < m.end) {
return None;
}
let size = info.size();
/* TODO - If our size is > 8 then do things via a runtime callback */
assert!(size <= 8, "I shouldn't be here!");
unsafe {
libafl_tcg_gen_asan(addr, size);
}
None
}
#[cfg(feature = "clippy")]
#[allow(unused_variables)]
unsafe fn libafl_tcg_gen_asan(addr: *mut TCGTemp, size: usize) {}
fn guest_trace_error_asan<QT, S>(
_hooks: &mut QemuHooks<QT, S>,
_state: Option<&mut S>,
_id: u64,
_addr: GuestAddr,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
panic!("I really shouldn't be here");
}
fn guest_trace_error_n_asan<QT, S>(
_hooks: &mut QemuHooks<QT, S>,
_state: Option<&mut S>,
_id: u64,
_addr: GuestAddr,
_n: usize,
) where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
panic!("I really shouldn't be here either");
}
impl<S> QemuHelper<S> for QemuAsanGuestHelper
where
S: UsesInput + HasMetadata,
{
fn first_exec<QT>(&self, hooks: &QemuHooks<QT, S>)
where
QT: QemuHelperTuple<S>,
{
hooks.reads(
Hook::Function(gen_readwrite_guest_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_n_asan::<QT, S>),
);
hooks.writes(
Hook::Function(gen_readwrite_guest_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_asan::<QT, S>),
Hook::Function(guest_trace_error_n_asan::<QT, S>),
);
}
}

View File

@ -1,7 +1,7 @@
//! Expose QEMU user `LibAFL` C api to Rust //! Expose QEMU user `LibAFL` C api to Rust
use core::{ use core::{
fmt, fmt::{self, Debug, Display, Formatter},
marker::PhantomData, marker::PhantomData,
mem::{transmute, MaybeUninit}, mem::{transmute, MaybeUninit},
ptr::{addr_of, copy_nonoverlapping, null}, ptr::{addr_of, copy_nonoverlapping, null},
@ -10,7 +10,6 @@ use std::{
cell::{OnceCell, Ref, RefCell, RefMut}, cell::{OnceCell, Ref, RefCell, RefMut},
collections::HashSet, collections::HashSet,
ffi::CString, ffi::CString,
fmt::{Debug, Display, Formatter},
ptr, ptr,
}; };
@ -32,7 +31,9 @@ pub use libafl_qemu_sys::{MapInfo, MmapPerms, MmapPermsIter};
use num_traits::Num; use num_traits::Num;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::{command::IsCommand, GuestReg, QemuHelperTuple, Regs, StdInstrumentationFilter}; use crate::{
command::IsCommand, sys::TCGTemp, GuestReg, QemuHelperTuple, Regs, StdInstrumentationFilter,
};
#[cfg(emulation_mode = "systemmode")] #[cfg(emulation_mode = "systemmode")]
pub mod systemmode; pub mod systemmode;
@ -1164,7 +1165,7 @@ impl Qemu {
pub fn add_read_hooks<T: Into<HookData>>( pub fn add_read_hooks<T: Into<HookData>>(
&self, &self,
data: T, data: T,
gen: Option<extern "C" fn(T, GuestAddr, MemAccessInfo) -> u64>, gen: Option<extern "C" fn(T, GuestAddr, *mut TCGTemp, MemAccessInfo) -> u64>,
exec1: Option<extern "C" fn(T, u64, GuestAddr)>, exec1: Option<extern "C" fn(T, u64, GuestAddr)>,
exec2: Option<extern "C" fn(T, u64, GuestAddr)>, exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
exec4: Option<extern "C" fn(T, u64, GuestAddr)>, exec4: Option<extern "C" fn(T, u64, GuestAddr)>,
@ -1173,8 +1174,14 @@ impl Qemu {
) -> ReadHookId { ) -> ReadHookId {
unsafe { unsafe {
let data: u64 = data.into().0; let data: u64 = data.into().0;
let gen: Option<extern "C" fn(u64, GuestAddr, libafl_qemu_sys::MemOpIdx) -> u64> = let gen: Option<
transmute(gen); unsafe extern "C" fn(
u64,
GuestAddr,
*mut TCGTemp,
libafl_qemu_sys::MemOpIdx,
) -> u64,
> = transmute(gen);
let exec1: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec1); let exec1: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec1);
let exec2: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec2); let exec2: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec2);
let exec4: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec4); let exec4: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec4);
@ -1192,7 +1199,7 @@ impl Qemu {
pub fn add_write_hooks<T: Into<HookData>>( pub fn add_write_hooks<T: Into<HookData>>(
&self, &self,
data: T, data: T,
gen: Option<extern "C" fn(T, GuestAddr, MemAccessInfo) -> u64>, gen: Option<extern "C" fn(T, GuestAddr, *mut TCGTemp, MemAccessInfo) -> u64>,
exec1: Option<extern "C" fn(T, u64, GuestAddr)>, exec1: Option<extern "C" fn(T, u64, GuestAddr)>,
exec2: Option<extern "C" fn(T, u64, GuestAddr)>, exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
exec4: Option<extern "C" fn(T, u64, GuestAddr)>, exec4: Option<extern "C" fn(T, u64, GuestAddr)>,
@ -1201,8 +1208,14 @@ impl Qemu {
) -> WriteHookId { ) -> WriteHookId {
unsafe { unsafe {
let data: u64 = data.into().0; let data: u64 = data.into().0;
let gen: Option<extern "C" fn(u64, GuestAddr, libafl_qemu_sys::MemOpIdx) -> u64> = let gen: Option<
transmute(gen); unsafe extern "C" fn(
u64,
GuestAddr,
*mut TCGTemp,
libafl_qemu_sys::MemOpIdx,
) -> u64,
> = transmute(gen);
let exec1: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec1); let exec1: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec1);
let exec2: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec2); let exec2: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec2);
let exec4: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec4); let exec4: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec4);
@ -1632,7 +1645,7 @@ where
pub fn add_read_hooks<T: Into<HookData>>( pub fn add_read_hooks<T: Into<HookData>>(
&self, &self,
data: T, data: T,
gen: Option<extern "C" fn(T, GuestAddr, MemAccessInfo) -> u64>, gen: Option<extern "C" fn(T, GuestAddr, *mut TCGTemp, MemAccessInfo) -> u64>,
exec1: Option<extern "C" fn(T, u64, GuestAddr)>, exec1: Option<extern "C" fn(T, u64, GuestAddr)>,
exec2: Option<extern "C" fn(T, u64, GuestAddr)>, exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
exec4: Option<extern "C" fn(T, u64, GuestAddr)>, exec4: Option<extern "C" fn(T, u64, GuestAddr)>,
@ -1650,7 +1663,7 @@ where
pub fn add_write_hooks<T: Into<HookData>>( pub fn add_write_hooks<T: Into<HookData>>(
&self, &self,
data: T, data: T,
gen: Option<extern "C" fn(T, GuestAddr, MemAccessInfo) -> u64>, gen: Option<extern "C" fn(T, GuestAddr, *mut TCGTemp, MemAccessInfo) -> u64>,
exec1: Option<extern "C" fn(T, u64, GuestAddr)>, exec1: Option<extern "C" fn(T, u64, GuestAddr)>,
exec2: Option<extern "C" fn(T, u64, GuestAddr)>, exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
exec4: Option<extern "C" fn(T, u64, GuestAddr)>, exec4: Option<extern "C" fn(T, u64, GuestAddr)>,

View File

@ -23,6 +23,7 @@ pub use crate::emu::SyscallHookResult;
use crate::{ use crate::{
emu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK}, emu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK},
helper::QemuHelperTuple, helper::QemuHelperTuple,
sys::TCGTemp,
BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, HookId, InstructionHookId, ReadHookId, BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, HookId, InstructionHookId, ReadHookId,
WriteHookId, WriteHookId,
}; };
@ -308,13 +309,7 @@ create_post_gen_wrapper!(block, (addr: GuestAddr, len: GuestUsize), 1, BlockHook
create_exec_wrapper!(block, (id: u64), 0, 1, BlockHookId); create_exec_wrapper!(block, (id: u64), 0, 1, BlockHookId);
static mut READ_HOOKS: Vec<Pin<Box<HookState<5, ReadHookId>>>> = vec![]; static mut READ_HOOKS: Vec<Pin<Box<HookState<5, ReadHookId>>>> = vec![];
create_gen_wrapper!( create_gen_wrapper!(read, (pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo), u64, 5, ReadHookId);
read,
(pc: GuestAddr, info: MemAccessInfo),
u64,
5,
ReadHookId
);
create_exec_wrapper!(read, (id: u64, addr: GuestAddr), 0, 5, ReadHookId); create_exec_wrapper!(read, (id: u64, addr: GuestAddr), 0, 5, ReadHookId);
create_exec_wrapper!(read, (id: u64, addr: GuestAddr), 1, 5, ReadHookId); create_exec_wrapper!(read, (id: u64, addr: GuestAddr), 1, 5, ReadHookId);
create_exec_wrapper!(read, (id: u64, addr: GuestAddr), 2, 5, ReadHookId); create_exec_wrapper!(read, (id: u64, addr: GuestAddr), 2, 5, ReadHookId);
@ -328,13 +323,7 @@ create_exec_wrapper!(
); );
static mut WRITE_HOOKS: Vec<Pin<Box<HookState<5, WriteHookId>>>> = vec![]; static mut WRITE_HOOKS: Vec<Pin<Box<HookState<5, WriteHookId>>>> = vec![];
create_gen_wrapper!( create_gen_wrapper!(write, (pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo), u64, 5, WriteHookId);
write,
(pc: GuestAddr, info: MemAccessInfo),
u64,
5,
WriteHookId
);
create_exec_wrapper!(write, (id: u64, addr: GuestAddr), 0, 5, WriteHookId); create_exec_wrapper!(write, (id: u64, addr: GuestAddr), 0, 5, WriteHookId);
create_exec_wrapper!(write, (id: u64, addr: GuestAddr), 1, 5, WriteHookId); create_exec_wrapper!(write, (id: u64, addr: GuestAddr), 1, 5, WriteHookId);
create_exec_wrapper!(write, (id: u64, addr: GuestAddr), 2, 5, WriteHookId); create_exec_wrapper!(write, (id: u64, addr: GuestAddr), 2, 5, WriteHookId);
@ -679,16 +668,23 @@ where
pub fn reads( pub fn reads(
&self, &self,
generation_hook: Hook< generation_hook: Hook<
fn(&mut Self, Option<&mut S>, pc: GuestAddr, info: MemAccessInfo) -> Option<u64>, fn(
&mut Self,
Option<&mut S>,
pc: GuestAddr,
addr: *mut TCGTemp,
info: MemAccessInfo,
) -> Option<u64>,
Box< Box<
dyn for<'a> FnMut( dyn for<'a> FnMut(
&'a mut Self, &'a mut Self,
Option<&'a mut S>, Option<&'a mut S>,
GuestAddr, GuestAddr,
*mut TCGTemp,
MemAccessInfo, MemAccessInfo,
) -> Option<u64>, ) -> Option<u64>,
>, >,
extern "C" fn(*const (), pc: GuestAddr, info: MemAccessInfo) -> u64, extern "C" fn(*const (), pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo) -> u64,
>, >,
execution_hook_1: Hook< execution_hook_1: Hook<
fn(&mut Self, Option<&mut S>, id: u64, addr: GuestAddr), fn(&mut Self, Option<&mut S>, id: u64, addr: GuestAddr),
@ -723,6 +719,7 @@ where
extern "C" fn( extern "C" fn(
&mut HookState<5, ReadHookId>, &mut HookState<5, ReadHookId>,
pc: GuestAddr, pc: GuestAddr,
addr: *mut TCGTemp,
info: MemAccessInfo, info: MemAccessInfo,
) -> u64 ) -> u64
); );
@ -786,16 +783,23 @@ where
pub fn writes( pub fn writes(
&self, &self,
generation_hook: Hook< generation_hook: Hook<
fn(&mut Self, Option<&mut S>, pc: GuestAddr, info: MemAccessInfo) -> Option<u64>, fn(
&mut Self,
Option<&mut S>,
pc: GuestAddr,
addr: *mut TCGTemp,
info: MemAccessInfo,
) -> Option<u64>,
Box< Box<
dyn for<'a> FnMut( dyn for<'a> FnMut(
&'a mut Self, &'a mut Self,
Option<&'a mut S>, Option<&'a mut S>,
GuestAddr, GuestAddr,
*mut TCGTemp,
MemAccessInfo, MemAccessInfo,
) -> Option<u64>, ) -> Option<u64>,
>, >,
extern "C" fn(*const (), pc: GuestAddr, info: MemAccessInfo) -> u64, extern "C" fn(*const (), pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo) -> u64,
>, >,
execution_hook_1: Hook< execution_hook_1: Hook<
fn(&mut Self, Option<&mut S>, id: u64, addr: GuestAddr), fn(&mut Self, Option<&mut S>, id: u64, addr: GuestAddr),
@ -830,6 +834,7 @@ where
extern "C" fn( extern "C" fn(
&mut HookState<5, WriteHookId>, &mut HookState<5, WriteHookId>,
pc: GuestAddr, pc: GuestAddr,
addr: *mut TCGTemp,
info: MemAccessInfo, info: MemAccessInfo,
) -> u64 ) -> u64
); );

View File

@ -98,6 +98,11 @@ pub mod asan;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub use asan::{init_qemu_with_asan, QemuAsanHelper}; pub use asan::{init_qemu_with_asan, QemuAsanHelper};
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub mod asan_guest;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub use asan_guest::{init_qemu_with_asan_guest, QemuAsanGuestHelper};
#[cfg(not(cpu_target = "hexagon"))] #[cfg(not(cpu_target = "hexagon"))]
pub mod calls; pub mod calls;
#[cfg(not(cpu_target = "hexagon"))] #[cfg(not(cpu_target = "hexagon"))]