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