[asan] Implement delayed activation of AddressSanitizer

This change adds ASAN_OPTIONS=start_deactivated=1 flag. When present, ASan will
start in "deactivated" mode, with no heap poisoning, no quarantine, no stack
trace gathering, and minimal redzones. All this features come back when
__asan_init is called for the constructor of an instrumented library.

The primary use case for this feature is Android. Code itself is not
Android-specific, and this patch includes a Linux test for it.

llvm-svn: 199377
This commit is contained in:
Evgeniy Stepanov 2014-01-16 12:31:50 +00:00
parent 3657cb0350
commit c61623b170
11 changed files with 258 additions and 64 deletions

View File

@ -2,6 +2,7 @@
set(ASAN_SOURCES
asan_allocator2.cc
asan_activation.cc
asan_fake_stack.cc
asan_globals.cc
asan_interceptors.cc

View File

@ -0,0 +1,69 @@
//===-- asan_activation.cc --------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of AddressSanitizer, an address sanity checker.
//
// ASan activation/deactivation logic.
//===----------------------------------------------------------------------===//
#include "asan_activation.h"
#include "asan_flags.h"
#include "asan_internal.h"
#include "sanitizer_common/sanitizer_flags.h"
namespace __asan {
static struct AsanDeactivatedFlags {
int quarantine_size;
int max_redzone;
int poison_heap;
int malloc_context_size;
} asan_deactivated_flags;
static bool asan_is_deactivated;
void AsanStartDeactivated() {
VReport(1, "Deactivating asan\n");
// Save flag values.
asan_deactivated_flags.quarantine_size = flags()->quarantine_size;
asan_deactivated_flags.max_redzone = flags()->max_redzone;
asan_deactivated_flags.poison_heap = flags()->poison_heap;
asan_deactivated_flags.malloc_context_size =
common_flags()->malloc_context_size;
flags()->quarantine_size = 0;
flags()->max_redzone = 16;
flags()->poison_heap = false;
common_flags()->malloc_context_size = 0;
asan_is_deactivated = true;
}
void AsanActivate() {
if (!asan_is_deactivated) return;
VReport(1, "Activating asan\n");
// Restore flag values.
// FIXME: this is not atomic, and there may be other threads alive.
flags()->quarantine_size = asan_deactivated_flags.quarantine_size;
flags()->max_redzone = asan_deactivated_flags.max_redzone;
flags()->poison_heap = asan_deactivated_flags.poison_heap;
common_flags()->malloc_context_size =
asan_deactivated_flags.malloc_context_size;
asan_is_deactivated = false;
VReport(
1,
"quarantine_size %d, max_redzone %d, poison_heap %d, malloc_context_size "
"%d\n",
flags()->quarantine_size, flags()->max_redzone, flags()->poison_heap,
common_flags()->malloc_context_size);
}
} // namespace __asan

View File

@ -0,0 +1,23 @@
//===-- asan_activation.h ---------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of AddressSanitizer, an address sanity checker.
//
// ASan activation/deactivation logic.
//===----------------------------------------------------------------------===//
#ifndef ASAN_ACTIVATION_H
#define ASAN_ACTIVATION_H
namespace __asan {
void AsanStartDeactivated();
void AsanActivate();
} // namespace __asan
#endif // ASAN_ACTIVATION_H

View File

@ -312,7 +312,7 @@ void InitializeAllocator() {
static void *Allocate(uptr size, uptr alignment, StackTrace *stack,
AllocType alloc_type, bool can_fill) {
if (!asan_inited)
__asan_init();
AsanInitFromRtl();
Flags &fl = *flags();
CHECK(stack);
const uptr min_alignment = SHADOW_GRANULARITY;
@ -357,6 +357,16 @@ static void *Allocate(uptr size, uptr alignment, StackTrace *stack,
AllocatorCache *cache = &fallback_allocator_cache;
allocated = allocator.Allocate(cache, needed_size, 8, false);
}
if (*(u8 *)MEM_TO_SHADOW((u64)allocated) == 0 && flags()->poison_heap) {
// Heap poisoning is enabled, but the allocator provides an unpoisoned
// chunk. This is possible if flags()->poison_heap was disabled for some
// time, for example, due to flags()->start_disabled.
// Anyway, poison the block before using it for anything else.
uptr allocated_size = allocator.GetActuallyAllocatedSize(allocated);
PoisonShadow((uptr)allocated, allocated_size, kAsanHeapLeftRedzoneMagic);
}
uptr alloc_beg = reinterpret_cast<uptr>(allocated);
uptr alloc_end = alloc_beg + needed_size;
uptr beg_plus_redzone = alloc_beg + rz_size;

View File

@ -115,6 +115,11 @@ struct Flags {
// If true, assume that dynamic initializers can never access globals from
// other modules, even if the latter are already initialized.
bool strict_init_order;
// If true, ASan tweaks a bunch of other flags (quarantine, redzone, heap
// poisoning) to reduce memory consumption as much as possible, and restores
// them to original values when the first instrumented module is loaded into
// the process. This is mainly intended to be used on Android.
bool start_deactivated;
};
extern Flags asan_flags_dont_use_directly;

View File

@ -73,7 +73,7 @@ static inline bool RangesOverlap(const char *offset1, uptr length1,
#define ENSURE_ASAN_INITED() do { \
CHECK(!asan_init_is_running); \
if (!asan_inited) { \
__asan_init(); \
AsanInitFromRtl(); \
} \
} while (0)

View File

@ -67,6 +67,8 @@ namespace __asan {
class AsanThread;
using __sanitizer::StackTrace;
void AsanInitFromRtl();
// asan_rtl.cc
void NORETURN ShowStatsAndAbort();

View File

@ -41,7 +41,7 @@ static malloc_zone_t asan_zone;
INTERCEPTOR(malloc_zone_t *, malloc_create_zone,
vm_size_t start_size, unsigned zone_flags) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
GET_STACK_TRACE_MALLOC;
uptr page_size = GetPageSizeCached();
uptr allocated_size = RoundUpTo(sizeof(asan_zone), page_size);
@ -60,34 +60,34 @@ INTERCEPTOR(malloc_zone_t *, malloc_create_zone,
}
INTERCEPTOR(malloc_zone_t *, malloc_default_zone, void) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
return &asan_zone;
}
INTERCEPTOR(malloc_zone_t *, malloc_default_purgeable_zone, void) {
// FIXME: ASan should support purgeable allocations.
// https://code.google.com/p/address-sanitizer/issues/detail?id=139
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
return &asan_zone;
}
INTERCEPTOR(void, malloc_make_purgeable, void *ptr) {
// FIXME: ASan should support purgeable allocations. Ignoring them is fine
// for now.
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
}
INTERCEPTOR(int, malloc_make_nonpurgeable, void *ptr) {
// FIXME: ASan should support purgeable allocations. Ignoring them is fine
// for now.
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
// Must return 0 if the contents were not purged since the last call to
// malloc_make_purgeable().
return 0;
}
INTERCEPTOR(void, malloc_set_zone_name, malloc_zone_t *zone, const char *name) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
// Allocate |strlen("asan-") + 1 + internal_strlen(name)| bytes.
size_t buflen = 6 + (name ? internal_strlen(name) : 0);
InternalScopedBuffer<char> new_name(buflen);
@ -102,44 +102,44 @@ INTERCEPTOR(void, malloc_set_zone_name, malloc_zone_t *zone, const char *name) {
}
INTERCEPTOR(void *, malloc, size_t size) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
GET_STACK_TRACE_MALLOC;
void *res = asan_malloc(size, &stack);
return res;
}
INTERCEPTOR(void, free, void *ptr) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
if (!ptr) return;
GET_STACK_TRACE_FREE;
asan_free(ptr, &stack, FROM_MALLOC);
}
INTERCEPTOR(void *, realloc, void *ptr, size_t size) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
GET_STACK_TRACE_MALLOC;
return asan_realloc(ptr, size, &stack);
}
INTERCEPTOR(void *, calloc, size_t nmemb, size_t size) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
GET_STACK_TRACE_MALLOC;
return asan_calloc(nmemb, size, &stack);
}
INTERCEPTOR(void *, valloc, size_t size) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
GET_STACK_TRACE_MALLOC;
return asan_memalign(GetPageSizeCached(), size, &stack, FROM_MALLOC);
}
INTERCEPTOR(size_t, malloc_good_size, size_t size) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
return asan_zone.introspect->good_size(&asan_zone, size);
}
INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) {
if (!asan_inited) __asan_init();
if (!asan_inited) AsanInitFromRtl();
CHECK(memptr);
GET_STACK_TRACE_MALLOC;
void *result = asan_memalign(alignment, size, &stack, FROM_MALLOC);

View File

@ -11,6 +11,7 @@
//
// Main file of the ASan run-time library.
//===----------------------------------------------------------------------===//
#include "asan_activation.h"
#include "asan_allocator.h"
#include "asan_interceptors.h"
#include "asan_interface_internal.h"
@ -138,6 +139,7 @@ static void ParseFlagsFromString(Flags *f, const char *str) {
ParseFlag(str, &f->alloc_dealloc_mismatch, "alloc_dealloc_mismatch");
ParseFlag(str, &f->strict_memcmp, "strict_memcmp");
ParseFlag(str, &f->strict_init_order, "strict_init_order");
ParseFlag(str, &f->start_deactivated, "start_deactivated");
}
void InitializeFlags(Flags *f, const char *env) {
@ -185,6 +187,7 @@ void InitializeFlags(Flags *f, const char *env) {
f->alloc_dealloc_mismatch = (SANITIZER_MAC == 0) && (SANITIZER_WINDOWS == 0);
f->strict_memcmp = true;
f->strict_init_order = false;
f->start_deactivated = false;
// Override from compile definition.
ParseFlagsFromString(f, MaybeUseAsanDefaultOptionsCompileDefiniton());
@ -397,55 +400,7 @@ static void PrintAddressSpaceLayout() {
kHighShadowBeg > kMidMemEnd);
}
} // namespace __asan
// ---------------------- Interface ---------------- {{{1
using namespace __asan; // NOLINT
#if !SANITIZER_SUPPORTS_WEAK_HOOKS
extern "C" {
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
const char* __asan_default_options() { return ""; }
} // extern "C"
#endif
int NOINLINE __asan_set_error_exit_code(int exit_code) {
int old = flags()->exitcode;
flags()->exitcode = exit_code;
return old;
}
void NOINLINE __asan_handle_no_return() {
int local_stack;
AsanThread *curr_thread = GetCurrentThread();
CHECK(curr_thread);
uptr PageSize = GetPageSizeCached();
uptr top = curr_thread->stack_top();
uptr bottom = ((uptr)&local_stack - PageSize) & ~(PageSize-1);
static const uptr kMaxExpectedCleanupSize = 64 << 20; // 64M
if (top - bottom > kMaxExpectedCleanupSize) {
static bool reported_warning = false;
if (reported_warning)
return;
reported_warning = true;
Report("WARNING: ASan is ignoring requested __asan_handle_no_return: "
"stack top: %p; bottom %p; size: %p (%zd)\n"
"False positive error reports may follow\n"
"For details see "
"http://code.google.com/p/address-sanitizer/issues/detail?id=189\n",
top, bottom, top - bottom, top - bottom);
return;
}
PoisonShadow(bottom, top - bottom, 0);
if (curr_thread->has_fake_stack())
curr_thread->fake_stack()->HandleNoReturn();
}
void NOINLINE __asan_set_death_callback(void (*callback)(void)) {
death_callback = callback;
}
void __asan_init() {
static void AsanInitInternal() {
if (asan_inited) return;
SanitizerToolName = "AddressSanitizer";
CHECK(!asan_init_is_running && "ASan init calls itself!");
@ -473,6 +428,9 @@ void __asan_init() {
VReport(1, "Parsed ASAN_OPTIONS: %s\n", options);
}
if (flags()->start_deactivated)
AsanStartDeactivated();
// Re-exec ourselves if we need to set additional env or command line args.
MaybeReexec();
@ -575,3 +533,64 @@ void __asan_init() {
VReport(1, "AddressSanitizer Init done\n");
}
// Initialize as requested from some part of ASan runtime library (interceptors,
// allocator, etc).
void AsanInitFromRtl() {
AsanInitInternal();
}
} // namespace __asan
// ---------------------- Interface ---------------- {{{1
using namespace __asan; // NOLINT
#if !SANITIZER_SUPPORTS_WEAK_HOOKS
extern "C" {
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
const char* __asan_default_options() { return ""; }
} // extern "C"
#endif
int NOINLINE __asan_set_error_exit_code(int exit_code) {
int old = flags()->exitcode;
flags()->exitcode = exit_code;
return old;
}
void NOINLINE __asan_handle_no_return() {
int local_stack;
AsanThread *curr_thread = GetCurrentThread();
CHECK(curr_thread);
uptr PageSize = GetPageSizeCached();
uptr top = curr_thread->stack_top();
uptr bottom = ((uptr)&local_stack - PageSize) & ~(PageSize-1);
static const uptr kMaxExpectedCleanupSize = 64 << 20; // 64M
if (top - bottom > kMaxExpectedCleanupSize) {
static bool reported_warning = false;
if (reported_warning)
return;
reported_warning = true;
Report("WARNING: ASan is ignoring requested __asan_handle_no_return: "
"stack top: %p; bottom %p; size: %p (%zd)\n"
"False positive error reports may follow\n"
"For details see "
"http://code.google.com/p/address-sanitizer/issues/detail?id=189\n",
top, bottom, top - bottom, top - bottom);
return;
}
PoisonShadow(bottom, top - bottom, 0);
if (curr_thread->has_fake_stack())
curr_thread->fake_stack()->HandleNoReturn();
}
void NOINLINE __asan_set_death_callback(void (*callback)(void)) {
death_callback = callback;
}
// Initialize as requested from instrumented application code.
// We use this call as a trigger to wake up ASan from deactivated state.
void __asan_init() {
AsanActivate();
AsanInitInternal();
}

View File

@ -0,0 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
extern "C" void do_another_bad_thing() {
char *volatile p = (char *)malloc(100);
printf("%hhx\n", p[105]);
}

View File

@ -0,0 +1,58 @@
// Test for ASAN_OPTIONS=start_deactivated=1 mode.
// Main executable is uninstrumented, but linked to ASan runtime. The shared
// library is instrumented. Memory errors before dlopen are not detected.
// RUN: %clangxx_asan -O0 %p/SharedLibs/start-deactivated-so.cc \
// RUN: -fPIC -shared -o %t-so.so
// RUN: %clangxx -O0 %s -c -o %t.o
// RUN: %clangxx_asan -O0 %t.o -o %t
// RUN: ASAN_OPTIONS=start_deactivated=1 not %t 2>&1 | FileCheck %s
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include "sanitizer/asan_interface.h"
void test_malloc_shadow() {
char *p = (char *)malloc(100);
char *q = (char *)__asan_region_is_poisoned(p + 95, 8);
fprintf(stderr, "=%zd=\n", q ? q - (p + 95) : -1);
free(p);
}
typedef void (*Fn)();
int main(int argc, char *argv[]) {
test_malloc_shadow();
// CHECK: =-1=
std::string path = std::string(argv[0]) + "-so.so";
void *dso = dlopen(path.c_str(), RTLD_NOW);
if (!dso) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}
test_malloc_shadow();
// CHECK: =5=
void *fn = dlsym(dso, "do_another_bad_thing");
if (!fn) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
return 1;
}
((Fn)fn)();
// CHECK: AddressSanitizer: heap-buffer-overflow
// CHECK: READ of size 1
// CHECK: {{#0 .* in do_another_bad_thing}}
// CHECK: is located 5 bytes to the right of 100-byte region
// CHECK: in do_another_bad_thing
return 0;
}