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.append(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
147INTERCEPTOR(void *, realloc, void *ptr, size_t size) {
148  COMMON_MALLOC_ENTER();
149  COMMON_MALLOC_REALLOC(ptr, size);
150  return p;
151}
152
153INTERCEPTOR(void *, calloc, size_t nmemb, size_t size) {
154  COMMON_MALLOC_ENTER();
155  COMMON_MALLOC_CALLOC(nmemb, size);
156  return p;
157}
158
159INTERCEPTOR(void *, valloc, size_t size) {
160  COMMON_MALLOC_ENTER();
161  COMMON_MALLOC_VALLOC(size);
162  return p;
163}
164
165INTERCEPTOR(size_t, malloc_good_size, size_t size) {
166  COMMON_MALLOC_ENTER();
167  return sanitizer_zone.introspect->good_size(&sanitizer_zone, size);
168}
169
170INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) {
171  COMMON_MALLOC_ENTER();
172  CHECK(memptr);
173  COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size);
174  return res;
175}
176
177namespace {
178
179// TODO(glider): the __sanitizer_mz_* functions should be united with the Linux
180// wrappers, as they are basically copied from there.
181extern "C"
182SANITIZER_INTERFACE_ATTRIBUTE
183size_t __sanitizer_mz_size(malloc_zone_t* zone, const void* ptr) {
184  COMMON_MALLOC_SIZE(ptr);
185  return size;
186}
187
188extern "C"
189SANITIZER_INTERFACE_ATTRIBUTE
190void *__sanitizer_mz_malloc(malloc_zone_t *zone, uptr size) {
191  COMMON_MALLOC_ENTER();
192  COMMON_MALLOC_MALLOC(size);
193  return p;
194}
195
196struct DlsymAlloc : public DlSymAllocator<DlsymAlloc> {
197  static bool UseImpl() { return !COMMON_MALLOC_SANITIZER_INITIALIZED; }
198};
199
200extern "C"
201SANITIZER_INTERFACE_ATTRIBUTE
202void *__sanitizer_mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) {
203  if (DlsymAlloc::Use())
204    return DlsymAlloc::Callocate(nmemb, size);
205  COMMON_MALLOC_CALLOC(nmemb, size);
206  return p;
207}
208
209extern "C"
210SANITIZER_INTERFACE_ATTRIBUTE
211void *__sanitizer_mz_valloc(malloc_zone_t *zone, size_t size) {
212  COMMON_MALLOC_ENTER();
213  COMMON_MALLOC_VALLOC(size);
214  return p;
215}
216
217// TODO(glider): the allocation callbacks need to be refactored.
218extern "C"
219SANITIZER_INTERFACE_ATTRIBUTE
220void __sanitizer_mz_free(malloc_zone_t *zone, void *ptr) {
221  if (!ptr) return;
222  if (DlsymAlloc::PointerIsMine(ptr))
223    return DlsymAlloc::Free(ptr);
224  COMMON_MALLOC_FREE(ptr);
225}
226
227#define GET_ZONE_FOR_PTR(ptr) \
228  malloc_zone_t *zone_ptr = WRAP(malloc_zone_from_ptr)(ptr); \
229  const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name
230
231extern "C"
232SANITIZER_INTERFACE_ATTRIBUTE
233void *__sanitizer_mz_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) {
234  if (!ptr) {
235    COMMON_MALLOC_MALLOC(new_size);
236    return p;
237  } else {
238    COMMON_MALLOC_SIZE(ptr);
239    if (size) {
240      COMMON_MALLOC_REALLOC(ptr, new_size);
241      return p;
242    } else {
243      // We can't recover from reallocating an unknown address, because
244      // this would require reading at most |new_size| bytes from
245      // potentially unaccessible memory.
246      GET_ZONE_FOR_PTR(ptr);
247      COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name);
248      return nullptr;
249    }
250  }
251}
252
253extern "C"
254SANITIZER_INTERFACE_ATTRIBUTE
255void __sanitizer_mz_destroy(malloc_zone_t* zone) {
256  // A no-op -- we will not be destroyed!
257  Report("__sanitizer_mz_destroy() called -- ignoring\n");
258}
259
260extern "C"
261SANITIZER_INTERFACE_ATTRIBUTE
262void *__sanitizer_mz_memalign(malloc_zone_t *zone, size_t align, size_t size) {
263  COMMON_MALLOC_ENTER();
264  COMMON_MALLOC_MEMALIGN(align, size);
265  return p;
266}
267
268// This public API exists purely for testing purposes.
269extern "C"
270SANITIZER_INTERFACE_ATTRIBUTE
271malloc_zone_t* __sanitizer_mz_default_zone() {
272  return &sanitizer_zone;
273}
274
275// This function is currently unused, and we build with -Werror.
276#if 0
277void __sanitizer_mz_free_definite_size(
278    malloc_zone_t* zone, void *ptr, size_t size) {
279  // TODO(glider): check that |size| is valid.
280  UNIMPLEMENTED();
281}
282#endif
283
284#ifndef COMMON_MALLOC_HAS_ZONE_ENUMERATOR
285#error "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be defined"
286#endif
287static_assert((COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 0 ||
288                  (COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 1,
289              "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be 0 or 1");
290
291#if COMMON_MALLOC_HAS_ZONE_ENUMERATOR
292// Forward declare and expect the implementation to provided by
293// includer.
294kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask,
295                            vm_address_t zone_address, memory_reader_t reader,
296                            vm_range_recorder_t recorder);
297#else
298// Provide stub implementation that fails.
299kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask,
300                            vm_address_t zone_address, memory_reader_t reader,
301                            vm_range_recorder_t recorder) {
302  // Not supported.
303  return KERN_FAILURE;
304}
305#endif
306
307#ifndef COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT
308#error "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be defined"
309#endif
310static_assert((COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 0 ||
311                  (COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 1,
312              "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be 0 or 1");
313#if COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT
314// Forward declare and expect the implementation to provided by
315// includer.
316void mi_extra_init(
317    sanitizer_malloc_introspection_t *mi);
318#else
319void mi_extra_init(
320    sanitizer_malloc_introspection_t *mi) {
321  // Just zero initialize the fields.
322  mi->allocator_ptr = 0;
323  mi->allocator_size = 0;
324}
325#endif
326
327size_t mi_good_size(malloc_zone_t *zone, size_t size) {
328  // I think it's always safe to return size, but we maybe could do better.
329  return size;
330}
331
332boolean_t mi_check(malloc_zone_t *zone) {
333  UNIMPLEMENTED();
334}
335
336void mi_print(malloc_zone_t *zone, boolean_t verbose) {
337  UNIMPLEMENTED();
338}
339
340void mi_log(malloc_zone_t *zone, void *address) {
341  // I don't think we support anything like this
342}
343
344void mi_force_lock(malloc_zone_t *zone) {
345  COMMON_MALLOC_FORCE_LOCK();
346}
347
348void mi_force_unlock(malloc_zone_t *zone) {
349  COMMON_MALLOC_FORCE_UNLOCK();
350}
351
352void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) {
353  COMMON_MALLOC_FILL_STATS(zone, stats);
354}
355
356boolean_t mi_zone_locked(malloc_zone_t *zone) {
357  // UNIMPLEMENTED();
358  return false;
359}
360
361}  // unnamed namespace
362
363namespace COMMON_MALLOC_NAMESPACE {
364
365void InitMallocZoneFields() {
366  static sanitizer_malloc_introspection_t sanitizer_zone_introspection;
367  // Ok to use internal_memset, these places are not performance-critical.
368  internal_memset(&sanitizer_zone_introspection, 0,
369                  sizeof(sanitizer_zone_introspection));
370
371  sanitizer_zone_introspection.enumerator = &mi_enumerator;
372  sanitizer_zone_introspection.good_size = &mi_good_size;
373  sanitizer_zone_introspection.check = &mi_check;
374  sanitizer_zone_introspection.print = &mi_print;
375  sanitizer_zone_introspection.log = &mi_log;
376  sanitizer_zone_introspection.force_lock = &mi_force_lock;
377  sanitizer_zone_introspection.force_unlock = &mi_force_unlock;
378  sanitizer_zone_introspection.statistics = &mi_statistics;
379  sanitizer_zone_introspection.zone_locked = &mi_zone_locked;
380
381  // Set current allocator enumeration version.
382  sanitizer_zone_introspection.allocator_enumeration_version =
383      GetMallocZoneAllocatorEnumerationVersion();
384
385  // Perform any sanitizer specific initialization.
386  mi_extra_init(&sanitizer_zone_introspection);
387
388  internal_memset(&sanitizer_zone, 0, sizeof(malloc_zone_t));
389
390  // Use version 6 for OSX >= 10.6.
391  sanitizer_zone.version = 6;
392  sanitizer_zone.zone_name = COMMON_MALLOC_ZONE_NAME;
393  sanitizer_zone.size = &__sanitizer_mz_size;
394  sanitizer_zone.malloc = &__sanitizer_mz_malloc;
395  sanitizer_zone.calloc = &__sanitizer_mz_calloc;
396  sanitizer_zone.valloc = &__sanitizer_mz_valloc;
397  sanitizer_zone.free = &__sanitizer_mz_free;
398  sanitizer_zone.realloc = &__sanitizer_mz_realloc;
399  sanitizer_zone.destroy = &__sanitizer_mz_destroy;
400  sanitizer_zone.batch_malloc = 0;
401  sanitizer_zone.batch_free = 0;
402  sanitizer_zone.free_definite_size = 0;
403  sanitizer_zone.memalign = &__sanitizer_mz_memalign;
404  sanitizer_zone.introspect = &sanitizer_zone_introspection;
405}
406
407void ReplaceSystemMalloc() {
408  InitMallocZoneFields();
409
410  // Register the zone.
411  malloc_zone_register(&sanitizer_zone);
412}
413
414}  // namespace COMMON_MALLOC_NAMESPACE
415