From fcbcc6114f47a3e053a603aa40e3f79c237b0795 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Fri, 4 Jan 2019 19:21:51 +0000 Subject: [PATCH] hwasan: Use system allocator to realloc and free untagged pointers in interceptor mode. The Android dynamic loader has a non-standard feature that allows libraries such as the hwasan runtime to interpose symbols even after the symbol already has a value. The new value of the symbol is used to relocate libraries loaded after the interposing library, but existing libraries keep the old value. This behaviour is activated by the DF_1_GLOBAL flag in DT_FLAGS_1, which is set by passing -z global to the linker, which is what we already do to link the hwasan runtime. What this means in practice is that if we have .so files that depend on interceptor-mode hwasan without the main executable depending on it, some of the libraries in the process will be using the hwasan allocator and some will be using the system allocator, and these allocators need to interact somehow. For example, if an instrumented library calls a function such as strdup that allocates memory on behalf of the caller, the instrumented library can reasonably expect to be able to call free to deallocate the memory. We can handle that relatively easily with hwasan by using tag 0 to represent allocations from the system allocator. If hwasan's realloc or free functions are passed a pointer with tag 0, the system allocator is called. One limitation is that this scheme doesn't work in reverse: if an instrumented library allocates memory, it must free the memory itself and cannot pass ownership to a system library. In a future change, we may want to expose an API for calling the system allocator so that instrumented libraries can safely transfer ownership of memory to system libraries. Differential Revision: https://reviews.llvm.org/D55986 llvm-svn: 350427 --- compiler-rt/lib/hwasan/hwasan.h | 2 +- compiler-rt/lib/hwasan/hwasan_allocator.cc | 41 +++++++++++++-- compiler-rt/lib/hwasan/hwasan_allocator.h | 6 +++ compiler-rt/lib/hwasan/hwasan_interceptors.cc | 12 ++--- compiler-rt/lib/hwasan/hwasan_linux.cc | 1 + compiler-rt/lib/hwasan/hwasan_new_delete.cc | 2 +- .../Posix/system-allocator-fallback.cc | 50 +++++++++++++++++++ compiler-rt/test/hwasan/lit.cfg | 6 ++- 8 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 compiler-rt/test/hwasan/TestCases/Posix/system-allocator-fallback.cc diff --git a/compiler-rt/lib/hwasan/hwasan.h b/compiler-rt/lib/hwasan/hwasan.h index 64b1f1a3d013..ce9e904c5c69 100644 --- a/compiler-rt/lib/hwasan/hwasan.h +++ b/compiler-rt/lib/hwasan/hwasan.h @@ -77,7 +77,6 @@ void InitializeInterceptors(); void HwasanAllocatorInit(); void HwasanAllocatorThreadFinish(); -void HwasanDeallocate(StackTrace *stack, void *ptr); void *hwasan_malloc(uptr size, StackTrace *stack); void *hwasan_calloc(uptr nmemb, uptr size, StackTrace *stack); @@ -88,6 +87,7 @@ void *hwasan_aligned_alloc(uptr alignment, uptr size, StackTrace *stack); void *hwasan_memalign(uptr alignment, uptr size, StackTrace *stack); int hwasan_posix_memalign(void **memptr, uptr alignment, uptr size, StackTrace *stack); +void hwasan_free(void *ptr, StackTrace *stack); void InstallTrapHandler(); void InstallAtExitHandler(); diff --git a/compiler-rt/lib/hwasan/hwasan_allocator.cc b/compiler-rt/lib/hwasan/hwasan_allocator.cc index 8fd2349d97f6..8487ed7e1e55 100644 --- a/compiler-rt/lib/hwasan/hwasan_allocator.cc +++ b/compiler-rt/lib/hwasan/hwasan_allocator.cc @@ -21,6 +21,11 @@ #include "hwasan_thread.h" #include "hwasan_report.h" +#if HWASAN_WITH_INTERCEPTORS +DEFINE_REAL(void *, realloc, void *ptr, uptr size) +DEFINE_REAL(void, free, void *ptr) +#endif + namespace __hwasan { static Allocator allocator; @@ -199,7 +204,7 @@ static bool PointerAndMemoryTagsMatch(void *tagged_ptr) { return ptr_tag == mem_tag; } -void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) { +static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) { CHECK(tagged_ptr); HWASAN_FREE_HOOK(tagged_ptr); @@ -253,8 +258,8 @@ void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) { } } -void *HwasanReallocate(StackTrace *stack, void *tagged_ptr_old, uptr new_size, - uptr alignment) { +static void *HwasanReallocate(StackTrace *stack, void *tagged_ptr_old, + uptr new_size, uptr alignment) { if (!PointerAndMemoryTagsMatch(tagged_ptr_old)) ReportInvalidFree(stack, reinterpret_cast(tagged_ptr_old)); @@ -271,7 +276,7 @@ void *HwasanReallocate(StackTrace *stack, void *tagged_ptr_old, uptr new_size, return tagged_ptr_new; } -void *HwasanCalloc(StackTrace *stack, uptr nmemb, uptr size) { +static void *HwasanCalloc(StackTrace *stack, uptr nmemb, uptr size) { if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { if (AllocatorMayReturnNull()) return nullptr; @@ -315,6 +320,14 @@ void *hwasan_calloc(uptr nmemb, uptr size, StackTrace *stack) { void *hwasan_realloc(void *ptr, uptr size, StackTrace *stack) { if (!ptr) return SetErrnoOnNull(HwasanAllocate(stack, size, sizeof(u64), false)); + +#if HWASAN_WITH_INTERCEPTORS + // A tag of 0 means that this is a system allocator allocation, so we must use + // the system allocator to realloc it. + if (!flags()->disable_allocator_tagging && GetTagFromPointer((uptr)ptr) == 0) + return REAL(realloc)(ptr, size); +#endif + if (size == 0) { HwasanDeallocate(stack, ptr); return nullptr; @@ -376,6 +389,17 @@ int hwasan_posix_memalign(void **memptr, uptr alignment, uptr size, return 0; } +void hwasan_free(void *ptr, StackTrace *stack) { +#if HWASAN_WITH_INTERCEPTORS + // A tag of 0 means that this is a system allocator allocation, so we must use + // the system allocator to free it. + if (!flags()->disable_allocator_tagging && GetTagFromPointer((uptr)ptr) == 0) + return REAL(free)(ptr); +#endif + + return HwasanDeallocate(stack, ptr); +} + } // namespace __hwasan using namespace __hwasan; @@ -385,6 +409,15 @@ void __hwasan_enable_allocator_tagging() { } void __hwasan_disable_allocator_tagging() { +#if HWASAN_WITH_INTERCEPTORS + // Allocator tagging must be enabled for the system allocator fallback to work + // correctly. This means that we can't disable it at runtime if it was enabled + // at startup since that might result in our deallocations going to the system + // allocator. If tagging was disabled at startup we avoid this problem by + // disabling the fallback altogether. + CHECK(flags()->disable_allocator_tagging); +#endif + atomic_store_relaxed(&hwasan_allocator_tagging_enabled, 0); } diff --git a/compiler-rt/lib/hwasan/hwasan_allocator.h b/compiler-rt/lib/hwasan/hwasan_allocator.h index 785a80625b12..6ab722fa6bef 100644 --- a/compiler-rt/lib/hwasan/hwasan_allocator.h +++ b/compiler-rt/lib/hwasan/hwasan_allocator.h @@ -14,6 +14,7 @@ #ifndef HWASAN_ALLOCATOR_H #define HWASAN_ALLOCATOR_H +#include "interception/interception.h" #include "sanitizer_common/sanitizer_allocator.h" #include "sanitizer_common/sanitizer_allocator_checks.h" #include "sanitizer_common/sanitizer_allocator_interface.h" @@ -26,6 +27,11 @@ #error Unsupported platform #endif +#if HWASAN_WITH_INTERCEPTORS +DECLARE_REAL(void *, realloc, void *ptr, uptr size) +DECLARE_REAL(void, free, void *ptr) +#endif + namespace __hwasan { struct Metadata { diff --git a/compiler-rt/lib/hwasan/hwasan_interceptors.cc b/compiler-rt/lib/hwasan/hwasan_interceptors.cc index d20bdd95ad3d..533426c2e383 100644 --- a/compiler-rt/lib/hwasan/hwasan_interceptors.cc +++ b/compiler-rt/lib/hwasan/hwasan_interceptors.cc @@ -17,6 +17,7 @@ #include "interception/interception.h" #include "hwasan.h" +#include "hwasan_allocator.h" #include "hwasan_mapping.h" #include "hwasan_thread.h" #include "hwasan_poisoning.h" @@ -44,11 +45,6 @@ using __sanitizer::atomic_load; using __sanitizer::atomic_store; using __sanitizer::atomic_uintptr_t; -DECLARE_REAL(SIZE_T, strlen, const char *s) -DECLARE_REAL(SIZE_T, strnlen, const char *s, SIZE_T maxlen) -DECLARE_REAL(void *, memcpy, void *dest, const void *src, uptr n) -DECLARE_REAL(void *, memset, void *dest, int c, uptr n) - bool IsInInterceptorScope() { Thread *t = GetCurrentThread(); return t && t->InInterceptorScope(); @@ -130,13 +126,13 @@ void * __sanitizer_pvalloc(uptr size) { void __sanitizer_free(void *ptr) { GET_MALLOC_STACK_TRACE; if (!ptr || UNLIKELY(IsInDlsymAllocPool(ptr))) return; - HwasanDeallocate(&stack, ptr); + hwasan_free(ptr, &stack); } void __sanitizer_cfree(void *ptr) { GET_MALLOC_STACK_TRACE; if (!ptr || UNLIKELY(IsInDlsymAllocPool(ptr))) return; - HwasanDeallocate(&stack, ptr); + hwasan_free(ptr, &stack); } uptr __sanitizer_malloc_usable_size(const void *ptr) { @@ -290,6 +286,8 @@ void InitializeInterceptors() { #if HWASAN_WITH_INTERCEPTORS INTERCEPT_FUNCTION(pthread_create); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(free); #endif inited = 1; diff --git a/compiler-rt/lib/hwasan/hwasan_linux.cc b/compiler-rt/lib/hwasan/hwasan_linux.cc index af9378c3101e..2238d3848d84 100644 --- a/compiler-rt/lib/hwasan/hwasan_linux.cc +++ b/compiler-rt/lib/hwasan/hwasan_linux.cc @@ -40,6 +40,7 @@ #include "sanitizer_common/sanitizer_procmaps.h" #if HWASAN_WITH_INTERCEPTORS && !SANITIZER_ANDROID +SANITIZER_INTERFACE_ATTRIBUTE THREADLOCAL uptr __hwasan_tls; #endif diff --git a/compiler-rt/lib/hwasan/hwasan_new_delete.cc b/compiler-rt/lib/hwasan/hwasan_new_delete.cc index 63ca74edd481..f2e8faf5da73 100644 --- a/compiler-rt/lib/hwasan/hwasan_new_delete.cc +++ b/compiler-rt/lib/hwasan/hwasan_new_delete.cc @@ -51,7 +51,7 @@ void *operator new[](size_t size, std::nothrow_t const&) { #define OPERATOR_DELETE_BODY \ GET_MALLOC_STACK_TRACE; \ - if (ptr) HwasanDeallocate(&stack, ptr) + if (ptr) hwasan_free(ptr, &stack) INTERCEPTOR_ATTRIBUTE void operator delete(void *ptr) NOEXCEPT { OPERATOR_DELETE_BODY; } diff --git a/compiler-rt/test/hwasan/TestCases/Posix/system-allocator-fallback.cc b/compiler-rt/test/hwasan/TestCases/Posix/system-allocator-fallback.cc new file mode 100644 index 000000000000..0e44aae7d6aa --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/Posix/system-allocator-fallback.cc @@ -0,0 +1,50 @@ +// RUN: %clangxx %s -o %t -ldl +// RUN: %clangxx_hwasan -shared %s -o %t.so -DSHARED_LIB -shared-libsan -Wl,-rpath,%compiler_rt_libdir +// RUN: %env_hwasan_opts=disable_allocator_tagging=0 %run %t + +#include + +// Test that allocations made by the system allocator can be realloc'd and freed +// by the hwasan allocator. + +typedef void run_test_fn(void *(*system_malloc)(size_t size)); + +#ifdef SHARED_LIB + +// Call the __sanitizer_ versions of these functions so that the test +// doesn't require the Android dynamic loader. +extern "C" void *__sanitizer_realloc(void *ptr, size_t size); +extern "C" void __sanitizer_free(void *ptr); + +extern "C" run_test_fn run_test; +void run_test(void *(*system_malloc)(size_t size)) { + void *mem = system_malloc(64); + mem = __sanitizer_realloc(mem, 128); + __sanitizer_free(mem); +} + +#else + +#include +#include +#include + +int main(int argc, char **argv) { + std::string path = argv[0]; + path += ".so"; + void *lib = dlopen(path.c_str(), RTLD_NOW); + if (!lib) { + printf("error in dlopen(): %s\n", dlerror()); + return 1; + } + + auto run_test = reinterpret_cast(dlsym(lib, "run_test")); + if (!run_test) { + printf("failed dlsym\n"); + return 1; + } + + run_test(malloc); +} + +#endif diff --git a/compiler-rt/test/hwasan/lit.cfg b/compiler-rt/test/hwasan/lit.cfg index 3ebba51d05e4..66008a632bcf 100644 --- a/compiler-rt/test/hwasan/lit.cfg +++ b/compiler-rt/test/hwasan/lit.cfg @@ -9,14 +9,18 @@ config.name = 'HWAddressSanitizer' + getattr(config, 'name_suffix', 'default') config.test_source_root = os.path.dirname(__file__) # Setup default compiler flags used with -fsanitize=memory option. -clang_hwasan_cflags = ["-fsanitize=hwaddress", "-mllvm", "-hwasan-generate-tags-with-calls", config.target_cflags] + config.debug_info_flags +clang_cflags = [config.target_cflags] + config.debug_info_flags +clang_cxxflags = config.cxx_mode_flags + clang_cflags +clang_hwasan_cflags = ["-fsanitize=hwaddress", "-mllvm", "-hwasan-generate-tags-with-calls"] + clang_cflags clang_hwasan_cxxflags = config.cxx_mode_flags + clang_hwasan_cflags def build_invocation(compile_flags): return " " + " ".join([config.clang] + compile_flags) + " " +config.substitutions.append( ("%clangxx ", build_invocation(clang_cxxflags)) ) config.substitutions.append( ("%clang_hwasan ", build_invocation(clang_hwasan_cflags)) ) config.substitutions.append( ("%clangxx_hwasan ", build_invocation(clang_hwasan_cxxflags)) ) +config.substitutions.append( ("%compiler_rt_libdir", config.compiler_rt_libdir) ) default_hwasan_opts_str = ':'.join(['disable_allocator_tagging=1', 'random_tags=0'] + config.default_sanitizer_opts) if default_hwasan_opts_str: