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