diff --git a/Makefile b/Makefile index 916a6ce59..7765c2b24 100755 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ libdragon: CXXFLAGS+=$(N64_CXXFLAGS) $(LIBDRAGON_CFLAGS) libdragon: ASFLAGS+=$(N64_ASFLAGS) $(LIBDRAGON_CFLAGS) libdragon: RSPASFLAGS+=$(N64_RSPASFLAGS) $(LIBDRAGON_CFLAGS) libdragon: LDFLAGS+=$(N64_LDFLAGS) -libdragon: libdragon.a libdragonsys.a gen-version +libdragon: libdragon.a libdragon_asan.a libdragonsys.a gen-version libdragonsys.a: $(BUILD_DIR)/system.o @@ -81,6 +81,7 @@ LIBDRAGON_OBJS += \ $(BUILD_DIR)/dma.o \ $(BUILD_DIR)/timer.o \ $(BUILD_DIR)/exception.o \ + $(BUILD_DIR)/asan_weak.o \ $(BUILD_DIR)/do_ctors.o \ $(BUILD_DIR)/dlfcn.o \ $(BUILD_DIR)/hashtable.o \ @@ -114,6 +115,7 @@ $(SOURCE_DIR)/magma/rsp_magma.h: $(BUILD_DIR)/magma/rsp_magma.o $(BUILD_DIR)/magma/magma.o: $(SOURCE_DIR)/magma/rsp_magma.h libdragon.a: $(LIBDRAGON_OBJS) +libdragon_asan.a: $(BUILD_DIR)/asan.o %.a: @echo " [AR] $@" @@ -186,6 +188,7 @@ install: install-mk libdragon @echo " [INSTALL] libdragon" mkdir -p $(INSTALLDIR)/$(N64_TARGET)/lib install -Cv -m 0644 libdragon.a $(INSTALLDIR)/$(N64_TARGET)/lib/libdragon.a + install -Cv -m 0644 libdragon_asan.a $(INSTALLDIR)/$(N64_TARGET)/lib/libdragon_asan.a install -Cv -m 0644 n64.ld $(INSTALLDIR)/$(N64_TARGET)/lib/n64.ld install -Cv -m 0644 dso.ld $(INSTALLDIR)/$(N64_TARGET)/lib/dso.ld install -Cv -m 0644 rsp.ld $(INSTALLDIR)/$(N64_TARGET)/lib/rsp.ld diff --git a/include/asan.h b/include/asan.h new file mode 100644 index 000000000..850aa47af --- /dev/null +++ b/include/asan.h @@ -0,0 +1,55 @@ +/** + * @file asan.h + * @author Giovanni Bajo + * @brief Emulator-assisted heap memory sanitizer (XASAN) + * @ingroup asan + * @defgroup asan asan + * + * XASAN is an emulator-assisted memory access sanitizer for the VR4300. + * Enable it at link time with `N64_ASAN=1` in n64.mk. + * + * When disabled (default), the functions resolve to weak no-op stubs with + * no runtime overhead beyond a call when actually used. + * + * @note Global object registration (`.xasan_globals`) is not implemented yet. + */ + +#ifndef LIBDRAGON_ASAN_H +#define LIBDRAGON_ASAN_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Mark a memory region as user-poisoned (inaccessible) + * + * @param ptr Base address (rounded down to 8 bytes) + * @param size Size in bytes (rounded up to 8 bytes) + */ +void asan_poison(void *ptr, size_t size); + +/** + * @brief Mark a memory region as accessible + * + * @param ptr Base address (rounded down to 8 bytes) + * @param size Size in bytes (rounded up to 8 bytes) + */ +void asan_unpoison(void *ptr, size_t size); + +/** + * @brief Return whether XASAN is linked in and active + * + * When the app is linked with `N64_ASAN=1` and the emulator supports XASAN, + * this returns true after startup initialization. + */ +bool asan_enabled(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBDRAGON_ASAN_H */ diff --git a/include/emux.h b/include/emux.h index e399727d9..94f701638 100644 --- a/include/emux.h +++ b/include/emux.h @@ -17,6 +17,7 @@ ///@cond #ifndef __ASSEMBLER__ #include +#include #define cast64(x) (uint64_t)(x) #else #define cast64(x) x @@ -44,6 +45,7 @@ #define EMUX_XPROF(slot, code) EMUX_OP(0x28, slot, 0, code) ///< Control profiler #define EMUX_XPROFREAD(slot, metric) EMUX_OP(0x29, slot, metric, 0x000) ///< Read profiler metric #define EMUX_XEXCEPTION(mask) EMUX_OP(0x2A, 0, mask, 0x000) ///< Set exception mask +#define EMUX_XASAN(rd, rt, code) EMUX_OP(0x2B, rd, rt, code) ///< XASAN memory sanitizer #define EMUX_XIOCTL(code) EMUX_OP(0x2C, 0, 0, code) ///< Modify emulator behavior /** @} */ @@ -56,8 +58,35 @@ #define EMUX_FEAT1_HEXDUMP (1 << 0x7) ///< Hexdump support #define EMUX_FEAT1_PROFILER (1 << 0x8) ///< Profiling support #define EMUX_FEAT1_EXCEPTION (1 << 0xA) ///< Exception support +#define EMUX_FEAT1_XASAN (1 << 0xB) ///< XASAN memory sanitizer support #define EMUX_FEAT1_IOCTL (1 << 0xC) ///< Emulator behavior support +#define EMUX_XASAN_DISABLE 0x0 ///< Disable XASAN checking (refcount--) +#define EMUX_XASAN_ENABLE 0x1 ///< Enable XASAN checking (refcount++) +#define EMUX_XASAN_POISON 0x2 ///< Poison a memory region +#define EMUX_XASAN_UNPOISON 0x3 ///< Unpoison a memory region + +#define EMUX_XASAN_TAG_ACCESSIBLE 0 ///< Accessible region +#define EMUX_XASAN_TAG_LEFT 1 ///< Left redzone +#define EMUX_XASAN_TAG_RIGHT 2 ///< Right redzone +#define EMUX_XASAN_TAG_FREED 3 ///< Freed memory +#define EMUX_XASAN_TAG_GLOBAL 4 ///< Global redzone +#define EMUX_XASAN_TAG_USER 5 ///< User-poisoned region +#define EMUX_XASAN_TAG_UNALLOC 6 ///< Unallocated heap memory + +#define EMUX_XASAN_ACCESS_UNKNOWN 0x00 +#define EMUX_XASAN_ACCESS_CPU_READ 0x01 +#define EMUX_XASAN_ACCESS_CPU_WRITE 0x02 +#define EMUX_XASAN_ACCESS_CPU_EXEC 0x03 +#define EMUX_XASAN_ACCESS_STACK 0x04 +#define EMUX_XASAN_ACCESS_RSP_DMA_READ 0x05 +#define EMUX_XASAN_ACCESS_RSP_DMA_WRITE 0x06 + +#define EMUX_XASAN_FAULT_UNKNOWN 0x00 +#define EMUX_XASAN_FAULT_PERMISSION 0x01 +#define EMUX_XASAN_FAULT_POISON 0x02 +#define EMUX_XASAN_FAULT_TAIL 0x03 + #define EMUX_LOG_ASCIIZ 0x000 ///< Log a zero-terminated string #define EMUX_LOG_LENGTH 0x001 ///< Log a non-zero-terminated string @@ -145,6 +174,7 @@ #define EMUX_EXCEPTION_CPU_CACHED_ACCESS (cast64(1) << 1) ///< CPU cached access to non-RDRAM area #define EMUX_EXCEPTION_CPU_64BIT_READ (cast64(1) << 2) ///< CPU 64-bit read from non-RDRAM area #define EMUX_EXCEPTION_CPU_UNMAPPED_ACCESS (cast64(1) << 3) ///< CPU access to RCP unmapped area +#define EMUX_EXCEPTION_XASAN (cast64(1) << 4) ///< XASAN memory access violation #ifndef __ASSEMBLER__ @@ -418,6 +448,65 @@ inline void emux_exception_set_mask(uint64_t mask) __asm__ __volatile__(" .word %1\n" :: "r"(__mask), "i"(EMUX_XEXCEPTION(REG_T0)) : "memory"); } +/** @brief Run an XASAN EMUX opcode with address and size operands */ +#define EMUX_XASAN_RUN(addr, size, code) do { \ + const int REG_T0 = 8; \ + const int REG_T1 = 9; \ + register const void *__xasan_addr asm("t0") = (const void *)(addr); \ + register size_t __xasan_size asm("t1") = (size_t)(size); \ + __asm__ __volatile__(" .word %2\n" \ + :: "r"(__xasan_addr), "r"(__xasan_size), "i"(EMUX_XASAN(REG_T0, REG_T1, code)) \ + : "memory"); \ +} while(0) + +/** @brief Enable XASAN checking in the emulator */ +inline void emux_xasan_enable(void) +{ + __asm__ __volatile__(" .word %0\n" :: "i"(EMUX_XASAN(0, 0, EMUX_XASAN_ENABLE)) : "memory"); +} + +/** @brief Disable XASAN checking in the emulator */ +inline void emux_xasan_disable(void) +{ + __asm__ __volatile__(" .word %0\n" :: "i"(EMUX_XASAN(0, 0, EMUX_XASAN_DISABLE)) : "memory"); +} + +/** @brief Mark a memory region as accessible */ +inline void emux_xasan_unpoison(const void *addr, size_t size) +{ + EMUX_XASAN_RUN(addr, size, EMUX_XASAN_UNPOISON); +} + +/** @brief Mark a memory region as user-poisoned */ +inline void emux_xasan_poison_user(const void *addr, size_t size) +{ + EMUX_XASAN_RUN(addr, size, EMUX_XASAN_POISON | (EMUX_XASAN_TAG_USER << 4)); +} + +/** @brief Mark a memory region as left redzone */ +inline void emux_xasan_poison_left(const void *addr, size_t size) +{ + EMUX_XASAN_RUN(addr, size, EMUX_XASAN_POISON | (EMUX_XASAN_TAG_LEFT << 4)); +} + +/** @brief Mark a memory region as right redzone */ +inline void emux_xasan_poison_right(const void *addr, size_t size) +{ + EMUX_XASAN_RUN(addr, size, EMUX_XASAN_POISON | (EMUX_XASAN_TAG_RIGHT << 4)); +} + +/** @brief Mark a memory region as freed */ +inline void emux_xasan_poison_freed(const void *addr, size_t size) +{ + EMUX_XASAN_RUN(addr, size, EMUX_XASAN_POISON | (EMUX_XASAN_TAG_FREED << 4)); +} + +/** @brief Mark a memory region as unallocated heap memory */ +inline void emux_xasan_poison_unalloc(const void *addr, size_t size) +{ + EMUX_XASAN_RUN(addr, size, EMUX_XASAN_POISON | (EMUX_XASAN_TAG_UNALLOC << 4)); +} + #ifdef __cplusplus } #endif diff --git a/include/libdragon.h b/include/libdragon.h index 40997c286..8e4707176 100644 --- a/include/libdragon.h +++ b/include/libdragon.h @@ -30,6 +30,7 @@ #include "entropy.h" #include "console.h" #include "emux.h" +#include "asan.h" #include "debug.h" #include "fat.h" #include "joybus.h" diff --git a/n64.mk b/n64.mk index fd293fc16..9a74a05fb 100644 --- a/n64.mk +++ b/n64.mk @@ -82,6 +82,13 @@ N64_CXXFLAGS = $(N64_C_AND_CXX_FLAGS) -std=gnu++17 N64_ASFLAGS = -mtune=vr4300 -march=vr4300 -mabi=o64 -Wa,--fatal-warnings -I$(N64_INCLUDEDIR) N64_RSPASFLAGS = -march=mips1 -mabi=32 -Wa,--fatal-warnings -I$(N64_INCLUDEDIR) N64_LDFLAGS = -g -L$(N64_LIBDIR) -ldragon -lm -ldragonsys -Tn64.ld --gc-sections --wrap __do_global_ctors + +ifeq ($(N64_ASAN),1) +N64_C_AND_CXX_FLAGS += -fstack-protector-strong +N64_LDFLAGS += -ldragon_asan +N64_LDFLAGS += --wrap=_malloc_r --wrap=_free_r --wrap=_realloc_r --wrap=_calloc_r --wrap=_memalign_r --wrap=sbrk_top --wrap=_mallinfo_r -u __asan_init +N64_LDFLAGS += --wrap=__stack_chk_fail +endif N64_DSOLDFLAGS = --emit-relocs --unresolved-symbols=ignore-all --nmagic -T$(N64_LIBDIR)/dso.ld N64_TOOLFLAGS = --toc diff --git a/src/asan.c b/src/asan.c new file mode 100644 index 000000000..ae109e485 --- /dev/null +++ b/src/asan.c @@ -0,0 +1,264 @@ +/** + * @file asan.c + * @author Giovanni Bajo + * @brief XASAN debug heap allocator and runtime + * @ingroup asan + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "asan.h" +#include "emux.h" +#include "debug.h" +#include "n64sys.h" +#include "exception_internal.h" + +/** @brief Left redzone padding between header and payload (bytes) */ +#define ASAN_LEFT_RZ 8 + +/** @brief Right redzone size (bytes) */ +#define ASAN_RIGHT_RZ 16 + +/** @brief Shadow / alignment granule (bytes) */ +#define ASAN_GRANULE 16 + +/** @brief Block header size (bytes); must be a multiple of ASAN_GRANULE */ +#define ASAN_HEADER_SIZE 16 + +/** @brief Offset from raw block start to user payload for plain malloc */ +#define ASAN_PAYLOAD_OFFSET (ASAN_HEADER_SIZE + ASAN_LEFT_RZ) + +/** @brief Magic value stored in allocation headers */ +#define ASAN_MAGIC 0x4153414eu + +typedef struct { + uint32_t magic; + void *raw; + size_t req_size; +} asan_hdr_t; + +/** @brief True after successful XASAN initialization */ +bool __asan_active; + +extern char *__heap_top; +extern char *__heap_end; + +extern void *__real__malloc_r(struct _reent *r, size_t n); +extern void __real__free_r(struct _reent *r, void *p); +extern void *__real__realloc_r(struct _reent *r, void *p, size_t n); +extern void *__real__memalign_r(struct _reent *r, size_t align, size_t n); +extern void *__real_sbrk_top(int incr); +extern struct mallinfo __real_mallinfo(void); +extern struct mallinfo __real__mallinfo_r(struct _reent *r); + +static inline void *asan_align_ptr(void *p, size_t align) +{ + uintptr_t v = (uintptr_t)p; + return (void *)((v + align - 1) & ~(align - 1)); +} + +static inline asan_hdr_t *asan_hdr(void *payload) +{ + return (asan_hdr_t *)((char *)payload - ASAN_HEADER_SIZE); +} + +static void asan_write_hdr(asan_hdr_t *hdr, void *raw, size_t req_size) +{ + hdr->magic = ASAN_MAGIC; + hdr->raw = raw; + hdr->req_size = req_size; +} + +static bool asan_valid_hdr(const asan_hdr_t *hdr) +{ + return hdr->magic == ASAN_MAGIC; +} + +static void asan_poison_region(const void *addr, size_t size, void (*poison_fn)(const void *, size_t)) +{ + if (!size) return; + poison_fn(addr, size); +} + +void asan_poison(void *ptr, size_t size) +{ + if (!__asan_active) return; + asan_poison_region(ptr, size, emux_xasan_poison_user); +} + +void asan_unpoison(void *ptr, size_t size) +{ + if (!__asan_active) return; + asan_poison_region(ptr, size, emux_xasan_unpoison); +} + +bool asan_enabled(void) +{ + return __asan_active; +} + +__attribute__((constructor(103))) void __asan_init(void) +{ + if (!(emux_detect(1) & EMUX_FEAT1_XASAN)) + return; + + sbrk(0); + emux_xasan_enable(); + emux_xasan_poison_unalloc(__heap_end, __heap_top - __heap_end); + __asan_active = true; +} + +void *__wrap__malloc_r(struct _reent *r, size_t n) +{ + if (!__asan_active) { + static bool warned; + if (!warned) { + warned = true; + assertf(0, "N64_ASAN=1 but the emulator/console does not support XASAN"); + } + return __real__malloc_r(r, n); + } + + size_t payload_size = n; + size_t total = ASAN_PAYLOAD_OFFSET + ASAN_GRANULE - 1 + payload_size + ASAN_RIGHT_RZ; + void *raw; + void *payload; + asan_hdr_t *hdr; + + emux_xasan_disable(); + raw = __real__malloc_r(r, total); + if (!raw) { + emux_xasan_enable(); + return NULL; + } + + payload = asan_align_ptr((char *)raw + ASAN_PAYLOAD_OFFSET, ASAN_GRANULE); + hdr = asan_hdr(payload); + asan_write_hdr(hdr, raw, n); + emux_xasan_enable(); + + emux_xasan_poison_left(raw, (char *)payload - (char *)raw); + emux_xasan_unpoison(payload, payload_size); + emux_xasan_poison_right((char *)payload + payload_size, ASAN_RIGHT_RZ); + + return payload; +} + +void __wrap__free_r(struct _reent *r, void *p) +{ + if (!p) + return; + + if (!__asan_active) { + __real__free_r(r, p); + return; + } + + asan_hdr_t *hdr = asan_hdr(p); + emux_xasan_disable(); + if (asan_valid_hdr(hdr)) { + void *raw = hdr->raw; + size_t payload_size = hdr->req_size; + size_t total = (char *)p + payload_size + ASAN_RIGHT_RZ - (char *)raw; + emux_xasan_poison_freed(raw, total); + __real__free_r(r, raw); + } else { + __real__free_r(r, p); + } + emux_xasan_enable(); +} + +void *__wrap__calloc_r(struct _reent *r, size_t a, size_t b) +{ + size_t n = a * b; + void *p = __wrap__malloc_r(r, n); + if (p) + memset(p, 0, n); + return p; +} + +void *__wrap__realloc_r(struct _reent *r, void *p, size_t n) +{ + if (!p) + return __wrap__malloc_r(r, n); + if (n == 0) { + __wrap__free_r(r, p); + return NULL; + } + + if (!__asan_active) + return __real__realloc_r(r, p, n); + + emux_xasan_disable(); + size_t old_size = asan_valid_hdr(asan_hdr(p)) ? asan_hdr(p)->req_size : n; + emux_xasan_enable(); + void *np = __wrap__malloc_r(r, n); + if (!np) + return NULL; + memcpy(np, p, old_size < n ? old_size : n); + __wrap__free_r(r, p); + return np; +} + +void *__wrap__memalign_r(struct _reent *r, size_t align, size_t n) +{ + if (!__asan_active) + return __real__memalign_r(r, align, n); + + if (align < ASAN_GRANULE) + align = ASAN_GRANULE; + + size_t payload_size = n; + size_t total = ASAN_PAYLOAD_OFFSET + align - 1 + payload_size + ASAN_RIGHT_RZ; + void *raw; + void *payload; + asan_hdr_t *hdr; + + emux_xasan_disable(); + raw = __real__malloc_r(r, total); + if (!raw) { + emux_xasan_enable(); + return NULL; + } + + payload = asan_align_ptr((char *)raw + ASAN_PAYLOAD_OFFSET, align); + hdr = asan_hdr(payload); + asan_write_hdr(hdr, raw, n); + emux_xasan_enable(); + + emux_xasan_poison_left(raw, (char *)payload - (char *)raw); + emux_xasan_unpoison(payload, payload_size); + emux_xasan_poison_right((char *)payload + payload_size, ASAN_RIGHT_RZ); + + return payload; +} + +void *__wrap_sbrk_top(int incr) +{ + void *p = __real_sbrk_top(incr); + if (p != (void *)-1 && __asan_active) + emux_xasan_unpoison(p, incr); + return p; +} + +struct mallinfo __wrap__mallinfo_r(struct _reent *r) +{ + if (!__asan_active) + return __real__mallinfo_r(r); + + emux_xasan_disable(); + struct mallinfo m = __real__mallinfo_r(r); + emux_xasan_enable(); + return m; +} + +void __wrap___stack_chk_fail(void) +{ + __inspector_stack_smashing(); +} diff --git a/src/asan_weak.c b/src/asan_weak.c new file mode 100644 index 000000000..e32963df1 --- /dev/null +++ b/src/asan_weak.c @@ -0,0 +1,25 @@ +/** + * @file asan_weak.c + * @author Giovanni Bajo + * @brief Weak no-op fallbacks for the ASAN public API + * @ingroup asan + */ + +#include "asan.h" + +__attribute__((weak)) void asan_poison(void *ptr, size_t size) +{ + (void)ptr; + (void)size; +} + +__attribute__((weak)) void asan_unpoison(void *ptr, size_t size) +{ + (void)ptr; + (void)size; +} + +__attribute__((weak)) bool asan_enabled(void) +{ + return false; +} diff --git a/src/asset.c b/src/asset.c index 2543171f3..7e5230c37 100644 --- a/src/asset.c +++ b/src/asset.c @@ -9,6 +9,7 @@ #include "compress/lz4_dec_internal.h" #include "compress/shrinkler_dec_internal.h" #include "utils.h" +#include "asan.h" #include #include #include @@ -167,6 +168,10 @@ static bool decompress_full(asset_compression_full_t *algo, int fd, size_t cmp_s int n; #ifdef N64 + // Unpoison 8 bytes past the buffer end because assembly decompressors can + // read there (without then using the contents of course). + asan_unpoison(s + bufsize, 8); + uint32_t rom_addr = 0; if (ioctl(fd, IODFS_GET_ROM_BASE, &rom_addr) >= 0) { // Invalid the portion of the buffer where we are going to load diff --git a/src/exception.c b/src/exception.c index 64e504b2b..f68b08bdd 100644 --- a/src/exception.c +++ b/src/exception.c @@ -10,6 +10,7 @@ #include "console.h" #include "n64sys.h" #include "debug.h" +#include "emux.h" #include "regsinternal.h" #include "kernel/kernel_internal.h" #include "kernel/ktls_internal.h" @@ -104,9 +105,11 @@ void __exception_dump_header(FILE *out, exception_t* ex) { fprintf(out, "Syscall code: %05lX\n", (*(uint32_t*)ex->regs->epc >> 6) & 0xfffff); break; - case EXCEPTION_CODE_EMUX: - fprintf(out, "On hardware, the console would now be frozen.\n"); + case EXCEPTION_CODE_EMUX: { + if (strncmp(ex->info, "ASAN", 4) != 0) + fprintf(out, "On hardware, the console would now be frozen.\n"); /* fall through */ + } case EXCEPTION_CODE_D_BUS_ERROR: { uint32_t opcode = *(uint32_t*)epc; uint64_t base = ex->regs->gpr[((opcode >> 21) & 0x1F)]; @@ -461,12 +464,46 @@ static const char* __get_exception_name(exception_t *ex) return "Integer divide by zero"; } return exceptionMap[ex->code]; case EXCEPTION_CODE_EMUX: { - uint32_t kind = C0_CACHEERR() & 0xFF; + uint32_t cacheerr = C0_CACHEERR(); + uint32_t kind = cacheerr & 0xFF; + uint32_t tag = (cacheerr >> 8) & 0xFF; + uint32_t access = (cacheerr >> 16) & 0xFF; + uint32_t fault = (cacheerr >> 24) & 0xFF; + static char emux_info[128]; C0_WRITE_CACHEERR(0); switch (kind) { case 0: return "Cached access to non-RDRAM area"; case 1: return "64-bit read from non-RDRAM area"; case 2: return "Access to RCP unmapped area"; + case 4: { + const char *access_name = "memory access"; + const char *tag_name = "unknown poison"; + const char *fault_name = "unknown"; + switch (access) { + case EMUX_XASAN_ACCESS_CPU_READ: access_name = "CPU Read"; break; + case EMUX_XASAN_ACCESS_CPU_WRITE: access_name = "CPU Write"; break; + case EMUX_XASAN_ACCESS_CPU_EXEC: access_name = "CPU Exec"; break; + case EMUX_XASAN_ACCESS_STACK: access_name = "Stack access"; break; + case EMUX_XASAN_ACCESS_RSP_DMA_READ: access_name = "RSP DMA Read"; break; + case EMUX_XASAN_ACCESS_RSP_DMA_WRITE: access_name = "RSP DMA Write"; break; + } + switch (tag) { + case EMUX_XASAN_TAG_ACCESSIBLE: tag_name = "accessible memory"; break; + case EMUX_XASAN_TAG_LEFT: tag_name = "left redzone"; break; + case EMUX_XASAN_TAG_RIGHT: tag_name = "right redzone"; break; + case EMUX_XASAN_TAG_FREED: tag_name = "freed memory"; break; + case EMUX_XASAN_TAG_GLOBAL: tag_name = "global redzone"; break; + case EMUX_XASAN_TAG_USER: tag_name = "poisoned region"; break; + case EMUX_XASAN_TAG_UNALLOC: tag_name = "unallocated heap memory"; break; + } + switch (fault) { + case EMUX_XASAN_FAULT_PERMISSION: fault_name = "permission"; break; + case EMUX_XASAN_FAULT_POISON: fault_name = "poison"; break; + case EMUX_XASAN_FAULT_TAIL: fault_name = "tail"; break; + } + snprintf(emux_info, sizeof(emux_info), "ASAN: %s to %s (%s)", access_name, tag_name, fault_name); + return emux_info; + } default: return "Unknown emux"; } } diff --git a/src/exception_internal.h b/src/exception_internal.h index d6ef59d2f..3dea5f227 100644 --- a/src/exception_internal.h +++ b/src/exception_internal.h @@ -59,6 +59,12 @@ void __inspector_assertion(const char *failedexpr, const char *msg, va_list args __attribute__((noreturn)) void __inspector_cppexception(const char *exctype, const char *what); +/** + * @brief Trigger the inspector for a stack smashing. + */ +__attribute__((noreturn)) +void __inspector_stack_smashing(void); + /** * @brief A page in the inspector. */ diff --git a/src/inspector.c b/src/inspector.c index 8023774e1..6a8c8dde7 100644 --- a/src/inspector.c +++ b/src/inspector.c @@ -30,7 +30,8 @@ enum Mode { MODE_EXCEPTION, MODE_ASSERTION, - MODE_CPP_EXCEPTION + MODE_CPP_EXCEPTION, + MODE_STACK_SMASHING }; enum { @@ -436,6 +437,17 @@ static void inspector_page_exception(surface_t *disp, exception_t* ex, joypad_bu bt_skip = 5; break; } + case MODE_STACK_SMASHING: { + printf("Stack Smashing\n"); + printf("\aOStack smashing detected\n"); + printf("The function return address was likely corrupted\n\n"); + + if (__kernel) { + printf("\aWThread:\n %s\n\n", kthread_current()->name); + } + bt_skip = 2; + break; + } } if (backtrace_count++ == 0) @@ -844,11 +856,13 @@ void __inspector_add_page(inspector_page_t page) { __attribute__((noreturn)) void __inspector_exception(exception_t* ex) { + emux_xasan_disable(); inspector(ex, MODE_EXCEPTION); } __attribute__((noreturn)) void __inspector_assertion(const char *failedexpr, const char *msg, va_list args) { + emux_xasan_disable(); asm volatile ( "move $a0, %0\n" "move $a1, %1\n" @@ -861,6 +875,7 @@ void __inspector_assertion(const char *failedexpr, const char *msg, va_list args __attribute__((noreturn)) void __inspector_cppexception(const char *exctype, const char *what) { + emux_xasan_disable(); asm volatile ( "move $a0, %0\n" "move $a1, %1\n" @@ -870,15 +885,25 @@ void __inspector_cppexception(const char *exctype, const char *what) { __builtin_unreachable(); } +__attribute__((noreturn)) +void __inspector_stack_smashing(void) { + emux_xasan_disable(); + asm volatile ( + "syscall 0x3\n" + ); + __builtin_unreachable(); +} + /** @brief Register the inspector as a syscall handler (global constructor run before main). */ -__attribute__((constructor)) +__attribute__((constructor(130))) void __inspector_init(void) { // Register SYSCALL 0x1 for assertion failures void handler(exception_t* ex, uint32_t code) { if (code == 1) inspector(ex, MODE_ASSERTION); if (code == 2) inspector(ex, MODE_CPP_EXCEPTION); + if (code == 3) inspector(ex, MODE_STACK_SMASHING); } - register_syscall_handler(handler, 0x00001, 0x00002); + register_syscall_handler(handler, 0x00001, 0x00003); if (emux_detect(1) & EMUX_FEAT1_EXCEPTION) { diff --git a/src/lspr3.c b/src/lspr3.c index 2142f3863..68f0ee31c 100644 --- a/src/lspr3.c +++ b/src/lspr3.c @@ -30,6 +30,7 @@ #include "utils.h" #include "yuv.h" #include "debug.h" +#include "asan.h" #include #include @@ -155,6 +156,7 @@ static void lspr3_decode_intra_slice( strm.pStart = rbsp; strm.pCurr = ((u64)(uintptr_t)rbsp) << 3; strm.pEnd = ((u64)(uintptr_t)(rbsp + rbsp_size)) << 3; + asan_unpoison(rbsp + rbsp_size, 8); // the bitstream decoder will fetch ahead beyond the end of the buffer // Reset the RSP decoder's per-frame state (notably last_packed_delta_buf) // before queuing any macroblock work for this slice. diff --git a/src/rsp.c b/src/rsp.c index ed2d57d08..5da672c6f 100644 --- a/src/rsp.c +++ b/src/rsp.c @@ -16,6 +16,7 @@ #include "rsp.h" #include "rdp.h" #include "debug.h" +#include "emux.h" #include "console.h" #include "regsinternal.h" #include "n64sys.h" @@ -220,6 +221,8 @@ void __rsp_crash(const char *file, int line, const char *func, const char *msg, // avoid being preempted for any reason. disable_interrupts(); + emux_xasan_disable(); + // Read the status registers right away. Its value can mutate at any time // so the earlier the better. uint32_t sp_status = *SP_STATUS;