You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
13 KiB
447 lines
13 KiB
/*-*- 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};
|
|
|