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