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