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