Skip to content

Commit

Permalink
[GWP-ASan] Integration with Scudo [5].
Browse files Browse the repository at this point in the history
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
  • Loading branch information
hctim committed Jun 17, 2019
1 parent 0cbf37a commit 21184ec
Show file tree
Hide file tree
Showing 21 changed files with 335 additions and 5 deletions.
9 changes: 9 additions & 0 deletions compiler-rt/lib/scudo/CMakeLists.txt
Expand Up @@ -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})

Expand Down
47 changes: 47 additions & 0 deletions compiler-rt/lib/scudo/scudo_allocator.cpp
Expand Up @@ -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>

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand Down
3 changes: 2 additions & 1 deletion compiler-rt/test/gwp_asan/CMakeLists.txt
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions compiler-rt/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 <cstdlib>

int main() {
char *Ptr = new char;
delete Ptr;
delete Ptr;
return 0;
}
15 changes: 15 additions & 0 deletions compiler-rt/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 <cstdlib>

int main() {
char *Ptr = new char[50];
delete[] Ptr;
delete[] Ptr;
return 0;
}
15 changes: 15 additions & 0 deletions compiler-rt/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 <cstdlib>

int main() {
void *Ptr = malloc(10);
free(Ptr);
free(Ptr);
return 0;
}
4 changes: 0 additions & 4 deletions compiler-rt/test/gwp_asan/dummy_test.cc

This file was deleted.

18 changes: 18 additions & 0 deletions compiler-rt/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 <cstdlib>

#include "page_size.h"

int main() {
char *Ptr =
reinterpret_cast<char *>(malloc(pageSize()));
volatile char x = *(Ptr + pageSize());
return 0;
}
18 changes: 18 additions & 0 deletions compiler-rt/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 <cstdlib>

#include "page_size.h"

int main() {
char *Ptr =
reinterpret_cast<char *>(malloc(pageSize()));
volatile char x = *(Ptr - 1);
return 0;
}
16 changes: 16 additions & 0 deletions compiler-rt/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 <cstdlib>

int main() {
char *Ptr =
reinterpret_cast<char *>(malloc(1));
free(Ptr - 1);
return 0;
}
16 changes: 16 additions & 0 deletions compiler-rt/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 <cstdlib>

int main() {
char *Ptr =
reinterpret_cast<char *>(malloc(1));
free(Ptr + 1);
return 0;
}
13 changes: 13 additions & 0 deletions compiler-rt/test/gwp_asan/lit.cfg
Expand Up @@ -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']:
Expand Down
13 changes: 13 additions & 0 deletions compiler-rt/test/gwp_asan/page_size.h
@@ -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_
44 changes: 44 additions & 0 deletions compiler-rt/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 <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;
}
28 changes: 28 additions & 0 deletions compiler-rt/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 <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;
}
18 changes: 18 additions & 0 deletions compiler-rt/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 <cstdlib>

int main() {
char *Ptr = new char;

*Ptr = 0x0;

delete Ptr;
volatile char x = *Ptr;
return 0;
}
20 changes: 20 additions & 0 deletions compiler-rt/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 <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 comments on commit 21184ec

Please sign in to comment.