Skip to content

Commit

Permalink
hwasan: Use system allocator to realloc and free untagged pointers in…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
pcc committed Jan 4, 2019
1 parent 0743cda commit fcbcc61
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 14 deletions.
2 changes: 1 addition & 1 deletion compiler-rt/lib/hwasan/hwasan.h
Expand Up @@ -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);
Expand All @@ -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();
Expand Down
41 changes: 37 additions & 4 deletions compiler-rt/lib/hwasan/hwasan_allocator.cc
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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<uptr>(tagged_ptr_old));

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
6 changes: 6 additions & 0 deletions compiler-rt/lib/hwasan/hwasan_allocator.h
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down
12 changes: 5 additions & 7 deletions compiler-rt/lib/hwasan/hwasan_interceptors.cc
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -290,6 +286,8 @@ void InitializeInterceptors() {

#if HWASAN_WITH_INTERCEPTORS
INTERCEPT_FUNCTION(pthread_create);
INTERCEPT_FUNCTION(realloc);
INTERCEPT_FUNCTION(free);
#endif

inited = 1;
Expand Down
1 change: 1 addition & 0 deletions compiler-rt/lib/hwasan/hwasan_linux.cc
Expand Up @@ -40,6 +40,7 @@
#include "sanitizer_common/sanitizer_procmaps.h"

#if HWASAN_WITH_INTERCEPTORS && !SANITIZER_ANDROID
SANITIZER_INTERFACE_ATTRIBUTE
THREADLOCAL uptr __hwasan_tls;
#endif

Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/lib/hwasan/hwasan_new_delete.cc
Expand Up @@ -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; }
Expand Down
@@ -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 <stddef.h>

// 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 <dlfcn.h>
#include <stdlib.h>
#include <string>

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<run_test_fn *>(dlsym(lib, "run_test"));
if (!run_test) {
printf("failed dlsym\n");
return 1;
}

run_test(malloc);
}

#endif
6 changes: 5 additions & 1 deletion compiler-rt/test/hwasan/lit.cfg
Expand Up @@ -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:
Expand Down

0 comments on commit fcbcc61

Please sign in to comment.