Index: lib/scudo/scudo_allocator.cpp =================================================================== --- lib/scudo/scudo_allocator.cpp +++ lib/scudo/scudo_allocator.cpp @@ -354,6 +354,8 @@ // Helper function that checks for a valid Scudo chunk. bool isValidPointer(const void *UserPtr) { + if (UNLIKELY(!ThreadInited)) + initThread(); uptr ChunkBeg = reinterpret_cast(UserPtr); if (!IsAligned(ChunkBeg, MinAlignment)) { return false; @@ -580,6 +582,14 @@ AllocatorQuarantine.Drain(&ThreadQuarantineCache, QuarantineCallback(&Cache)); } + + uptr getStats(AllocatorStat StatType) { + if (UNLIKELY(!ThreadInited)) + initThread(); + uptr stats[AllocatorStatCount]; + BackendAllocator.GetStats(stats); + return stats[StatType]; + } }; static Allocator Instance(LINKER_INITIALIZED); @@ -664,15 +674,11 @@ // MallocExtension helper functions uptr __sanitizer_get_current_allocated_bytes() { - uptr stats[AllocatorStatCount]; - getAllocator().GetStats(stats); - return stats[AllocatorStatAllocated]; + return Instance.getStats(AllocatorStatAllocated); } uptr __sanitizer_get_heap_size() { - uptr stats[AllocatorStatCount]; - getAllocator().GetStats(stats); - return stats[AllocatorStatMapped]; + return Instance.getStats(AllocatorStatMapped); } uptr __sanitizer_get_free_bytes() { Index: lib/scudo/scudo_flags.cpp =================================================================== --- lib/scudo/scudo_flags.cpp +++ lib/scudo/scudo_flags.cpp @@ -68,7 +68,7 @@ // Sanity checks and default settings for the Quarantine parameters. if (f->QuarantineSizeMb < 0) { - const int DefaultQuarantineSizeMb = 64; + const int DefaultQuarantineSizeMb = FIRST_32_SECOND_64(16, 64); f->QuarantineSizeMb = DefaultQuarantineSizeMb; } // We enforce an upper limit for the quarantine size of 4Gb. @@ -76,7 +76,8 @@ dieWithMessage("ERROR: the quarantine size is too large\n"); } if (f->ThreadLocalQuarantineSizeKb < 0) { - const int DefaultThreadLocalQuarantineSizeKb = 1024; + const int DefaultThreadLocalQuarantineSizeKb = + FIRST_32_SECOND_64(256, 1024); f->ThreadLocalQuarantineSizeKb = DefaultThreadLocalQuarantineSizeKb; } // And an upper limit of 128Mb for the thread quarantine cache. @@ -84,6 +85,10 @@ dieWithMessage("ERROR: the per thread quarantine cache size is too " "large\n"); } + if (f->ThreadLocalQuarantineSizeKb == 0 && f->QuarantineSizeMb > 0) { + dieWithMessage("ERROR: ThreadLocalQuarantineSizeKb can be set to 0 only " + "when QuarantineSizeMb is set to 0\n"); + } } Flags *getFlags() { Index: lib/scudo/scudo_flags.inc =================================================================== --- lib/scudo/scudo_flags.inc +++ lib/scudo/scudo_flags.inc @@ -15,12 +15,14 @@ # error "Define SCUDO_FLAG prior to including this file!" #endif -SCUDO_FLAG(int, QuarantineSizeMb, 64, +// Default value is set in scudo_flags.cpp based on architecture. +SCUDO_FLAG(int, QuarantineSizeMb, -1, "Size (in Mb) of quarantine used to delay the actual deallocation " "of chunks. Lower value may reduce memory usage but decrease the " "effectiveness of the mitigation.") -SCUDO_FLAG(int, ThreadLocalQuarantineSizeKb, 1024, +// Default value is set in scudo_flags.cpp based on architecture. +SCUDO_FLAG(int, ThreadLocalQuarantineSizeKb, -1, "Size (in Kb) of per-thread cache used to offload the global " "quarantine. Lower value may reduce memory usage but might increase " "the contention on the global quarantine.") Index: test/scudo/alignment.cpp =================================================================== --- test/scudo/alignment.cpp +++ test/scudo/alignment.cpp @@ -14,8 +14,7 @@ assert(argc == 2); if (!strcmp(argv[1], "pointers")) { void *p = malloc(1U << 16); - if (!p) - return 1; + assert(p); free(reinterpret_cast(reinterpret_cast(p) | 1)); } return 0; Index: test/scudo/double-free.cpp =================================================================== --- test/scudo/double-free.cpp +++ test/scudo/double-free.cpp @@ -16,30 +16,26 @@ assert(argc == 2); if (!strcmp(argv[1], "malloc")) { void *p = malloc(sizeof(int)); - if (!p) - return 1; + assert(p); free(p); free(p); } if (!strcmp(argv[1], "new")) { int *p = new int; - if (!p) - return 1; + assert(p); delete p; delete p; } if (!strcmp(argv[1], "newarray")) { int *p = new int[8]; - if (!p) - return 1; + assert(p); delete[] p; delete[] p; } if (!strcmp(argv[1], "memalign")) { void *p = nullptr; posix_memalign(&p, 0x100, sizeof(int)); - if (!p) - return 1; + assert(p); free(p); free(p); } Index: test/scudo/interface.cpp =================================================================== --- test/scudo/interface.cpp +++ test/scudo/interface.cpp @@ -1,9 +1,13 @@ // RUN: %clang_scudo %s -lstdc++ -o %t -// RUN: %run %t 2>&1 +// RUN: %run %t ownership 2>&1 +// RUN: %run %t ownership-and-size 2>&1 +// RUN: %run %t heap-size 2>&1 // Tests that the sanitizer interface functions behave appropriately. #include +#include +#include #include @@ -11,18 +15,33 @@ int main(int argc, char **argv) { - void *p; - std::vector sizes{1, 8, 16, 32, 1024, 32768, - 1 << 16, 1 << 17, 1 << 20, 1 << 24}; - for (size_t size : sizes) { - p = malloc(size); - if (!p) - return 1; - if (!__sanitizer_get_ownership(p)) - return 1; - if (__sanitizer_get_allocated_size(p) < size) - return 1; - free(p); + assert(argc == 2); + + if (!strcmp(argv[1], "ownership")) { + // Ensures that __sanitizer_get_ownership can be called before any other + // allocator function, and that it behaves properly on a pointer not owned + // by us. + assert(!__sanitizer_get_ownership(argv)); + } + if (!strcmp(argv[1], "ownership-and-size")) { + // Tests that __sanitizer_get_ownership and __sanitizer_get_allocated_size + // behave properly on chunks allocated by the Primary and Secondary. + void *p; + std::vector sizes{1, 8, 16, 32, 1024, 32768, + 1 << 16, 1 << 17, 1 << 20, 1 << 24}; + for (size_t size : sizes) { + p = malloc(size); + assert(p); + assert(__sanitizer_get_ownership(p)); + assert(__sanitizer_get_allocated_size(p) >= size); + free(p); + } } + if (!strcmp(argv[1], "heap-size")) { + // Ensures that __sanitizer_get_heap_size can be called before any other + // allocator function. At this point, this heap size should be 0. + assert(__sanitizer_get_heap_size() == 0); + } + return 0; } Index: test/scudo/lit.cfg =================================================================== --- test/scudo/lit.cfg +++ test/scudo/lit.cfg @@ -19,12 +19,12 @@ # C flags. c_flags = ([config.target_cflags] + ["-std=c++11", - "-lrt", - "-ldl", "-pthread", "-fPIE", "-pie", - "-O0"]) + "-O0", + "-UNDEBUG", + "-Wl,--gc-sections"]) def build_invocation(compile_flags): return " " + " ".join([config.clang] + compile_flags) + " " Index: test/scudo/malloc.cpp =================================================================== --- test/scudo/malloc.cpp +++ test/scudo/malloc.cpp @@ -5,6 +5,7 @@ // intended. Tests various sizes serviced by the primary and secondary // allocators. +#include #include #include @@ -18,8 +19,7 @@ std::vector offsets{1, 0, -1, -7, -8, -15, -16, -31, -32}; p = malloc(0); - if (!p) - return 1; + assert(p); free(p); for (ssize_t size : sizes) { for (int offset: offsets) { @@ -27,8 +27,7 @@ if (actual_size <= 0) continue; p = malloc(actual_size); - if (!p) - return 1; + assert(p); memset(p, 0xff, actual_size); free(p); } Index: test/scudo/memalign.cpp =================================================================== --- test/scudo/memalign.cpp +++ test/scudo/memalign.cpp @@ -29,12 +29,10 @@ if (!strcmp(argv[1], "valid")) { posix_memalign(&p, alignment, size); - if (!p) - return 1; + assert(p); free(p); p = aligned_alloc(alignment, size); - if (!p) - return 1; + assert(p); free(p); // Tests various combinations of alignment and sizes for (int i = (sizeof(void *) == 4) ? 3 : 4; i < 19; i++) { @@ -43,8 +41,7 @@ size = 0x800 * j; for (int k = 0; k < 3; k++) { p = memalign(alignment, size - (2 * sizeof(void *) * k)); - if (!p) - return 1; + assert(p); free(p); } } @@ -54,8 +51,7 @@ for (int i = 19; i <= 24; i++) { for (int k = 0; k < 3; k++) { p = memalign(alignment, 0x1000 - (2 * sizeof(void *) * k)); - if (!p) - return 1; + assert(p); free(p); } } Index: test/scudo/mismatch.cpp =================================================================== --- test/scudo/mismatch.cpp +++ test/scudo/mismatch.cpp @@ -10,29 +10,26 @@ // caught when the related option is set. #include +#include #include #include -#include int main(int argc, char **argv) { assert(argc == 2); if (!strcmp(argv[1], "mallocdel")) { int *p = (int *)malloc(16); - if (!p) - return 1; + assert(p); delete p; } if (!strcmp(argv[1], "newfree")) { int *p = new int; - if (!p) - return 1; + assert(p); free((void *)p); } if (!strcmp(argv[1], "memaligndel")) { int *p = (int *)memalign(16, 16); - if (!p) - return 1; + assert(p); delete p; } return 0; Index: test/scudo/options.cpp =================================================================== --- test/scudo/options.cpp +++ test/scudo/options.cpp @@ -6,8 +6,9 @@ // Tests that the options can be passed using getScudoDefaultOptions, and that // the environment ones take precedence over them. -#include +#include #include +#include extern "C" const char* __scudo_default_options() { return "DeallocationTypeMismatch=0"; // Defaults to true in scudo_flags.inc. @@ -16,8 +17,7 @@ int main(int argc, char **argv) { int *p = (int *)malloc(16); - if (!p) - return 1; + assert(p); delete p; return 0; } Index: test/scudo/overflow.cpp =================================================================== --- test/scudo/overflow.cpp +++ test/scudo/overflow.cpp @@ -10,20 +10,20 @@ int main(int argc, char **argv) { - assert(argc == 2); ssize_t offset = sizeof(void *) == 8 ? 8 : 0; + + assert(argc == 2); + if (!strcmp(argv[1], "malloc")) { // Simulate a header corruption of an allocated chunk (1-bit) void *p = malloc(1U << 4); - if (!p) - return 1; + assert(p); ((char *)p)[-(offset + 1)] ^= 1; free(p); } if (!strcmp(argv[1], "quarantine")) { void *p = malloc(1U << 4); - if (!p) - return 1; + assert(p); free(p); // Simulate a header corruption of a quarantined chunk ((char *)p)[-(offset + 2)] ^= 1; Index: test/scudo/preinit.cpp =================================================================== --- test/scudo/preinit.cpp +++ test/scudo/preinit.cpp @@ -4,6 +4,7 @@ // Verifies that calling malloc in a preinit_array function succeeds, and that // the resulting pointer can be freed at program termination. +#include #include #include @@ -23,8 +24,7 @@ int main(int argc, char **argv) { void *p = malloc(1); - if (!p) - return 1; + assert(p); free(p); return 0; @@ -34,4 +34,3 @@ void (*__local_preinit)(void) = __init; __attribute__((section(".fini_array"), used)) void (*__local_fini)(void) = __fini; - Index: test/scudo/quarantine.cpp =================================================================== --- test/scudo/quarantine.cpp +++ test/scudo/quarantine.cpp @@ -1,43 +1,57 @@ // RUN: %clang_scudo %s -o %t -// RUN: SCUDO_OPTIONS=QuarantineSizeMb=1 %run %t 2>&1 +// RUN: SCUDO_OPTIONS="QuarantineSizeMb=0:ThreadLocalQuarantineSizeKb=0" %run %t zeroquarantine 2>&1 +// RUN: SCUDO_OPTIONS=QuarantineSizeMb=1 %run %t smallquarantine 2>&1 // Tests that the quarantine prevents a chunk from being reused right away. // Also tests that a chunk will eventually become available again for // allocation when the recycling criteria has been met. +#include #include #include #include +#include + int main(int argc, char **argv) { void *p, *old_p; - size_t size = 1U << 16; + size_t allocated_bytes, size = 1U << 16; + + assert(argc == 2); - // The delayed freelist will prevent a chunk from being available right away - p = malloc(size); - if (!p) - return 1; - old_p = p; - free(p); - p = malloc(size); - if (!p) - return 1; - if (old_p == p) - return 1; - free(p); - - // Eventually the chunk should become available again - bool found = false; - for (int i = 0; i < 0x100 && found == false; i++) { + if (!strcmp(argv[1], "zeroquarantine")) { + // Verifies that a chunk is deallocated right away when the local and + // global quarantine sizes are 0. + allocated_bytes = __sanitizer_get_current_allocated_bytes(); + p = malloc(size); + assert(p); + assert(__sanitizer_get_current_allocated_bytes() > allocated_bytes); + free(p); + assert(__sanitizer_get_current_allocated_bytes() == allocated_bytes); + } + if (!strcmp(argv[1], "smallquarantine")) { + // The delayed freelist will prevent a chunk from being available right + // away. + p = malloc(size); + assert(p); + old_p = p; + free(p); p = malloc(size); - if (!p) - return 1; - found = (p == old_p); + assert(p); + assert(old_p != p); free(p); + + // Eventually the chunk should become available again. + bool found = false; + for (int i = 0; i < 0x100 && found == false; i++) { + p = malloc(size); + assert(p); + found = (p == old_p); + free(p); + } + assert(found == true); } - if (found == false) - return 1; return 0; } Index: test/scudo/realloc.cpp =================================================================== --- test/scudo/realloc.cpp +++ test/scudo/realloc.cpp @@ -23,48 +23,41 @@ std::vector sizes{1, 16, 1024, 32768, 1 << 16, 1 << 17, 1 << 20}; assert(argc == 2); + for (size_t size : sizes) { if (!strcmp(argv[1], "pointers")) { old_p = p = realloc(nullptr, size); - if (!p) - return 1; + assert(p); size = malloc_usable_size(p); // Our realloc implementation will return the same pointer if the size // requested is lower than or equal to the usable size of the associated // chunk. p = realloc(p, size - 1); - if (p != old_p) - return 1; + assert(p == old_p); p = realloc(p, size); - if (p != old_p) - return 1; + assert(p == old_p); // And a new one if the size is greater. p = realloc(p, size + 1); - if (p == old_p) - return 1; + assert(p != old_p); // A size of 0 will free the chunk and return nullptr. p = realloc(p, 0); - if (p) - return 1; + assert(!p); old_p = nullptr; } if (!strcmp(argv[1], "contents")) { p = realloc(nullptr, size); - if (!p) - return 1; + assert(p); for (int i = 0; i < size; i++) reinterpret_cast(p)[i] = 'A'; p = realloc(p, size + 1); // The contents of the reallocated chunk must match the original one. for (int i = 0; i < size; i++) - if (reinterpret_cast(p)[i] != 'A') - return 1; + assert(reinterpret_cast(p)[i] == 'A'); } if (!strcmp(argv[1], "memalign")) { // A chunk coming from memalign cannot be reallocated. p = memalign(16, size); - if (!p) - return 1; + assert(p); p = realloc(p, size); free(p); } Index: test/scudo/secondary.cpp =================================================================== --- test/scudo/secondary.cpp +++ test/scudo/secondary.cpp @@ -5,12 +5,12 @@ // Test that we hit a guard page when writing past the end of a chunk // allocated by the Secondary allocator, or writing too far in front of it. +#include #include +#include #include #include #include -#include -#include void handler(int signo, siginfo_t *info, void *uctx) { if (info->si_code == SEGV_ACCERR) { @@ -33,8 +33,7 @@ a.sa_flags = SA_SIGINFO; char *p = (char *)malloc(size); - if (!p) - return 1; + assert(p); memset(p, 'A', size); // This should not trigger anything. // Set up the SIGSEGV handler now, as the rest should trigger an AV. sigaction(SIGSEGV, &a, nullptr); Index: test/scudo/sized-delete.cpp =================================================================== --- test/scudo/sized-delete.cpp +++ test/scudo/sized-delete.cpp @@ -10,11 +10,12 @@ // option is passed and the sizes do not match between allocation and // deallocation functions. -#include #include #include #include +#include + int main(int argc, char **argv) { assert(argc == 2); Index: test/scudo/sizes.cpp =================================================================== --- test/scudo/sizes.cpp +++ test/scudo/sizes.cpp @@ -23,35 +23,28 @@ // Currently the maximum size the allocator can allocate is 1ULL<<40 bytes. size_t size = std::numeric_limits::max(); void *p = malloc(size); - if (p) - return 1; + assert(!p); size = (1ULL << 40) - 16; p = malloc(size); - if (p) - return 1; + assert(!p); } if (!strcmp(argv[1], "calloc")) { // Trigger an overflow in calloc. size_t size = std::numeric_limits::max(); void *p = calloc((size / 0x1000) + 1, 0x1000); - if (p) - return 1; + assert(!p); } if (!strcmp(argv[1], "usable")) { // Playing with the actual usable size of a chunk. void *p = malloc(1007); - if (!p) - return 1; + assert(p); size_t size = malloc_usable_size(p); - if (size < 1007) - return 1; + assert(size >= 1007); memset(p, 'A', size); p = realloc(p, 2014); - if (!p) - return 1; + assert(p); size = malloc_usable_size(p); - if (size < 2014) - return 1; + assert(size >= 2014); memset(p, 'B', size); free(p); }