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.
318 lines
10 KiB
318 lines
10 KiB
//===- subzero/runtime/szrt_asan.c - AddressSanitizer Runtime -----*- C -*-===//
|
|
//
|
|
// The Subzero Code Generator
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// \brief Provides the AddressSanitizer runtime.
|
|
///
|
|
/// Exposes functions for initializing the shadow memory region and managing it
|
|
/// on loads, stores, and allocations.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <sched.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
|
|
#if _POSIX_THREADS
|
|
|
|
#include <pthread.h>
|
|
typedef pthread_mutex_t mutex_t;
|
|
#define MUTEX_INITIALIZER (PTHREAD_MUTEX_INITIALIZER)
|
|
#define MUTEX_LOCK(mutex) (pthread_mutex_lock(&(mutex)))
|
|
#define MUTEX_UNLOCK(mutex) (pthread_mutex_unlock(&(mutex)))
|
|
|
|
#else // !_POSIX_THREADS
|
|
|
|
typedef uint32_t mutex_t;
|
|
#define MUTEX_INITIALIZER (0)
|
|
#define MUTEX_LOCK(mutex) \
|
|
while (__sync_swap((mutex), 1) != 0) { \
|
|
sched_yield(); \
|
|
}
|
|
#define MUTEX_UNLOCK(mutex) (__sync_swap((mutex), 0))
|
|
|
|
#endif // _POSIX_THREADS
|
|
|
|
#define RZ_SIZE (32)
|
|
#define SHADOW_SCALE_LOG2 (3)
|
|
#define SHADOW_SCALE ((size_t)1 << SHADOW_SCALE_LOG2)
|
|
#define DEBUG (0)
|
|
|
|
// Assuming 48 bit address space on 64 bit systems
|
|
#define SHADOW_LENGTH_64 (1u << (48 - SHADOW_SCALE_LOG2))
|
|
#define SHADOW_LENGTH_32 (1u << (32 - SHADOW_SCALE_LOG2))
|
|
#define WORD_SIZE (sizeof(uint32_t))
|
|
#define IS_32_BIT (sizeof(void *) == WORD_SIZE)
|
|
|
|
#define SHADOW_OFFSET(p) ((uintptr_t)(p) % SHADOW_SCALE)
|
|
#define IS_SHADOW_ALIGNED(p) (SHADOW_OFFSET(p) == 0)
|
|
|
|
#define MEM2SHADOW(p) (((uintptr_t)(p) >> SHADOW_SCALE_LOG2) + shadow_offset)
|
|
#define SHADOW2MEM(p) \
|
|
((uintptr_t)((char *)(p)-shadow_offset) << SHADOW_SCALE_LOG2)
|
|
|
|
#define QUARANTINE_MAX_SIZE ((size_t)1 << 28) // 256 MB
|
|
|
|
#define STACK_POISON_VAL ((char)-1)
|
|
#define HEAP_POISON_VAL ((char)-2)
|
|
#define GLOBAL_POISON_VAL ((char)-3)
|
|
#define FREED_POISON_VAL ((char)-4)
|
|
#define MEMTYPE_INDEX(x) (-1 - (x))
|
|
static const char *memtype_names[] = {"stack", "heap", "global", "freed"};
|
|
|
|
#define ACCESS_LOAD (0)
|
|
#define ACCESS_STORE (1)
|
|
static const char *access_names[] = {"load from", "store to"};
|
|
|
|
#if DEBUG
|
|
#define DUMP(args...) \
|
|
do { \
|
|
printf(args); \
|
|
} while (false);
|
|
#else // !DEBUG
|
|
#define DUMP(args...)
|
|
#endif // DEBUG
|
|
|
|
static char *shadow_offset = NULL;
|
|
|
|
static bool __asan_check(char *, int);
|
|
static void __asan_error(char *, int, int, void *);
|
|
static void __asan_get_redzones(char *, char **, char **);
|
|
|
|
void __asan_init(int, void **, int *);
|
|
void __asan_check_load(char *, int);
|
|
void __asan_check_store(char *, int);
|
|
void *__asan_malloc(size_t);
|
|
void *__asan_calloc(size_t, size_t);
|
|
void *__asan_realloc(char *, size_t);
|
|
void __asan_free(char *);
|
|
void __asan_poison(char *, int, char);
|
|
void __asan_unpoison(char *, int);
|
|
|
|
struct quarantine_entry {
|
|
struct quarantine_entry *next;
|
|
size_t size;
|
|
};
|
|
|
|
mutex_t quarantine_lock = MUTEX_INITIALIZER;
|
|
uint64_t quarantine_size = 0;
|
|
struct quarantine_entry *quarantine_head = NULL;
|
|
struct quarantine_entry *quarantine_tail = NULL;
|
|
|
|
static void __asan_error(char *ptr, int size, int access, void *ret_addr) {
|
|
char *shadow_addr = MEM2SHADOW(ptr);
|
|
char shadow_val = *shadow_addr;
|
|
if (shadow_val > 0)
|
|
shadow_val = *(shadow_addr + 1);
|
|
assert(access == ACCESS_LOAD || access == ACCESS_STORE);
|
|
const char *access_name = access_names[access];
|
|
assert(shadow_val == STACK_POISON_VAL || shadow_val == HEAP_POISON_VAL ||
|
|
shadow_val == GLOBAL_POISON_VAL || shadow_val == FREED_POISON_VAL);
|
|
const char *memtype = memtype_names[MEMTYPE_INDEX(shadow_val)];
|
|
fprintf(stderr, "%p: Illegal %d byte %s %s object at %p\n", ret_addr, size,
|
|
access_name, memtype, ptr);
|
|
fprintf(stderr, "(address of __asan_error symbol is %p)\n", __asan_error);
|
|
abort();
|
|
}
|
|
|
|
// check only the first byte of each word unless strict
|
|
static bool __asan_check(char *ptr, int size) {
|
|
assert(size == 1 || size == 2 || size == 4 || size == 8);
|
|
char *shadow_addr = (char *)MEM2SHADOW(ptr);
|
|
char shadow_val = *shadow_addr;
|
|
DUMP("check %d bytes at %p: %p + %d (%d)\n", size, ptr, shadow_addr,
|
|
(uintptr_t)ptr % SHADOW_SCALE, shadow_val);
|
|
if (size == SHADOW_SCALE) {
|
|
return shadow_val == 0;
|
|
}
|
|
return shadow_val == 0 || (char)SHADOW_OFFSET(ptr) + size <= shadow_val;
|
|
}
|
|
|
|
static void __asan_get_redzones(char *ptr, char **left, char **right) {
|
|
char *rz_left = ptr - RZ_SIZE;
|
|
char *rz_right = *(char **)rz_left;
|
|
if (left != NULL)
|
|
*left = rz_left;
|
|
if (right != NULL)
|
|
*right = rz_right;
|
|
}
|
|
|
|
void __asan_check_load(char *ptr, int size) {
|
|
// aligned single word accesses may be widened single byte accesses, but for
|
|
// all else use strict check
|
|
int check_size =
|
|
(size == WORD_SIZE && (uintptr_t)ptr % WORD_SIZE == 0) ? 1 : size;
|
|
if (!__asan_check(ptr, check_size))
|
|
__asan_error(ptr, size, ACCESS_LOAD, __builtin_return_address(0));
|
|
}
|
|
|
|
void __asan_check_store(char *ptr, int size) {
|
|
// stores may never be partially out of bounds so use strict check
|
|
if (!__asan_check(ptr, size))
|
|
__asan_error(ptr, size, ACCESS_STORE, __builtin_return_address(0));
|
|
}
|
|
|
|
void __asan_init(int n_rzs, void **rzs, int *rz_sizes) {
|
|
// ensure the redzones are large enough to hold metadata
|
|
assert(RZ_SIZE >= sizeof(void *) && RZ_SIZE >= sizeof(size_t));
|
|
assert(shadow_offset == NULL);
|
|
size_t length = (IS_32_BIT) ? SHADOW_LENGTH_32 : SHADOW_LENGTH_64;
|
|
int prot = PROT_READ | PROT_WRITE;
|
|
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
|
int fd = -1;
|
|
off_t offset = 0;
|
|
shadow_offset = mmap((void *)length, length, prot, flags, fd, offset);
|
|
if (shadow_offset == NULL)
|
|
fprintf(stderr, "unable to allocate shadow memory\n");
|
|
else
|
|
DUMP("set up shadow memory at %p\n", shadow_offset);
|
|
if (mprotect(MEM2SHADOW(shadow_offset), length >> SHADOW_SCALE_LOG2,
|
|
PROT_NONE))
|
|
fprintf(stderr, "could not protect bad region\n");
|
|
else
|
|
DUMP("protected bad region\n");
|
|
|
|
// poison global redzones
|
|
DUMP("poisioning %d global redzones\n", n_rzs);
|
|
for (int i = 0; i < n_rzs; i++) {
|
|
DUMP("(%d) poisoning redzone of size %d at %p\n", i, rz_sizes[i], rzs[i]);
|
|
__asan_poison(rzs[i], rz_sizes[i], GLOBAL_POISON_VAL);
|
|
}
|
|
}
|
|
|
|
void *__asan_malloc(size_t size) {
|
|
DUMP("malloc() called with size %d\n", size);
|
|
size_t padding =
|
|
(IS_SHADOW_ALIGNED(size)) ? 0 : SHADOW_SCALE - SHADOW_OFFSET(size);
|
|
size_t rz_left_size = RZ_SIZE;
|
|
size_t rz_right_size = RZ_SIZE + padding;
|
|
void *rz_left;
|
|
int err = posix_memalign(&rz_left, SHADOW_SCALE,
|
|
rz_left_size + size + rz_right_size);
|
|
if (err != 0) {
|
|
assert(err == ENOMEM);
|
|
return NULL;
|
|
}
|
|
void *ret = rz_left + rz_left_size;
|
|
void *rz_right = ret + size;
|
|
__asan_poison(rz_left, rz_left_size, HEAP_POISON_VAL);
|
|
__asan_poison(rz_right, rz_right_size, HEAP_POISON_VAL);
|
|
// record size and location data so we can find it again
|
|
*(void **)rz_left = rz_right;
|
|
*(size_t *)rz_right = rz_right_size;
|
|
assert((uintptr_t)ret % 8 == 0);
|
|
return ret;
|
|
}
|
|
|
|
void *__asan_calloc(size_t nmemb, size_t size) {
|
|
size_t alloc_size = nmemb * size;
|
|
void *ret = __asan_malloc(alloc_size);
|
|
memset(ret, 0, alloc_size);
|
|
return ret;
|
|
}
|
|
|
|
void *__asan_realloc(char *ptr, size_t size) {
|
|
if (ptr == NULL)
|
|
return __asan_malloc(size);
|
|
if (size == 0) {
|
|
__asan_free(ptr);
|
|
return NULL;
|
|
}
|
|
char *rz_right;
|
|
__asan_get_redzones(ptr, NULL, &rz_right);
|
|
size_t old_size = rz_right - ptr;
|
|
if (size == old_size)
|
|
return ptr;
|
|
char *new_alloc = __asan_malloc(size);
|
|
if (new_alloc == NULL)
|
|
return NULL;
|
|
size_t copyable = (size < old_size) ? size : old_size;
|
|
memcpy(new_alloc, ptr, copyable);
|
|
__asan_free(ptr);
|
|
return new_alloc;
|
|
}
|
|
|
|
void __asan_free(char *ptr) {
|
|
DUMP("free() called on %p\n", ptr);
|
|
if (ptr == NULL)
|
|
return;
|
|
if (*(char *)MEM2SHADOW(ptr) == FREED_POISON_VAL) {
|
|
fprintf(stderr, "%p: Double free of object at %p\n",
|
|
__builtin_return_address(0), ptr);
|
|
fprintf(stderr, "(address of __asan_error symbol is %p)\n", __asan_error);
|
|
abort();
|
|
}
|
|
char *rz_left, *rz_right;
|
|
__asan_get_redzones(ptr, &rz_left, &rz_right);
|
|
size_t rz_right_size = *(size_t *)rz_right;
|
|
size_t total_size = rz_right_size + (rz_right - rz_left);
|
|
__asan_poison(rz_left, total_size, FREED_POISON_VAL);
|
|
|
|
// place allocation in quarantine
|
|
struct quarantine_entry *entry = (struct quarantine_entry *)rz_left;
|
|
assert(entry != NULL);
|
|
entry->next = NULL;
|
|
entry->size = total_size;
|
|
|
|
DUMP("Placing %d bytes at %p in quarantine\n", entry->size, entry);
|
|
MUTEX_LOCK(&quarantine_lock);
|
|
if (quarantine_tail != NULL)
|
|
quarantine_tail->next = entry;
|
|
quarantine_tail = entry;
|
|
if (quarantine_head == NULL)
|
|
quarantine_head = entry;
|
|
quarantine_size += total_size;
|
|
DUMP("Quarantine size is %llu\n", quarantine_size);
|
|
|
|
// free old objects as necessary
|
|
while (quarantine_size > QUARANTINE_MAX_SIZE) {
|
|
struct quarantine_entry *freed = quarantine_head;
|
|
assert(freed != NULL);
|
|
__asan_unpoison((char *)freed, freed->size);
|
|
quarantine_size -= freed->size;
|
|
quarantine_head = freed->next;
|
|
DUMP("Releasing %d bytes at %p from quarantine\n", freed->size, freed);
|
|
DUMP("Quarantine size is %llu\n", quarantine_size);
|
|
free(freed);
|
|
}
|
|
MUTEX_UNLOCK(&quarantine_lock);
|
|
}
|
|
|
|
void __asan_poison(char *ptr, int size, char poison_val) {
|
|
char *end = ptr + size;
|
|
assert(IS_SHADOW_ALIGNED(end));
|
|
DUMP("poison %d bytes at %p: %p - %p\n", size, ptr, MEM2SHADOW(ptr),
|
|
MEM2SHADOW(end));
|
|
size_t offset = SHADOW_OFFSET(ptr);
|
|
*(char *)MEM2SHADOW(ptr) = (offset == 0) ? poison_val : offset;
|
|
ptr += SHADOW_OFFSET(size);
|
|
assert(IS_SHADOW_ALIGNED(ptr));
|
|
int len = (end - ptr) >> SHADOW_SCALE_LOG2;
|
|
memset(MEM2SHADOW(ptr), poison_val, len);
|
|
}
|
|
|
|
void __asan_unpoison(char *ptr, int size) {
|
|
char *end = ptr + size;
|
|
assert(IS_SHADOW_ALIGNED(end));
|
|
DUMP("unpoison %d bytes at %p: %p - %p\n", size, ptr, MEM2SHADOW(ptr),
|
|
MEM2SHADOW(end));
|
|
*(char *)MEM2SHADOW(ptr) = 0;
|
|
ptr += SHADOW_OFFSET(size);
|
|
assert(IS_SHADOW_ALIGNED(ptr));
|
|
memset(MEM2SHADOW(ptr), 0, (end - ptr) >> SHADOW_SCALE_LOG2);
|
|
}
|