WorksButNotTested 374f8735fa
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>
2024-04-10 01:11:28 +02:00

376 lines
12 KiB
C

/*******************************************************************************
Copyright (c) 2019-2020, Andrea Fioraldi
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
#include "libqasan.h"
#ifdef DEBUG
int __qasan_debug;
#endif
int __qasan_log;
void __libqasan_print_maps(void) {
int fd = open("/proc/self/maps", O_RDONLY);
char buf[4096] = {0};
read(fd, buf, 4095);
close(fd);
size_t len = __libqasan_strlen(buf);
QASAN_LOG("Guest process maps:\n");
int i;
char *line = NULL;
for (i = 0; i < len; i++) {
if (!line) line = &buf[i];
if (buf[i] == '\n') {
buf[i] = 0;
QASAN_LOG("%s\n", line);
line = NULL;
}
}
if (line) { QASAN_LOG("%s\n", line); }
QASAN_LOG("\n");
}
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;
__libqasan_init_hooks();
if (getenv("AFL_INST_LIBS") || getenv("QASAN_HOTPACH")) {
__libqasan_hotpatch();
}
#ifdef DEBUG
__qasan_debug = getenv("QASAN_DEBUG") != NULL;
#endif
__qasan_log = getenv("QASAN_LOG") != NULL;
QASAN_LOG("QEMU-AddressSanitizer (v%s)\n", QASAN_VERSTR);
QASAN_LOG(
"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) {
typeof(&__libc_start_main) orig = dlsym(RTLD_NEXT, "__libc_start_main");
__libqasan_init();
return orig(main, argc, argv, init, fini, rtld_fini, stack_end);
}