[Sanitizers] Operator new() interceptors always die on allocation error

Summary:
Operator new interceptors behavior is now controlled by their nothrow
property as well as by allocator_may_return_null flag value:

- allocator_may_return_null=* + new()        - die on allocation error
- allocator_may_return_null=0 + new(nothrow) - die on allocation error
- allocator_may_return_null=1 + new(nothrow) - return null

Ideally new() should throw std::bad_alloc exception, but that is not
trivial to achieve, hence TODO.

Reviewers: eugenis

Subscribers: kubamracek, llvm-commits

Differential Revision: https://reviews.llvm.org/D34731

llvm-svn: 306604
This commit is contained in:
Alex Shlyapnikov 2017-06-28 21:58:57 +00:00
parent 7c525903ef
commit 4b450685d3
12 changed files with 346 additions and 168 deletions

View File

@ -160,7 +160,11 @@ struct QuarantineCallback {
}
void *Allocate(uptr size) {
return get_allocator().Allocate(cache_, size, 1);
void *res = get_allocator().Allocate(cache_, size, 1);
// TODO(alekseys): Consider making quarantine OOM-friendly.
if (UNLIKELY(!res))
return DieOnFailure::OnOOM();
return res;
}
void Deallocate(void *p) {
@ -524,8 +528,7 @@ struct Allocator {
// Expects the chunk to already be marked as quarantined by using
// AtomicallySetQuarantineFlagIfAllocated.
void QuarantineChunk(AsanChunk *m, void *ptr, BufferedStackTrace *stack,
AllocType alloc_type) {
void QuarantineChunk(AsanChunk *m, void *ptr, BufferedStackTrace *stack) {
CHECK_EQ(m->chunk_state, CHUNK_QUARANTINE);
CHECK_GE(m->alloc_tid, 0);
if (SANITIZER_WORDSIZE == 64) // On 32-bits this resides in user area.
@ -603,7 +606,7 @@ struct Allocator {
ReportNewDeleteSizeMismatch(p, delete_size, stack);
}
QuarantineChunk(m, ptr, stack, alloc_type);
QuarantineChunk(m, ptr, stack);
}
void *Reallocate(void *old_ptr, uptr new_size, BufferedStackTrace *stack) {

View File

@ -63,12 +63,17 @@ struct nothrow_t {};
enum class align_val_t: size_t {};
} // namespace std
#define OPERATOR_NEW_BODY(type) \
// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
#define OPERATOR_NEW_BODY(type, nothrow) \
GET_STACK_TRACE_MALLOC;\
return asan_memalign(0, size, &stack, type);
#define OPERATOR_NEW_BODY_ALIGN(type) \
void *res = asan_memalign(0, size, &stack, type);\
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM();\
return res;
#define OPERATOR_NEW_BODY_ALIGN(type, nothrow) \
GET_STACK_TRACE_MALLOC;\
return asan_memalign((uptr)align, size, &stack, type);
void *res = asan_memalign((uptr)align, size, &stack, type);\
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM();\
return res;
// On OS X it's not enough to just provide our own 'operator new' and
// 'operator delete' implementations, because they're going to be in the
@ -79,40 +84,42 @@ enum class align_val_t: size_t {};
// OS X we need to intercept them using their mangled names.
#if !SANITIZER_MAC
CXX_OPERATOR_ATTRIBUTE
void *operator new(size_t size) { OPERATOR_NEW_BODY(FROM_NEW); }
void *operator new(size_t size)
{ OPERATOR_NEW_BODY(FROM_NEW, false /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new[](size_t size) { OPERATOR_NEW_BODY(FROM_NEW_BR); }
void *operator new[](size_t size)
{ OPERATOR_NEW_BODY(FROM_NEW_BR, false /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new(size_t size, std::nothrow_t const&)
{ OPERATOR_NEW_BODY(FROM_NEW); }
{ OPERATOR_NEW_BODY(FROM_NEW, true /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new[](size_t size, std::nothrow_t const&)
{ OPERATOR_NEW_BODY(FROM_NEW_BR); }
{ OPERATOR_NEW_BODY(FROM_NEW_BR, true /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new(size_t size, std::align_val_t align)
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW); }
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW, false /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new[](size_t size, std::align_val_t align)
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW_BR); }
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW_BR, false /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new(size_t size, std::align_val_t align, std::nothrow_t const&)
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW); }
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW, true /*nothrow*/); }
CXX_OPERATOR_ATTRIBUTE
void *operator new[](size_t size, std::align_val_t align, std::nothrow_t const&)
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW_BR); }
{ OPERATOR_NEW_BODY_ALIGN(FROM_NEW_BR, true /*nothrow*/); }
#else // SANITIZER_MAC
INTERCEPTOR(void *, _Znwm, size_t size) {
OPERATOR_NEW_BODY(FROM_NEW);
OPERATOR_NEW_BODY(FROM_NEW, false /*nothrow*/);
}
INTERCEPTOR(void *, _Znam, size_t size) {
OPERATOR_NEW_BODY(FROM_NEW_BR);
OPERATOR_NEW_BODY(FROM_NEW_BR, false /*nothrow*/);
}
INTERCEPTOR(void *, _ZnwmRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(FROM_NEW);
OPERATOR_NEW_BODY(FROM_NEW, true /*nothrow*/);
}
INTERCEPTOR(void *, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(FROM_NEW_BR);
OPERATOR_NEW_BODY(FROM_NEW_BR, true /*nothrow*/);
}
#endif

View File

@ -199,19 +199,26 @@ INTERCEPTOR(int, mprobe, void *ptr) {
}
#endif // SANITIZER_INTERCEPT_MCHECK_MPROBE
#define OPERATOR_NEW_BODY \
ENSURE_LSAN_INITED; \
GET_STACK_TRACE_MALLOC; \
return Allocate(stack, size, 1, kAlwaysClearMemory);
// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
#define OPERATOR_NEW_BODY(nothrow) \
ENSURE_LSAN_INITED; \
GET_STACK_TRACE_MALLOC; \
void *res = Allocate(stack, size, 1, kAlwaysClearMemory);\
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM();\
return res;
INTERCEPTOR_ATTRIBUTE
void *operator new(size_t size) { OPERATOR_NEW_BODY; }
void *operator new(size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
INTERCEPTOR_ATTRIBUTE
void *operator new[](size_t size) { OPERATOR_NEW_BODY; }
void *operator new[](size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
INTERCEPTOR_ATTRIBUTE
void *operator new(size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; }
void *operator new(size_t size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(true /*nothrow*/);
}
INTERCEPTOR_ATTRIBUTE
void *operator new[](size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; }
void *operator new[](size_t size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(true /*nothrow*/);
}
#define OPERATOR_DELETE_BODY \
ENSURE_LSAN_INITED; \

View File

@ -14,6 +14,7 @@
#include "msan.h"
#include "interception/interception.h"
#include "sanitizer_common/sanitizer_allocator.h"
#if MSAN_REPLACE_OPERATORS_NEW_AND_DELETE
@ -27,18 +28,25 @@ namespace std {
} // namespace std
#define OPERATOR_NEW_BODY \
// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
#define OPERATOR_NEW_BODY(nothrow) \
GET_MALLOC_STACK_TRACE; \
return MsanReallocate(&stack, 0, size, sizeof(u64), false)
void *res = MsanReallocate(&stack, 0, size, sizeof(u64), false);\
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM();\
return res
INTERCEPTOR_ATTRIBUTE
void *operator new(size_t size) { OPERATOR_NEW_BODY; }
void *operator new(size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
INTERCEPTOR_ATTRIBUTE
void *operator new[](size_t size) { OPERATOR_NEW_BODY; }
void *operator new[](size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
INTERCEPTOR_ATTRIBUTE
void *operator new(size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; }
void *operator new(size_t size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(true /*nothrow*/);
}
INTERCEPTOR_ATTRIBUTE
void *operator new[](size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; }
void *operator new[](size_t size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(true /*nothrow*/);
}
#define OPERATOR_DELETE_BODY \
GET_MALLOC_STACK_TRACE; \

View File

@ -246,11 +246,11 @@ void *ReturnNullOrDieOnFailure::OnOOM() {
ReportAllocatorCannotReturnNull();
}
void *DieOnFailure::OnBadRequest() {
void NORETURN *DieOnFailure::OnBadRequest() {
ReportAllocatorCannotReturnNull();
}
void *DieOnFailure::OnOOM() {
void NORETURN *DieOnFailure::OnOOM() {
atomic_store_relaxed(&allocator_out_of_memory, 1);
ReportAllocatorCannotReturnNull();
}

View File

@ -39,8 +39,8 @@ struct ReturnNullOrDieOnFailure {
};
// Always dies on the failure.
struct DieOnFailure {
static void *OnBadRequest();
static void *OnOOM();
static void NORETURN *OnBadRequest();
static void NORETURN *OnOOM();
};
// Returns true if allocator detected OOM condition. Can be used to avoid memory

View File

@ -26,13 +26,18 @@ namespace std {
struct nothrow_t {};
} // namespace std
// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
CXX_OPERATOR_ATTRIBUTE
void *operator new(size_t size) {
return scudoMalloc(size, FromNew);
void *res = scudoMalloc(size, FromNew);
if (UNLIKELY(!res)) DieOnFailure::OnOOM();
return res;
}
CXX_OPERATOR_ATTRIBUTE
void *operator new[](size_t size) {
return scudoMalloc(size, FromNewArray);
void *res = scudoMalloc(size, FromNewArray);
if (UNLIKELY(!res)) DieOnFailure::OnOOM();
return res;
}
CXX_OPERATOR_ATTRIBUTE
void *operator new(size_t size, std::nothrow_t const&) {

View File

@ -12,6 +12,7 @@
// Interceptors for operators new and delete.
//===----------------------------------------------------------------------===//
#include "interception/interception.h"
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "tsan_interceptors.h"
@ -24,13 +25,15 @@ struct nothrow_t {};
DECLARE_REAL(void *, malloc, uptr size)
DECLARE_REAL(void, free, void *ptr)
#define OPERATOR_NEW_BODY(mangled_name) \
// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
#define OPERATOR_NEW_BODY(mangled_name, nothrow) \
if (cur_thread()->in_symbolizer) \
return InternalAlloc(size); \
void *p = 0; \
{ \
SCOPED_INTERCEPTOR_RAW(mangled_name, size); \
p = user_alloc(thr, pc, size); \
if (!nothrow && UNLIKELY(!p)) DieOnFailure::OnOOM(); \
} \
invoke_malloc_hook(p, size); \
return p;
@ -38,25 +41,25 @@ DECLARE_REAL(void, free, void *ptr)
SANITIZER_INTERFACE_ATTRIBUTE
void *operator new(__sanitizer::uptr size);
void *operator new(__sanitizer::uptr size) {
OPERATOR_NEW_BODY(_Znwm);
OPERATOR_NEW_BODY(_Znwm, false /*nothrow*/);
}
SANITIZER_INTERFACE_ATTRIBUTE
void *operator new[](__sanitizer::uptr size);
void *operator new[](__sanitizer::uptr size) {
OPERATOR_NEW_BODY(_Znam);
OPERATOR_NEW_BODY(_Znam, false /*nothrow*/);
}
SANITIZER_INTERFACE_ATTRIBUTE
void *operator new(__sanitizer::uptr size, std::nothrow_t const&);
void *operator new(__sanitizer::uptr size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(_ZnwmRKSt9nothrow_t);
OPERATOR_NEW_BODY(_ZnwmRKSt9nothrow_t, true /*nothrow*/);
}
SANITIZER_INTERFACE_ATTRIBUTE
void *operator new[](__sanitizer::uptr size, std::nothrow_t const&);
void *operator new[](__sanitizer::uptr size, std::nothrow_t const&) {
OPERATOR_NEW_BODY(_ZnamRKSt9nothrow_t);
OPERATOR_NEW_BODY(_ZnamRKSt9nothrow_t, true /*nothrow*/);
}
#define OPERATOR_DELETE_BODY(mangled_name) \

View File

@ -1,68 +1,97 @@
// Test the behavior of malloc/calloc/realloc when the allocation size is huge.
// Test the behavior of malloc/calloc/realloc/new when the allocation size is
// more than ASan allocator's max allowed one.
// By default (allocator_may_return_null=0) the process should crash.
// With allocator_may_return_null=1 the allocator should return 0.
// With allocator_may_return_null=1 the allocator should return 0, except the
// operator new(), which should crash anyway (operator new(std::nothrow) should
// return nullptr, indeed).
//
// RUN: %clangxx_asan -O0 %s -o %t
// RUN: not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t calloc 2>&1 | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t calloc 2>&1 | FileCheck %s --check-prefix=CHECK-cNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 | FileCheck %s --check-prefix=CHECK-coNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t realloc 2>&1 | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t realloc 2>&1 | FileCheck %s --check-prefix=CHECK-rNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mrNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrNULL
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnCRASH
// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnNULL
#include <limits.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <limits>
#include <new>
int main(int argc, char **argv) {
// Disable stderr buffering. Needed on Windows.
setvbuf(stderr, NULL, _IONBF, 0);
volatile size_t size = std::numeric_limits<size_t>::max() - 10000;
assert(argc == 2);
void *x = 0;
if (!strcmp(argv[1], "malloc")) {
fprintf(stderr, "malloc:\n");
x = malloc(size);
}
if (!strcmp(argv[1], "calloc")) {
fprintf(stderr, "calloc:\n");
x = calloc(size / 4, 4);
}
const char *action = argv[1];
fprintf(stderr, "%s:\n", action);
if (!strcmp(argv[1], "calloc-overflow")) {
fprintf(stderr, "calloc-overflow:\n");
static const size_t kMaxAllowedMallocSizePlusOne =
#if __LP64__ || defined(_WIN64)
(1ULL << 40) + 1;
#else
(3UL << 30) + 1;
#endif
void *x = 0;
if (!strcmp(action, "malloc")) {
x = malloc(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "calloc")) {
x = calloc((kMaxAllowedMallocSizePlusOne / 4) + 1, 4);
} else if (!strcmp(action, "calloc-overflow")) {
volatile size_t kMaxSizeT = std::numeric_limits<size_t>::max();
size_t kArraySize = 4096;
volatile size_t kArraySize2 = kMaxSizeT / kArraySize + 10;
x = calloc(kArraySize, kArraySize2);
}
if (!strcmp(argv[1], "realloc")) {
fprintf(stderr, "realloc:\n");
x = realloc(0, size);
}
if (!strcmp(argv[1], "realloc-after-malloc")) {
fprintf(stderr, "realloc-after-malloc:\n");
} else if (!strcmp(action, "realloc")) {
x = realloc(0, kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "realloc-after-malloc")) {
char *t = (char*)malloc(100);
*t = 42;
x = realloc(t, size);
x = realloc(t, kMaxAllowedMallocSizePlusOne);
assert(*t == 42);
free(t);
} else if (!strcmp(action, "new")) {
x = operator new(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "new-nothrow")) {
x = operator new(kMaxAllowedMallocSizePlusOne, std::nothrow);
} else {
assert(0);
}
// The NULL pointer is printed differently on different systems, while (long)0
// is always the same.
fprintf(stderr, "x: %lx\n", (long)x);
free(x);
return x != 0;
}
// CHECK-mCRASH: malloc:
// CHECK-mCRASH: AddressSanitizer's allocator is terminating the process
// CHECK-cCRASH: calloc:
@ -73,6 +102,10 @@ int main(int argc, char **argv) {
// CHECK-rCRASH: AddressSanitizer's allocator is terminating the process
// CHECK-mrCRASH: realloc-after-malloc:
// CHECK-mrCRASH: AddressSanitizer's allocator is terminating the process
// CHECK-nCRASH: new:
// CHECK-nCRASH: AddressSanitizer's allocator is terminating the process
// CHECK-nnCRASH: new-nothrow:
// CHECK-nnCRASH: AddressSanitizer's allocator is terminating the process
// CHECK-mNULL: malloc:
// CHECK-mNULL: x: 0
@ -84,3 +117,5 @@ int main(int argc, char **argv) {
// CHECK-rNULL: x: 0
// CHECK-mrNULL: realloc-after-malloc:
// CHECK-mrNULL: x: 0
// CHECK-nnNULL: new-nothrow:
// CHECK-nnNULL: x: 0

View File

@ -1,63 +1,98 @@
// Test the behavior of malloc/calloc/realloc when the allocation size is huge.
// Test the behavior of malloc/calloc/realloc/new when the allocation size is
// more than MSan allocator's max allowed one.
// By default (allocator_may_return_null=0) the process should crash.
// With allocator_may_return_null=1 the allocator should return 0.
// With allocator_may_return_null=1 the allocator should return 0, except the
// operator new(), which should crash anyway (operator new(std::nothrow) should
// return nullptr, indeed).
//
// RUN: %clangxx_msan -O0 %s -o %t
// RUN: not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t calloc 2>&1 | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t calloc 2>&1 | FileCheck %s --check-prefix=CHECK-cNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 | FileCheck %s --check-prefix=CHECK-coNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t realloc 2>&1 | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t realloc 2>&1 | FileCheck %s --check-prefix=CHECK-rNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mrNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrNULL
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnCRASH
// RUN: MSAN_OPTIONS=allocator_may_return_null=1 %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnNULL
#include <limits.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <limits>
int main(int argc, char **argv) {
volatile size_t size = std::numeric_limits<size_t>::max() - 10000;
assert(argc == 2);
char *x = 0;
if (!strcmp(argv[1], "malloc")) {
fprintf(stderr, "malloc:\n");
x = (char*)malloc(size);
}
if (!strcmp(argv[1], "calloc")) {
fprintf(stderr, "calloc:\n");
x = (char*)calloc(size / 4, 4);
}
#include <new>
if (!strcmp(argv[1], "calloc-overflow")) {
fprintf(stderr, "calloc-overflow:\n");
int main(int argc, char **argv) {
// Disable stderr buffering. Needed on Windows.
setvbuf(stderr, NULL, _IONBF, 0);
assert(argc == 2);
const char *action = argv[1];
fprintf(stderr, "%s:\n", action);
static const size_t kMaxAllowedMallocSizePlusOne =
#if __LP64__ || defined(_WIN64)
(8UL << 30) + 1;
#else
(2UL << 30) + 1;
#endif
void *x = 0;
if (!strcmp(action, "malloc")) {
x = malloc(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "calloc")) {
x = calloc((kMaxAllowedMallocSizePlusOne / 4) + 1, 4);
} else if (!strcmp(action, "calloc-overflow")) {
volatile size_t kMaxSizeT = std::numeric_limits<size_t>::max();
size_t kArraySize = 4096;
volatile size_t kArraySize2 = kMaxSizeT / kArraySize + 10;
x = (char*)calloc(kArraySize, kArraySize2);
}
if (!strcmp(argv[1], "realloc")) {
fprintf(stderr, "realloc:\n");
x = (char*)realloc(0, size);
}
if (!strcmp(argv[1], "realloc-after-malloc")) {
fprintf(stderr, "realloc-after-malloc:\n");
x = calloc(kArraySize, kArraySize2);
} else if (!strcmp(action, "realloc")) {
x = realloc(0, kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "realloc-after-malloc")) {
char *t = (char*)malloc(100);
*t = 42;
x = (char*)realloc(t, size);
x = realloc(t, kMaxAllowedMallocSizePlusOne);
assert(*t == 42);
free(t);
} else if (!strcmp(action, "new")) {
x = operator new(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "new-nothrow")) {
x = operator new(kMaxAllowedMallocSizePlusOne, std::nothrow);
} else {
assert(0);
}
// The NULL pointer is printed differently on different systems, while (long)0
// is always the same.
fprintf(stderr, "x: %lx\n", (long)x);
free(x);
return x != 0;
}
// CHECK-mCRASH: malloc:
// CHECK-mCRASH: MemorySanitizer's allocator is terminating the process
// CHECK-cCRASH: calloc:
@ -68,6 +103,10 @@ int main(int argc, char **argv) {
// CHECK-rCRASH: MemorySanitizer's allocator is terminating the process
// CHECK-mrCRASH: realloc-after-malloc:
// CHECK-mrCRASH: MemorySanitizer's allocator is terminating the process
// CHECK-nCRASH: new:
// CHECK-nCRASH: MemorySanitizer's allocator is terminating the process
// CHECK-nnCRASH: new-nothrow:
// CHECK-nnCRASH: MemorySanitizer's allocator is terminating the process
// CHECK-mNULL: malloc:
// CHECK-mNULL: x: 0
@ -79,3 +118,5 @@ int main(int argc, char **argv) {
// CHECK-rNULL: x: 0
// CHECK-mrNULL: realloc-after-malloc:
// CHECK-mrNULL: x: 0
// CHECK-nnNULL: new-nothrow:
// CHECK-nnNULL: x: 0

View File

@ -1,8 +1,12 @@
// RUN: %clang_scudo %s -o %t
// RUN: %clang_scudo %s -lstdc++ -o %t
// RUN: SCUDO_OPTIONS=allocator_may_return_null=0 not %run %t malloc 2>&1 | FileCheck %s
// RUN: SCUDO_OPTIONS=allocator_may_return_null=1 %run %t malloc 2>&1
// RUN: SCUDO_OPTIONS=allocator_may_return_null=0 not %run %t calloc 2>&1 | FileCheck %s
// RUN: SCUDO_OPTIONS=allocator_may_return_null=1 %run %t calloc 2>&1
// RUN: SCUDO_OPTIONS=allocator_may_return_null=0 not %run %t new 2>&1 | FileCheck %s
// RUN: SCUDO_OPTIONS=allocator_may_return_null=1 not %run %t new 2>&1 | FileCheck %s
// RUN: SCUDO_OPTIONS=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 | FileCheck %s
// RUN: SCUDO_OPTIONS=allocator_may_return_null=1 %run %t new-nothrow 2>&1
// RUN: %run %t usable 2>&1
// Tests for various edge cases related to sizes, notably the maximum size the
@ -15,26 +19,38 @@
#include <string.h>
#include <limits>
#include <new>
int main(int argc, char **argv)
{
int main(int argc, char **argv) {
assert(argc == 2);
if (!strcmp(argv[1], "malloc")) {
// Currently the maximum size the allocator can allocate is 1ULL<<40 bytes.
size_t size = std::numeric_limits<size_t>::max();
void *p = malloc(size);
const char *action = argv[1];
fprintf(stderr, "%s:\n", action);
#if __LP64__ || defined(_WIN64)
static const size_t kMaxAllowedMallocSize = 1ULL << 40;
static const size_t kChunkHeaderSize = 16;
#else
static const size_t kMaxAllowedMallocSize = 2UL << 30;
static const size_t kChunkHeaderSize = 8;
#endif
if (!strcmp(action, "malloc")) {
void *p = malloc(kMaxAllowedMallocSize);
assert(!p);
size = (1ULL << 40) - 16;
p = malloc(size);
p = malloc(kMaxAllowedMallocSize - kChunkHeaderSize);
assert(!p);
}
if (!strcmp(argv[1], "calloc")) {
} else if (!strcmp(action, "calloc")) {
// Trigger an overflow in calloc.
size_t size = std::numeric_limits<size_t>::max();
void *p = calloc((size / 0x1000) + 1, 0x1000);
assert(!p);
}
if (!strcmp(argv[1], "usable")) {
} else if (!strcmp(action, "new")) {
void *p = operator new(kMaxAllowedMallocSize);
assert(!p);
} else if (!strcmp(action, "new-nothrow")) {
void *p = operator new(kMaxAllowedMallocSize, std::nothrow);
assert(!p);
} else if (!strcmp(action, "usable")) {
// Playing with the actual usable size of a chunk.
void *p = malloc(1007);
assert(p);
@ -47,7 +63,10 @@ int main(int argc, char **argv)
assert(size >= 2014);
memset(p, 'B', size);
free(p);
} else {
assert(0);
}
return 0;
}

View File

@ -1,56 +1,90 @@
// Test the behavior of malloc/calloc/realloc when the allocation size is huge.
// Test the behavior of malloc/calloc/realloc/new when the allocation size is
// more than TSan allocator's max allowed one.
// By default (allocator_may_return_null=0) the process should crash.
// With allocator_may_return_null=1 the allocator should return 0.
// With allocator_may_return_null=1 the allocator should return 0, except the
// operator new(), which should crash anyway (operator new(std::nothrow) should
// return nullptr, indeed).
//
// RUN: %clangxx_tsan -O0 %s -o %t
// RUN: not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t calloc 2>&1 | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t realloc 2>&1 | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mNULL
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cNULL
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coNULL
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rNULL
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrNULL
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnCRASH
// RUN: %env_tsan_opts=allocator_may_return_null=1 %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnNULL
#include <limits.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <limits>
int main(int argc, char **argv) {
volatile size_t size = std::numeric_limits<size_t>::max() - 10000;
assert(argc == 2);
char *x = 0;
if (!strcmp(argv[1], "malloc")) {
fprintf(stderr, "malloc:\n");
x = (char*)malloc(size);
}
if (!strcmp(argv[1], "calloc")) {
fprintf(stderr, "calloc:\n");
x = (char*)calloc(size / 4, 4);
}
#include <new>
if (!strcmp(argv[1], "calloc-overflow")) {
fprintf(stderr, "calloc-overflow:\n");
int main(int argc, char **argv) {
// Disable stderr buffering. Needed on Windows.
setvbuf(stderr, NULL, _IONBF, 0);
assert(argc == 2);
const char *action = argv[1];
fprintf(stderr, "%s:\n", action);
static const size_t kMaxAllowedMallocSizePlusOne = (1ULL << 40) + 1;
void *x = 0;
if (!strcmp(action, "malloc")) {
x = malloc(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "calloc")) {
x = calloc((kMaxAllowedMallocSizePlusOne / 4) + 1, 4);
} else if (!strcmp(action, "calloc-overflow")) {
volatile size_t kMaxSizeT = std::numeric_limits<size_t>::max();
size_t kArraySize = 4096;
volatile size_t kArraySize2 = kMaxSizeT / kArraySize + 10;
x = (char*)calloc(kArraySize, kArraySize2);
}
if (!strcmp(argv[1], "realloc")) {
fprintf(stderr, "realloc:\n");
x = (char*)realloc(0, size);
}
if (!strcmp(argv[1], "realloc-after-malloc")) {
fprintf(stderr, "realloc-after-malloc:\n");
x = calloc(kArraySize, kArraySize2);
} else if (!strcmp(action, "realloc")) {
x = realloc(0, kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "realloc-after-malloc")) {
char *t = (char*)malloc(100);
*t = 42;
x = (char*)realloc(t, size);
x = realloc(t, kMaxAllowedMallocSizePlusOne);
assert(*t == 42);
} else if (!strcmp(action, "new")) {
x = operator new(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "new-nothrow")) {
x = operator new(kMaxAllowedMallocSizePlusOne, std::nothrow);
} else {
assert(0);
}
fprintf(stderr, "x: %p\n", x);
// The NULL pointer is printed differently on different systems, while (long)0
// is always the same.
fprintf(stderr, "x: %lx\n", (long)x);
free(x);
return x != 0;
}
// CHECK-mCRASH: malloc:
// CHECK-mCRASH: ThreadSanitizer's allocator is terminating the process
// CHECK-cCRASH: calloc:
@ -61,4 +95,20 @@ int main(int argc, char **argv) {
// CHECK-rCRASH: ThreadSanitizer's allocator is terminating the process
// CHECK-mrCRASH: realloc-after-malloc:
// CHECK-mrCRASH: ThreadSanitizer's allocator is terminating the process
// CHECK-nCRASH: new:
// CHECK-nCRASH: ThreadSanitizer's allocator is terminating the process
// CHECK-nnCRASH: new-nothrow:
// CHECK-nnCRASH: ThreadSanitizer's allocator is terminating the process
// CHECK-mNULL: malloc:
// CHECK-mNULL: x: 0
// CHECK-cNULL: calloc:
// CHECK-cNULL: x: 0
// CHECK-coNULL: calloc-overflow:
// CHECK-coNULL: x: 0
// CHECK-rNULL: realloc:
// CHECK-rNULL: x: 0
// CHECK-mrNULL: realloc-after-malloc:
// CHECK-mrNULL: x: 0
// CHECK-nnNULL: new-nothrow:
// CHECK-nnNULL: x: 0