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