From 374f8735fa96c13ca2f74d0ead7fb40615504bed Mon Sep 17 00:00:00 2001 From: WorksButNotTested <62701594+WorksButNotTested@users.noreply.github.com> Date: Wed, 10 Apr 2024 00:11:28 +0100 Subject: [PATCH] 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 Co-authored-by: Dominik Maier Co-authored-by: Andrea Fioraldi --- fuzzers/qemu_launcher/Makefile.toml | 41 ++- fuzzers/qemu_launcher/src/client.rs | 47 ++- fuzzers/qemu_launcher/src/options.rs | 9 + libafl_qemu/Cargo.toml | 3 +- libafl_qemu/build_linux.rs | 11 +- libafl_qemu/libafl_qemu_build/src/build.rs | 2 +- libafl_qemu/libafl_qemu_sys/build_linux.rs | 3 - libafl_qemu/libafl_qemu_sys/src/lib.rs | 1 + .../src/x86_64_stub_bindings.rs | 18 +- libafl_qemu/libqasan/Makefile | 8 +- libafl_qemu/libqasan/libqasan.c | 284 +++++++++++++++++ libafl_qemu/libqasan/libqasan.h | 8 + libafl_qemu/libqasan/malloc.c | 57 ++-- libafl_qemu/libqasan/qasan.h | 67 ++-- libafl_qemu/src/asan.rs | 6 +- libafl_qemu/src/asan_guest.rs | 300 ++++++++++++++++++ libafl_qemu/src/emu.rs | 35 +- libafl_qemu/src/hooks.rs | 41 +-- libafl_qemu/src/lib.rs | 5 + 19 files changed, 841 insertions(+), 105 deletions(-) create mode 100644 libafl_qemu/src/asan_guest.rs diff --git a/fuzzers/qemu_launcher/Makefile.toml b/fuzzers/qemu_launcher/Makefile.toml index b64103f258..6f508eaf03 100644 --- a/fuzzers/qemu_launcher/Makefile.toml +++ b/fuzzers/qemu_launcher/Makefile.toml @@ -216,8 +216,7 @@ ${CROSS_CXX} \ -I"${TARGET_DIR}/build-zlib/zlib/include" \ -L"${TARGET_DIR}/build-zlib/zlib/lib" \ -o"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}" \ - -lm \ - -static + -lm ''' dependencies = [ "libpng" ] @@ -280,6 +279,42 @@ args = [ ] 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] linux_alias = "test_unix" 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 echo "Fuzzer does not generate any testcases or any crashes" echo "Logs:" - tail fuzz.log + cat fuzz.log exit 1 else echo "Fuzzer is working" diff --git a/fuzzers/qemu_launcher/src/client.rs b/fuzzers/qemu_launcher/src/client.rs index 5604fef72b..483e4a5b4e 100644 --- a/fuzzers/qemu_launcher/src/client.rs +++ b/fuzzers/qemu_launcher/src/client.rs @@ -12,6 +12,7 @@ use libafl_bolts::{core_affinity::CoreId, rands::StdRand, tuples::tuple_list}; use libafl_qemu::injections::QemuInjectionHelper; use libafl_qemu::{ asan::{init_qemu_with_asan, QemuAsanHelper}, + asan_guest::{init_qemu_with_asan_guest, QemuAsanGuestHelper}, cmplog::QemuCmpLogHelper, edges::QemuEdgeCoverageHelper, elf::EasyElf, @@ -110,12 +111,22 @@ impl<'a> Client<'a> { let mut env = self.env(); log::debug!("ENV: {:#?}", env); - let (qemu, mut asan) = { - if self.options.is_asan_core(core_id) { + let is_asan = 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)?; - (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 { - (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}"); 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 edge_coverage_helper = QemuEdgeCoverageHelper::new(self.coverage_filter(&qemu)?); @@ -184,6 +194,27 @@ impl<'a> Client<'a> { 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 { if let Some(injection_helper) = injection_helper { instance.build().run( @@ -203,6 +234,12 @@ impl<'a> Client<'a> { 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 { if let Some(injection_helper) = injection_helper { instance.build().run( diff --git a/fuzzers/qemu_launcher/src/options.rs b/fuzzers/qemu_launcher/src/options.rs index 284d9baee8..20e9f598de 100644 --- a/fuzzers/qemu_launcher/src/options.rs +++ b/fuzzers/qemu_launcher/src/options.rs @@ -51,6 +51,9 @@ pub struct FuzzerOptions { #[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)] pub asan_cores: Option, + #[arg(long, help = "Cpu cores to use for ASAN", value_parser = Cores::from_cmdline)] + pub asan_guest_cores: Option, + #[arg(long, help = "Cpu cores to use for CmpLog", value_parser = Cores::from_cmdline)] pub cmplog_cores: Option, @@ -113,6 +116,12 @@ impl FuzzerOptions { .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 { self.cmplog_cores .as_ref() diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 5f34422e9a..c5a0688837 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -16,7 +16,7 @@ features = ["document-features", "default", "python", "x86_64", "usermode"] rustdoc-args = ["--cfg", "docsrs"] [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§ document-features = ["dep:document-features"] @@ -29,6 +29,7 @@ python = ["pyo3", "pyo3-build-config", "libafl_qemu_sys/python"] ## Fork support fork = ["libafl/fork"] ## Build libqasan for address sanitization +build_libgasan = [] build_libqasan = [] #! ## The following architecture features are mutually exclusive. diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index 61f3130f31..ca521c671e 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -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 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: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) 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); @@ -96,7 +98,7 @@ pub fn build() { .write_to_file(binding_file) .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 = fs::canonicalize(qasan_dir).unwrap(); println!("cargo:rerun-if-changed={}", qasan_dir.display()); @@ -114,6 +116,5 @@ pub fn build() { .status() .expect("make failed") .success()); - // println!("cargo:rerun-if-changed={}/libqasan.so", target_dir.display()); } } diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index cc9ba3b12e..c47c9a5ac1 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -8,7 +8,7 @@ use which::which; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -const QEMU_REVISION: &str = "821ad471430360c4eed644d07d59f0d603ef23f6"; +const QEMU_REVISION: &str = "f1e48d694ca31045169853ca65b1a5a95e8746e9"; #[allow(clippy::module_name_repetitions)] pub struct BuildResult { diff --git a/libafl_qemu/libafl_qemu_sys/build_linux.rs b/libafl_qemu/libafl_qemu_sys/build_linux.rs index a6d09da552..449e606dff 100644 --- a/libafl_qemu/libafl_qemu_sys/build_linux.rs +++ b/libafl_qemu/libafl_qemu_sys/build_linux.rs @@ -32,9 +32,6 @@ pub fn build() { println!("cargo:rustc-cfg=emulation_mode=\"{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 // Else, we default to `x86_64` - having a default makes CI easier :) assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc", "hexagon"); diff --git a/libafl_qemu/libafl_qemu_sys/src/lib.rs b/libafl_qemu/libafl_qemu_sys/src/lib.rs index cf08702d68..de4978d7e1 100644 --- a/libafl_qemu/libafl_qemu_sys/src/lib.rs +++ b/libafl_qemu/libafl_qemu_sys/src/lib.rs @@ -120,6 +120,7 @@ pub type GuestVirtAddr = crate::vaddr; pub type GuestHwAddrInfo = crate::qemu_plugin_hwaddr; +#[derive(Debug)] #[repr(C)] #[cfg_attr(feature = "python", pyclass(unsendable))] pub struct MapInfo { diff --git a/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs b/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs index 280e0567c3..51e20864d5 100644 --- a/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs +++ b/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs @@ -13500,7 +13500,14 @@ extern "C" { } extern "C" { pub fn libafl_add_read_hook( - gen: ::std::option::Option u64>, + gen: ::std::option::Option< + unsafe extern "C" fn( + data: u64, + pc: target_ulong, + addr: *mut TCGTemp, + oi: MemOpIdx, + ) -> u64, + >, exec1: ::std::option::Option, exec2: ::std::option::Option, exec4: ::std::option::Option, @@ -13513,7 +13520,14 @@ extern "C" { } extern "C" { pub fn libafl_add_write_hook( - gen: ::std::option::Option u64>, + gen: ::std::option::Option< + unsafe extern "C" fn( + data: u64, + pc: target_ulong, + addr: *mut TCGTemp, + oi: MemOpIdx, + ) -> u64, + >, exec1: ::std::option::Option, exec2: ::std::option::Option, exec4: ::std::option::Option, diff --git a/libafl_qemu/libqasan/Makefile b/libafl_qemu/libqasan/Makefile index 1044994f7c..a262b26c4d 100644 --- a/libafl_qemu/libqasan/Makefile +++ b/libafl_qemu/libqasan/Makefile @@ -15,13 +15,16 @@ 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 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 -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) $(CC) $(CFLAGS) -fPIC -shared $(SRC) -o $@ $(LDFLAGS) @@ -30,4 +33,5 @@ $(OUT_DIR)/libqasan.so: $(HDR) $(SRC) clean: rm -f *.o *.so *~ a.out core core.[1-9][0-9]* + rm -f $(OUT_DIR)/libgasan.so rm -f $(OUT_DIR)/libqasan.so diff --git a/libafl_qemu/libqasan/libqasan.c b/libafl_qemu/libqasan/libqasan.c index b9927d7697..5d6397a4a7 100644 --- a/libafl_qemu/libqasan/libqasan.c +++ b/libafl_qemu/libqasan/libqasan.c @@ -57,6 +57,47 @@ void __libqasan_print_maps(void) { 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() { if (__libqasan_is_initialized) { return; } __libqasan_is_initialized = 1; @@ -77,9 +118,252 @@ __attribute__((constructor)) void __libqasan_init() { "Copyright (C) 2019-2021 Andrea Fioraldi \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(); } } +#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 (*init)(int, char **, char **), void (*fini)(void), void (*rtld_fini)(void), void *stack_end) { diff --git a/libafl_qemu/libqasan/libqasan.h b/libafl_qemu/libqasan/libqasan.h index e611513000..6c94f20bb5 100644 --- a/libafl_qemu/libqasan/libqasan.h +++ b/libafl_qemu/libqasan/libqasan.h @@ -42,6 +42,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "qasan.h" #include "printf/printf.h" +#ifdef ASAN_GUEST + #include + #include +#endif + #define QASAN_LOG(msg...) \ do { \ if (__qasan_log) { \ @@ -81,6 +86,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. extern int __qasan_debug; 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_malloc(void); diff --git a/libafl_qemu/libqasan/malloc.c b/libafl_qemu/libqasan/malloc.c index afb389bc78..be5f56aff2 100644 --- a/libafl_qemu/libqasan/malloc.c +++ b/libafl_qemu/libqasan/malloc.c @@ -72,9 +72,9 @@ struct chunk_struct { #ifdef USE_LIBC_ALLOC -void *(*__lq_libc_malloc)(size_t); +void *(*__lq_libc_memalign)(size_t, size_t); void (*__lq_libc_free)(void *); - #define backend_malloc __lq_libc_malloc + #define backend_memalign __lq_libc_memalign #define backend_free __lq_libc_free #define TMP_ZONE_SIZE 4096 @@ -84,9 +84,9 @@ static unsigned char __tmp_alloc_zone[TMP_ZONE_SIZE]; #else // From dlmalloc.c -void *dlmalloc(size_t); +void *dlmemalign(size_t, size_t); void dlfree(void *); - #define backend_malloc dlmalloc + #define backend_memalign dlmemalign #define backend_free dlfree #endif @@ -140,7 +140,7 @@ void __libqasan_init_malloc(void) { if (__libqasan_malloc_initialized) return; #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"); #endif @@ -168,26 +168,23 @@ void *__libqasan_malloc(size_t size) { #ifdef USE_LIBC_ALLOC void *r = &__tmp_alloc_zone[__tmp_alloc_zone_idx]; - - if (size & (ALLOC_ALIGN_SIZE - 1)) - __tmp_alloc_zone_idx += - (size & ~(ALLOC_ALIGN_SIZE - 1)) + ALLOC_ALIGN_SIZE; - else - __tmp_alloc_zone_idx += size; - + __tmp_alloc_zone_idx += qasan_align_up(size, ALLOC_ALIGN_SIZE); return r; #endif } 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); 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->aligned_orig = NULL; @@ -195,12 +192,9 @@ void *__libqasan_malloc(size_t size) { QASAN_ALLOC(&p[1], (char *)&p[1] + size); QASAN_POISON(p->redzone, REDZONE_SIZE, ASAN_HEAP_LEFT_RZ); - if (size & (ALLOC_ALIGN_SIZE - 1)) - QASAN_POISON((char *)&p[1] + size, - (size & ~(ALLOC_ALIGN_SIZE - 1)) + 8 - size + REDZONE_SIZE, - ASAN_HEAP_RIGHT_RZ); - else - QASAN_POISON((char *)&p[1] + size, REDZONE_SIZE, ASAN_HEAP_RIGHT_RZ); + QASAN_POISON((char *)&p[1] + size, + qasan_align_up(size, ALLOC_ALIGN_SIZE) - size + REDZONE_SIZE, + ASAN_HEAP_RIGHT_RZ); __libqasan_memset(&p[1], 0xff, size); @@ -235,11 +229,7 @@ void __libqasan_free(void *ptr) { } QASAN_SWAP(state); - - if (n & (ALLOC_ALIGN_SIZE - 1)) - n = (n & ~(ALLOC_ALIGN_SIZE - 1)) + ALLOC_ALIGN_SIZE; - - QASAN_POISON(ptr, n, ASAN_HEAP_FREED); + QASAN_POISON(ptr, qasan_align_up(n, ALLOC_ALIGN_SIZE), ASAN_HEAP_FREED); 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 - 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); 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); 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_POISON(p->redzone, REDZONE_SIZE, ASAN_HEAP_LEFT_RZ); - if (len & (ALLOC_ALIGN_SIZE - 1)) - QASAN_POISON( - data + len, - (len & ~(ALLOC_ALIGN_SIZE - 1)) + ALLOC_ALIGN_SIZE - len + REDZONE_SIZE, - ASAN_HEAP_RIGHT_RZ); - else - QASAN_POISON(data + len, REDZONE_SIZE, ASAN_HEAP_RIGHT_RZ); + QASAN_POISON(data + len, + qasan_align_up(len, ALLOC_ALIGN_SIZE) - len + REDZONE_SIZE, + ASAN_HEAP_RIGHT_RZ); __libqasan_memset(data, 0xff, len); diff --git a/libafl_qemu/libqasan/qasan.h b/libafl_qemu/libqasan/qasan.h index 05b59ef5c7..cff2fd81b3 100644 --- a/libafl_qemu/libqasan/qasan.h +++ b/libafl_qemu/libqasan/qasan.h @@ -76,27 +76,58 @@ enum { #include -#define QASAN_CALL0(action) syscall(QASAN_FAKESYS_NR, action, NULL, NULL, NULL) -#define QASAN_CALL1(action, arg1) \ - syscall(QASAN_FAKESYS_NR, action, arg1, NULL, NULL) -#define QASAN_CALL2(action, arg1, arg2) \ - syscall(QASAN_FAKESYS_NR, action, arg1, arg2, NULL) -#define QASAN_CALL3(action, arg1, arg2, arg3) \ - syscall(QASAN_FAKESYS_NR, action, arg1, arg2, arg3) +#ifdef ASAN_GUEST + #include -#define QASAN_LOAD(ptr, len) QASAN_CALL2(QASAN_ACTION_CHECK_LOAD, ptr, len) -#define QASAN_STORE(ptr, len) QASAN_CALL2(QASAN_ACTION_CHECK_STORE, ptr, len) +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); -#define QASAN_POISON(ptr, len, poison_byte) \ - QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, poison_byte) -#define QASAN_USER_POISON(ptr, len) \ - QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, ASAN_USER) -#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) +void qasan_alloc(const char *start, const char *end); +void qasan_dealloc(const char *start); +int qasan_swap(int state); -#define QASAN_ALLOC(start, end) QASAN_CALL2(QASAN_ACTION_ALLOC, start, end) -#define QASAN_DEALLOC(ptr) QASAN_CALL1(QASAN_ACTION_DEALLOC, ptr) + #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_SWAP(state) QASAN_CALL1(QASAN_ACTION_SWAP_STATE, state) + #define QASAN_CALL0(action) \ + syscall(QASAN_FAKESYS_NR, action, NULL, NULL, NULL) + #define QASAN_CALL1(action, arg1) \ + syscall(QASAN_FAKESYS_NR, action, arg1, NULL, NULL) + #define QASAN_CALL2(action, arg1, arg2) \ + syscall(QASAN_FAKESYS_NR, action, arg1, arg2, NULL) + #define QASAN_CALL3(action, arg1, arg2, arg3) \ + syscall(QASAN_FAKESYS_NR, action, arg1, arg2, arg3) + + #define QASAN_LOAD(ptr, len) QASAN_CALL2(QASAN_ACTION_CHECK_LOAD, ptr, len) + #define QASAN_STORE(ptr, len) QASAN_CALL2(QASAN_ACTION_CHECK_STORE, ptr, len) + + #define QASAN_POISON(ptr, len, poison_byte) \ + QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, poison_byte) + #define QASAN_USER_POISON(ptr, len) \ + QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, ASAN_USER) + #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_ALLOC(start, end) QASAN_CALL2(QASAN_ACTION_ALLOC, start, end) + #define QASAN_DEALLOC(ptr) QASAN_CALL1(QASAN_ACTION_DEALLOC, ptr) + + #define QASAN_SWAP(state) QASAN_CALL1(QASAN_ACTION_SWAP_STATE, state) +#endif #endif diff --git a/libafl_qemu/src/asan.rs b/libafl_qemu/src/asan.rs index 8c95f21b08..a2a9439c13 100644 --- a/libafl_qemu/src/asan.rs +++ b/libafl_qemu/src/asan.rs @@ -9,7 +9,6 @@ use std::{ use addr2line::object::{Object, ObjectSection}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, HasMetadata}; -use libafl_qemu_sys::GuestAddr; use libc::{ 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}, snapshot::QemuSnapshotHelper, - Qemu, Regs, + sys::TCGTemp, + GuestAddr, Qemu, Regs, }; // TODO at some point, merge parts with libafl_frida @@ -1011,6 +1011,7 @@ pub fn gen_readwrite_asan( hooks: &mut QemuHooks, _state: Option<&mut S>, pc: GuestAddr, + _addr: *mut TCGTemp, _info: MemAccessInfo, ) -> Option where @@ -1171,6 +1172,7 @@ pub fn gen_write_asan_snapshot( hooks: &mut QemuHooks, _state: Option<&mut S>, pc: GuestAddr, + _addr: *mut TCGTemp, _info: MemAccessInfo, ) -> Option where diff --git a/libafl_qemu/src/asan_guest.rs b/libafl_qemu/src/asan_guest.rs new file mode 100644 index 0000000000..e7850221d1 --- /dev/null +++ b/libafl_qemu/src/asan_guest.rs @@ -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, + 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, +} + +#[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::>(); + + 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::>(); + + 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 HasInstrumentationFilter + for QemuAsanGuestHelper +{ + fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { + &self.filter + } + + fn filter_mut(&mut self) -> &mut QemuInstrumentationAddressRangeFilter { + &mut self.filter + } +} + +fn gen_readwrite_guest_asan( + hooks: &mut QemuHooks, + _state: Option<&mut S>, + pc: GuestAddr, + addr: *mut TCGTemp, + info: MemAccessInfo, +) -> Option +where + S: UsesInput, + QT: QemuHelperTuple, +{ + let h = hooks.match_helper_mut::().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( + _hooks: &mut QemuHooks, + _state: Option<&mut S>, + _id: u64, + _addr: GuestAddr, +) where + S: UsesInput, + QT: QemuHelperTuple, +{ + panic!("I really shouldn't be here"); +} + +fn guest_trace_error_n_asan( + _hooks: &mut QemuHooks, + _state: Option<&mut S>, + _id: u64, + _addr: GuestAddr, + _n: usize, +) where + S: UsesInput, + QT: QemuHelperTuple, +{ + panic!("I really shouldn't be here either"); +} + +impl QemuHelper for QemuAsanGuestHelper +where + S: UsesInput + HasMetadata, +{ + fn first_exec(&self, hooks: &QemuHooks) + where + QT: QemuHelperTuple, + { + hooks.reads( + Hook::Function(gen_readwrite_guest_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_n_asan::), + ); + + hooks.writes( + Hook::Function(gen_readwrite_guest_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_asan::), + Hook::Function(guest_trace_error_n_asan::), + ); + } +} diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index 9e3043898e..a58c990cca 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -1,7 +1,7 @@ //! Expose QEMU user `LibAFL` C api to Rust use core::{ - fmt, + fmt::{self, Debug, Display, Formatter}, marker::PhantomData, mem::{transmute, MaybeUninit}, ptr::{addr_of, copy_nonoverlapping, null}, @@ -10,7 +10,6 @@ use std::{ cell::{OnceCell, Ref, RefCell, RefMut}, collections::HashSet, ffi::CString, - fmt::{Debug, Display, Formatter}, ptr, }; @@ -32,7 +31,9 @@ pub use libafl_qemu_sys::{MapInfo, MmapPerms, MmapPermsIter}; use num_traits::Num; 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")] pub mod systemmode; @@ -1164,7 +1165,7 @@ impl Qemu { pub fn add_read_hooks>( &self, data: T, - gen: Option u64>, + gen: Option u64>, exec1: Option, exec2: Option, exec4: Option, @@ -1173,8 +1174,14 @@ impl Qemu { ) -> ReadHookId { unsafe { let data: u64 = data.into().0; - let gen: Option u64> = - transmute(gen); + let gen: Option< + unsafe extern "C" fn( + u64, + GuestAddr, + *mut TCGTemp, + libafl_qemu_sys::MemOpIdx, + ) -> u64, + > = transmute(gen); let exec1: Option = transmute(exec1); let exec2: Option = transmute(exec2); let exec4: Option = transmute(exec4); @@ -1192,7 +1199,7 @@ impl Qemu { pub fn add_write_hooks>( &self, data: T, - gen: Option u64>, + gen: Option u64>, exec1: Option, exec2: Option, exec4: Option, @@ -1201,8 +1208,14 @@ impl Qemu { ) -> WriteHookId { unsafe { let data: u64 = data.into().0; - let gen: Option u64> = - transmute(gen); + let gen: Option< + unsafe extern "C" fn( + u64, + GuestAddr, + *mut TCGTemp, + libafl_qemu_sys::MemOpIdx, + ) -> u64, + > = transmute(gen); let exec1: Option = transmute(exec1); let exec2: Option = transmute(exec2); let exec4: Option = transmute(exec4); @@ -1632,7 +1645,7 @@ where pub fn add_read_hooks>( &self, data: T, - gen: Option u64>, + gen: Option u64>, exec1: Option, exec2: Option, exec4: Option, @@ -1650,7 +1663,7 @@ where pub fn add_write_hooks>( &self, data: T, - gen: Option u64>, + gen: Option u64>, exec1: Option, exec2: Option, exec4: Option, diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index d2961fc5a5..b58aeaec7a 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -23,6 +23,7 @@ pub use crate::emu::SyscallHookResult; use crate::{ emu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK}, helper::QemuHelperTuple, + sys::TCGTemp, BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, HookId, InstructionHookId, ReadHookId, 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); static mut READ_HOOKS: Vec>>> = vec![]; -create_gen_wrapper!( - read, - (pc: GuestAddr, info: MemAccessInfo), - u64, - 5, - ReadHookId -); +create_gen_wrapper!(read, (pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo), u64, 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), 2, 5, ReadHookId); @@ -328,13 +323,7 @@ create_exec_wrapper!( ); static mut WRITE_HOOKS: Vec>>> = vec![]; -create_gen_wrapper!( - write, - (pc: GuestAddr, info: MemAccessInfo), - u64, - 5, - WriteHookId -); +create_gen_wrapper!(write, (pc: GuestAddr, addr: *mut TCGTemp, info: MemAccessInfo), u64, 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), 2, 5, WriteHookId); @@ -679,16 +668,23 @@ where pub fn reads( &self, generation_hook: Hook< - fn(&mut Self, Option<&mut S>, pc: GuestAddr, info: MemAccessInfo) -> Option, + fn( + &mut Self, + Option<&mut S>, + pc: GuestAddr, + addr: *mut TCGTemp, + info: MemAccessInfo, + ) -> Option, Box< dyn for<'a> FnMut( &'a mut Self, Option<&'a mut S>, GuestAddr, + *mut TCGTemp, MemAccessInfo, ) -> Option, >, - 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< fn(&mut Self, Option<&mut S>, id: u64, addr: GuestAddr), @@ -723,6 +719,7 @@ where extern "C" fn( &mut HookState<5, ReadHookId>, pc: GuestAddr, + addr: *mut TCGTemp, info: MemAccessInfo, ) -> u64 ); @@ -786,16 +783,23 @@ where pub fn writes( &self, generation_hook: Hook< - fn(&mut Self, Option<&mut S>, pc: GuestAddr, info: MemAccessInfo) -> Option, + fn( + &mut Self, + Option<&mut S>, + pc: GuestAddr, + addr: *mut TCGTemp, + info: MemAccessInfo, + ) -> Option, Box< dyn for<'a> FnMut( &'a mut Self, Option<&'a mut S>, GuestAddr, + *mut TCGTemp, MemAccessInfo, ) -> Option, >, - 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< fn(&mut Self, Option<&mut S>, id: u64, addr: GuestAddr), @@ -830,6 +834,7 @@ where extern "C" fn( &mut HookState<5, WriteHookId>, pc: GuestAddr, + addr: *mut TCGTemp, info: MemAccessInfo, ) -> u64 ); diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index 3c21f3ae08..9b7f43fa7c 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -98,6 +98,11 @@ pub mod asan; #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] 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"))] pub mod calls; #[cfg(not(cpu_target = "hexagon"))]