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