| //===-- asan_test_mac.cpp -------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file is a part of AddressSanitizer, an address sanity checker. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "asan_test_utils.h" |
| |
| #include "asan_mac_test.h" |
| |
| #include <malloc/malloc.h> |
| #include <AvailabilityMacros.h> // For MAC_OS_X_VERSION_* |
| #include <CoreFoundation/CFString.h> |
| |
| TEST(AddressSanitizerMac, CFAllocatorDefaultDoubleFree) { |
| EXPECT_DEATH( |
| CFAllocatorDefaultDoubleFree(NULL), |
| "attempting double-free"); |
| } |
| |
| void CFAllocator_DoubleFreeOnPthread() { |
| pthread_t child; |
| PTHREAD_CREATE(&child, NULL, CFAllocatorDefaultDoubleFree, NULL); |
| PTHREAD_JOIN(child, NULL); // Shouldn't be reached. |
| } |
| |
| TEST(AddressSanitizerMac, CFAllocatorDefaultDoubleFree_ChildPhread) { |
| EXPECT_DEATH(CFAllocator_DoubleFreeOnPthread(), "attempting double-free"); |
| } |
| |
| namespace { |
| |
| void *GLOB; |
| |
| void *CFAllocatorAllocateToGlob(void *unused) { |
| GLOB = CFAllocatorAllocate(NULL, 100, /*hint*/0); |
| return NULL; |
| } |
| |
| void *CFAllocatorDeallocateFromGlob(void *unused) { |
| char *p = (char*)GLOB; |
| p[100] = 'A'; // ASan should report an error here. |
| CFAllocatorDeallocate(NULL, GLOB); |
| return NULL; |
| } |
| |
| void CFAllocator_PassMemoryToAnotherThread() { |
| pthread_t th1, th2; |
| PTHREAD_CREATE(&th1, NULL, CFAllocatorAllocateToGlob, NULL); |
| PTHREAD_JOIN(th1, NULL); |
| PTHREAD_CREATE(&th2, NULL, CFAllocatorDeallocateFromGlob, NULL); |
| PTHREAD_JOIN(th2, NULL); |
| } |
| |
| TEST(AddressSanitizerMac, CFAllocator_PassMemoryToAnotherThread) { |
| EXPECT_DEATH(CFAllocator_PassMemoryToAnotherThread(), |
| "heap-buffer-overflow"); |
| } |
| |
| } // namespace |
| |
| // TODO(glider): figure out whether we still need these tests. Is it correct |
| // to intercept the non-default CFAllocators? |
| TEST(AddressSanitizerMac, DISABLED_CFAllocatorSystemDefaultDoubleFree) { |
| EXPECT_DEATH( |
| CFAllocatorSystemDefaultDoubleFree(), |
| "attempting double-free"); |
| } |
| |
| // We're intercepting malloc, so kCFAllocatorMalloc is routed to ASan. |
| TEST(AddressSanitizerMac, CFAllocatorMallocDoubleFree) { |
| EXPECT_DEATH(CFAllocatorMallocDoubleFree(), "attempting double-free"); |
| } |
| |
| TEST(AddressSanitizerMac, DISABLED_CFAllocatorMallocZoneDoubleFree) { |
| EXPECT_DEATH(CFAllocatorMallocZoneDoubleFree(), "attempting double-free"); |
| } |
| |
| // For libdispatch tests below we check that ASan got to the shadow byte |
| // legend, i.e. managed to print the thread stacks (this almost certainly |
| // means that the libdispatch task creation has been intercepted correctly). |
| TEST(AddressSanitizerMac, GCDDispatchAsync) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDDispatchAsync(), "Shadow byte legend"); |
| } |
| |
| TEST(AddressSanitizerMac, GCDDispatchSync) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDDispatchSync(), "Shadow byte legend"); |
| } |
| |
| |
| TEST(AddressSanitizerMac, GCDReuseWqthreadsAsync) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDReuseWqthreadsAsync(), "Shadow byte legend"); |
| } |
| |
| TEST(AddressSanitizerMac, GCDReuseWqthreadsSync) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDReuseWqthreadsSync(), "Shadow byte legend"); |
| } |
| |
| TEST(AddressSanitizerMac, GCDDispatchAfter) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte legend"); |
| } |
| |
| TEST(AddressSanitizerMac, GCDSourceEvent) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDSourceEvent(), "Shadow byte legend"); |
| } |
| |
| TEST(AddressSanitizerMac, GCDSourceCancel) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDSourceCancel(), "Shadow byte legend"); |
| } |
| |
| TEST(AddressSanitizerMac, GCDGroupAsync) { |
| // Make sure the whole ASan report is printed, i.e. that we don't die |
| // on a CHECK. |
| EXPECT_DEATH(TestGCDGroupAsync(), "Shadow byte legend"); |
| } |
| |
| void *MallocIntrospectionLockWorker(void *_) { |
| const int kNumPointers = 100; |
| int i; |
| void *pointers[kNumPointers]; |
| for (i = 0; i < kNumPointers; i++) { |
| pointers[i] = malloc(i + 1); |
| } |
| for (i = 0; i < kNumPointers; i++) { |
| free(pointers[i]); |
| } |
| |
| return NULL; |
| } |
| |
| void *MallocIntrospectionLockForker(void *_) { |
| pid_t result = fork(); |
| if (result == -1) { |
| perror("fork"); |
| } |
| assert(result != -1); |
| if (result == 0) { |
| // Call malloc in the child process to make sure we won't deadlock. |
| void *ptr = malloc(42); |
| free(ptr); |
| exit(0); |
| } else { |
| // Return in the parent process. |
| return NULL; |
| } |
| } |
| |
| TEST(AddressSanitizerMac, MallocIntrospectionLock) { |
| // Incorrect implementation of force_lock and force_unlock in our malloc zone |
| // will cause forked processes to deadlock. |
| // TODO(glider): need to detect that none of the child processes deadlocked. |
| const int kNumWorkers = 5, kNumIterations = 100; |
| int i, iter; |
| for (iter = 0; iter < kNumIterations; iter++) { |
| pthread_t workers[kNumWorkers], forker; |
| for (i = 0; i < kNumWorkers; i++) { |
| PTHREAD_CREATE(&workers[i], 0, MallocIntrospectionLockWorker, 0); |
| } |
| PTHREAD_CREATE(&forker, 0, MallocIntrospectionLockForker, 0); |
| for (i = 0; i < kNumWorkers; i++) { |
| PTHREAD_JOIN(workers[i], 0); |
| } |
| PTHREAD_JOIN(forker, 0); |
| } |
| } |
| |
| void *TSDAllocWorker(void *test_key) { |
| if (test_key) { |
| void *mem = malloc(10); |
| pthread_setspecific(*(pthread_key_t*)test_key, mem); |
| } |
| return NULL; |
| } |
| |
| TEST(AddressSanitizerMac, DISABLED_TSDWorkqueueTest) { |
| pthread_t th; |
| pthread_key_t test_key; |
| pthread_key_create(&test_key, CallFreeOnWorkqueue); |
| PTHREAD_CREATE(&th, NULL, TSDAllocWorker, &test_key); |
| PTHREAD_JOIN(th, NULL); |
| pthread_key_delete(test_key); |
| } |
| |
| // Test that CFStringCreateCopy does not copy constant strings. |
| TEST(AddressSanitizerMac, CFStringCreateCopy) { |
| CFStringRef str = CFSTR("Hello world!\n"); |
| CFStringRef str2 = CFStringCreateCopy(0, str); |
| EXPECT_EQ(str, str2); |
| } |
| |
| TEST(AddressSanitizerMac, NSObjectOOB) { |
| // Make sure that our allocators are used for NSObjects. |
| EXPECT_DEATH(TestOOBNSObjects(), "heap-buffer-overflow"); |
| } |
| |
| // Make sure that correct pointer is passed to free() when deallocating a |
| // NSURL object. |
| // See https://github.com/google/sanitizers/issues/70. |
| TEST(AddressSanitizerMac, NSURLDeallocation) { |
| TestNSURLDeallocation(); |
| } |
| |
| // See https://github.com/google/sanitizers/issues/109. |
| TEST(AddressSanitizerMac, Mstats) { |
| malloc_statistics_t stats1, stats2; |
| malloc_zone_statistics(/*all zones*/NULL, &stats1); |
| const size_t kMallocSize = 100000; |
| void *alloc = Ident(malloc(kMallocSize)); |
| malloc_zone_statistics(/*all zones*/NULL, &stats2); |
| EXPECT_GT(stats2.blocks_in_use, stats1.blocks_in_use); |
| EXPECT_GE(stats2.size_in_use - stats1.size_in_use, kMallocSize); |
| free(alloc); |
| // Even the default OSX allocator may not change the stats after free(). |
| } |
| |