448 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
/*-*- 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};
 |