forked from OSchip/llvm-project
[GWP-ASan] Integration with Scudo [5].
Summary: See D60593 for further information. This patch adds GWP-ASan support to the Scudo hardened allocator. It also implements end-to-end integration tests using Scudo as the backing allocator. The tests include crash handling for buffer over/underflow as well as use-after-free detection. Reviewers: vlad.tsyrklevich, cryptoad Reviewed By: vlad.tsyrklevich, cryptoad Subscribers: kubamracek, mgorny, #sanitizers, llvm-commits, morehouse Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D62929 llvm-svn: 363584
This commit is contained in:
parent
0cbf37af1e
commit
21184ec5c4
|
@ -30,6 +30,15 @@ set(SCUDO_MINIMAL_OBJECT_LIBS
|
|||
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})
|
||||
|
||||
|
|
|
@ -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 <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -213,6 +218,10 @@ QuarantineCacheT *getQuarantineCache(ScudoTSD *TSD) {
|
|||
return reinterpret_cast<QuarantineCacheT *>(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 @@ struct Allocator {
|
|||
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 @@ struct Allocator {
|
|||
__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 @@ struct Allocator {
|
|||
// 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 @@ struct Allocator {
|
|||
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 @@ static BackendT &getBackend() {
|
|||
|
||||
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() {
|
||||
|
|
|
@ -6,7 +6,8 @@ set(GWP_ASAN_TESTSUITES)
|
|||
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
|
||||
|
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
char *Ptr = new char;
|
||||
delete Ptr;
|
||||
delete Ptr;
|
||||
return 0;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
char *Ptr = new char[50];
|
||||
delete[] Ptr;
|
||||
delete[] Ptr;
|
||||
return 0;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
void *Ptr = malloc(10);
|
||||
free(Ptr);
|
||||
free(Ptr);
|
||||
return 0;
|
||||
}
|
|
@ -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; }
|
|
@ -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 <cstdlib>
|
||||
|
||||
#include "page_size.h"
|
||||
|
||||
int main() {
|
||||
char *Ptr =
|
||||
reinterpret_cast<char *>(malloc(pageSize()));
|
||||
volatile char x = *(Ptr + pageSize());
|
||||
return 0;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
#include "page_size.h"
|
||||
|
||||
int main() {
|
||||
char *Ptr =
|
||||
reinterpret_cast<char *>(malloc(pageSize()));
|
||||
volatile char x = *(Ptr - 1);
|
||||
return 0;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
char *Ptr =
|
||||
reinterpret_cast<char *>(malloc(1));
|
||||
free(Ptr - 1);
|
||||
return 0;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
char *Ptr =
|
||||
reinterpret_cast<char *>(malloc(1));
|
||||
free(Ptr + 1);
|
||||
return 0;
|
||||
}
|
|
@ -20,11 +20,24 @@ if not config.android:
|
|||
|
||||
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']:
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef PAGE_SIZE_
|
||||
#define PAGE_SIZE_
|
||||
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
# include <unistd.h>
|
||||
unsigned pageSize() {
|
||||
return sysconf(_SC_PAGESIZE);
|
||||
}
|
||||
#else
|
||||
# error "GWP-ASan is not supported on this platform."
|
||||
#endif
|
||||
|
||||
#endif // PAGE_SIZE_
|
|
@ -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 <stdlib.h>
|
||||
|
||||
int main() {
|
||||
#if defined(TEST_MALLOC)
|
||||
// realloc(nullptr, size) is equivalent to malloc(size).
|
||||
char *Ptr = reinterpret_cast<char *>(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;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
void* Pointers[16];
|
||||
for (unsigned i = 0; i < 16; ++i) {
|
||||
char *Ptr = reinterpret_cast<char*>(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;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
char *Ptr = new char;
|
||||
|
||||
*Ptr = 0x0;
|
||||
|
||||
delete Ptr;
|
||||
volatile char x = *Ptr;
|
||||
return 0;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 <cstdlib>
|
||||
|
||||
int main() {
|
||||
char *Ptr = reinterpret_cast<char *>(malloc(10));
|
||||
|
||||
for (unsigned i = 0; i < 10; ++i) {
|
||||
*(Ptr + i) = 0x0;
|
||||
}
|
||||
|
||||
free(Ptr);
|
||||
volatile char x = *Ptr;
|
||||
return 0;
|
||||
}
|
|
@ -239,6 +239,9 @@ if config.use_lld:
|
|||
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':
|
||||
|
|
|
@ -41,6 +41,7 @@ set_default("android_ndk_version", @ANDROID_NDK_VERSION@)
|
|||
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:
|
||||
|
|
|
@ -49,6 +49,10 @@ if config.android:
|
|||
# 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 += ':'
|
||||
|
|
Loading…
Reference in New Issue