Commit e8392d82 by Thomas Lively

Subzero: Added ASan quarantine for recently freed objects

parent cbd3dbc6
......@@ -18,6 +18,7 @@
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <sched.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
......@@ -26,10 +27,30 @@
#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)
#define DEBUG (1)
// Assuming 48 bit address space on 64 bit systems
#define SHADOW_LENGTH_64 (1u << (48 - SHADOW_SCALE_LOG2))
......@@ -44,11 +65,14 @@
#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"};
static const char *memtype_names[] = {"stack", "heap", "global", "freed"};
#define ACCESS_LOAD (0)
#define ACCESS_STORE (1)
......@@ -79,6 +103,16 @@ 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) {
char *shadow_addr = MEM2SHADOW(ptr);
char shadow_val = *shadow_addr;
......@@ -87,7 +121,7 @@ static void __asan_error(char *ptr, int size, int access) {
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 == GLOBAL_POISON_VAL || shadow_val == FREED_POISON_VAL);
const char *memtype = memtype_names[MEMTYPE_INDEX(shadow_val)];
fprintf(stderr, "Illegal %d byte %s %s object at %p\n", size, access_name,
memtype, ptr);
......@@ -213,19 +247,51 @@ void *__asan_realloc(char *ptr, size_t size) {
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, "Double free of object at %p\n", ptr);
abort();
}
char *rz_left, *rz_right;
__asan_get_redzones(ptr, &rz_left, &rz_right);
size_t rz_right_size = *(size_t *)rz_right;
__asan_unpoison(rz_left, RZ_SIZE);
__asan_unpoison(rz_right, rz_right_size);
free(rz_left);
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));
// redzones should be no greater than RZ_SIZE + RZ_SIZE-1 for alignment
assert(size < 2 * RZ_SIZE);
DUMP("poison %d bytes at %p: %p - %p\n", size, ptr, MEM2SHADOW(ptr),
MEM2SHADOW(end));
size_t offset = SHADOW_OFFSET(ptr);
......@@ -239,7 +305,6 @@ void __asan_poison(char *ptr, int size, char poison_val) {
void __asan_unpoison(char *ptr, int size) {
char *end = ptr + size;
assert(IS_SHADOW_ALIGNED(end));
assert(size < 2 * RZ_SIZE);
DUMP("unpoison %d bytes at %p: %p - %p\n", size, ptr, MEM2SHADOW(ptr),
MEM2SHADOW(end));
*(char *)MEM2SHADOW(ptr) = 0;
......
; Test that double frees are detected
; REQUIRES: no_minimal_build
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols \
; RUN: %t.pexe -o %t && %t 2>&1 | FileCheck --check-prefix=ERR %s
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols -O2 \
; RUN: %t.pexe -o %t && %t 2>&1 | FileCheck --check-prefix=ERR %s
declare external i32 @malloc(i32)
declare external void @free(i32)
declare external void @exit(i32)
define void @_start(i32 %arg) {
%alloc = call i32 @malloc(i32 42)
call void @free(i32 %alloc)
call void @free(i32 %alloc)
call void @exit(i32 1)
ret void
}
; ERR: Double free of object at
; Test that the quarantine for recently freed objects works
; REQUIRES: no_minimal_build
; Test with an illegal load from a freed block
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols \
; RUN: %t.pexe -o %t && %t 2>&1 | FileCheck --check-prefix=LOAD %s
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols -O2 \
; RUN: %t.pexe -o %t && %t 2>&1 | FileCheck --check-prefix=LOAD %s
; Test with an illegal store to a freed block
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols \
; RUN: %t.pexe -o %t && %t 1 2>&1 | FileCheck --check-prefix=STORE %s
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols -O2 \
; RUN: %t.pexe -o %t && %t 1 2>&1 | FileCheck --check-prefix=STORE %s
; Test that freed objects eventually get out of quarantine and are unpoisoned
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols \
; RUN: %t.pexe -o %t && %t 1 2 2>&1 | FileCheck --check-prefix=NONE %s \
; RUN: --allow-empty
; RUN: llvm-as %s -o - | pnacl-freeze > %t.pexe && %S/../../pydir/szbuild.py \
; RUN: --fsanitize-address --sz=-allow-externally-defined-symbols -O2 \
; RUN: %t.pexe -o %t && %t 1 2 2>&1 | FileCheck --check-prefix=NONE %s \
; RUN: --allow-empty
declare external i32 @malloc(i32)
declare external void @free(i32)
declare external void @exit(i32)
; make three 100MB allocations
define void @_start(i32 %arg) {
%argcaddr = add i32 %arg, 8
%argcptr = inttoptr i32 %argcaddr to i32*
%argc = load i32, i32* %argcptr, align 1
%alloc1addr = call i32 @malloc(i32 104857600)
%alloc2addr = call i32 @malloc(i32 104857600)
%alloc3addr = call i32 @malloc(i32 104857600)
%alloc1 = inttoptr i32 %alloc1addr to i32*
%alloc2 = inttoptr i32 %alloc2addr to i32*
%alloc3 = inttoptr i32 %alloc3addr to i32*
call void @free(i32 %alloc1addr)
call void @free(i32 %alloc2addr)
call void @free(i32 %alloc3addr)
switch i32 %argc, label %error [i32 1, label %bad_load
i32 2, label %bad_store
i32 3, label %no_err]
bad_load:
%result_load = load i32, i32* %alloc2, align 1
br label %error
bad_store:
store i32 42, i32* %alloc3, align 1
br label %error
no_err:
%result_no_err = load i32, i32* %alloc1, align 1
call void @exit(i32 0)
unreachable
error:
call void @exit(i32 1)
unreachable
}
; LOAD: Illegal 4 byte load from freed object at
; STORE: Illegal 4 byte store to freed object at
; NONE-NOT: Illegal
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment