Home | History | Annotate | Download | only in asan
      1 //===-- asan_rtl.cc ---------------------------------------------*- C++ -*-===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 // This file is a part of AddressSanitizer, an address sanity checker.
     11 //
     12 // Mac-specific malloc interception.
     13 //===----------------------------------------------------------------------===//
     14 
     15 #ifdef __APPLE__
     16 
     17 #include <AvailabilityMacros.h>
     18 #include <CoreFoundation/CFBase.h>
     19 #include <malloc/malloc.h>
     20 #include <setjmp.h>
     21 
     22 #include "asan_allocator.h"
     23 #include "asan_interceptors.h"
     24 #include "asan_internal.h"
     25 #include "asan_stack.h"
     26 
     27 // Similar code is used in Google Perftools,
     28 // http://code.google.com/p/google-perftools.
     29 
     30 // ---------------------- Replacement functions ---------------- {{{1
     31 using namespace __asan;  // NOLINT
     32 
     33 // The free() implementation provided by OS X calls malloc_zone_from_ptr()
     34 // to find the owner of |ptr|. If the result is NULL, an invalid free() is
     35 // reported. Our implementation falls back to asan_free() in this case
     36 // in order to print an ASan-style report.
     37 extern "C"
     38 void free(void *ptr) {
     39   malloc_zone_t *zone = malloc_zone_from_ptr(ptr);
     40   if (zone) {
     41 #if defined(MAC_OS_X_VERSION_10_6) && \
     42     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
     43     if ((zone->version >= 6) && (zone->free_definite_size)) {
     44       zone->free_definite_size(zone, ptr, malloc_size(ptr));
     45     } else {
     46       malloc_zone_free(zone, ptr);
     47     }
     48 #else
     49     malloc_zone_free(zone, ptr);
     50 #endif
     51   } else {
     52     GET_STACK_TRACE_HERE_FOR_FREE(ptr);
     53     asan_free(ptr, &stack);
     54   }
     55 }
     56 
     57 // TODO(glider): do we need both zones?
     58 static malloc_zone_t *system_malloc_zone = NULL;
     59 static malloc_zone_t *system_purgeable_zone = NULL;
     60 
     61 // We need to provide wrappers around all the libc functions.
     62 namespace {
     63 // TODO(glider): the mz_* functions should be united with the Linux wrappers,
     64 // as they are basically copied from there.
     65 size_t mz_size(malloc_zone_t* zone, const void* ptr) {
     66   // Fast path: check whether this pointer belongs to the original malloc zone.
     67   // We cannot just call malloc_zone_from_ptr(), because it in turn
     68   // calls our mz_size().
     69   if (system_malloc_zone) {
     70     if ((system_malloc_zone->size)(system_malloc_zone, ptr)) return 0;
     71   }
     72   return asan_mz_size(ptr);
     73 }
     74 
     75 void *mz_malloc(malloc_zone_t *zone, size_t size) {
     76   if (!asan_inited) {
     77     CHECK(system_malloc_zone);
     78     return malloc_zone_malloc(system_malloc_zone, size);
     79   }
     80   GET_STACK_TRACE_HERE_FOR_MALLOC;
     81   return asan_malloc(size, &stack);
     82 }
     83 
     84 void *cf_malloc(CFIndex size, CFOptionFlags hint, void *info) {
     85   if (!asan_inited) {
     86     CHECK(system_malloc_zone);
     87     return malloc_zone_malloc(system_malloc_zone, size);
     88   }
     89   GET_STACK_TRACE_HERE_FOR_MALLOC;
     90   return asan_malloc(size, &stack);
     91 }
     92 
     93 void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) {
     94   if (!asan_inited) {
     95     // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
     96     const size_t kCallocPoolSize = 1024;
     97     static uintptr_t calloc_memory_for_dlsym[kCallocPoolSize];
     98     static size_t allocated;
     99     size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize;
    100     void *mem = (void*)&calloc_memory_for_dlsym[allocated];
    101     allocated += size_in_words;
    102     CHECK(allocated < kCallocPoolSize);
    103     return mem;
    104   }
    105   GET_STACK_TRACE_HERE_FOR_MALLOC;
    106   return asan_calloc(nmemb, size, &stack);
    107 }
    108 
    109 void *mz_valloc(malloc_zone_t *zone, size_t size) {
    110   if (!asan_inited) {
    111     CHECK(system_malloc_zone);
    112     return malloc_zone_valloc(system_malloc_zone, size);
    113   }
    114   GET_STACK_TRACE_HERE_FOR_MALLOC;
    115   return asan_memalign(kPageSize, size, &stack);
    116 }
    117 
    118 void print_zone_for_ptr(void *ptr) {
    119   malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr);
    120   if (orig_zone) {
    121     if (orig_zone->zone_name) {
    122       Printf("malloc_zone_from_ptr(%p) = %p, which is %s\n",
    123              ptr, orig_zone, orig_zone->zone_name);
    124     } else {
    125       Printf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n",
    126              ptr, orig_zone);
    127     }
    128   } else {
    129     Printf("malloc_zone_from_ptr(%p) = NULL\n", ptr);
    130   }
    131 }
    132 
    133 // TODO(glider): the allocation callbacks need to be refactored.
    134 void mz_free(malloc_zone_t *zone, void *ptr) {
    135   if (!ptr) return;
    136   malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr);
    137   // For some reason Chromium calls mz_free() for pointers that belong to
    138   // DefaultPurgeableMallocZone instead of asan_zone. We might want to
    139   // fix this someday.
    140   if (orig_zone == system_purgeable_zone) {
    141     system_purgeable_zone->free(system_purgeable_zone, ptr);
    142     return;
    143   }
    144   if (asan_mz_size(ptr)) {
    145     GET_STACK_TRACE_HERE_FOR_FREE(ptr);
    146     asan_free(ptr, &stack);
    147   } else {
    148     // Let us just leak this memory for now.
    149     Printf("mz_free(%p) -- attempting to free unallocated memory.\n"
    150            "AddressSanitizer is ignoring this error on Mac OS now.\n", ptr);
    151     print_zone_for_ptr(ptr);
    152     GET_STACK_TRACE_HERE_FOR_FREE(ptr);
    153     stack.PrintStack();
    154     return;
    155   }
    156 }
    157 
    158 void cf_free(void *ptr, void *info) {
    159   if (!ptr) return;
    160   malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr);
    161   // For some reason Chromium calls mz_free() for pointers that belong to
    162   // DefaultPurgeableMallocZone instead of asan_zone. We might want to
    163   // fix this someday.
    164   if (orig_zone == system_purgeable_zone) {
    165     system_purgeable_zone->free(system_purgeable_zone, ptr);
    166     return;
    167   }
    168   if (asan_mz_size(ptr)) {
    169     GET_STACK_TRACE_HERE_FOR_FREE(ptr);
    170     asan_free(ptr, &stack);
    171   } else {
    172     // Let us just leak this memory for now.
    173     Printf("cf_free(%p) -- attempting to free unallocated memory.\n"
    174            "AddressSanitizer is ignoring this error on Mac OS now.\n", ptr);
    175     print_zone_for_ptr(ptr);
    176     GET_STACK_TRACE_HERE_FOR_FREE(ptr);
    177     stack.PrintStack();
    178     return;
    179   }
    180 }
    181 
    182 void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) {
    183   if (!ptr) {
    184     GET_STACK_TRACE_HERE_FOR_MALLOC;
    185     return asan_malloc(size, &stack);
    186   } else {
    187     if (asan_mz_size(ptr)) {
    188       GET_STACK_TRACE_HERE_FOR_MALLOC;
    189       return asan_realloc(ptr, size, &stack);
    190     } else {
    191       // We can't recover from reallocating an unknown address, because
    192       // this would require reading at most |size| bytes from
    193       // potentially unaccessible memory.
    194       Printf("mz_realloc(%p) -- attempting to realloc unallocated memory.\n"
    195              "This is an unrecoverable problem, exiting now.\n", ptr);
    196       print_zone_for_ptr(ptr);
    197       GET_STACK_TRACE_HERE_FOR_FREE(ptr);
    198       stack.PrintStack();
    199       ShowStatsAndAbort();
    200       return NULL;  // unreachable
    201     }
    202   }
    203 }
    204 
    205 void *cf_realloc(void *ptr, CFIndex size, CFOptionFlags hint, void *info) {
    206   if (!ptr) {
    207     GET_STACK_TRACE_HERE_FOR_MALLOC;
    208     return asan_malloc(size, &stack);
    209   } else {
    210     if (asan_mz_size(ptr)) {
    211       GET_STACK_TRACE_HERE_FOR_MALLOC;
    212       return asan_realloc(ptr, size, &stack);
    213     } else {
    214       // We can't recover from reallocating an unknown address, because
    215       // this would require reading at most |size| bytes from
    216       // potentially unaccessible memory.
    217       Printf("cf_realloc(%p) -- attempting to realloc unallocated memory.\n"
    218              "This is an unrecoverable problem, exiting now.\n", ptr);
    219       print_zone_for_ptr(ptr);
    220       GET_STACK_TRACE_HERE_FOR_FREE(ptr);
    221       stack.PrintStack();
    222       ShowStatsAndAbort();
    223       return NULL;  // unreachable
    224     }
    225   }
    226 }
    227 
    228 void mz_destroy(malloc_zone_t* zone) {
    229   // A no-op -- we will not be destroyed!
    230   Printf("mz_destroy() called -- ignoring\n");
    231 }
    232   // from AvailabilityMacros.h
    233 #if defined(MAC_OS_X_VERSION_10_6) && \
    234     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
    235 void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) {
    236   if (!asan_inited) {
    237     CHECK(system_malloc_zone);
    238     return malloc_zone_memalign(system_malloc_zone, align, size);
    239   }
    240   GET_STACK_TRACE_HERE_FOR_MALLOC;
    241   return asan_memalign(align, size, &stack);
    242 }
    243 
    244 // This function is currently unused, and we build with -Werror.
    245 #if 0
    246 void mz_free_definite_size(malloc_zone_t* zone, void *ptr, size_t size) {
    247   // TODO(glider): check that |size| is valid.
    248   UNIMPLEMENTED();
    249 }
    250 #endif
    251 #endif
    252 
    253 // malloc_introspection callbacks.  I'm not clear on what all of these do.
    254 kern_return_t mi_enumerator(task_t task, void *,
    255                             unsigned type_mask, vm_address_t zone_address,
    256                             memory_reader_t reader,
    257                             vm_range_recorder_t recorder) {
    258   // Should enumerate all the pointers we have.  Seems like a lot of work.
    259   return KERN_FAILURE;
    260 }
    261 
    262 size_t mi_good_size(malloc_zone_t *zone, size_t size) {
    263   // I think it's always safe to return size, but we maybe could do better.
    264   return size;
    265 }
    266 
    267 boolean_t mi_check(malloc_zone_t *zone) {
    268   UNIMPLEMENTED();
    269   return true;
    270 }
    271 
    272 void mi_print(malloc_zone_t *zone, boolean_t verbose) {
    273   UNIMPLEMENTED();
    274   return;
    275 }
    276 
    277 void mi_log(malloc_zone_t *zone, void *address) {
    278   // I don't think we support anything like this
    279 }
    280 
    281 void mi_force_lock(malloc_zone_t *zone) {
    282   asan_mz_force_lock();
    283 }
    284 
    285 void mi_force_unlock(malloc_zone_t *zone) {
    286   asan_mz_force_unlock();
    287 }
    288 
    289 // This function is currently unused, and we build with -Werror.
    290 #if 0
    291 void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) {
    292   // TODO(csilvers): figure out how to fill these out
    293   // TODO(glider): port this from tcmalloc when ready.
    294   stats->blocks_in_use = 0;
    295   stats->size_in_use = 0;
    296   stats->max_size_in_use = 0;
    297   stats->size_allocated = 0;
    298 }
    299 #endif
    300 
    301 #if defined(MAC_OS_X_VERSION_10_6) && \
    302     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
    303 boolean_t mi_zone_locked(malloc_zone_t *zone) {
    304   // UNIMPLEMENTED();
    305   return false;
    306 }
    307 #endif
    308 
    309 }  // unnamed namespace
    310 
    311 extern bool kCFUseCollectableAllocator;  // is GC on?
    312 
    313 namespace __asan {
    314 void ReplaceSystemMalloc() {
    315   static malloc_introspection_t asan_introspection;
    316   // Ok to use internal_memset, these places are not performance-critical.
    317   internal_memset(&asan_introspection, 0, sizeof(asan_introspection));
    318 
    319   asan_introspection.enumerator = &mi_enumerator;
    320   asan_introspection.good_size = &mi_good_size;
    321   asan_introspection.check = &mi_check;
    322   asan_introspection.print = &mi_print;
    323   asan_introspection.log = &mi_log;
    324   asan_introspection.force_lock = &mi_force_lock;
    325   asan_introspection.force_unlock = &mi_force_unlock;
    326 
    327   static malloc_zone_t asan_zone;
    328   internal_memset(&asan_zone, 0, sizeof(malloc_zone_t));
    329 
    330   // Start with a version 4 zone which is used for OS X 10.4 and 10.5.
    331   asan_zone.version = 4;
    332   asan_zone.zone_name = "asan";
    333   asan_zone.size = &mz_size;
    334   asan_zone.malloc = &mz_malloc;
    335   asan_zone.calloc = &mz_calloc;
    336   asan_zone.valloc = &mz_valloc;
    337   asan_zone.free = &mz_free;
    338   asan_zone.realloc = &mz_realloc;
    339   asan_zone.destroy = &mz_destroy;
    340   asan_zone.batch_malloc = NULL;
    341   asan_zone.batch_free = NULL;
    342   asan_zone.introspect = &asan_introspection;
    343 
    344   // from AvailabilityMacros.h
    345 #if defined(MAC_OS_X_VERSION_10_6) && \
    346     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
    347   // Switch to version 6 on OSX 10.6 to support memalign.
    348   asan_zone.version = 6;
    349   asan_zone.free_definite_size = 0;
    350   asan_zone.memalign = &mz_memalign;
    351   asan_introspection.zone_locked = &mi_zone_locked;
    352 
    353   // Request the default purgable zone to force its creation. The
    354   // current default zone is registered with the purgable zone for
    355   // doing tiny and small allocs.  Sadly, it assumes that the default
    356   // zone is the szone implementation from OS X and will crash if it
    357   // isn't.  By creating the zone now, this will be true and changing
    358   // the default zone won't cause a problem.  (OS X 10.6 and higher.)
    359   system_purgeable_zone = malloc_default_purgeable_zone();
    360 #endif
    361 
    362   // Register the ASan zone. At this point, it will not be the
    363   // default zone.
    364   malloc_zone_register(&asan_zone);
    365 
    366   // Unregister and reregister the default zone.  Unregistering swaps
    367   // the specified zone with the last one registered which for the
    368   // default zone makes the more recently registered zone the default
    369   // zone.  The default zone is then re-registered to ensure that
    370   // allocations made from it earlier will be handled correctly.
    371   // Things are not guaranteed to work that way, but it's how they work now.
    372   system_malloc_zone = malloc_default_zone();
    373   malloc_zone_unregister(system_malloc_zone);
    374   malloc_zone_register(system_malloc_zone);
    375   // Make sure the default allocator was replaced.
    376   CHECK(malloc_default_zone() == &asan_zone);
    377 
    378   if (FLAG_replace_cfallocator) {
    379     static CFAllocatorContext asan_context =
    380         { /*version*/ 0, /*info*/ &asan_zone,
    381           /*retain*/ NULL, /*release*/ NULL,
    382           /*copyDescription*/NULL,
    383           /*allocate*/ &cf_malloc,
    384           /*reallocate*/ &cf_realloc,
    385           /*deallocate*/ &cf_free,
    386           /*preferredSize*/ NULL };
    387     CFAllocatorRef cf_asan =
    388         CFAllocatorCreate(kCFAllocatorUseContext, &asan_context);
    389     CFAllocatorSetDefault(cf_asan);
    390   }
    391 }
    392 }  // namespace __asan
    393 
    394 #endif  // __APPLE__
    395