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