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:
parent
47c41c2925
commit
374f8735fa
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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<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)]
|
||||
pub cmplog_cores: Option<Cores>,
|
||||
|
||||
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
@ -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 {
|
||||
|
@ -13500,7 +13500,14 @@ extern "C" {
|
||||
}
|
||||
extern "C" {
|
||||
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)>,
|
||||
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)>,
|
||||
@ -13513,7 +13520,14 @@ extern "C" {
|
||||
}
|
||||
extern "C" {
|
||||
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)>,
|
||||
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)>,
|
||||
|
@ -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
|
||||
|
@ -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 <andreafioraldi@gmail.com>\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) {
|
||||
|
@ -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 <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#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);
|
||||
|
||||
|
@ -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,
|
||||
qasan_align_up(size, ALLOC_ALIGN_SIZE) - size + REDZONE_SIZE,
|
||||
ASAN_HEAP_RIGHT_RZ);
|
||||
else
|
||||
QASAN_POISON((char *)&p[1] + 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,
|
||||
QASAN_POISON(data + len,
|
||||
qasan_align_up(len, ALLOC_ALIGN_SIZE) - len + REDZONE_SIZE,
|
||||
ASAN_HEAP_RIGHT_RZ);
|
||||
else
|
||||
QASAN_POISON(data + len, REDZONE_SIZE, ASAN_HEAP_RIGHT_RZ);
|
||||
|
||||
__libqasan_memset(data, 0xff, len);
|
||||
|
||||
|
@ -76,27 +76,58 @@ enum {
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#define QASAN_CALL0(action) syscall(QASAN_FAKESYS_NR, action, NULL, NULL, NULL)
|
||||
#define QASAN_CALL1(action, arg1) \
|
||||
#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) \
|
||||
syscall(QASAN_FAKESYS_NR, action, arg1, NULL, NULL)
|
||||
#define QASAN_CALL2(action, arg1, arg2) \
|
||||
#define QASAN_CALL2(action, arg1, arg2) \
|
||||
syscall(QASAN_FAKESYS_NR, action, arg1, arg2, NULL)
|
||||
#define QASAN_CALL3(action, arg1, arg2, arg3) \
|
||||
#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_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) \
|
||||
#define QASAN_POISON(ptr, len, poison_byte) \
|
||||
QASAN_CALL3(QASAN_ACTION_POISON, ptr, len, poison_byte)
|
||||
#define QASAN_USER_POISON(ptr, len) \
|
||||
#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_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_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)
|
||||
#define QASAN_SWAP(state) QASAN_CALL1(QASAN_ACTION_SWAP_STATE, state)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -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<QT, S>(
|
||||
hooks: &mut QemuHooks<QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
_addr: *mut TCGTemp,
|
||||
_info: MemAccessInfo,
|
||||
) -> Option<u64>
|
||||
where
|
||||
@ -1171,6 +1172,7 @@ pub fn gen_write_asan_snapshot<QT, S>(
|
||||
hooks: &mut QemuHooks<QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
_addr: *mut TCGTemp,
|
||||
_info: MemAccessInfo,
|
||||
) -> Option<u64>
|
||||
where
|
||||
|
300
libafl_qemu/src/asan_guest.rs
Normal file
300
libafl_qemu/src/asan_guest.rs
Normal 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>),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<T: Into<HookData>>(
|
||||
&self,
|
||||
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)>,
|
||||
exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
|
||||
exec4: Option<extern "C" fn(T, u64, GuestAddr)>,
|
||||
@ -1173,8 +1174,14 @@ impl Qemu {
|
||||
) -> ReadHookId {
|
||||
unsafe {
|
||||
let data: u64 = data.into().0;
|
||||
let gen: Option<extern "C" fn(u64, GuestAddr, libafl_qemu_sys::MemOpIdx) -> u64> =
|
||||
transmute(gen);
|
||||
let gen: Option<
|
||||
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 exec2: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec2);
|
||||
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>>(
|
||||
&self,
|
||||
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)>,
|
||||
exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
|
||||
exec4: Option<extern "C" fn(T, u64, GuestAddr)>,
|
||||
@ -1201,8 +1208,14 @@ impl Qemu {
|
||||
) -> WriteHookId {
|
||||
unsafe {
|
||||
let data: u64 = data.into().0;
|
||||
let gen: Option<extern "C" fn(u64, GuestAddr, libafl_qemu_sys::MemOpIdx) -> u64> =
|
||||
transmute(gen);
|
||||
let gen: Option<
|
||||
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 exec2: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec2);
|
||||
let exec4: Option<extern "C" fn(u64, u64, GuestAddr)> = transmute(exec4);
|
||||
@ -1632,7 +1645,7 @@ where
|
||||
pub fn add_read_hooks<T: Into<HookData>>(
|
||||
&self,
|
||||
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)>,
|
||||
exec2: 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>>(
|
||||
&self,
|
||||
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)>,
|
||||
exec2: Option<extern "C" fn(T, u64, GuestAddr)>,
|
||||
exec4: Option<extern "C" fn(T, u64, GuestAddr)>,
|
||||
|
@ -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<Pin<Box<HookState<5, ReadHookId>>>> = 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<Pin<Box<HookState<5, WriteHookId>>>> = 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<u64>,
|
||||
fn(
|
||||
&mut Self,
|
||||
Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
addr: *mut TCGTemp,
|
||||
info: MemAccessInfo,
|
||||
) -> Option<u64>,
|
||||
Box<
|
||||
dyn for<'a> FnMut(
|
||||
&'a mut Self,
|
||||
Option<&'a mut S>,
|
||||
GuestAddr,
|
||||
*mut TCGTemp,
|
||||
MemAccessInfo,
|
||||
) -> 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<
|
||||
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<u64>,
|
||||
fn(
|
||||
&mut Self,
|
||||
Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
addr: *mut TCGTemp,
|
||||
info: MemAccessInfo,
|
||||
) -> Option<u64>,
|
||||
Box<
|
||||
dyn for<'a> FnMut(
|
||||
&'a mut Self,
|
||||
Option<&'a mut S>,
|
||||
GuestAddr,
|
||||
*mut TCGTemp,
|
||||
MemAccessInfo,
|
||||
) -> 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<
|
||||
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
|
||||
);
|
||||
|
@ -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"))]
|
||||
|
Loading…
x
Reference in New Issue
Block a user