1 //===-- asan_malloc_mac.cc ------------------------------------------------===// 2 // 3 // This file is distributed under the University of Illinois Open Source 4 // License. See LICENSE.TXT for details. 5 // 6 //===----------------------------------------------------------------------===// 7 // 8 // This file is a part of AddressSanitizer, an address sanity checker. 9 // 10 // Mac-specific malloc interception. 11 //===----------------------------------------------------------------------===// 12 13 #include "sanitizer_common/sanitizer_platform.h" 14 #if SANITIZER_MAC 15 16 #include <AvailabilityMacros.h> 17 #include <CoreFoundation/CFBase.h> 18 #include <dlfcn.h> 19 #include <malloc/malloc.h> 20 #include <sys/mman.h> 21 22 #include "asan_allocator.h" 23 #include "asan_interceptors.h" 24 #include "asan_internal.h" 25 #include "asan_report.h" 26 #include "asan_stack.h" 27 #include "asan_stats.h" 28 #include "sanitizer_common/sanitizer_mac.h" 29 30 // Similar code is used in Google Perftools, 31 // http://code.google.com/p/google-perftools. 32 33 // ---------------------- Replacement functions ---------------- {{{1 34 using namespace __asan; // NOLINT 35 36 // TODO(glider): do we need both zones? 37 static malloc_zone_t *system_malloc_zone = 0; 38 static malloc_zone_t asan_zone; 39 40 INTERCEPTOR(malloc_zone_t *, malloc_create_zone, 41 vm_size_t start_size, unsigned zone_flags) { 42 ENSURE_ASAN_INITED(); 43 GET_STACK_TRACE_MALLOC; 44 uptr page_size = GetPageSizeCached(); 45 uptr allocated_size = RoundUpTo(sizeof(asan_zone), page_size); 46 malloc_zone_t *new_zone = 47 (malloc_zone_t*)asan_memalign(page_size, allocated_size, 48 &stack, FROM_MALLOC); 49 internal_memcpy(new_zone, &asan_zone, sizeof(asan_zone)); 50 new_zone->zone_name = NULL; // The name will be changed anyway. 51 if (GetMacosVersion() >= MACOS_VERSION_LION) { 52 // Prevent the client app from overwriting the zone contents. 53 // Library functions that need to modify the zone will set PROT_WRITE on it. 54 // This matches the behavior of malloc_create_zone() on OSX 10.7 and higher. 55 mprotect(new_zone, allocated_size, PROT_READ); 56 } 57 return new_zone; 58 } 59 60 INTERCEPTOR(malloc_zone_t *, malloc_default_zone, void) { 61 ENSURE_ASAN_INITED(); 62 return &asan_zone; 63 } 64 65 INTERCEPTOR(malloc_zone_t *, malloc_default_purgeable_zone, void) { 66 // FIXME: ASan should support purgeable allocations. 67 // https://code.google.com/p/address-sanitizer/issues/detail?id=139 68 ENSURE_ASAN_INITED(); 69 return &asan_zone; 70 } 71 72 INTERCEPTOR(void, malloc_make_purgeable, void *ptr) { 73 // FIXME: ASan should support purgeable allocations. Ignoring them is fine 74 // for now. 75 ENSURE_ASAN_INITED(); 76 } 77 78 INTERCEPTOR(int, malloc_make_nonpurgeable, void *ptr) { 79 // FIXME: ASan should support purgeable allocations. Ignoring them is fine 80 // for now. 81 ENSURE_ASAN_INITED(); 82 // Must return 0 if the contents were not purged since the last call to 83 // malloc_make_purgeable(). 84 return 0; 85 } 86 87 INTERCEPTOR(void, malloc_set_zone_name, malloc_zone_t *zone, const char *name) { 88 ENSURE_ASAN_INITED(); 89 // Allocate |strlen("asan-") + 1 + internal_strlen(name)| bytes. 90 size_t buflen = 6 + (name ? internal_strlen(name) : 0); 91 InternalScopedBuffer<char> new_name(buflen); 92 if (name && zone->introspect == asan_zone.introspect) { 93 internal_snprintf(new_name.data(), buflen, "asan-%s", name); 94 name = new_name.data(); 95 } 96 97 // Call the system malloc's implementation for both external and our zones, 98 // since that appropriately changes VM region protections on the zone. 99 REAL(malloc_set_zone_name)(zone, name); 100 } 101 102 INTERCEPTOR(void *, malloc, size_t size) { 103 ENSURE_ASAN_INITED(); 104 GET_STACK_TRACE_MALLOC; 105 void *res = asan_malloc(size, &stack); 106 return res; 107 } 108 109 INTERCEPTOR(void, free, void *ptr) { 110 ENSURE_ASAN_INITED(); 111 if (!ptr) return; 112 GET_STACK_TRACE_FREE; 113 asan_free(ptr, &stack, FROM_MALLOC); 114 } 115 116 INTERCEPTOR(void *, realloc, void *ptr, size_t size) { 117 ENSURE_ASAN_INITED(); 118 GET_STACK_TRACE_MALLOC; 119 return asan_realloc(ptr, size, &stack); 120 } 121 122 INTERCEPTOR(void *, calloc, size_t nmemb, size_t size) { 123 ENSURE_ASAN_INITED(); 124 GET_STACK_TRACE_MALLOC; 125 return asan_calloc(nmemb, size, &stack); 126 } 127 128 INTERCEPTOR(void *, valloc, size_t size) { 129 ENSURE_ASAN_INITED(); 130 GET_STACK_TRACE_MALLOC; 131 return asan_memalign(GetPageSizeCached(), size, &stack, FROM_MALLOC); 132 } 133 134 INTERCEPTOR(size_t, malloc_good_size, size_t size) { 135 ENSURE_ASAN_INITED(); 136 return asan_zone.introspect->good_size(&asan_zone, size); 137 } 138 139 INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) { 140 ENSURE_ASAN_INITED(); 141 CHECK(memptr); 142 GET_STACK_TRACE_MALLOC; 143 void *result = asan_memalign(alignment, size, &stack, FROM_MALLOC); 144 if (result) { 145 *memptr = result; 146 return 0; 147 } 148 return -1; 149 } 150 151 namespace { 152 153 // TODO(glider): the mz_* functions should be united with the Linux wrappers, 154 // as they are basically copied from there. 155 size_t mz_size(malloc_zone_t* zone, const void* ptr) { 156 return asan_mz_size(ptr); 157 } 158 159 void *mz_malloc(malloc_zone_t *zone, size_t size) { 160 if (UNLIKELY(!asan_inited)) { 161 CHECK(system_malloc_zone); 162 return malloc_zone_malloc(system_malloc_zone, size); 163 } 164 GET_STACK_TRACE_MALLOC; 165 return asan_malloc(size, &stack); 166 } 167 168 void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { 169 if (UNLIKELY(!asan_inited)) { 170 // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym. 171 const size_t kCallocPoolSize = 1024; 172 static uptr calloc_memory_for_dlsym[kCallocPoolSize]; 173 static size_t allocated; 174 size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize; 175 void *mem = (void*)&calloc_memory_for_dlsym[allocated]; 176 allocated += size_in_words; 177 CHECK(allocated < kCallocPoolSize); 178 return mem; 179 } 180 GET_STACK_TRACE_MALLOC; 181 return asan_calloc(nmemb, size, &stack); 182 } 183 184 void *mz_valloc(malloc_zone_t *zone, size_t size) { 185 if (UNLIKELY(!asan_inited)) { 186 CHECK(system_malloc_zone); 187 return malloc_zone_valloc(system_malloc_zone, size); 188 } 189 GET_STACK_TRACE_MALLOC; 190 return asan_memalign(GetPageSizeCached(), size, &stack, FROM_MALLOC); 191 } 192 193 #define GET_ZONE_FOR_PTR(ptr) \ 194 malloc_zone_t *zone_ptr = malloc_zone_from_ptr(ptr); \ 195 const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name 196 197 void ALWAYS_INLINE free_common(void *context, void *ptr) { 198 if (!ptr) return; 199 GET_STACK_TRACE_FREE; 200 // FIXME: need to retire this flag. 201 if (!flags()->mac_ignore_invalid_free) { 202 asan_free(ptr, &stack, FROM_MALLOC); 203 } else { 204 GET_ZONE_FOR_PTR(ptr); 205 WarnMacFreeUnallocated((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); 206 return; 207 } 208 } 209 210 // TODO(glider): the allocation callbacks need to be refactored. 211 void mz_free(malloc_zone_t *zone, void *ptr) { 212 free_common(zone, ptr); 213 } 214 215 void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) { 216 if (!ptr) { 217 GET_STACK_TRACE_MALLOC; 218 return asan_malloc(size, &stack); 219 } else { 220 if (asan_mz_size(ptr)) { 221 GET_STACK_TRACE_MALLOC; 222 return asan_realloc(ptr, size, &stack); 223 } else { 224 // We can't recover from reallocating an unknown address, because 225 // this would require reading at most |size| bytes from 226 // potentially unaccessible memory. 227 GET_STACK_TRACE_FREE; 228 GET_ZONE_FOR_PTR(ptr); 229 ReportMacMzReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); 230 } 231 } 232 } 233 234 void mz_destroy(malloc_zone_t* zone) { 235 // A no-op -- we will not be destroyed! 236 Report("mz_destroy() called -- ignoring\n"); 237 } 238 239 // from AvailabilityMacros.h 240 #if defined(MAC_OS_X_VERSION_10_6) && \ 241 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 242 void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { 243 if (UNLIKELY(!asan_inited)) { 244 CHECK(system_malloc_zone); 245 return malloc_zone_memalign(system_malloc_zone, align, size); 246 } 247 GET_STACK_TRACE_MALLOC; 248 return asan_memalign(align, size, &stack, FROM_MALLOC); 249 } 250 251 // This function is currently unused, and we build with -Werror. 252 #if 0 253 void mz_free_definite_size(malloc_zone_t* zone, void *ptr, size_t size) { 254 // TODO(glider): check that |size| is valid. 255 UNIMPLEMENTED(); 256 } 257 #endif 258 #endif 259 260 kern_return_t mi_enumerator(task_t task, void *, 261 unsigned type_mask, vm_address_t zone_address, 262 memory_reader_t reader, 263 vm_range_recorder_t recorder) { 264 // Should enumerate all the pointers we have. Seems like a lot of work. 265 return KERN_FAILURE; 266 } 267 268 size_t mi_good_size(malloc_zone_t *zone, size_t size) { 269 // I think it's always safe to return size, but we maybe could do better. 270 return size; 271 } 272 273 boolean_t mi_check(malloc_zone_t *zone) { 274 UNIMPLEMENTED(); 275 } 276 277 void mi_print(malloc_zone_t *zone, boolean_t verbose) { 278 UNIMPLEMENTED(); 279 } 280 281 void mi_log(malloc_zone_t *zone, void *address) { 282 // I don't think we support anything like this 283 } 284 285 void mi_force_lock(malloc_zone_t *zone) { 286 asan_mz_force_lock(); 287 } 288 289 void mi_force_unlock(malloc_zone_t *zone) { 290 asan_mz_force_unlock(); 291 } 292 293 void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { 294 AsanMallocStats malloc_stats; 295 FillMallocStatistics(&malloc_stats); 296 CHECK(sizeof(malloc_statistics_t) == sizeof(AsanMallocStats)); 297 internal_memcpy(stats, &malloc_stats, sizeof(malloc_statistics_t)); 298 } 299 300 #if defined(MAC_OS_X_VERSION_10_6) && \ 301 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 302 boolean_t mi_zone_locked(malloc_zone_t *zone) { 303 // UNIMPLEMENTED(); 304 return false; 305 } 306 #endif 307 308 } // unnamed namespace 309 310 namespace __asan { 311 312 void ReplaceSystemMalloc() { 313 static malloc_introspection_t asan_introspection; 314 // Ok to use internal_memset, these places are not performance-critical. 315 internal_memset(&asan_introspection, 0, sizeof(asan_introspection)); 316 317 asan_introspection.enumerator = &mi_enumerator; 318 asan_introspection.good_size = &mi_good_size; 319 asan_introspection.check = &mi_check; 320 asan_introspection.print = &mi_print; 321 asan_introspection.log = &mi_log; 322 asan_introspection.force_lock = &mi_force_lock; 323 asan_introspection.force_unlock = &mi_force_unlock; 324 asan_introspection.statistics = &mi_statistics; 325 326 internal_memset(&asan_zone, 0, sizeof(malloc_zone_t)); 327 328 // Start with a version 4 zone which is used for OS X 10.4 and 10.5. 329 asan_zone.version = 4; 330 asan_zone.zone_name = "asan"; 331 asan_zone.size = &mz_size; 332 asan_zone.malloc = &mz_malloc; 333 asan_zone.calloc = &mz_calloc; 334 asan_zone.valloc = &mz_valloc; 335 asan_zone.free = &mz_free; 336 asan_zone.realloc = &mz_realloc; 337 asan_zone.destroy = &mz_destroy; 338 asan_zone.batch_malloc = 0; 339 asan_zone.batch_free = 0; 340 asan_zone.introspect = &asan_introspection; 341 342 // from AvailabilityMacros.h 343 #if defined(MAC_OS_X_VERSION_10_6) && \ 344 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 345 // Switch to version 6 on OSX 10.6 to support memalign. 346 asan_zone.version = 6; 347 asan_zone.free_definite_size = 0; 348 asan_zone.memalign = &mz_memalign; 349 asan_introspection.zone_locked = &mi_zone_locked; 350 #endif 351 352 // Register the ASan zone. 353 malloc_zone_register(&asan_zone); 354 } 355 } // namespace __asan 356 357 #endif // SANITIZER_MAC 358