Index: compiler-rt/trunk/lib/scudo/CMakeLists.txt =================================================================== --- compiler-rt/trunk/lib/scudo/CMakeLists.txt +++ compiler-rt/trunk/lib/scudo/CMakeLists.txt @@ -30,6 +30,15 @@ RTSanitizerCommonNoTermination RTSanitizerCommonLibc RTInterception) + +if (COMPILER_RT_HAS_GWP_ASAN) + # Currently, Scudo uses the GwpAsan flag parser. This backs onto the flag + # parsing mechanism of sanitizer_common. Once Scudo has its own flag parsing, + # and parses GwpAsan options, you can remove this dependency. + list(APPEND SCUDO_MINIMAL_OBJECT_LIBS RTGwpAsan RTGwpAsanOptionsParser) + list(APPEND SCUDO_CFLAGS -DGWP_ASAN_HOOKS) +endif() + set(SCUDO_OBJECT_LIBS ${SCUDO_MINIMAL_OBJECT_LIBS}) set(SCUDO_DYNAMIC_LIBS ${SCUDO_MINIMAL_DYNAMIC_LIBS}) Index: compiler-rt/trunk/lib/scudo/scudo_allocator.cpp =================================================================== --- compiler-rt/trunk/lib/scudo/scudo_allocator.cpp +++ compiler-rt/trunk/lib/scudo/scudo_allocator.cpp @@ -25,6 +25,11 @@ #include "sanitizer_common/sanitizer_allocator_interface.h" #include "sanitizer_common/sanitizer_quarantine.h" +#ifdef GWP_ASAN_HOOKS +# include "gwp_asan/guarded_pool_allocator.h" +# include "gwp_asan/optional/options_parser.h" +#endif // GWP_ASAN_HOOKS + #include #include @@ -213,6 +218,10 @@ return reinterpret_cast(TSD->QuarantineCachePlaceHolder); } +#ifdef GWP_ASAN_HOOKS +static gwp_asan::GuardedPoolAllocator GuardedAlloc; +#endif // GWP_ASAN_HOOKS + struct Allocator { static const uptr MaxAllowedMallocSize = FIRST_32_SECOND_64(2UL << 30, 1ULL << 40); @@ -291,6 +300,14 @@ void *allocate(uptr Size, uptr Alignment, AllocType Type, bool ForceZeroContents = false) { initThreadMaybe(); + +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.shouldSample())) { + if (void *Ptr = GuardedAlloc.allocate(Size)) + return Ptr; + } +#endif // GWP_ASAN_HOOKS + if (UNLIKELY(Alignment > MaxAlignment)) { if (AllocatorMayReturnNull()) return nullptr; @@ -434,6 +451,14 @@ __sanitizer_free_hook(Ptr); if (UNLIKELY(!Ptr)) return; + +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) { + GuardedAlloc.deallocate(Ptr); + return; + } +#endif // GWP_ASAN_HOOKS + if (UNLIKELY(!Chunk::isAligned(Ptr))) dieWithMessage("misaligned pointer when deallocating address %p\n", Ptr); UnpackedHeader Header; @@ -463,6 +488,18 @@ // size still fits in the chunk. void *reallocate(void *OldPtr, uptr NewSize) { initThreadMaybe(); + +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.pointerIsMine(OldPtr))) { + size_t OldSize = GuardedAlloc.getSize(OldPtr); + void *NewPtr = allocate(NewSize, MinAlignment, FromMalloc); + if (NewPtr) + memcpy(NewPtr, OldPtr, (NewSize < OldSize) ? NewSize : OldSize); + GuardedAlloc.deallocate(OldPtr); + return NewPtr; + } +#endif // GWP_ASAN_HOOKS + if (UNLIKELY(!Chunk::isAligned(OldPtr))) dieWithMessage("misaligned address when reallocating address %p\n", OldPtr); @@ -504,6 +541,12 @@ initThreadMaybe(); if (UNLIKELY(!Ptr)) return 0; + +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) + return GuardedAlloc.getSize(Ptr); +#endif // GWP_ASAN_HOOKS + UnpackedHeader Header; Chunk::loadHeader(Ptr, &Header); // Getting the usable size of a chunk only makes sense if it's allocated. @@ -626,6 +669,10 @@ void initScudo() { Instance.init(); +#ifdef GWP_ASAN_HOOKS + gwp_asan::options::initOptions(); + GuardedAlloc.init(gwp_asan::options::getOptions()); +#endif // GWP_ASAN_HOOKS } void ScudoTSD::init() { Index: compiler-rt/trunk/test/gwp_asan/CMakeLists.txt =================================================================== --- compiler-rt/trunk/test/gwp_asan/CMakeLists.txt +++ compiler-rt/trunk/test/gwp_asan/CMakeLists.txt @@ -6,7 +6,8 @@ set(GWP_ASAN_UNITTEST_DEPS) set(GWP_ASAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} - gwp_asan) + gwp_asan + scudo) # Longstanding issues in the Android test runner means that compiler-rt unit # tests don't work on Android due to libc++ link-time issues. Looks like the Index: compiler-rt/trunk/test/gwp_asan/double_delete.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/double_delete.cpp +++ compiler-rt/trunk/test/gwp_asan/double_delete.cpp @@ -0,0 +1,15 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Double free occurred when trying to free memory at: + +#include + +int main() { + char *Ptr = new char; + delete Ptr; + delete Ptr; + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/double_deletea.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/double_deletea.cpp +++ compiler-rt/trunk/test/gwp_asan/double_deletea.cpp @@ -0,0 +1,15 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Double free occurred when trying to free memory at: + +#include + +int main() { + char *Ptr = new char[50]; + delete[] Ptr; + delete[] Ptr; + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/double_free.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/double_free.cpp +++ compiler-rt/trunk/test/gwp_asan/double_free.cpp @@ -0,0 +1,15 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Double free occurred when trying to free memory at: + +#include + +int main() { + void *Ptr = malloc(10); + free(Ptr); + free(Ptr); + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/dummy_test.cc =================================================================== --- compiler-rt/trunk/test/gwp_asan/dummy_test.cc +++ compiler-rt/trunk/test/gwp_asan/dummy_test.cc @@ -1,4 +0,0 @@ -// Exists to simply stop warnings about lit not discovering any tests here. -// RUN: %clang %s -o %s.o - -int main() { return 0; } Index: compiler-rt/trunk/test/gwp_asan/heap_buffer_overflow.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/heap_buffer_overflow.cpp +++ compiler-rt/trunk/test/gwp_asan/heap_buffer_overflow.cpp @@ -0,0 +1,18 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Buffer overflow occurred when accessing memory at: +// CHECK: is located {{[0-9]+}} bytes to the right + +#include + +#include "page_size.h" + +int main() { + char *Ptr = + reinterpret_cast(malloc(pageSize())); + volatile char x = *(Ptr + pageSize()); + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/heap_buffer_underflow.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/heap_buffer_underflow.cpp +++ compiler-rt/trunk/test/gwp_asan/heap_buffer_underflow.cpp @@ -0,0 +1,18 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Buffer underflow occurred when accessing memory at: +// CHECK: is located 1 bytes to the left + +#include + +#include "page_size.h" + +int main() { + char *Ptr = + reinterpret_cast(malloc(pageSize())); + volatile char x = *(Ptr - 1); + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/invalid_free_left.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/invalid_free_left.cpp +++ compiler-rt/trunk/test/gwp_asan/invalid_free_left.cpp @@ -0,0 +1,16 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Invalid (wild) free occurred when trying to free memory at: +// CHECK: is located 1 bytes to the left of + +#include + +int main() { + char *Ptr = + reinterpret_cast(malloc(1)); + free(Ptr - 1); + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/invalid_free_right.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/invalid_free_right.cpp +++ compiler-rt/trunk/test/gwp_asan/invalid_free_right.cpp @@ -0,0 +1,16 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Invalid (wild) free occurred when trying to free memory at: +// CHECK: is located 1 bytes to the right + +#include + +int main() { + char *Ptr = + reinterpret_cast(malloc(1)); + free(Ptr + 1); + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/lit.cfg =================================================================== --- compiler-rt/trunk/test/gwp_asan/lit.cfg +++ compiler-rt/trunk/test/gwp_asan/lit.cfg @@ -20,11 +20,24 @@ cxx_flags = (c_flags + config.cxx_mode_flags + ["-std=c++11"]) +gwp_asan_flags = ["-fsanitize=scudo"] + def build_invocation(compile_flags): return " " + " ".join([config.clang] + compile_flags) + " " # Add substitutions. config.substitutions.append(("%clang ", build_invocation(c_flags))) +config.substitutions.append(("%clang_gwp_asan ", build_invocation(c_flags + gwp_asan_flags))) +config.substitutions.append(("%clangxx_gwp_asan ", build_invocation(cxx_flags + gwp_asan_flags))) + +# Platform-specific default GWP_ASAN for lit tests. Ensure that GWP-ASan is +# enabled and that it samples every allocation. +default_gwp_asan_options = 'Enabled=1:SampleRate=1' + +config.environment['GWP_ASAN_OPTIONS'] = default_gwp_asan_options +default_gwp_asan_options += ':' +config.substitutions.append(('%env_gwp_asan_options=', + 'env GWP_ASAN_OPTIONS=' + default_gwp_asan_options)) # GWP-ASan tests are currently supported on Linux only. if config.host_os not in ['Linux']: Index: compiler-rt/trunk/test/gwp_asan/page_size.h =================================================================== --- compiler-rt/trunk/test/gwp_asan/page_size.h +++ compiler-rt/trunk/test/gwp_asan/page_size.h @@ -0,0 +1,13 @@ +#ifndef PAGE_SIZE_ +#define PAGE_SIZE_ + +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +# include +unsigned pageSize() { + return sysconf(_SC_PAGESIZE); +} +#else +# error "GWP-ASan is not supported on this platform." +#endif + +#endif // PAGE_SIZE_ Index: compiler-rt/trunk/test/gwp_asan/realloc.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/realloc.cpp +++ compiler-rt/trunk/test/gwp_asan/realloc.cpp @@ -0,0 +1,44 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t -DTEST_MALLOC +// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix CHECK-MALLOC + +// Check both C++98 and C. +// RUN: %clangxx_gwp_asan -std=c++98 %s -o %t -DTEST_FREE +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s --check-prefix CHECK-FREE +// RUN: cp %s %t.c && %clang_gwp_asan %t.c -o %t -DTEST_FREE +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s --check-prefix CHECK-FREE + +// Ensure GWP-ASan stub implementation of realloc() in Scudo works to-spec. In +// particular, the behaviour regarding realloc of size zero is interesting, as +// it's defined as free(). + +#include + +int main() { +#if defined(TEST_MALLOC) + // realloc(nullptr, size) is equivalent to malloc(size). + char *Ptr = reinterpret_cast(realloc(nullptr, 1)); + *Ptr = 0; + // Trigger an INVALID_FREE to the right. + free(Ptr + 1); + + // CHECK-MALLOC: GWP-ASan detected a memory error + // CHECK-MALLOC: Invalid (wild) free occurred when trying to free memory at: + // CHECK-MALLOC: is located 1 bytes to the right of a 1-byte allocation +#elif defined(TEST_FREE) + char *Ptr = (char *) malloc(1); + // realloc(ptr, 0) is equivalent to free(ptr) and must return nullptr. Note + // that this is only the specification in C++98 and C. + if (realloc(Ptr, 0) != NULL) { + + } + // Trigger a USE_AFTER_FREE. + *Ptr = 0; + + // CHECK-FREE: GWP-ASan detected a memory error + // CHECK-FREE: Use after free occurred when accessing memory at: + // CHECK-FREE: is a 1-byte allocation +#endif + + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/repeated_alloc.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/repeated_alloc.cpp +++ compiler-rt/trunk/test/gwp_asan/repeated_alloc.cpp @@ -0,0 +1,28 @@ +// REQUIRES: gwp_asan +// This test ensures that normal allocation/memory access/deallocation works +// as expected and we didn't accidentally break the supporting allocator. + +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: %env_gwp_asan_options=MaxSimultaneousAllocations=1 %run %t +// RUN: %env_gwp_asan_options=MaxSimultaneousAllocations=2 %run %t +// RUN: %env_gwp_asan_options=MaxSimultaneousAllocations=11 %run %t +// RUN: %env_gwp_asan_options=MaxSimultaneousAllocations=12 %run %t +// RUN: %env_gwp_asan_options=MaxSimultaneousAllocations=13 %run %t + +#include + +int main() { + void* Pointers[16]; + for (unsigned i = 0; i < 16; ++i) { + char *Ptr = reinterpret_cast(malloc(1 << i)); + Pointers[i] = Ptr; + *Ptr = 0; + Ptr[(1 << i) - 1] = 0; + } + + for (unsigned i = 0; i < 16; ++i) { + free(Pointers[i]); + } + + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/use_after_delete.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/use_after_delete.cpp +++ compiler-rt/trunk/test/gwp_asan/use_after_delete.cpp @@ -0,0 +1,18 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Use after free occurred when accessing memory at: + +#include + +int main() { + char *Ptr = new char; + + *Ptr = 0x0; + + delete Ptr; + volatile char x = *Ptr; + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/use_after_deletea.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/use_after_deletea.cpp +++ compiler-rt/trunk/test/gwp_asan/use_after_deletea.cpp @@ -0,0 +1,20 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Use after free occurred when accessing memory at: + +#include + +int main() { + char *Ptr = new char[10]; + + for (unsigned i = 0; i < 10; ++i) { + *(Ptr + i) = 0x0; + } + + delete[] Ptr; + volatile char x = *Ptr; + return 0; +} Index: compiler-rt/trunk/test/gwp_asan/use_after_free.cpp =================================================================== --- compiler-rt/trunk/test/gwp_asan/use_after_free.cpp +++ compiler-rt/trunk/test/gwp_asan/use_after_free.cpp @@ -0,0 +1,20 @@ +// REQUIRES: gwp_asan +// RUN: %clangxx_gwp_asan %s -o %t +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s + +// CHECK: GWP-ASan detected a memory error +// CHECK: Use after free occurred when accessing memory at: + +#include + +int main() { + char *Ptr = reinterpret_cast(malloc(10)); + + for (unsigned i = 0; i < 10; ++i) { + *(Ptr + i) = 0x0; + } + + free(Ptr); + volatile char x = *Ptr; + return 0; +} Index: compiler-rt/trunk/test/lit.common.cfg =================================================================== --- compiler-rt/trunk/test/lit.common.cfg +++ compiler-rt/trunk/test/lit.common.cfg @@ -239,6 +239,9 @@ if config.can_symbolize: config.available_features.add('can-symbolize') +if config.gwp_asan: + config.available_features.add('gwp_asan') + lit.util.usePlatformSdkOnDarwin(config, lit_config) if config.host_os == 'Darwin': Index: compiler-rt/trunk/test/lit.common.configured.in =================================================================== --- compiler-rt/trunk/test/lit.common.configured.in +++ compiler-rt/trunk/test/lit.common.configured.in @@ -41,6 +41,7 @@ set_default("android_serial", "@ANDROID_SERIAL_FOR_TESTING@") set_default("android_files_to_push", []) set_default("have_rpc_xdr_h", @HAVE_RPC_XDR_H@) +set_default("gwp_asan", @COMPILER_RT_HAS_GWP_ASAN_PYBOOL@) config.available_features.add('target-is-%s' % config.target_arch) if config.enable_per_target_runtime_dir: Index: compiler-rt/trunk/test/scudo/lit.cfg =================================================================== --- compiler-rt/trunk/test/scudo/lit.cfg +++ compiler-rt/trunk/test/scudo/lit.cfg @@ -49,6 +49,10 @@ # Android defaults to abort_on_error=1, which doesn't work for us. default_scudo_opts = 'abort_on_error=0' +# Disable GWP-ASan for scudo internal tests. +if config.gwp_asan: + config.environment['GWP_ASAN_OPTIONS'] = 'Enabled=0' + if default_scudo_opts: config.environment['SCUDO_OPTIONS'] = default_scudo_opts default_scudo_opts += ':'