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