1 //===-- asan_malloc_linux.cpp ---------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file is a part of AddressSanitizer, an address sanity checker.
10 //
11 // Linux-specific malloc interception.
12 // We simply define functions like malloc, free, realloc, etc.
13 // They will replace the corresponding libc functions automagically.
14 //===----------------------------------------------------------------------===//
15
16 #include "sanitizer_common/sanitizer_platform.h"
17 #if SANITIZER_FREEBSD || SANITIZER_FUCHSIA || SANITIZER_LINUX || \
18 SANITIZER_NETBSD || SANITIZER_SOLARIS
19
20 # include "asan_allocator.h"
21 # include "asan_interceptors.h"
22 # include "asan_internal.h"
23 # include "asan_stack.h"
24 # include "sanitizer_common/sanitizer_allocator_checks.h"
25 # include "sanitizer_common/sanitizer_errno.h"
26 # include "sanitizer_common/sanitizer_tls_get_addr.h"
27
28 // ---------------------- Replacement functions ---------------- {{{1
29 using namespace __asan;
30
31 static uptr allocated_for_dlsym;
32 static uptr last_dlsym_alloc_size_in_words;
33 static const uptr kDlsymAllocPoolSize = 1024;
34 static uptr alloc_memory_for_dlsym[kDlsymAllocPoolSize];
35
IsInDlsymAllocPool(const void * ptr)36 static inline bool IsInDlsymAllocPool(const void *ptr) {
37 uptr off = (uptr)ptr - (uptr)alloc_memory_for_dlsym;
38 return off < allocated_for_dlsym * sizeof(alloc_memory_for_dlsym[0]);
39 }
40
AllocateFromLocalPool(uptr size_in_bytes)41 static void *AllocateFromLocalPool(uptr size_in_bytes) {
42 uptr size_in_words = RoundUpTo(size_in_bytes, kWordSize) / kWordSize;
43 void *mem = (void*)&alloc_memory_for_dlsym[allocated_for_dlsym];
44 last_dlsym_alloc_size_in_words = size_in_words;
45 allocated_for_dlsym += size_in_words;
46 CHECK_LT(allocated_for_dlsym, kDlsymAllocPoolSize);
47 return mem;
48 }
49
DeallocateFromLocalPool(const void * ptr)50 static void DeallocateFromLocalPool(const void *ptr) {
51 // Hack: since glibc 2.27 dlsym no longer uses stack-allocated memory to store
52 // error messages and instead uses malloc followed by free. To avoid pool
53 // exhaustion due to long object filenames, handle that special case here.
54 uptr prev_offset = allocated_for_dlsym - last_dlsym_alloc_size_in_words;
55 void *prev_mem = (void*)&alloc_memory_for_dlsym[prev_offset];
56 if (prev_mem == ptr) {
57 REAL(memset)(prev_mem, 0, last_dlsym_alloc_size_in_words * kWordSize);
58 allocated_for_dlsym = prev_offset;
59 last_dlsym_alloc_size_in_words = 0;
60 }
61 }
62
PosixMemalignFromLocalPool(void ** memptr,uptr alignment,uptr size_in_bytes)63 static int PosixMemalignFromLocalPool(void **memptr, uptr alignment,
64 uptr size_in_bytes) {
65 if (UNLIKELY(!CheckPosixMemalignAlignment(alignment)))
66 return errno_EINVAL;
67
68 CHECK(alignment >= kWordSize);
69
70 uptr addr = (uptr)&alloc_memory_for_dlsym[allocated_for_dlsym];
71 uptr aligned_addr = RoundUpTo(addr, alignment);
72 uptr aligned_size = RoundUpTo(size_in_bytes, kWordSize);
73
74 uptr *end_mem = (uptr*)(aligned_addr + aligned_size);
75 uptr allocated = end_mem - alloc_memory_for_dlsym;
76 if (allocated >= kDlsymAllocPoolSize)
77 return errno_ENOMEM;
78
79 allocated_for_dlsym = allocated;
80 *memptr = (void*)aligned_addr;
81 return 0;
82 }
83
MaybeInDlsym()84 static inline bool MaybeInDlsym() {
85 // Fuchsia doesn't use dlsym-based interceptors.
86 return !SANITIZER_FUCHSIA && asan_init_is_running;
87 }
88
UseLocalPool()89 static inline bool UseLocalPool() { return MaybeInDlsym(); }
90
ReallocFromLocalPool(void * ptr,uptr size)91 static void *ReallocFromLocalPool(void *ptr, uptr size) {
92 const uptr offset = (uptr)ptr - (uptr)alloc_memory_for_dlsym;
93 const uptr copy_size = Min(size, kDlsymAllocPoolSize - offset);
94 void *new_ptr;
95 if (UNLIKELY(UseLocalPool())) {
96 new_ptr = AllocateFromLocalPool(size);
97 } else {
98 ENSURE_ASAN_INITED();
99 GET_STACK_TRACE_MALLOC;
100 new_ptr = asan_malloc(size, &stack);
101 }
102 internal_memcpy(new_ptr, ptr, copy_size);
103 return new_ptr;
104 }
105
INTERCEPTOR(void,free,void * ptr)106 INTERCEPTOR(void, free, void *ptr) {
107 if (UNLIKELY(IsInDlsymAllocPool(ptr))) {
108 DeallocateFromLocalPool(ptr);
109 return;
110 }
111 GET_STACK_TRACE_FREE;
112 asan_free(ptr, &stack, FROM_MALLOC);
113 }
114
115 #if SANITIZER_INTERCEPT_CFREE
INTERCEPTOR(void,cfree,void * ptr)116 INTERCEPTOR(void, cfree, void *ptr) {
117 if (UNLIKELY(IsInDlsymAllocPool(ptr)))
118 return;
119 GET_STACK_TRACE_FREE;
120 asan_free(ptr, &stack, FROM_MALLOC);
121 }
122 #endif // SANITIZER_INTERCEPT_CFREE
123
INTERCEPTOR(void *,malloc,uptr size)124 INTERCEPTOR(void*, malloc, uptr size) {
125 if (UNLIKELY(UseLocalPool()))
126 // Hack: dlsym calls malloc before REAL(malloc) is retrieved from dlsym.
127 return AllocateFromLocalPool(size);
128 ENSURE_ASAN_INITED();
129 GET_STACK_TRACE_MALLOC;
130 return asan_malloc(size, &stack);
131 }
132
INTERCEPTOR(void *,calloc,uptr nmemb,uptr size)133 INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
134 if (UNLIKELY(UseLocalPool()))
135 // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
136 return AllocateFromLocalPool(nmemb * size);
137 ENSURE_ASAN_INITED();
138 GET_STACK_TRACE_MALLOC;
139 return asan_calloc(nmemb, size, &stack);
140 }
141
INTERCEPTOR(void *,realloc,void * ptr,uptr size)142 INTERCEPTOR(void*, realloc, void *ptr, uptr size) {
143 if (UNLIKELY(IsInDlsymAllocPool(ptr)))
144 return ReallocFromLocalPool(ptr, size);
145 if (UNLIKELY(UseLocalPool()))
146 return AllocateFromLocalPool(size);
147 ENSURE_ASAN_INITED();
148 GET_STACK_TRACE_MALLOC;
149 return asan_realloc(ptr, size, &stack);
150 }
151
152 #if SANITIZER_INTERCEPT_REALLOCARRAY
INTERCEPTOR(void *,reallocarray,void * ptr,uptr nmemb,uptr size)153 INTERCEPTOR(void*, reallocarray, void *ptr, uptr nmemb, uptr size) {
154 ENSURE_ASAN_INITED();
155 GET_STACK_TRACE_MALLOC;
156 return asan_reallocarray(ptr, nmemb, size, &stack);
157 }
158 #endif // SANITIZER_INTERCEPT_REALLOCARRAY
159
160 #if SANITIZER_INTERCEPT_MEMALIGN
INTERCEPTOR(void *,memalign,uptr boundary,uptr size)161 INTERCEPTOR(void*, memalign, uptr boundary, uptr size) {
162 GET_STACK_TRACE_MALLOC;
163 return asan_memalign(boundary, size, &stack, FROM_MALLOC);
164 }
165
INTERCEPTOR(void *,__libc_memalign,uptr boundary,uptr size)166 INTERCEPTOR(void*, __libc_memalign, uptr boundary, uptr size) {
167 GET_STACK_TRACE_MALLOC;
168 void *res = asan_memalign(boundary, size, &stack, FROM_MALLOC);
169 DTLS_on_libc_memalign(res, size);
170 return res;
171 }
172 #endif // SANITIZER_INTERCEPT_MEMALIGN
173
174 #if SANITIZER_INTERCEPT_ALIGNED_ALLOC
INTERCEPTOR(void *,aligned_alloc,uptr boundary,uptr size)175 INTERCEPTOR(void*, aligned_alloc, uptr boundary, uptr size) {
176 GET_STACK_TRACE_MALLOC;
177 return asan_aligned_alloc(boundary, size, &stack);
178 }
179 #endif // SANITIZER_INTERCEPT_ALIGNED_ALLOC
180
INTERCEPTOR(uptr,malloc_usable_size,void * ptr)181 INTERCEPTOR(uptr, malloc_usable_size, void *ptr) {
182 GET_CURRENT_PC_BP_SP;
183 (void)sp;
184 return asan_malloc_usable_size(ptr, pc, bp);
185 }
186
187 #if SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
188 // We avoid including malloc.h for portability reasons.
189 // man mallinfo says the fields are "long", but the implementation uses int.
190 // It doesn't matter much -- we just need to make sure that the libc's mallinfo
191 // is not called.
192 struct fake_mallinfo {
193 int x[10];
194 };
195
INTERCEPTOR(struct fake_mallinfo,mallinfo,void)196 INTERCEPTOR(struct fake_mallinfo, mallinfo, void) {
197 struct fake_mallinfo res;
198 REAL(memset)(&res, 0, sizeof(res));
199 return res;
200 }
201
INTERCEPTOR(int,mallopt,int cmd,int value)202 INTERCEPTOR(int, mallopt, int cmd, int value) {
203 return 0;
204 }
205 #endif // SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
206
INTERCEPTOR(int,posix_memalign,void ** memptr,uptr alignment,uptr size)207 INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) {
208 if (UNLIKELY(UseLocalPool()))
209 return PosixMemalignFromLocalPool(memptr, alignment, size);
210 GET_STACK_TRACE_MALLOC;
211 return asan_posix_memalign(memptr, alignment, size, &stack);
212 }
213
INTERCEPTOR(void *,valloc,uptr size)214 INTERCEPTOR(void*, valloc, uptr size) {
215 GET_STACK_TRACE_MALLOC;
216 return asan_valloc(size, &stack);
217 }
218
219 #if SANITIZER_INTERCEPT_PVALLOC
INTERCEPTOR(void *,pvalloc,uptr size)220 INTERCEPTOR(void*, pvalloc, uptr size) {
221 GET_STACK_TRACE_MALLOC;
222 return asan_pvalloc(size, &stack);
223 }
224 #endif // SANITIZER_INTERCEPT_PVALLOC
225
INTERCEPTOR(void,malloc_stats,void)226 INTERCEPTOR(void, malloc_stats, void) {
227 __asan_print_accumulated_stats();
228 }
229
230 #if SANITIZER_ANDROID
231 // Format of __libc_malloc_dispatch has changed in Android L.
232 // While we are moving towards a solution that does not depend on bionic
233 // internals, here is something to support both K* and L releases.
234 struct MallocDebugK {
235 void *(*malloc)(uptr bytes);
236 void (*free)(void *mem);
237 void *(*calloc)(uptr n_elements, uptr elem_size);
238 void *(*realloc)(void *oldMem, uptr bytes);
239 void *(*memalign)(uptr alignment, uptr bytes);
240 uptr (*malloc_usable_size)(void *mem);
241 };
242
243 struct MallocDebugL {
244 void *(*calloc)(uptr n_elements, uptr elem_size);
245 void (*free)(void *mem);
246 fake_mallinfo (*mallinfo)(void);
247 void *(*malloc)(uptr bytes);
248 uptr (*malloc_usable_size)(void *mem);
249 void *(*memalign)(uptr alignment, uptr bytes);
250 int (*posix_memalign)(void **memptr, uptr alignment, uptr size);
251 void* (*pvalloc)(uptr size);
252 void *(*realloc)(void *oldMem, uptr bytes);
253 void* (*valloc)(uptr size);
254 };
255
256 ALIGNED(32) const MallocDebugK asan_malloc_dispatch_k = {
257 WRAP(malloc), WRAP(free), WRAP(calloc),
258 WRAP(realloc), WRAP(memalign), WRAP(malloc_usable_size)};
259
260 ALIGNED(32) const MallocDebugL asan_malloc_dispatch_l = {
261 WRAP(calloc), WRAP(free), WRAP(mallinfo),
262 WRAP(malloc), WRAP(malloc_usable_size), WRAP(memalign),
263 WRAP(posix_memalign), WRAP(pvalloc), WRAP(realloc),
264 WRAP(valloc)};
265
266 namespace __asan {
ReplaceSystemMalloc()267 void ReplaceSystemMalloc() {
268 void **__libc_malloc_dispatch_p =
269 (void **)AsanDlSymNext("__libc_malloc_dispatch");
270 if (__libc_malloc_dispatch_p) {
271 // Decide on K vs L dispatch format by the presence of
272 // __libc_malloc_default_dispatch export in libc.
273 void *default_dispatch_p = AsanDlSymNext("__libc_malloc_default_dispatch");
274 if (default_dispatch_p)
275 *__libc_malloc_dispatch_p = (void *)&asan_malloc_dispatch_k;
276 else
277 *__libc_malloc_dispatch_p = (void *)&asan_malloc_dispatch_l;
278 }
279 }
280 } // namespace __asan
281
282 #else // SANITIZER_ANDROID
283
284 namespace __asan {
ReplaceSystemMalloc()285 void ReplaceSystemMalloc() {
286 }
287 } // namespace __asan
288 #endif // SANITIZER_ANDROID
289
290 #endif // SANITIZER_FREEBSD || SANITIZER_FUCHSIA || SANITIZER_LINUX ||
291 // SANITIZER_NETBSD || SANITIZER_SOLARIS
292