diff --git a/compiler-rt/include/sanitizer/lsan_interface.h b/compiler-rt/include/sanitizer/lsan_interface.h index df256c0e5384..cfc3d9c636b3 100644 --- a/compiler-rt/include/sanitizer/lsan_interface.h +++ b/compiler-rt/include/sanitizer/lsan_interface.h @@ -23,13 +23,30 @@ extern "C" { // be treated as non-leaks. Disable/enable pairs may be nested. void __lsan_disable(); void __lsan_enable(); + // The heap object into which p points will be treated as a non-leak. void __lsan_ignore_object(const void *p); + + // Memory regions registered through this interface will be treated as sources + // of live pointers during leak checking. Useful if you store pointers in + // mapped memory. + // Points of note: + // - __lsan_unregister_root_region() must be called with the same pointer and + // size that have earlier been passed to __lsan_register_root_region() + // - LSan will skip any inaccessible memory when scanning a root region. E.g., + // if you map memory within a larger region that you have mprotect'ed, you can + // register the entire large region. + // - the implementation is not optimized for performance. This interface is + // intended to be used for a small number of relatively static regions. + void __lsan_register_root_region(const void *p, size_t size); + void __lsan_unregister_root_region(const void *p, size_t size); + // The user may optionally provide this function to disallow leak checking // for the program it is linked into (if the return value is non-zero). This // function must be defined as returning a constant value; any behavior beyond // that is unsupported. int __lsan_is_turned_off(); + // Calling this function makes LSan enter the leak checking phase immediately. // Use this if normal end-of-process leak checking happens too late (e.g. if // you have intentional memory leaks in your shutdown code). Calling this diff --git a/compiler-rt/lib/lsan/lit_tests/TestCases/register_root_region.cc b/compiler-rt/lib/lsan/lit_tests/TestCases/register_root_region.cc new file mode 100644 index 000000000000..27349aaef4b3 --- /dev/null +++ b/compiler-rt/lib/lsan/lit_tests/TestCases/register_root_region.cc @@ -0,0 +1,32 @@ +// Test for __lsan_(un)register_root_region(). +// RUN: LSAN_BASE="use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t foo 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:use_root_regions=0 not %t 2>&1 | FileCheck %s + +#include +#include +#include +#include +#include + +#include + +int main(int argc, char *argv[]) { + size_t size = getpagesize() * 2; + void *p = + mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(p); + // Make half of the memory inaccessible. LSan must not crash trying to read it. + assert(0 == mprotect((char *)p + size / 2, size / 2, PROT_NONE)); + + __lsan_register_root_region(p, size); + *((void **)p) = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", p); + if (argc > 1) + __lsan_unregister_root_region(p, size); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s) diff --git a/compiler-rt/lib/lsan/lsan_common.cc b/compiler-rt/lib/lsan/lsan_common.cc index 8ad96f23e238..0ff492fc930a 100644 --- a/compiler-rt/lib/lsan/lsan_common.cc +++ b/compiler-rt/lib/lsan/lsan_common.cc @@ -17,6 +17,7 @@ #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_flags.h" #include "sanitizer_common/sanitizer_placement_new.h" +#include "sanitizer_common/sanitizer_procmaps.h" #include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_stacktrace.h" #include "sanitizer_common/sanitizer_stoptheworld.h" @@ -26,7 +27,8 @@ #if CAN_SANITIZE_LEAKS namespace __lsan { -// This mutex is used to prevent races between DoLeakCheck and IgnoreObject. +// This mutex is used to prevent races between DoLeakCheck and IgnoreObject, and +// also to protect the global list of root regions. BlockingMutex global_mutex(LINKER_INITIALIZED); THREADLOCAL int disable_counter; @@ -46,6 +48,7 @@ static void InitializeFlags() { f->use_globals = true; f->use_stacks = true; f->use_tls = true; + f->use_root_regions = true; f->use_unaligned = false; f->use_poisoned = false; f->verbosity = 0; @@ -58,6 +61,7 @@ static void InitializeFlags() { ParseFlag(options, &f->use_globals, "use_globals"); ParseFlag(options, &f->use_stacks, "use_stacks"); ParseFlag(options, &f->use_tls, "use_tls"); + ParseFlag(options, &f->use_root_regions, "use_root_regions"); ParseFlag(options, &f->use_unaligned, "use_unaligned"); ParseFlag(options, &f->use_poisoned, "use_poisoned"); ParseFlag(options, &f->report_objects, "report_objects"); @@ -77,8 +81,8 @@ SuppressionContext *suppression_ctx; void InitializeSuppressions() { CHECK(!suppression_ctx); - ALIGNED(64) static char placeholder_[sizeof(SuppressionContext)]; - suppression_ctx = new(placeholder_) SuppressionContext; + ALIGNED(64) static char placeholder[sizeof(SuppressionContext)]; + suppression_ctx = new(placeholder) SuppressionContext; char *suppressions_from_file; uptr buffer_size; if (ReadFileToBuffer(flags()->suppressions, &suppressions_from_file, @@ -93,8 +97,22 @@ void InitializeSuppressions() { suppression_ctx->Parse(__lsan_default_suppressions()); } +struct RootRegion { + const void *begin; + uptr size; +}; + +InternalMmapVector *root_regions; + +void InitializeRootRegions() { + CHECK(!root_regions); + ALIGNED(64) static char placeholder[sizeof(InternalMmapVector)]; + root_regions = new(placeholder) InternalMmapVector(1); +} + void InitCommonLsan() { InitializeFlags(); + InitializeRootRegions(); if (common_flags()->detect_leaks) { // Initialization which can fail or print warnings should only be done if // LSan is actually enabled. @@ -245,6 +263,38 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads, } } +static void ProcessRootRegion(Frontier *frontier, uptr root_begin, + uptr root_end) { + MemoryMappingLayout proc_maps(/*cache_enabled*/true); + uptr begin, end, prot; + while (proc_maps.Next(&begin, &end, + /*offset*/ 0, /*filename*/ 0, /*filename_size*/ 0, + &prot)) { + uptr intersection_begin = Max(root_begin, begin); + uptr intersection_end = Min(end, root_end); + if (intersection_begin >= intersection_end) continue; + bool is_readable = prot & MemoryMappingLayout::kProtectionRead; + if (flags()->log_pointers) + Report("Root region %p-%p intersects with mapped region %p-%p (%s)\n", + root_begin, root_end, begin, end, + is_readable ? "readable" : "unreadable"); + if (is_readable) + ScanRangeForPointers(intersection_begin, intersection_end, frontier, + "ROOT", kReachable); + } +} + +// Scans root regions for heap pointers. +static void ProcessRootRegions(Frontier *frontier) { + if (!flags()->use_root_regions) return; + CHECK(root_regions); + for (uptr i = 0; i < root_regions->size(); i++) { + RootRegion region = (*root_regions)[i]; + uptr begin_addr = reinterpret_cast(region.begin); + ProcessRootRegion(frontier, begin_addr, begin_addr + region.size); + } +} + static void FloodFillTag(Frontier *frontier, ChunkTag tag) { while (frontier->size()) { uptr next_chunk = frontier->back(); @@ -284,6 +334,7 @@ static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads) { if (flags()->use_globals) ProcessGlobalRegions(&frontier); ProcessThreads(suspended_threads, &frontier); + ProcessRootRegions(&frontier); FloodFillTag(&frontier, kReachable); // The check here is relatively expensive, so we do this in a separate flood // fill. That way we can skip the check for chunks that are reachable @@ -570,6 +621,46 @@ void __lsan_ignore_object(const void *p) { #endif // CAN_SANITIZE_LEAKS } +SANITIZER_INTERFACE_ATTRIBUTE +void __lsan_register_root_region(const void *begin, uptr size) { +#if CAN_SANITIZE_LEAKS + BlockingMutexLock l(&global_mutex); + CHECK(root_regions); + RootRegion region = {begin, size}; + root_regions->push_back(region); + if (flags()->verbosity) + Report("Registered root region at %p of size %llu\n", begin, size); +#endif // CAN_SANITIZE_LEAKS +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __lsan_unregister_root_region(const void *begin, uptr size) { +#if CAN_SANITIZE_LEAKS + BlockingMutexLock l(&global_mutex); + CHECK(root_regions); + bool removed = false; + for (uptr i = 0; i < root_regions->size(); i++) { + RootRegion region = (*root_regions)[i]; + if (region.begin == begin && region.size == size) { + removed = true; + uptr last_index = root_regions->size() - 1; + (*root_regions)[i] = (*root_regions)[last_index]; + root_regions->pop_back(); + if (flags()->verbosity) + Report("Unregistered root region at %p of size %llu\n", begin, size); + break; + } + } + if (!removed) { + Report( + "__lsan_unregister_root_region(): region at %p of size %llu has not " + "been registered.\n", + begin, size); + Die(); + } +#endif // CAN_SANITIZE_LEAKS +} + SANITIZER_INTERFACE_ATTRIBUTE void __lsan_disable() { #if CAN_SANITIZE_LEAKS diff --git a/compiler-rt/lib/lsan/lsan_common.h b/compiler-rt/lib/lsan/lsan_common.h index 714f8b650109..452824e12f19 100644 --- a/compiler-rt/lib/lsan/lsan_common.h +++ b/compiler-rt/lib/lsan/lsan_common.h @@ -63,6 +63,8 @@ struct Flags { bool use_registers; // TLS and thread-specific storage. bool use_tls; + // Regions added via __lsan_register_root_region(). + bool use_root_regions; // Consider unaligned pointers valid. bool use_unaligned;