Skip to content

Commit d3213c7

Browse files
committedJun 30, 2017
[LSan] Make LSan allocator allocator_may_return_null compliant
Summary: An attempt to reland D34786 (which caused bot failres on Mac), now with properly intercepted operators new() and delete(). LSan allocator used to always return nullptr on too big allocation requests (the definition of "too big" depends on platform and bitness), now it follows policy configured by allocator_may_return_null flag Reviewers: eugenis Subscribers: llvm-commits Differential Revision: https://reviews.llvm.org/D34845 llvm-svn: 306845
1 parent f0d6169 commit d3213c7

File tree

3 files changed

+169
-16
lines changed

3 files changed

+169
-16
lines changed
 

‎compiler-rt/lib/lsan/lsan_allocator.cc

+4-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void *Allocate(const StackTrace &stack, uptr size, uptr alignment,
7474
size = 1;
7575
if (size > kMaxAllowedMallocSize) {
7676
Report("WARNING: LeakSanitizer failed to allocate %zu bytes\n", size);
77-
return nullptr;
77+
return Allocator::FailureHandler::OnBadRequest();
7878
}
7979
void *p = allocator.Allocate(GetAllocatorCache(), size, alignment);
8080
// Do not rely on the allocator to clear the memory (it's slow).
@@ -99,7 +99,7 @@ void *Reallocate(const StackTrace &stack, void *p, uptr new_size,
9999
if (new_size > kMaxAllowedMallocSize) {
100100
Report("WARNING: LeakSanitizer failed to allocate %zu bytes\n", new_size);
101101
allocator.Deallocate(GetAllocatorCache(), p);
102-
return nullptr;
102+
return Allocator::FailureHandler::OnBadRequest();
103103
}
104104
p = allocator.Reallocate(GetAllocatorCache(), p, new_size, alignment);
105105
RegisterAllocation(stack, p, new_size);
@@ -134,6 +134,8 @@ void *lsan_realloc(void *p, uptr size, const StackTrace &stack) {
134134
}
135135

136136
void *lsan_calloc(uptr nmemb, uptr size, const StackTrace &stack) {
137+
if (CheckForCallocOverflow(size, nmemb))
138+
return Allocator::FailureHandler::OnBadRequest();
137139
size *= nmemb;
138140
return Allocate(stack, size, 1, true);
139141
}

‎compiler-rt/lib/lsan/lsan_interceptors.cc

+42-14
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
7070
CHECK(allocated < kCallocPoolSize);
7171
return mem;
7272
}
73-
if (CheckForCallocOverflow(size, nmemb)) return nullptr;
7473
ENSURE_LSAN_INITED;
7574
GET_STACK_TRACE_MALLOC;
7675
return lsan_calloc(nmemb, size, stack);
@@ -199,6 +198,7 @@ INTERCEPTOR(int, mprobe, void *ptr) {
199198
}
200199
#endif // SANITIZER_INTERCEPT_MCHECK_MPROBE
201200

201+
202202
// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
203203
#define OPERATOR_NEW_BODY(nothrow) \
204204
ENSURE_LSAN_INITED; \
@@ -207,22 +207,28 @@ INTERCEPTOR(int, mprobe, void *ptr) {
207207
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM();\
208208
return res;
209209

210+
#define OPERATOR_DELETE_BODY \
211+
ENSURE_LSAN_INITED; \
212+
Deallocate(ptr);
213+
214+
// On OS X it's not enough to just provide our own 'operator new' and
215+
// 'operator delete' implementations, because they're going to be in the runtime
216+
// dylib, and the main executable will depend on both the runtime dylib and
217+
// libstdc++, each of has its implementation of new and delete.
218+
// To make sure that C++ allocation/deallocation operators are overridden on
219+
// OS X we need to intercept them using their mangled names.
220+
#if !SANITIZER_MAC
221+
210222
INTERCEPTOR_ATTRIBUTE
211223
void *operator new(size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
212224
INTERCEPTOR_ATTRIBUTE
213225
void *operator new[](size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
214226
INTERCEPTOR_ATTRIBUTE
215-
void *operator new(size_t size, std::nothrow_t const&) {
216-
OPERATOR_NEW_BODY(true /*nothrow*/);
217-
}
227+
void *operator new(size_t size, std::nothrow_t const&)
228+
{ OPERATOR_NEW_BODY(true /*nothrow*/); }
218229
INTERCEPTOR_ATTRIBUTE
219-
void *operator new[](size_t size, std::nothrow_t const&) {
220-
OPERATOR_NEW_BODY(true /*nothrow*/);
221-
}
222-
223-
#define OPERATOR_DELETE_BODY \
224-
ENSURE_LSAN_INITED; \
225-
Deallocate(ptr);
230+
void *operator new[](size_t size, std::nothrow_t const&)
231+
{ OPERATOR_NEW_BODY(true /*nothrow*/); }
226232

227233
INTERCEPTOR_ATTRIBUTE
228234
void operator delete(void *ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
@@ -231,9 +237,31 @@ void operator delete[](void *ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
231237
INTERCEPTOR_ATTRIBUTE
232238
void operator delete(void *ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; }
233239
INTERCEPTOR_ATTRIBUTE
234-
void operator delete[](void *ptr, std::nothrow_t const &) {
235-
OPERATOR_DELETE_BODY;
236-
}
240+
void operator delete[](void *ptr, std::nothrow_t const &)
241+
{ OPERATOR_DELETE_BODY; }
242+
243+
#else // SANITIZER_MAC
244+
245+
INTERCEPTOR(void *, _Znwm, size_t size)
246+
{ OPERATOR_NEW_BODY(false /*nothrow*/); }
247+
INTERCEPTOR(void *, _Znam, size_t size)
248+
{ OPERATOR_NEW_BODY(false /*nothrow*/); }
249+
INTERCEPTOR(void *, _ZnwmRKSt9nothrow_t, size_t size, std::nothrow_t const&)
250+
{ OPERATOR_NEW_BODY(true /*nothrow*/); }
251+
INTERCEPTOR(void *, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const&)
252+
{ OPERATOR_NEW_BODY(true /*nothrow*/); }
253+
254+
INTERCEPTOR(void, _ZdlPv, void *ptr)
255+
{ OPERATOR_DELETE_BODY; }
256+
INTERCEPTOR(void, _ZdaPv, void *ptr)
257+
{ OPERATOR_DELETE_BODY; }
258+
INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void *ptr, std::nothrow_t const&)
259+
{ OPERATOR_DELETE_BODY; }
260+
INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void *ptr, std::nothrow_t const&)
261+
{ OPERATOR_DELETE_BODY; }
262+
263+
#endif // !SANITIZER_MAC
264+
237265

238266
///// Thread initialization and finalization. /////
239267

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Test the behavior of malloc/calloc/realloc/new when the allocation size is
2+
// more than LSan allocator's max allowed one.
3+
// By default (allocator_may_return_null=0) the process should crash.
4+
// With allocator_may_return_null=1 the allocator should return 0, except the
5+
// operator new(), which should crash anyway (operator new(std::nothrow) should
6+
// return nullptr, indeed).
7+
//
8+
// RUN: %clangxx_lsan -O0 %s -o %t
9+
// RUN: not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
10+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t malloc 2>&1 \
11+
// RUN: | FileCheck %s --check-prefix=CHECK-mCRASH
12+
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t malloc 2>&1 \
13+
// RUN: | FileCheck %s --check-prefix=CHECK-mNULL
14+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t calloc 2>&1 \
15+
// RUN: | FileCheck %s --check-prefix=CHECK-cCRASH
16+
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t calloc 2>&1 \
17+
// RUN: | FileCheck %s --check-prefix=CHECK-cNULL
18+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 \
19+
// RUN: | FileCheck %s --check-prefix=CHECK-coCRASH
20+
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 \
21+
// RUN: | FileCheck %s --check-prefix=CHECK-coNULL
22+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t realloc 2>&1 \
23+
// RUN: | FileCheck %s --check-prefix=CHECK-rCRASH
24+
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t realloc 2>&1 \
25+
// RUN: | FileCheck %s --check-prefix=CHECK-rNULL
26+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 \
27+
// RUN: | FileCheck %s --check-prefix=CHECK-mrCRASH
28+
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 \
29+
// RUN: | FileCheck %s --check-prefix=CHECK-mrNULL
30+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t new 2>&1 \
31+
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
32+
// RUN: %env_lsan_opts=allocator_may_return_null=1 not %run %t new 2>&1 \
33+
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
34+
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 \
35+
// RUN: | FileCheck %s --check-prefix=CHECK-nnCRASH
36+
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t new-nothrow 2>&1 \
37+
// RUN: | FileCheck %s --check-prefix=CHECK-nnNULL
38+
39+
#include <assert.h>
40+
#include <string.h>
41+
#include <stdio.h>
42+
#include <stdlib.h>
43+
#include <limits>
44+
#include <new>
45+
46+
int main(int argc, char **argv) {
47+
// Disable stderr buffering. Needed on Windows.
48+
setvbuf(stderr, NULL, _IONBF, 0);
49+
50+
assert(argc == 2);
51+
const char *action = argv[1];
52+
fprintf(stderr, "%s:\n", action);
53+
54+
// Use max of ASan and LSan allocator limits to cover both "lsan" and
55+
// "lsan + asan" configs.
56+
static const size_t kMaxAllowedMallocSizePlusOne =
57+
#if __LP64__ || defined(_WIN64)
58+
(1ULL << 40) + 1;
59+
#else
60+
(3UL << 30) + 1;
61+
#endif
62+
63+
void *x = 0;
64+
if (!strcmp(action, "malloc")) {
65+
x = malloc(kMaxAllowedMallocSizePlusOne);
66+
} else if (!strcmp(action, "calloc")) {
67+
x = calloc((kMaxAllowedMallocSizePlusOne / 4) + 1, 4);
68+
} else if (!strcmp(action, "calloc-overflow")) {
69+
volatile size_t kMaxSizeT = std::numeric_limits<size_t>::max();
70+
size_t kArraySize = 4096;
71+
volatile size_t kArraySize2 = kMaxSizeT / kArraySize + 10;
72+
x = calloc(kArraySize, kArraySize2);
73+
} else if (!strcmp(action, "realloc")) {
74+
x = realloc(0, kMaxAllowedMallocSizePlusOne);
75+
} else if (!strcmp(action, "realloc-after-malloc")) {
76+
char *t = (char*)malloc(100);
77+
*t = 42;
78+
x = realloc(t, kMaxAllowedMallocSizePlusOne);
79+
assert(*t == 42);
80+
free(t);
81+
} else if (!strcmp(action, "new")) {
82+
x = operator new(kMaxAllowedMallocSizePlusOne);
83+
} else if (!strcmp(action, "new-nothrow")) {
84+
x = operator new(kMaxAllowedMallocSizePlusOne, std::nothrow);
85+
} else {
86+
assert(0);
87+
}
88+
89+
// The NULL pointer is printed differently on different systems, while (long)0
90+
// is always the same.
91+
fprintf(stderr, "x: %zu\n", (size_t)x);
92+
free(x);
93+
94+
return x != 0;
95+
}
96+
97+
// CHECK-mCRASH: malloc:
98+
// CHECK-mCRASH: Sanitizer's allocator is terminating the process
99+
// CHECK-cCRASH: calloc:
100+
// CHECK-cCRASH: Sanitizer's allocator is terminating the process
101+
// CHECK-coCRASH: calloc-overflow:
102+
// CHECK-coCRASH: Sanitizer's allocator is terminating the process
103+
// CHECK-rCRASH: realloc:
104+
// CHECK-rCRASH: Sanitizer's allocator is terminating the process
105+
// CHECK-mrCRASH: realloc-after-malloc:
106+
// CHECK-mrCRASH: Sanitizer's allocator is terminating the process
107+
// CHECK-nCRASH: new:
108+
// CHECK-nCRASH: Sanitizer's allocator is terminating the process
109+
// CHECK-nnCRASH: new-nothrow:
110+
// CHECK-nnCRASH: Sanitizer's allocator is terminating the process
111+
112+
// CHECK-mNULL: malloc:
113+
// CHECK-mNULL: x: 0
114+
// CHECK-cNULL: calloc:
115+
// CHECK-cNULL: x: 0
116+
// CHECK-coNULL: calloc-overflow:
117+
// CHECK-coNULL: x: 0
118+
// CHECK-rNULL: realloc:
119+
// CHECK-rNULL: x: 0
120+
// CHECK-mrNULL: realloc-after-malloc:
121+
// CHECK-mrNULL: x: 0
122+
// CHECK-nnNULL: new-nothrow:
123+
// CHECK-nnNULL: x: 0

0 commit comments

Comments
 (0)
Please sign in to comment.