1292925Sdim//===-- sanitizer_malloc_mac.inc --------------------------------*- C++ -*-===//
2292925Sdim//
3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4353358Sdim// See https://llvm.org/LICENSE.txt for license information.
5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6292925Sdim//
7292925Sdim//===----------------------------------------------------------------------===//
8292925Sdim//
9292925Sdim// This file contains Mac-specific malloc interceptors and a custom zone
10292925Sdim// implementation, which together replace the system allocator.
11292925Sdim//
12292925Sdim//===----------------------------------------------------------------------===//
13292925Sdim
14292925Sdim#include "sanitizer_common/sanitizer_platform.h"
15292925Sdim#if !SANITIZER_MAC
16292925Sdim#error "This file should only be compiled on Darwin."
17292925Sdim#endif
18292925Sdim
19292925Sdim#include <AvailabilityMacros.h>
20292925Sdim#include <CoreFoundation/CFBase.h>
21292925Sdim#include <dlfcn.h>
22292925Sdim#include <malloc/malloc.h>
23292925Sdim#include <sys/mman.h>
24292925Sdim
25292925Sdim#include "interception/interception.h"
26292925Sdim#include "sanitizer_common/sanitizer_mac.h"
27292925Sdim
28292925Sdim// Similar code is used in Google Perftools,
29292925Sdim// https://github.com/gperftools/gperftools.
30292925Sdim
31341825Sdimnamespace __sanitizer {
32344779Sdim
33341825Sdimextern malloc_zone_t sanitizer_zone;
34344779Sdim
35344779Sdimstruct sanitizer_malloc_introspection_t : public malloc_introspection_t {
36344779Sdim  // IMPORTANT: Do not change the order, alignment, or types of these fields to
37344779Sdim  // maintain binary compatibility. You should only add fields to this struct.
38344779Sdim
39344779Sdim  // Used to track changes to the allocator that will affect
40344779Sdim  // zone enumeration.
41344779Sdim  u64 allocator_enumeration_version;
42353358Sdim  uptr allocator_ptr;
43353358Sdim  uptr allocator_size;
44344779Sdim};
45344779Sdim
46344779Sdimu64 GetMallocZoneAllocatorEnumerationVersion() {
47344779Sdim  // This represents the current allocator ABI version.
48344779Sdim  // This field should be incremented every time the Allocator
49344779Sdim  // ABI changes in a way that breaks allocator enumeration.
50344779Sdim  return 0;
51341825Sdim}
52292925Sdim
53344779Sdim}  // namespace __sanitizer
54344779Sdim
55292925SdimINTERCEPTOR(malloc_zone_t *, malloc_create_zone,
56292925Sdim                             vm_size_t start_size, unsigned zone_flags) {
57292925Sdim  COMMON_MALLOC_ENTER();
58292925Sdim  uptr page_size = GetPageSizeCached();
59292925Sdim  uptr allocated_size = RoundUpTo(sizeof(sanitizer_zone), page_size);
60292925Sdim  COMMON_MALLOC_MEMALIGN(page_size, allocated_size);
61292925Sdim  malloc_zone_t *new_zone = (malloc_zone_t *)p;
62292925Sdim  internal_memcpy(new_zone, &sanitizer_zone, sizeof(sanitizer_zone));
63292925Sdim  new_zone->zone_name = NULL;  // The name will be changed anyway.
64292925Sdim  if (GetMacosVersion() >= MACOS_VERSION_LION) {
65292925Sdim    // Prevent the client app from overwriting the zone contents.
66292925Sdim    // Library functions that need to modify the zone will set PROT_WRITE on it.
67292925Sdim    // This matches the behavior of malloc_create_zone() on OSX 10.7 and higher.
68292925Sdim    mprotect(new_zone, allocated_size, PROT_READ);
69292925Sdim  }
70314564Sdim  // We're explicitly *NOT* registering the zone.
71292925Sdim  return new_zone;
72292925Sdim}
73292925Sdim
74314564SdimINTERCEPTOR(void, malloc_destroy_zone, malloc_zone_t *zone) {
75314564Sdim  COMMON_MALLOC_ENTER();
76314564Sdim  // We don't need to do anything here.  We're not registering new zones, so we
77314564Sdim  // don't to unregister.  Just un-mprotect and free() the zone.
78314564Sdim  if (GetMacosVersion() >= MACOS_VERSION_LION) {
79314564Sdim    uptr page_size = GetPageSizeCached();
80314564Sdim    uptr allocated_size = RoundUpTo(sizeof(sanitizer_zone), page_size);
81314564Sdim    mprotect(zone, allocated_size, PROT_READ | PROT_WRITE);
82314564Sdim  }
83321369Sdim  if (zone->zone_name) {
84321369Sdim    COMMON_MALLOC_FREE((void *)zone->zone_name);
85321369Sdim  }
86314564Sdim  COMMON_MALLOC_FREE(zone);
87314564Sdim}
88314564Sdim
89292925SdimINTERCEPTOR(malloc_zone_t *, malloc_default_zone, void) {
90292925Sdim  COMMON_MALLOC_ENTER();
91292925Sdim  return &sanitizer_zone;
92292925Sdim}
93292925Sdim
94360784SdimINTERCEPTOR(malloc_zone_t *, malloc_zone_from_ptr, const void *ptr) {
95360784Sdim  COMMON_MALLOC_ENTER();
96360784Sdim  size_t size = sanitizer_zone.size(&sanitizer_zone, ptr);
97360784Sdim  if (size) { // Claimed by sanitizer zone?
98360784Sdim    return &sanitizer_zone;
99360784Sdim  }
100360784Sdim  return REAL(malloc_zone_from_ptr)(ptr);
101360784Sdim}
102360784Sdim
103292925SdimINTERCEPTOR(malloc_zone_t *, malloc_default_purgeable_zone, void) {
104292925Sdim  // FIXME: ASan should support purgeable allocations.
105292925Sdim  // https://github.com/google/sanitizers/issues/139
106292925Sdim  COMMON_MALLOC_ENTER();
107292925Sdim  return &sanitizer_zone;
108292925Sdim}
109292925Sdim
110292925SdimINTERCEPTOR(void, malloc_make_purgeable, void *ptr) {
111292925Sdim  // FIXME: ASan should support purgeable allocations. Ignoring them is fine
112292925Sdim  // for now.
113292925Sdim  COMMON_MALLOC_ENTER();
114292925Sdim}
115292925Sdim
116292925SdimINTERCEPTOR(int, malloc_make_nonpurgeable, void *ptr) {
117292925Sdim  // FIXME: ASan should support purgeable allocations. Ignoring them is fine
118292925Sdim  // for now.
119292925Sdim  COMMON_MALLOC_ENTER();
120292925Sdim  // Must return 0 if the contents were not purged since the last call to
121292925Sdim  // malloc_make_purgeable().
122292925Sdim  return 0;
123292925Sdim}
124292925Sdim
125292925SdimINTERCEPTOR(void, malloc_set_zone_name, malloc_zone_t *zone, const char *name) {
126292925Sdim  COMMON_MALLOC_ENTER();
127292925Sdim  // Allocate |sizeof(COMMON_MALLOC_ZONE_NAME "-") + internal_strlen(name)|
128292925Sdim  // bytes.
129292925Sdim  size_t buflen =
130292925Sdim      sizeof(COMMON_MALLOC_ZONE_NAME "-") + (name ? internal_strlen(name) : 0);
131292925Sdim  InternalScopedString new_name(buflen);
132292925Sdim  if (name && zone->introspect == sanitizer_zone.introspect) {
133292925Sdim    new_name.append(COMMON_MALLOC_ZONE_NAME "-%s", name);
134292925Sdim    name = new_name.data();
135292925Sdim  }
136292925Sdim
137292925Sdim  // Call the system malloc's implementation for both external and our zones,
138292925Sdim  // since that appropriately changes VM region protections on the zone.
139292925Sdim  REAL(malloc_set_zone_name)(zone, name);
140292925Sdim}
141292925Sdim
142292925SdimINTERCEPTOR(void *, malloc, size_t size) {
143292925Sdim  COMMON_MALLOC_ENTER();
144292925Sdim  COMMON_MALLOC_MALLOC(size);
145292925Sdim  return p;
146292925Sdim}
147292925Sdim
148292925SdimINTERCEPTOR(void, free, void *ptr) {
149292925Sdim  COMMON_MALLOC_ENTER();
150292925Sdim  if (!ptr) return;
151292925Sdim  COMMON_MALLOC_FREE(ptr);
152292925Sdim}
153292925Sdim
154292925SdimINTERCEPTOR(void *, realloc, void *ptr, size_t size) {
155292925Sdim  COMMON_MALLOC_ENTER();
156292925Sdim  COMMON_MALLOC_REALLOC(ptr, size);
157292925Sdim  return p;
158292925Sdim}
159292925Sdim
160292925SdimINTERCEPTOR(void *, calloc, size_t nmemb, size_t size) {
161292925Sdim  COMMON_MALLOC_ENTER();
162292925Sdim  COMMON_MALLOC_CALLOC(nmemb, size);
163292925Sdim  return p;
164292925Sdim}
165292925Sdim
166292925SdimINTERCEPTOR(void *, valloc, size_t size) {
167292925Sdim  COMMON_MALLOC_ENTER();
168292925Sdim  COMMON_MALLOC_VALLOC(size);
169292925Sdim  return p;
170292925Sdim}
171292925Sdim
172292925SdimINTERCEPTOR(size_t, malloc_good_size, size_t size) {
173292925Sdim  COMMON_MALLOC_ENTER();
174292925Sdim  return sanitizer_zone.introspect->good_size(&sanitizer_zone, size);
175292925Sdim}
176292925Sdim
177292925SdimINTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) {
178292925Sdim  COMMON_MALLOC_ENTER();
179292925Sdim  CHECK(memptr);
180341825Sdim  COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size);
181341825Sdim  return res;
182292925Sdim}
183292925Sdim
184292925Sdimnamespace {
185292925Sdim
186292925Sdim// TODO(glider): the __sanitizer_mz_* functions should be united with the Linux
187292925Sdim// wrappers, as they are basically copied from there.
188292925Sdimextern "C"
189292925SdimSANITIZER_INTERFACE_ATTRIBUTE
190292925Sdimsize_t __sanitizer_mz_size(malloc_zone_t* zone, const void* ptr) {
191292925Sdim  COMMON_MALLOC_SIZE(ptr);
192292925Sdim  return size;
193292925Sdim}
194292925Sdim
195292925Sdimextern "C"
196292925SdimSANITIZER_INTERFACE_ATTRIBUTE
197292925Sdimvoid *__sanitizer_mz_malloc(malloc_zone_t *zone, uptr size) {
198292925Sdim  COMMON_MALLOC_ENTER();
199292925Sdim  COMMON_MALLOC_MALLOC(size);
200292925Sdim  return p;
201292925Sdim}
202292925Sdim
203292925Sdimextern "C"
204292925SdimSANITIZER_INTERFACE_ATTRIBUTE
205292925Sdimvoid *__sanitizer_mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) {
206292925Sdim  if (UNLIKELY(!COMMON_MALLOC_SANITIZER_INITIALIZED)) {
207292925Sdim    // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
208292925Sdim    const size_t kCallocPoolSize = 1024;
209292925Sdim    static uptr calloc_memory_for_dlsym[kCallocPoolSize];
210292925Sdim    static size_t allocated;
211292925Sdim    size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize;
212292925Sdim    void *mem = (void*)&calloc_memory_for_dlsym[allocated];
213292925Sdim    allocated += size_in_words;
214292925Sdim    CHECK(allocated < kCallocPoolSize);
215292925Sdim    return mem;
216292925Sdim  }
217292925Sdim  COMMON_MALLOC_CALLOC(nmemb, size);
218292925Sdim  return p;
219292925Sdim}
220292925Sdim
221292925Sdimextern "C"
222292925SdimSANITIZER_INTERFACE_ATTRIBUTE
223292925Sdimvoid *__sanitizer_mz_valloc(malloc_zone_t *zone, size_t size) {
224292925Sdim  COMMON_MALLOC_ENTER();
225292925Sdim  COMMON_MALLOC_VALLOC(size);
226292925Sdim  return p;
227292925Sdim}
228292925Sdim
229292925Sdim// TODO(glider): the allocation callbacks need to be refactored.
230292925Sdimextern "C"
231292925SdimSANITIZER_INTERFACE_ATTRIBUTE
232292925Sdimvoid __sanitizer_mz_free(malloc_zone_t *zone, void *ptr) {
233292925Sdim  if (!ptr) return;
234292925Sdim  COMMON_MALLOC_FREE(ptr);
235292925Sdim}
236292925Sdim
237292925Sdim#define GET_ZONE_FOR_PTR(ptr) \
238360784Sdim  malloc_zone_t *zone_ptr = WRAP(malloc_zone_from_ptr)(ptr); \
239292925Sdim  const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name
240292925Sdim
241292925Sdimextern "C"
242292925SdimSANITIZER_INTERFACE_ATTRIBUTE
243292925Sdimvoid *__sanitizer_mz_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) {
244292925Sdim  if (!ptr) {
245292925Sdim    COMMON_MALLOC_MALLOC(new_size);
246292925Sdim    return p;
247292925Sdim  } else {
248292925Sdim    COMMON_MALLOC_SIZE(ptr);
249292925Sdim    if (size) {
250292925Sdim      COMMON_MALLOC_REALLOC(ptr, new_size);
251292925Sdim      return p;
252292925Sdim    } else {
253292925Sdim      // We can't recover from reallocating an unknown address, because
254292925Sdim      // this would require reading at most |new_size| bytes from
255292925Sdim      // potentially unaccessible memory.
256292925Sdim      GET_ZONE_FOR_PTR(ptr);
257292925Sdim      COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name);
258292925Sdim      return nullptr;
259292925Sdim    }
260292925Sdim  }
261292925Sdim}
262292925Sdim
263292925Sdimextern "C"
264292925SdimSANITIZER_INTERFACE_ATTRIBUTE
265292925Sdimvoid __sanitizer_mz_destroy(malloc_zone_t* zone) {
266292925Sdim  // A no-op -- we will not be destroyed!
267292925Sdim  Report("__sanitizer_mz_destroy() called -- ignoring\n");
268292925Sdim}
269292925Sdim
270292925Sdimextern "C"
271292925SdimSANITIZER_INTERFACE_ATTRIBUTE
272292925Sdimvoid *__sanitizer_mz_memalign(malloc_zone_t *zone, size_t align, size_t size) {
273292925Sdim  COMMON_MALLOC_ENTER();
274292925Sdim  COMMON_MALLOC_MEMALIGN(align, size);
275292925Sdim  return p;
276292925Sdim}
277292925Sdim
278344779Sdim// This public API exists purely for testing purposes.
279344779Sdimextern "C"
280344779SdimSANITIZER_INTERFACE_ATTRIBUTE
281344779Sdimmalloc_zone_t* __sanitizer_mz_default_zone() {
282344779Sdim  return &sanitizer_zone;
283344779Sdim}
284344779Sdim
285292925Sdim// This function is currently unused, and we build with -Werror.
286292925Sdim#if 0
287292925Sdimvoid __sanitizer_mz_free_definite_size(
288292925Sdim    malloc_zone_t* zone, void *ptr, size_t size) {
289292925Sdim  // TODO(glider): check that |size| is valid.
290292925Sdim  UNIMPLEMENTED();
291292925Sdim}
292292925Sdim#endif
293292925Sdim
294353358Sdim#ifndef COMMON_MALLOC_HAS_ZONE_ENUMERATOR
295353358Sdim#error "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be defined"
296353358Sdim#endif
297353358Sdimstatic_assert((COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 0 ||
298353358Sdim                  (COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 1,
299353358Sdim              "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be 0 or 1");
300353358Sdim
301353358Sdim#if COMMON_MALLOC_HAS_ZONE_ENUMERATOR
302353358Sdim// Forward declare and expect the implementation to provided by
303353358Sdim// includer.
304353358Sdimkern_return_t mi_enumerator(task_t task, void *, unsigned type_mask,
305353358Sdim                            vm_address_t zone_address, memory_reader_t reader,
306353358Sdim                            vm_range_recorder_t recorder);
307353358Sdim#else
308353358Sdim// Provide stub implementation that fails.
309353358Sdimkern_return_t mi_enumerator(task_t task, void *, unsigned type_mask,
310353358Sdim                            vm_address_t zone_address, memory_reader_t reader,
311292925Sdim                            vm_range_recorder_t recorder) {
312353358Sdim  // Not supported.
313292925Sdim  return KERN_FAILURE;
314292925Sdim}
315353358Sdim#endif
316292925Sdim
317353358Sdim#ifndef COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT
318353358Sdim#error "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be defined"
319353358Sdim#endif
320353358Sdimstatic_assert((COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 0 ||
321353358Sdim                  (COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 1,
322353358Sdim              "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be 0 or 1");
323353358Sdim#if COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT
324353358Sdim// Forward declare and expect the implementation to provided by
325353358Sdim// includer.
326353358Sdimvoid mi_extra_init(
327353358Sdim    sanitizer_malloc_introspection_t *mi);
328353358Sdim#else
329353358Sdimvoid mi_extra_init(
330353358Sdim    sanitizer_malloc_introspection_t *mi) {
331353358Sdim  // Just zero initialize the fields.
332353358Sdim  mi->allocator_ptr = 0;
333353358Sdim  mi->allocator_size = 0;
334353358Sdim}
335353358Sdim#endif
336353358Sdim
337292925Sdimsize_t mi_good_size(malloc_zone_t *zone, size_t size) {
338292925Sdim  // I think it's always safe to return size, but we maybe could do better.
339292925Sdim  return size;
340292925Sdim}
341292925Sdim
342292925Sdimboolean_t mi_check(malloc_zone_t *zone) {
343292925Sdim  UNIMPLEMENTED();
344292925Sdim}
345292925Sdim
346292925Sdimvoid mi_print(malloc_zone_t *zone, boolean_t verbose) {
347292925Sdim  UNIMPLEMENTED();
348292925Sdim}
349292925Sdim
350292925Sdimvoid mi_log(malloc_zone_t *zone, void *address) {
351292925Sdim  // I don't think we support anything like this
352292925Sdim}
353292925Sdim
354292925Sdimvoid mi_force_lock(malloc_zone_t *zone) {
355292925Sdim  COMMON_MALLOC_FORCE_LOCK();
356292925Sdim}
357292925Sdim
358292925Sdimvoid mi_force_unlock(malloc_zone_t *zone) {
359292925Sdim  COMMON_MALLOC_FORCE_UNLOCK();
360292925Sdim}
361292925Sdim
362292925Sdimvoid mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) {
363292925Sdim  COMMON_MALLOC_FILL_STATS(zone, stats);
364292925Sdim}
365292925Sdim
366292925Sdimboolean_t mi_zone_locked(malloc_zone_t *zone) {
367292925Sdim  // UNIMPLEMENTED();
368292925Sdim  return false;
369292925Sdim}
370292925Sdim
371292925Sdim}  // unnamed namespace
372292925Sdim
373292925Sdimnamespace COMMON_MALLOC_NAMESPACE {
374292925Sdim
375344779Sdimvoid InitMallocZoneFields() {
376344779Sdim  static sanitizer_malloc_introspection_t sanitizer_zone_introspection;
377292925Sdim  // Ok to use internal_memset, these places are not performance-critical.
378292925Sdim  internal_memset(&sanitizer_zone_introspection, 0,
379292925Sdim                  sizeof(sanitizer_zone_introspection));
380292925Sdim
381292925Sdim  sanitizer_zone_introspection.enumerator = &mi_enumerator;
382292925Sdim  sanitizer_zone_introspection.good_size = &mi_good_size;
383292925Sdim  sanitizer_zone_introspection.check = &mi_check;
384292925Sdim  sanitizer_zone_introspection.print = &mi_print;
385292925Sdim  sanitizer_zone_introspection.log = &mi_log;
386292925Sdim  sanitizer_zone_introspection.force_lock = &mi_force_lock;
387292925Sdim  sanitizer_zone_introspection.force_unlock = &mi_force_unlock;
388292925Sdim  sanitizer_zone_introspection.statistics = &mi_statistics;
389292925Sdim  sanitizer_zone_introspection.zone_locked = &mi_zone_locked;
390292925Sdim
391344779Sdim  // Set current allocator enumeration version.
392344779Sdim  sanitizer_zone_introspection.allocator_enumeration_version =
393344779Sdim      GetMallocZoneAllocatorEnumerationVersion();
394344779Sdim
395353358Sdim  // Perform any sanitizer specific initialization.
396353358Sdim  mi_extra_init(&sanitizer_zone_introspection);
397353358Sdim
398292925Sdim  internal_memset(&sanitizer_zone, 0, sizeof(malloc_zone_t));
399292925Sdim
400292925Sdim  // Use version 6 for OSX >= 10.6.
401292925Sdim  sanitizer_zone.version = 6;
402292925Sdim  sanitizer_zone.zone_name = COMMON_MALLOC_ZONE_NAME;
403292925Sdim  sanitizer_zone.size = &__sanitizer_mz_size;
404292925Sdim  sanitizer_zone.malloc = &__sanitizer_mz_malloc;
405292925Sdim  sanitizer_zone.calloc = &__sanitizer_mz_calloc;
406292925Sdim  sanitizer_zone.valloc = &__sanitizer_mz_valloc;
407292925Sdim  sanitizer_zone.free = &__sanitizer_mz_free;
408292925Sdim  sanitizer_zone.realloc = &__sanitizer_mz_realloc;
409292925Sdim  sanitizer_zone.destroy = &__sanitizer_mz_destroy;
410292925Sdim  sanitizer_zone.batch_malloc = 0;
411292925Sdim  sanitizer_zone.batch_free = 0;
412292925Sdim  sanitizer_zone.free_definite_size = 0;
413292925Sdim  sanitizer_zone.memalign = &__sanitizer_mz_memalign;
414292925Sdim  sanitizer_zone.introspect = &sanitizer_zone_introspection;
415344779Sdim}
416292925Sdim
417344779Sdimvoid ReplaceSystemMalloc() {
418344779Sdim  InitMallocZoneFields();
419344779Sdim
420292925Sdim  // Register the zone.
421292925Sdim  malloc_zone_register(&sanitizer_zone);
422292925Sdim}
423292925Sdim
424292925Sdim}  // namespace COMMON_MALLOC_NAMESPACE
425