/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ │ This program is free software; you can redistribute it and/or modify │ │ it under the terms of the GNU General Public License as published by │ │ the Free Software Foundation; version 2 of the License. │ │ │ │ This program is distributed in the hope that it will be useful, but │ │ WITHOUT ANY WARRANTY; without even the implied warranty of │ │ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ │ General Public License for more details. │ │ │ │ You should have received a copy of the GNU General Public License │ │ along with this program; if not, write to the Free Software │ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/conv/itoa.h" #include "libc/log/asan.h" #include "libc/log/backtrace.h" #include "libc/log/log.h" #include "libc/mem/hook/hook.h" #include "libc/runtime/directmap.h" #include "libc/runtime/memtrack.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" #include "third_party/dlmalloc/dlmalloc.h" STATIC_YOINK("_init_asan"); /** * @fileoverview Cosmopolitan Address Sanitizer Runtime. * * Someone brilliant at Google figured out a way to improve upon memory * protection. Rather than invent another Java or Rust they changed GCC * so it can emit fast code, that checks the validity of each memory op * with byte granularity, by probing shadow memory. * * AddressSanitizer dedicates one-eighth of the virtual address space * to its shadow memory and uses a direct mapping with a scale and * offset to translate an application address to its corresponding * shadow address. Given the application memory address Addr, the * address of the shadow byte is computed as (Addr>>3)+Offset." * * We use the following encoding for each shadow byte: 0 means that * all 8 bytes of the corresponding application memory region are * addressable; k (1 ≤ k ≤ 7) means that the first k bytes are * addressible; any negative value indicates that the entire 8-byte * word is unaddressable. We use different negative values to * distinguish between different kinds of unaddressable memory (heap * redzones, stack redzones, global redzones, freed memory). * * Here's what the generated code looks like for 64-bit reads: * * movq %addr,%tmp * shrq $3,%tmp * cmpb $0,0x7fff8000(%tmp) * jnz abort * movq (%addr),%dst */ #define HOOK(HOOK, IMPL) \ if (weaken(HOOK)) { \ *weaken(HOOK) = IMPL; \ } struct AsanSourceLocation { const char *filename; int line; int column; }; struct AsanAccessInfo { const char *addr; const char *first_bad_addr; size_t size; bool iswrite; unsigned long ip; }; struct AsanGlobal { const char *addr; size_t size; size_t size_with_redzone; const void *name; const void *module_name; unsigned long has_cxx_init; struct AsanSourceLocation *location; char *odr_indicator; }; struct AsanMorgue { unsigned i; void *p[16]; }; static struct AsanMorgue __asan_morgue; static const char *__asan_dscribe_free_poison(int c) { switch (c) { case kAsanHeapFree: return "heap double free"; case kAsanRelocated: return "free after relocate"; case kAsanStackFree: return "stack double free"; default: return "invalid pointer"; } } static const char *__asan_describe_access_poison(int c) { switch (c) { case kAsanHeapFree: return "heap use after free"; case kAsanStackFree: return "stack use after release"; case kAsanRelocated: return "heap use after relocate"; case kAsanHeapUnderrun: return "heap underrun"; case kAsanHeapOverrun: return "heap overrun"; case kAsanGlobalOverrun: return "global overrun"; case kAsanGlobalUnregistered: return "global unregistered"; case kAsanStackUnderrun: return "stack underflow"; case kAsanStackOverrun: return "stack overflow"; case kAsanAllocaOverrun: return "alloca overflow"; case kAsanUnscoped: return "unscoped"; default: DebugBreak(); return "poisoned"; } } static noreturn void __asan_die(const char *msg, size_t size) { write(STDERR_FILENO, msg, size); die(); } static char *__asan_report_start(char *p) { bool ansi; const char *term; term = getenv("TERM"); ansi = !term || strcmp(term, "dumb") != 0; if (ansi) p = stpcpy(p, "\r\e[J\e[1;91m"); p = stpcpy(p, "asan error"); if (ansi) p = stpcpy(p, "\e[0m"); return stpcpy(p, ": "); } static noreturn void __asan_report_deallocate_fault(void *addr, int c) { char *p, ibuf[21], buf[256]; p = __asan_report_start(buf); p = stpcpy(p, __asan_dscribe_free_poison(c)); p = stpcpy(p, " "); p = mempcpy(p, ibuf, int64toarray_radix10(c, ibuf)); p = stpcpy(p, " at 0x"); p = mempcpy(p, ibuf, uint64toarray_fixed16((intptr_t)addr, ibuf, 48)); p = stpcpy(p, "\r\n"); __asan_die(buf, p - buf); } static noreturn void __asan_report_memory_fault(uint8_t *addr, int size, const char *kind) { char *p, ibuf[21], buf[256]; p = __asan_report_start(buf); p = stpcpy(p, __asan_describe_access_poison(*(char *)SHADOW((intptr_t)addr))); p = stpcpy(p, " "); p = mempcpy(p, ibuf, uint64toarray_radix10(size, ibuf)); p = stpcpy(p, "-byte "); p = stpcpy(p, kind); p = stpcpy(p, " at 0x"); p = mempcpy(p, ibuf, uint64toarray_fixed16((intptr_t)addr, ibuf, 48)); p = stpcpy(p, "\r\n"); __asan_die(buf, p - buf); } static const void *__asan_morgue_add(void *p) { void *r; r = __asan_morgue.p[__asan_morgue.i]; __asan_morgue.p[__asan_morgue.i] = p; __asan_morgue.i += 1; __asan_morgue.i &= ARRAYLEN(__asan_morgue.p) - 1; return r; } static void __asan_morgue_flush(void) { void *p; unsigned i; for (i = 0; i < ARRAYLEN(__asan_morgue.p); ++i) { p = __asan_morgue.p[i]; __asan_morgue.p[i] = NULL; dlfree(p); } } static void *__asan_allocate(size_t align, size_t size, int underrun, int overrun) { char *p, *s; size_t q, r, i; if (!(p = dlmemalign(align, ROUNDUP(size, 8) + 16))) return NULL; s = (char *)SHADOW((intptr_t)p - 16); q = size / 8; r = size % 8; *s++ = underrun; *s++ = underrun; memset(s, 0, q); s += q; if (r) *s++ = r; *s++ = overrun; *s++ = overrun; return p; } static void __asan_deallocate(char *p, int kind) { char *s; s = (char *)SHADOW((intptr_t)p); if ((*s < 0 && *s != kAsanHeapOverrun) || *s >= 8) { __asan_report_deallocate_fault(p, *s); } memset(s, kind, dlmalloc_usable_size(p) >> 3); dlfree(__asan_morgue_add(p)); } static void __asan_poison_redzone(intptr_t addr, size_t size, size_t redsize, int kind) { char *s; intptr_t p; size_t a, b, w; w = (intptr_t)addr & 7; p = (intptr_t)addr - w; a = w + size; b = w + redsize; s = (char *)SHADOW(p + a); if (a & 7) *s++ = a & 7; memset(s, kind, (b - ROUNDUP(a, 8)) >> 3); } static size_t __asan_malloc_usable_size(const void *vp) { char *s; size_t n; for (n = 0, s = (char *)SHADOW((intptr_t)vp);; ++s) { if (!*s) { n += 8; } else if (*s > 0) { n += *s & 7; } else { break; } } return n; } static void __asan_free(void *p) { if (!p) return; __asan_deallocate(p, kAsanHeapFree); } static void *__asan_memalign(size_t align, size_t size) { return __asan_allocate(align, size, kAsanHeapUnderrun, kAsanHeapOverrun); } static void *__asan_malloc(size_t size) { return __asan_memalign(16, size); } static void *__asan_calloc(size_t n, size_t m) { char *p; size_t size; if (__builtin_mul_overflow(n, m, &size)) size = -1; if ((p = __asan_malloc(size))) memset(p, 0, size); return p; } static void *__asan_realloc(void *p, size_t n) { char *p2; if (p) { if (n) { if ((p2 = __asan_malloc(n))) { memcpy(p2, p, min(n, dlmalloc_usable_size(p))); __asan_deallocate(p, kAsanRelocated); } } else { __asan_free(p); p2 = NULL; } } else { p2 = __asan_malloc(n); } return p2; } static void *__asan_valloc(size_t n) { return __asan_memalign(PAGESIZE, n); } static void *__asan_pvalloc(size_t n) { return __asan_valloc(ROUNDUP(n, PAGESIZE)); } void __asan_register_globals(struct AsanGlobal g[], int n) { unsigned i; for (i = 0; i < n; ++i) { __asan_poison_redzone((intptr_t)g[i].addr, g[i].size, g[i].size_with_redzone, kAsanGlobalOverrun); } } void __asan_unregister_globals(struct AsanGlobal g[], int n) { unsigned i; intptr_t a, b; for (i = 0; i < n; ++i) { a = ROUNDUP((intptr_t)g[i].addr, 8); b = ROUNDDOWN((intptr_t)g[i].addr + g[i].size_with_redzone, 8); if (b > a) { memset((char *)SHADOW(a), kAsanGlobalUnregistered, (b - a) >> 3); } } } void *__asan_stack_malloc(size_t size, int classid) { return __asan_allocate(32, size, kAsanStackUnderrun, kAsanStackOverrun); } void __asan_stack_free(char *p, size_t size, int classid) { dlfree(p); } void __asan_report_load_n(uint8_t *addr, int size) { __asan_report_memory_fault(addr, size, "load"); } void __asan_report_store_n(uint8_t *addr, int size) { __asan_report_memory_fault(addr, size, "store"); } void __asan_poison_stack_memory(uintptr_t p, size_t n) { memset((char *)SHADOW(p), kAsanUnscoped, n >> 3); if (n & 7) *(char *)SHADOW(p + n) = 8 - (n & 7); } void __asan_unpoison_stack_memory(uintptr_t p, size_t n) { memset((char *)SHADOW(p), 0, n >> 3); if (n & 7) *(char *)SHADOW(p + n) = n & 7; } void __asan_alloca_poison(intptr_t addr, size_t size) { __asan_poison_redzone(addr, size, size + 32, kAsanAllocaOverrun); } void __asan_allocas_unpoison(uintptr_t top, uintptr_t bottom) { memset((char *)SHADOW(top), 0, (bottom - top) >> 3); } void *__asan_addr_is_in_fake_stack(void *fakestack, void *addr, void **beg, void **end) { return NULL; } void *__asan_get_current_fake_stack(void) { return NULL; } void __asan_install_malloc_hooks(void) { HOOK(hook$free, __asan_free); HOOK(hook$malloc, __asan_malloc); HOOK(hook$calloc, __asan_calloc); HOOK(hook$valloc, __asan_valloc); HOOK(hook$pvalloc, __asan_pvalloc); HOOK(hook$realloc, __asan_realloc); HOOK(hook$memalign, __asan_memalign); HOOK(hook$malloc_usable_size, __asan_malloc_usable_size); } static bool __asan_is_mapped(int x) { int i = FindMemoryInterval(&_mmi, x); return i < _mmi.i && x >= _mmi.p[i].x && x <= _mmi.p[i].y; } void __asan_map_shadow(void *p, size_t n) { int i, x, a, b; struct DirectMap sm; a = SHADOW((uintptr_t)p) >> 16; b = ROUNDUP(SHADOW(ROUNDUP((uintptr_t)p + n, 8)), 1 << 16) >> 16; for (; a < b; ++a) { if (!__asan_is_mapped(a)) { sm = DirectMap((void *)((uintptr_t)a << 16), 1 << 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (sm.addr == MAP_FAILED || TrackMemoryInterval(&_mmi, a, a, sm.maphandle, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED) == -1) { abort(); } } } } static char *__asan_get_stack_base(void) { register uintptr_t rsp asm("rsp"); return (char *)ROUNDDOWN(ROUNDDOWN(rsp, STACKSIZE), FRAMESIZE); } static textstartup size_t __asan_get_auxv_size(intptr_t *auxv) { unsigned i; for (i = 0;; i += 2) { if (!auxv[i]) break; } return (i + 2) * sizeof(intptr_t); } static textstartup void __asan_shadow_string_list(char **list) { for (; *list; ++list) { __asan_map_shadow(*list, strlen(*list) + 1); } } textstartup void __asan_init(int argc, char **argv, char **envp, intptr_t *auxv) { static bool once; if (once) return; __asan_map_shadow(_base, _end - _base); __asan_map_shadow(__asan_get_stack_base(), STACKSIZE); __asan_shadow_string_list(argv); __asan_shadow_string_list(envp); __asan_map_shadow(auxv, __asan_get_auxv_size(auxv)); __asan_install_malloc_hooks(); } static textstartup void __asan_ctor(void) { __cxa_atexit(__asan_morgue_flush, NULL, NULL); } const void *const g_asan_ctor[] initarray = {__asan_ctor};