]>
Commit | Line | Data |
---|---|---|
55aea9f5 MO |
1 | //===-- sanitizer_malloc_mac.inc --------------------------------*- C++ -*-===// |
2 | // | |
b667dd70 ML |
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 | |
55aea9f5 MO |
6 | // |
7 | //===----------------------------------------------------------------------===// | |
8 | // | |
9 | // This file contains Mac-specific malloc interceptors and a custom zone | |
10 | // implementation, which together replace the system allocator. | |
11 | // | |
12 | //===----------------------------------------------------------------------===// | |
13 | ||
14 | #include "sanitizer_common/sanitizer_platform.h" | |
15 | #if !SANITIZER_MAC | |
16 | #error "This file should only be compiled on Darwin." | |
17 | #endif | |
18 | ||
19 | #include <AvailabilityMacros.h> | |
20 | #include <CoreFoundation/CFBase.h> | |
21 | #include <dlfcn.h> | |
22 | #include <malloc/malloc.h> | |
23 | #include <sys/mman.h> | |
24 | ||
25 | #include "interception/interception.h" | |
26 | #include "sanitizer_common/sanitizer_mac.h" | |
27 | ||
28 | // Similar code is used in Google Perftools, | |
10189819 | 29 | // https://github.com/gperftools/gperftools. |
55aea9f5 | 30 | |
eac97531 | 31 | namespace __sanitizer { |
b667dd70 | 32 | |
eac97531 | 33 | extern malloc_zone_t sanitizer_zone; |
b667dd70 ML |
34 | |
35 | struct sanitizer_malloc_introspection_t : public malloc_introspection_t { | |
36 | // IMPORTANT: Do not change the order, alignment, or types of these fields to | |
37 | // maintain binary compatibility. You should only add fields to this struct. | |
38 | ||
39 | // Used to track changes to the allocator that will affect | |
40 | // zone enumeration. | |
41 | u64 allocator_enumeration_version; | |
42 | uptr allocator_ptr; | |
43 | uptr allocator_size; | |
44 | }; | |
45 | ||
46 | u64 GetMallocZoneAllocatorEnumerationVersion() { | |
47 | // This represents the current allocator ABI version. | |
48 | // This field should be incremented every time the Allocator | |
49 | // ABI changes in a way that breaks allocator enumeration. | |
50 | return 0; | |
eac97531 | 51 | } |
55aea9f5 | 52 | |
b667dd70 ML |
53 | } // namespace __sanitizer |
54 | ||
55aea9f5 MO |
55 | INTERCEPTOR(malloc_zone_t *, malloc_create_zone, |
56 | vm_size_t start_size, unsigned zone_flags) { | |
57 | COMMON_MALLOC_ENTER(); | |
58 | uptr page_size = GetPageSizeCached(); | |
59 | uptr allocated_size = RoundUpTo(sizeof(sanitizer_zone), page_size); | |
60 | COMMON_MALLOC_MEMALIGN(page_size, allocated_size); | |
61 | malloc_zone_t *new_zone = (malloc_zone_t *)p; | |
62 | internal_memcpy(new_zone, &sanitizer_zone, sizeof(sanitizer_zone)); | |
63 | new_zone->zone_name = NULL; // The name will be changed anyway. | |
64 | if (GetMacosVersion() >= MACOS_VERSION_LION) { | |
65 | // Prevent the client app from overwriting the zone contents. | |
66 | // Library functions that need to modify the zone will set PROT_WRITE on it. | |
67 | // This matches the behavior of malloc_create_zone() on OSX 10.7 and higher. | |
68 | mprotect(new_zone, allocated_size, PROT_READ); | |
69 | } | |
5d3805fc | 70 | // We're explicitly *NOT* registering the zone. |
55aea9f5 MO |
71 | return new_zone; |
72 | } | |
73 | ||
5d3805fc JJ |
74 | INTERCEPTOR(void, malloc_destroy_zone, malloc_zone_t *zone) { |
75 | COMMON_MALLOC_ENTER(); | |
76 | // We don't need to do anything here. We're not registering new zones, so we | |
77 | // don't to unregister. Just un-mprotect and free() the zone. | |
78 | if (GetMacosVersion() >= MACOS_VERSION_LION) { | |
79 | uptr page_size = GetPageSizeCached(); | |
80 | uptr allocated_size = RoundUpTo(sizeof(sanitizer_zone), page_size); | |
81 | mprotect(zone, allocated_size, PROT_READ | PROT_WRITE); | |
82 | } | |
83 | if (zone->zone_name) { | |
84 | COMMON_MALLOC_FREE((void *)zone->zone_name); | |
85 | } | |
86 | COMMON_MALLOC_FREE(zone); | |
87 | } | |
88 | ||
55aea9f5 MO |
89 | INTERCEPTOR(malloc_zone_t *, malloc_default_zone, void) { |
90 | COMMON_MALLOC_ENTER(); | |
91 | return &sanitizer_zone; | |
92 | } | |
93 | ||
b667dd70 ML |
94 | INTERCEPTOR(malloc_zone_t *, malloc_zone_from_ptr, const void *ptr) { |
95 | COMMON_MALLOC_ENTER(); | |
96 | size_t size = sanitizer_zone.size(&sanitizer_zone, ptr); | |
97 | if (size) { // Claimed by sanitizer zone? | |
98 | return &sanitizer_zone; | |
99 | } | |
100 | return REAL(malloc_zone_from_ptr)(ptr); | |
101 | } | |
102 | ||
55aea9f5 MO |
103 | INTERCEPTOR(malloc_zone_t *, malloc_default_purgeable_zone, void) { |
104 | // FIXME: ASan should support purgeable allocations. | |
10189819 | 105 | // https://github.com/google/sanitizers/issues/139 |
55aea9f5 MO |
106 | COMMON_MALLOC_ENTER(); |
107 | return &sanitizer_zone; | |
108 | } | |
109 | ||
110 | INTERCEPTOR(void, malloc_make_purgeable, void *ptr) { | |
111 | // FIXME: ASan should support purgeable allocations. Ignoring them is fine | |
112 | // for now. | |
113 | COMMON_MALLOC_ENTER(); | |
114 | } | |
115 | ||
116 | INTERCEPTOR(int, malloc_make_nonpurgeable, void *ptr) { | |
117 | // FIXME: ASan should support purgeable allocations. Ignoring them is fine | |
118 | // for now. | |
119 | COMMON_MALLOC_ENTER(); | |
120 | // Must return 0 if the contents were not purged since the last call to | |
121 | // malloc_make_purgeable(). | |
122 | return 0; | |
123 | } | |
124 | ||
125 | INTERCEPTOR(void, malloc_set_zone_name, malloc_zone_t *zone, const char *name) { | |
126 | COMMON_MALLOC_ENTER(); | |
127 | // Allocate |sizeof(COMMON_MALLOC_ZONE_NAME "-") + internal_strlen(name)| | |
128 | // bytes. | |
129 | size_t buflen = | |
130 | sizeof(COMMON_MALLOC_ZONE_NAME "-") + (name ? internal_strlen(name) : 0); | |
131 | InternalScopedString new_name(buflen); | |
132 | if (name && zone->introspect == sanitizer_zone.introspect) { | |
133 | new_name.append(COMMON_MALLOC_ZONE_NAME "-%s", name); | |
134 | name = new_name.data(); | |
135 | } | |
136 | ||
137 | // Call the system malloc's implementation for both external and our zones, | |
138 | // since that appropriately changes VM region protections on the zone. | |
139 | REAL(malloc_set_zone_name)(zone, name); | |
140 | } | |
141 | ||
142 | INTERCEPTOR(void *, malloc, size_t size) { | |
143 | COMMON_MALLOC_ENTER(); | |
144 | COMMON_MALLOC_MALLOC(size); | |
145 | return p; | |
146 | } | |
147 | ||
148 | INTERCEPTOR(void, free, void *ptr) { | |
149 | COMMON_MALLOC_ENTER(); | |
150 | if (!ptr) return; | |
151 | COMMON_MALLOC_FREE(ptr); | |
152 | } | |
153 | ||
154 | INTERCEPTOR(void *, realloc, void *ptr, size_t size) { | |
155 | COMMON_MALLOC_ENTER(); | |
156 | COMMON_MALLOC_REALLOC(ptr, size); | |
157 | return p; | |
158 | } | |
159 | ||
160 | INTERCEPTOR(void *, calloc, size_t nmemb, size_t size) { | |
161 | COMMON_MALLOC_ENTER(); | |
162 | COMMON_MALLOC_CALLOC(nmemb, size); | |
163 | return p; | |
164 | } | |
165 | ||
166 | INTERCEPTOR(void *, valloc, size_t size) { | |
167 | COMMON_MALLOC_ENTER(); | |
168 | COMMON_MALLOC_VALLOC(size); | |
169 | return p; | |
170 | } | |
171 | ||
172 | INTERCEPTOR(size_t, malloc_good_size, size_t size) { | |
173 | COMMON_MALLOC_ENTER(); | |
174 | return sanitizer_zone.introspect->good_size(&sanitizer_zone, size); | |
175 | } | |
176 | ||
177 | INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) { | |
178 | COMMON_MALLOC_ENTER(); | |
179 | CHECK(memptr); | |
eac97531 ML |
180 | COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size); |
181 | return res; | |
55aea9f5 MO |
182 | } |
183 | ||
184 | namespace { | |
185 | ||
186 | // TODO(glider): the __sanitizer_mz_* functions should be united with the Linux | |
187 | // wrappers, as they are basically copied from there. | |
188 | extern "C" | |
189 | SANITIZER_INTERFACE_ATTRIBUTE | |
190 | size_t __sanitizer_mz_size(malloc_zone_t* zone, const void* ptr) { | |
191 | COMMON_MALLOC_SIZE(ptr); | |
192 | return size; | |
193 | } | |
194 | ||
195 | extern "C" | |
196 | SANITIZER_INTERFACE_ATTRIBUTE | |
197 | void *__sanitizer_mz_malloc(malloc_zone_t *zone, uptr size) { | |
198 | COMMON_MALLOC_ENTER(); | |
199 | COMMON_MALLOC_MALLOC(size); | |
200 | return p; | |
201 | } | |
202 | ||
203 | extern "C" | |
204 | SANITIZER_INTERFACE_ATTRIBUTE | |
205 | void *__sanitizer_mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { | |
206 | if (UNLIKELY(!COMMON_MALLOC_SANITIZER_INITIALIZED)) { | |
207 | // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym. | |
208 | const size_t kCallocPoolSize = 1024; | |
209 | static uptr calloc_memory_for_dlsym[kCallocPoolSize]; | |
210 | static size_t allocated; | |
211 | size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize; | |
212 | void *mem = (void*)&calloc_memory_for_dlsym[allocated]; | |
213 | allocated += size_in_words; | |
214 | CHECK(allocated < kCallocPoolSize); | |
215 | return mem; | |
216 | } | |
217 | COMMON_MALLOC_CALLOC(nmemb, size); | |
218 | return p; | |
219 | } | |
220 | ||
221 | extern "C" | |
222 | SANITIZER_INTERFACE_ATTRIBUTE | |
223 | void *__sanitizer_mz_valloc(malloc_zone_t *zone, size_t size) { | |
224 | COMMON_MALLOC_ENTER(); | |
225 | COMMON_MALLOC_VALLOC(size); | |
226 | return p; | |
227 | } | |
228 | ||
55aea9f5 MO |
229 | // TODO(glider): the allocation callbacks need to be refactored. |
230 | extern "C" | |
231 | SANITIZER_INTERFACE_ATTRIBUTE | |
232 | void __sanitizer_mz_free(malloc_zone_t *zone, void *ptr) { | |
10189819 MO |
233 | if (!ptr) return; |
234 | COMMON_MALLOC_FREE(ptr); | |
55aea9f5 MO |
235 | } |
236 | ||
10189819 | 237 | #define GET_ZONE_FOR_PTR(ptr) \ |
b667dd70 | 238 | malloc_zone_t *zone_ptr = WRAP(malloc_zone_from_ptr)(ptr); \ |
10189819 MO |
239 | const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name |
240 | ||
55aea9f5 MO |
241 | extern "C" |
242 | SANITIZER_INTERFACE_ATTRIBUTE | |
243 | void *__sanitizer_mz_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) { | |
244 | if (!ptr) { | |
245 | COMMON_MALLOC_MALLOC(new_size); | |
246 | return p; | |
247 | } else { | |
248 | COMMON_MALLOC_SIZE(ptr); | |
249 | if (size) { | |
250 | COMMON_MALLOC_REALLOC(ptr, new_size); | |
251 | return p; | |
252 | } else { | |
253 | // We can't recover from reallocating an unknown address, because | |
254 | // this would require reading at most |new_size| bytes from | |
255 | // potentially unaccessible memory. | |
256 | GET_ZONE_FOR_PTR(ptr); | |
257 | COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name); | |
258 | return nullptr; | |
259 | } | |
260 | } | |
261 | } | |
262 | ||
263 | extern "C" | |
264 | SANITIZER_INTERFACE_ATTRIBUTE | |
265 | void __sanitizer_mz_destroy(malloc_zone_t* zone) { | |
266 | // A no-op -- we will not be destroyed! | |
267 | Report("__sanitizer_mz_destroy() called -- ignoring\n"); | |
268 | } | |
269 | ||
270 | extern "C" | |
271 | SANITIZER_INTERFACE_ATTRIBUTE | |
272 | void *__sanitizer_mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { | |
273 | COMMON_MALLOC_ENTER(); | |
274 | COMMON_MALLOC_MEMALIGN(align, size); | |
275 | return p; | |
276 | } | |
277 | ||
b667dd70 ML |
278 | // This public API exists purely for testing purposes. |
279 | extern "C" | |
280 | SANITIZER_INTERFACE_ATTRIBUTE | |
281 | malloc_zone_t* __sanitizer_mz_default_zone() { | |
282 | return &sanitizer_zone; | |
283 | } | |
284 | ||
55aea9f5 MO |
285 | // This function is currently unused, and we build with -Werror. |
286 | #if 0 | |
287 | void __sanitizer_mz_free_definite_size( | |
288 | malloc_zone_t* zone, void *ptr, size_t size) { | |
289 | // TODO(glider): check that |size| is valid. | |
290 | UNIMPLEMENTED(); | |
291 | } | |
292 | #endif | |
293 | ||
b667dd70 ML |
294 | #ifndef COMMON_MALLOC_HAS_ZONE_ENUMERATOR |
295 | #error "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be defined" | |
296 | #endif | |
297 | static_assert((COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 0 || | |
298 | (COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 1, | |
299 | "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be 0 or 1"); | |
300 | ||
301 | #if COMMON_MALLOC_HAS_ZONE_ENUMERATOR | |
302 | // Forward declare and expect the implementation to provided by | |
303 | // includer. | |
304 | kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, | |
305 | vm_address_t zone_address, memory_reader_t reader, | |
306 | vm_range_recorder_t recorder); | |
307 | #else | |
308 | // Provide stub implementation that fails. | |
309 | kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, | |
310 | vm_address_t zone_address, memory_reader_t reader, | |
55aea9f5 | 311 | vm_range_recorder_t recorder) { |
b667dd70 | 312 | // Not supported. |
55aea9f5 MO |
313 | return KERN_FAILURE; |
314 | } | |
b667dd70 ML |
315 | #endif |
316 | ||
317 | #ifndef COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT | |
318 | #error "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be defined" | |
319 | #endif | |
320 | static_assert((COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 0 || | |
321 | (COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 1, | |
322 | "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be 0 or 1"); | |
323 | #if COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT | |
324 | // Forward declare and expect the implementation to provided by | |
325 | // includer. | |
326 | void mi_extra_init( | |
327 | sanitizer_malloc_introspection_t *mi); | |
328 | #else | |
329 | void mi_extra_init( | |
330 | sanitizer_malloc_introspection_t *mi) { | |
331 | // Just zero initialize the fields. | |
332 | mi->allocator_ptr = 0; | |
333 | mi->allocator_size = 0; | |
334 | } | |
335 | #endif | |
55aea9f5 MO |
336 | |
337 | size_t mi_good_size(malloc_zone_t *zone, size_t size) { | |
338 | // I think it's always safe to return size, but we maybe could do better. | |
339 | return size; | |
340 | } | |
341 | ||
342 | boolean_t mi_check(malloc_zone_t *zone) { | |
343 | UNIMPLEMENTED(); | |
344 | } | |
345 | ||
346 | void mi_print(malloc_zone_t *zone, boolean_t verbose) { | |
347 | UNIMPLEMENTED(); | |
348 | } | |
349 | ||
350 | void mi_log(malloc_zone_t *zone, void *address) { | |
351 | // I don't think we support anything like this | |
352 | } | |
353 | ||
354 | void mi_force_lock(malloc_zone_t *zone) { | |
355 | COMMON_MALLOC_FORCE_LOCK(); | |
356 | } | |
357 | ||
358 | void mi_force_unlock(malloc_zone_t *zone) { | |
359 | COMMON_MALLOC_FORCE_UNLOCK(); | |
360 | } | |
361 | ||
362 | void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { | |
363 | COMMON_MALLOC_FILL_STATS(zone, stats); | |
364 | } | |
365 | ||
366 | boolean_t mi_zone_locked(malloc_zone_t *zone) { | |
367 | // UNIMPLEMENTED(); | |
368 | return false; | |
369 | } | |
370 | ||
371 | } // unnamed namespace | |
372 | ||
373 | namespace COMMON_MALLOC_NAMESPACE { | |
374 | ||
b667dd70 ML |
375 | void InitMallocZoneFields() { |
376 | static sanitizer_malloc_introspection_t sanitizer_zone_introspection; | |
55aea9f5 MO |
377 | // Ok to use internal_memset, these places are not performance-critical. |
378 | internal_memset(&sanitizer_zone_introspection, 0, | |
379 | sizeof(sanitizer_zone_introspection)); | |
380 | ||
381 | sanitizer_zone_introspection.enumerator = &mi_enumerator; | |
382 | sanitizer_zone_introspection.good_size = &mi_good_size; | |
383 | sanitizer_zone_introspection.check = &mi_check; | |
384 | sanitizer_zone_introspection.print = &mi_print; | |
385 | sanitizer_zone_introspection.log = &mi_log; | |
386 | sanitizer_zone_introspection.force_lock = &mi_force_lock; | |
387 | sanitizer_zone_introspection.force_unlock = &mi_force_unlock; | |
388 | sanitizer_zone_introspection.statistics = &mi_statistics; | |
389 | sanitizer_zone_introspection.zone_locked = &mi_zone_locked; | |
390 | ||
b667dd70 ML |
391 | // Set current allocator enumeration version. |
392 | sanitizer_zone_introspection.allocator_enumeration_version = | |
393 | GetMallocZoneAllocatorEnumerationVersion(); | |
394 | ||
395 | // Perform any sanitizer specific initialization. | |
396 | mi_extra_init(&sanitizer_zone_introspection); | |
397 | ||
55aea9f5 MO |
398 | internal_memset(&sanitizer_zone, 0, sizeof(malloc_zone_t)); |
399 | ||
400 | // Use version 6 for OSX >= 10.6. | |
401 | sanitizer_zone.version = 6; | |
402 | sanitizer_zone.zone_name = COMMON_MALLOC_ZONE_NAME; | |
403 | sanitizer_zone.size = &__sanitizer_mz_size; | |
404 | sanitizer_zone.malloc = &__sanitizer_mz_malloc; | |
405 | sanitizer_zone.calloc = &__sanitizer_mz_calloc; | |
406 | sanitizer_zone.valloc = &__sanitizer_mz_valloc; | |
407 | sanitizer_zone.free = &__sanitizer_mz_free; | |
408 | sanitizer_zone.realloc = &__sanitizer_mz_realloc; | |
409 | sanitizer_zone.destroy = &__sanitizer_mz_destroy; | |
410 | sanitizer_zone.batch_malloc = 0; | |
411 | sanitizer_zone.batch_free = 0; | |
412 | sanitizer_zone.free_definite_size = 0; | |
413 | sanitizer_zone.memalign = &__sanitizer_mz_memalign; | |
414 | sanitizer_zone.introspect = &sanitizer_zone_introspection; | |
b667dd70 ML |
415 | } |
416 | ||
417 | void ReplaceSystemMalloc() { | |
418 | InitMallocZoneFields(); | |
55aea9f5 MO |
419 | |
420 | // Register the zone. | |
421 | malloc_zone_register(&sanitizer_zone); | |
422 | } | |
423 | ||
424 | } // namespace COMMON_MALLOC_NAMESPACE |