Index: lib/asan/asan_globals.cc =================================================================== --- lib/asan/asan_globals.cc +++ lib/asan/asan_globals.cc @@ -233,7 +233,7 @@ } } -static void UnregisterGlobal(const Global *g) { +static void UnregisterGlobal(const Global *g, ListOfGlobals **prev_list) { CHECK(asan_inited); if (flags()->report_globals >= 2) ReportGlobal(*g, "Removed"); @@ -243,9 +243,35 @@ CHECK(AddrIsAlignedByGranularity(g->size_with_redzone)); if (CanPoisonMemory()) PoisonShadowForGlobal(g, 0); - // We unpoison the shadow memory for the global but we do not remove it from - // the list because that would require O(n^2) time with the current list - // implementation. It might not be worth doing anyway. + + // We unpoisoned the shadow memory for the global and remove it from the list + // of globals to prevent GetGlobalsForAddress from accessing unmapped memory. + ListOfGlobals *l; + if (*prev_list && (l = (*prev_list)->next) && l->g == g) { + // Optimization: when an array of globals is unregistered, the items to be + // removed succeed each other so take advantage of that. + (*prev_list)->next = l->next; + } else { + // The start node in the list is initially unknown, so try to find it. + ListOfGlobals *last_l = nullptr; + for (ListOfGlobals *l = list_of_all_globals; l; l = l->next) { + if (l->g == g) { + // Found the global, now unlink it from the list and remember the + // position for the next UnregisterGlobal call. + if (last_l) { + last_l->next = l->next; + *prev_list = last_l; + } else { + list_of_all_globals = l->next; + *prev_list = nullptr; + } + // Allocator for 'l' does not have a delete operator, just leak it. + break; + } else { + last_l = l; + } + } + } // Release ODR indicator. if (UseODRIndicator(g)) { @@ -398,13 +424,17 @@ void __asan_unregister_globals(__asan_global *globals, uptr n) { if (!flags()->report_globals) return; BlockingMutexLock lock(&mu_for_globals); - for (uptr i = 0; i < n; i++) { + // Cache the offset within the globals list and walk backwards the globals + // array to have O(n) time for removing items from the globals list (where n + // is the globals list size). Without this cache it could become O(n^2). + ListOfGlobals *prev_list = nullptr; + for (uptr i = n; i-- > 0; ) { if (SANITIZER_WINDOWS && globals[i].beg == 0) { // Skip globals that look like padding from the MSVC incremental linker. // See comment in __asan_register_globals. continue; } - UnregisterGlobal(&globals[i]); + UnregisterGlobal(&globals[i], &prev_list); } // Unpoison the metadata. Index: test/asan/TestCases/Posix/dlclose-globals.cc =================================================================== --- /dev/null +++ test/asan/TestCases/Posix/dlclose-globals.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_asan -O0 -DSHARED_LIB %s -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -O0 %s %libdl -o %t +// RUN: %env_asan_opts=handle_segv=0 %run %t %t-so.so 2>&1 | FileCheck %s + +#include +#include +#include + +#ifndef SHARED_LIB +int main(int argc, char **argv) { + if (argc != 2) { + printf("Usage: %s test.so\n", argv[0]); + return 1; + } + const char *libname = argv[1]; + void *handle = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (!handle) { + printf("dlopen failed: %s\n", dlerror()); + return 2; + } + char *ver = (char *)dlsym(handle, "VERSION"); + if (!ver) { + printf("dlsym(VERSION) failed: %s\n", dlerror()); + return 3; + } + + // CHECK: VERSION: dummy 1.0 + printf("VERSION: %s\n", ver); + fflush(stdout); + dlclose(handle); + + // CHECK: AddressSanitizer can not describe address in more detail (wild memory access suspected). + __asan_describe_address(0); + return 0; +} +#else // SHARED_LIB +extern "C" const char VERSION[] = "dummy 1.0"; +#endif // SHARED_LIB Index: test/asan/TestCases/Windows/freelibrary-globals.cc =================================================================== --- /dev/null +++ test/asan/TestCases/Windows/freelibrary-globals.cc @@ -0,0 +1,35 @@ +// RUN: %clang_cl_asan -LD -O0 -DSHARED_LIB %s -Fe%t.dll +// RUN: %clang_cl_asan -O0 %s -Fe%te.exe +// RUN: %env_asan_opts=handle_segv=0 %run %te.exe %t.dll 2>&1 | FileCheck %s + +#include +#include +#include + +#ifndef SHARED_LIB +int main(int argc, char **argv) { + if (argc != 2) { + printf("Usage: %s test.dll\n", argv[0]); + return 1; + } + const char *libname = argv[1]; + HMODULE handle = LoadLibrary(libname); + if (!handle) + return 2; + + char *ver = (char *)GetProcAddress(handle, "VERSION"); + if (!ver) + return 3; + + // CHECK: VERSION: dummy 1.0 + printf("VERSION: %s\n", ver); + fflush(stdout); + FreeLibrary(handle); + + // CHECK: AddressSanitizer can not describe address in more detail (wild memory access suspected). + __asan_describe_address(0); + return 0; +} +#else // SHARED_LIB +extern "C" __declspec(dllexport) const char VERSION[] = "dummy 1.0"; +#endif // SHARED_LIB