forked from OSchip/llvm-project
[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:
parent
3657cb0350
commit
c61623b170
|
@ -2,6 +2,7 @@
|
|||
|
||||
set(ASAN_SOURCES
|
||||
asan_allocator2.cc
|
||||
asan_activation.cc
|
||||
asan_fake_stack.cc
|
||||
asan_globals.cc
|
||||
asan_interceptors.cc
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -67,6 +67,8 @@ namespace __asan {
|
|||
class AsanThread;
|
||||
using __sanitizer::StackTrace;
|
||||
|
||||
void AsanInitFromRtl();
|
||||
|
||||
// asan_rtl.cc
|
||||
void NORETURN ShowStatsAndAbort();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue