//===-- asan_globals.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. // // Handle globals. //===----------------------------------------------------------------------===// #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_mapping.h" #include "asan_poisoning.h" #include "asan_report.h" #include "asan_stack.h" #include "asan_stats.h" #include "asan_suppressions.h" #include "asan_thread.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_dense_map.h" #include "sanitizer_common/sanitizer_list.h" #include "sanitizer_common/sanitizer_mutex.h" #include "sanitizer_common/sanitizer_placement_new.h" #include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_symbolizer.h" #include "sanitizer_common/sanitizer_thread_safety.h" namespace __asan { typedef __asan_global Global; struct GlobalListNode { const Global *g = nullptr; GlobalListNode *next = nullptr; }; typedef IntrusiveList ListOfGlobals; static Mutex mu_for_globals; static ListOfGlobals list_of_all_globals SANITIZER_GUARDED_BY(mu_for_globals); struct DynInitGlobal { Global g = {}; bool initialized = false; DynInitGlobal *next = nullptr; }; // We want to remember where a certain range of globals was registered. struct GlobalRegistrationSite { u32 stack_id; Global *g_first, *g_last; }; typedef InternalMmapVector GlobalRegistrationSiteVector; static GlobalRegistrationSiteVector *global_registration_site_vector; static ListOfGlobals &GlobalsByIndicator(uptr odr_indicator) SANITIZER_REQUIRES(mu_for_globals) { using MapOfGlobals = DenseMap; static MapOfGlobals *globals_by_indicator = nullptr; if (!globals_by_indicator) { alignas( alignof(MapOfGlobals)) static char placeholder[sizeof(MapOfGlobals)]; globals_by_indicator = new (placeholder) MapOfGlobals(); } return (*globals_by_indicator)[odr_indicator]; } static const char *current_dynamic_init_module_name SANITIZER_GUARDED_BY(mu_for_globals) = nullptr; using DynInitGlobalsByModule = DenseMap>; // TODO: Add a NoDestroy helper, this patter is very common in sanitizers. static DynInitGlobalsByModule &DynInitGlobals() SANITIZER_REQUIRES(mu_for_globals) { static DynInitGlobalsByModule *globals_by_module = nullptr; if (!globals_by_module) { alignas(alignof(DynInitGlobalsByModule)) static char placeholder[sizeof(DynInitGlobalsByModule)]; globals_by_module = new (placeholder) DynInitGlobalsByModule(); } return *globals_by_module; } ALWAYS_INLINE void PoisonShadowForGlobal(const Global *g, u8 value) { FastPoisonShadow(g->beg, g->size_with_redzone, value); } ALWAYS_INLINE void PoisonRedZones(const Global &g) { uptr aligned_size = RoundUpTo(g.size, ASAN_SHADOW_GRANULARITY); FastPoisonShadow(g.beg + aligned_size, g.size_with_redzone - aligned_size, kAsanGlobalRedzoneMagic); if (g.size != aligned_size) { FastPoisonShadowPartialRightRedzone( g.beg + RoundDownTo(g.size, ASAN_SHADOW_GRANULARITY), g.size % ASAN_SHADOW_GRANULARITY, ASAN_SHADOW_GRANULARITY, kAsanGlobalRedzoneMagic); } } const uptr kMinimalDistanceFromAnotherGlobal = 64; static void AddGlobalToList(ListOfGlobals &list, const Global *g) { list.push_front(new (GetGlobalLowLevelAllocator()) GlobalListNode{g}); } static void UnpoisonDynamicGlobals(IntrusiveList &dyn_globals, bool mark_initialized) { for (auto &dyn_g : dyn_globals) { const Global *g = &dyn_g.g; if (dyn_g.initialized) continue; // Unpoison the whole global. PoisonShadowForGlobal(g, 0); // Poison redzones back. PoisonRedZones(*g); if (mark_initialized) dyn_g.initialized = true; } } static void PoisonDynamicGlobals( const IntrusiveList &dyn_globals) { for (auto &dyn_g : dyn_globals) { const Global *g = &dyn_g.g; if (dyn_g.initialized) continue; PoisonShadowForGlobal(g, kAsanInitializationOrderMagic); } } static bool IsAddressNearGlobal(uptr addr, const __asan_global &g) { if (addr <= g.beg - kMinimalDistanceFromAnotherGlobal) return false; if (addr >= g.beg + g.size_with_redzone) return false; return true; } static void ReportGlobal(const Global &g, const char *prefix) { DataInfo info; bool symbolized = Symbolizer::GetOrInit()->SymbolizeData(g.beg, &info); Report( "%s Global[%p]: beg=%p size=%zu/%zu name=%s source=%s module=%s " "dyn_init=%zu " "odr_indicator=%p\n", prefix, (void *)&g, (void *)g.beg, g.size, g.size_with_redzone, g.name, g.module_name, (symbolized ? info.module : "?"), g.has_dynamic_init, (void *)g.odr_indicator); if (symbolized && info.line != 0) { Report(" location: name=%s, %d\n", info.file, static_cast(info.line)); } else if (g.gcc_location != 0) { // Fallback to Global::gcc_location Report(" location: name=%s, %d\n", g.gcc_location->filename, g.gcc_location->line_no); } } static u32 FindRegistrationSite(const Global *g) { mu_for_globals.CheckLocked(); CHECK(global_registration_site_vector); for (uptr i = 0, n = global_registration_site_vector->size(); i < n; i++) { GlobalRegistrationSite &grs = (*global_registration_site_vector)[i]; if (g >= grs.g_first && g <= grs.g_last) return grs.stack_id; } return 0; } int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites, int max_globals) { if (!flags()->report_globals) return 0; Lock lock(&mu_for_globals); int res = 0; for (const auto &l : list_of_all_globals) { const Global &g = *l.g; if (flags()->report_globals >= 2) ReportGlobal(g, "Search"); if (IsAddressNearGlobal(addr, g)) { internal_memcpy(&globals[res], &g, sizeof(g)); if (reg_sites) reg_sites[res] = FindRegistrationSite(&g); res++; if (res == max_globals) break; } } return res; } enum GlobalSymbolState { UNREGISTERED = 0, REGISTERED = 1 }; // Check ODR violation for given global G via special ODR indicator. We use // this method in case compiler instruments global variables through their // local aliases. static void CheckODRViolationViaIndicator(const Global *g) SANITIZER_REQUIRES(mu_for_globals) { // Instrumentation requests to skip ODR check. if (g->odr_indicator == UINTPTR_MAX) return; ListOfGlobals &relevant_globals = GlobalsByIndicator(g->odr_indicator); u8 *odr_indicator = reinterpret_cast(g->odr_indicator); if (*odr_indicator == REGISTERED) { // If *odr_indicator is REGISTERED, some module have already registered // externally visible symbol with the same name. This is an ODR violation. for (const auto &l : relevant_globals) { if ((flags()->detect_odr_violation >= 2 || g->size != l.g->size) && !IsODRViolationSuppressed(g->name)) ReportODRViolation(g, FindRegistrationSite(g), l.g, FindRegistrationSite(l.g)); } } else { // UNREGISTERED *odr_indicator = REGISTERED; } AddGlobalToList(relevant_globals, g); } // Clang provides two different ways for global variables protection: // it can poison the global itself or its private alias. In former // case we may poison same symbol multiple times, that can help us to // cheaply detect ODR violation: if we try to poison an already poisoned // global, we have ODR violation error. // In latter case, we poison each symbol exactly once, so we use special // indicator symbol to perform similar check. // In either case, compiler provides a special odr_indicator field to Global // structure, that can contain two kinds of values: // 1) Non-zero value. In this case, odr_indicator is an address of // corresponding indicator variable for given global. // 2) Zero. This means that we don't use private aliases for global variables // and can freely check ODR violation with the first method. // // This routine chooses between two different methods of ODR violation // detection. static inline bool UseODRIndicator(const Global *g) { return g->odr_indicator > 0; } // Register a global variable. // This function may be called more than once for every global // so we store the globals in a map. static void RegisterGlobal(const Global *g) SANITIZER_REQUIRES(mu_for_globals) { CHECK(AsanInited()); if (flags()->report_globals >= 2) ReportGlobal(*g, "Added"); CHECK(flags()->report_globals); CHECK(AddrIsInMem(g->beg)); if (!AddrIsAlignedByGranularity(g->beg)) { Report("The following global variable is not properly aligned.\n"); Report("This may happen if another global with the same name\n"); Report("resides in another non-instrumented module.\n"); Report("Or the global comes from a C file built w/o -fno-common.\n"); Report("In either case this is likely an ODR violation bug,\n"); Report("but AddressSanitizer can not provide more details.\n"); ReportODRViolation(g, FindRegistrationSite(g), g, FindRegistrationSite(g)); CHECK(AddrIsAlignedByGranularity(g->beg)); } CHECK(AddrIsAlignedByGranularity(g->size_with_redzone)); if (flags()->detect_odr_violation) { // Try detecting ODR (One Definition Rule) violation, i.e. the situation // where two globals with the same name are defined in different modules. if (UseODRIndicator(g)) CheckODRViolationViaIndicator(g); } if (CanPoisonMemory()) PoisonRedZones(*g); AddGlobalToList(list_of_all_globals, g); if (g->has_dynamic_init) { DynInitGlobals()[g->module_name].push_back( new (GetGlobalLowLevelAllocator()) DynInitGlobal{*g, false}); } } static void UnregisterGlobal(const Global *g) SANITIZER_REQUIRES(mu_for_globals) { CHECK(AsanInited()); if (flags()->report_globals >= 2) ReportGlobal(*g, "Removed"); CHECK(flags()->report_globals); CHECK(AddrIsInMem(g->beg)); CHECK(AddrIsAlignedByGranularity(g->beg)); 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. // Release ODR indicator. if (UseODRIndicator(g) && g->odr_indicator != UINTPTR_MAX) { u8 *odr_indicator = reinterpret_cast(g->odr_indicator); *odr_indicator = UNREGISTERED; } } void StopInitOrderChecking() { if (!flags()->check_initialization_order) return; Lock lock(&mu_for_globals); flags()->check_initialization_order = false; DynInitGlobals().forEach([&](auto &kv) { UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false); return true; }); } static bool IsASCII(unsigned char c) { return /*0x00 <= c &&*/ c <= 0x7F; } const char *MaybeDemangleGlobalName(const char *name) { // We can spoil names of globals with C linkage, so use an heuristic // approach to check if the name should be demangled. bool should_demangle = false; if (name[0] == '_' && name[1] == 'Z') should_demangle = true; else if (SANITIZER_WINDOWS && name[0] == '\01' && name[1] == '?') should_demangle = true; return should_demangle ? Symbolizer::GetOrInit()->Demangle(name) : name; } // Check if the global is a zero-terminated ASCII string. If so, print it. void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g) { for (uptr p = g.beg; p < g.beg + g.size - 1; p++) { unsigned char c = *(unsigned char *)p; if (c == '\0' || !IsASCII(c)) return; } if (*(char *)(g.beg + g.size - 1) != '\0') return; str->AppendF(" '%s' is ascii string '%s'\n", MaybeDemangleGlobalName(g.name), (char *)g.beg); } void PrintGlobalLocation(InternalScopedString *str, const __asan_global &g, bool print_module_name) { DataInfo info; if (Symbolizer::GetOrInit()->SymbolizeData(g.beg, &info) && info.line != 0) { str->AppendF("%s:%d", info.file, static_cast(info.line)); } else if (g.gcc_location != 0) { // Fallback to Global::gcc_location str->AppendF("%s", g.gcc_location->filename ? g.gcc_location->filename : g.module_name); if (g.gcc_location->line_no) str->AppendF(":%d", g.gcc_location->line_no); if (g.gcc_location->column_no) str->AppendF(":%d", g.gcc_location->column_no); } else { str->AppendF("%s", g.module_name); } if (print_module_name && info.module) str->AppendF(" in %s", info.module); } } // namespace __asan // ---------------------- Interface ---------------- {{{1 using namespace __asan; // Apply __asan_register_globals to all globals found in the same loaded // executable or shared library as `flag'. The flag tracks whether globals have // already been registered or not for this image. void __asan_register_image_globals(uptr *flag) { if (*flag) return; AsanApplyToGlobals(__asan_register_globals, flag); *flag = 1; } // This mirrors __asan_register_image_globals. void __asan_unregister_image_globals(uptr *flag) { if (!*flag) return; AsanApplyToGlobals(__asan_unregister_globals, flag); *flag = 0; } void __asan_register_elf_globals(uptr *flag, void *start, void *stop) { if (*flag || start == stop) return; CHECK_EQ(0, ((uptr)stop - (uptr)start) % sizeof(__asan_global)); __asan_global *globals_start = (__asan_global*)start; __asan_global *globals_stop = (__asan_global*)stop; __asan_register_globals(globals_start, globals_stop - globals_start); *flag = 1; } void __asan_unregister_elf_globals(uptr *flag, void *start, void *stop) { if (!*flag || start == stop) return; CHECK_EQ(0, ((uptr)stop - (uptr)start) % sizeof(__asan_global)); __asan_global *globals_start = (__asan_global*)start; __asan_global *globals_stop = (__asan_global*)stop; __asan_unregister_globals(globals_start, globals_stop - globals_start); *flag = 0; } // Register an array of globals. void __asan_register_globals(__asan_global *globals, uptr n) { if (!flags()->report_globals) return; GET_STACK_TRACE_MALLOC; u32 stack_id = StackDepotPut(stack); Lock lock(&mu_for_globals); if (!global_registration_site_vector) { global_registration_site_vector = new (GetGlobalLowLevelAllocator()) GlobalRegistrationSiteVector; global_registration_site_vector->reserve(128); } GlobalRegistrationSite site = {stack_id, &globals[0], &globals[n - 1]}; global_registration_site_vector->push_back(site); if (flags()->report_globals >= 2) { PRINT_CURRENT_STACK(); Printf("=== ID %d; %p %p\n", stack_id, (void *)&globals[0], (void *)&globals[n - 1]); } for (uptr i = 0; i < n; i++) { if (SANITIZER_WINDOWS && globals[i].beg == 0) { // The MSVC incremental linker may pad globals out to 256 bytes. As long // as __asan_global is less than 256 bytes large and its size is a power // of two, we can skip over the padding. static_assert( sizeof(__asan_global) < 256 && (sizeof(__asan_global) & (sizeof(__asan_global) - 1)) == 0, "sizeof(__asan_global) incompatible with incremental linker padding"); // If these are padding bytes, the rest of the global should be zero. CHECK(globals[i].size == 0 && globals[i].size_with_redzone == 0 && globals[i].name == nullptr && globals[i].module_name == nullptr && globals[i].odr_indicator == 0); continue; } RegisterGlobal(&globals[i]); } // Poison the metadata. It should not be accessible to user code. PoisonShadow(reinterpret_cast(globals), n * sizeof(__asan_global), kAsanGlobalRedzoneMagic); } // Unregister an array of globals. // We must do this when a shared objects gets dlclosed. void __asan_unregister_globals(__asan_global *globals, uptr n) { if (!flags()->report_globals) return; Lock lock(&mu_for_globals); for (uptr i = 0; i < n; i++) { 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]); } // Unpoison the metadata. PoisonShadow(reinterpret_cast(globals), n * sizeof(__asan_global), 0); } // This method runs immediately prior to dynamic initialization in each TU, // when all dynamically initialized globals are unpoisoned. This method // poisons all global variables not defined in this TU, so that a dynamic // initializer can only touch global variables in the same TU. void __asan_before_dynamic_init(const char *module_name) { if (!flags()->check_initialization_order || !CanPoisonMemory()) return; bool strict_init_order = flags()->strict_init_order; CHECK(module_name); CHECK(AsanInited()); Lock lock(&mu_for_globals); if (current_dynamic_init_module_name == module_name) return; if (flags()->report_globals >= 3) Printf("DynInitPoison module: %s\n", module_name); if (current_dynamic_init_module_name == nullptr) { // First call, poison all globals from other modules. DynInitGlobals().forEach([&](auto &kv) { if (kv.first != module_name) { PoisonDynamicGlobals(kv.second); } else { UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/!strict_init_order); } return true; }); } else { // Module changed. PoisonDynamicGlobals(DynInitGlobals()[current_dynamic_init_module_name]); UnpoisonDynamicGlobals(DynInitGlobals()[module_name], /*mark_initialized=*/!strict_init_order); } current_dynamic_init_module_name = module_name; } // Maybe SANITIZER_CAN_USE_PREINIT_ARRAY is to conservative for `.init_array`, // however we should not make mistake here. If `UnpoisonBeforeMain` was not // executed at all we will have false reports on globals. #if SANITIZER_CAN_USE_PREINIT_ARRAY // This optimization aims to reduce the overhead of `__asan_after_dynamic_init` // calls by leveraging incremental unpoisoning/poisoning in // `__asan_before_dynamic_init`. We expect most `__asan_after_dynamic_init // calls` to be no-ops. However, to ensure all globals are unpoisoned before the // `main`, we force `UnpoisonBeforeMain` to fully execute // `__asan_after_dynamic_init`. // With lld, `UnpoisonBeforeMain` runs after standard `.init_array`, making it // the final `__asan_after_dynamic_init` call for the static runtime. In // contrast, GNU ld executes it earlier, causing subsequent // `__asan_after_dynamic_init` calls to perform full unpoisoning, losing the // optimization. bool allow_after_dynamic_init SANITIZER_GUARDED_BY(mu_for_globals) = false; static void UnpoisonBeforeMain(void) { { Lock lock(&mu_for_globals); if (allow_after_dynamic_init) return; allow_after_dynamic_init = true; } if (flags()->report_globals >= 3) Printf("UnpoisonBeforeMain\n"); __asan_after_dynamic_init(); } __attribute__((section(".init_array.65537"), used)) static void ( *asan_after_init_array)(void) = UnpoisonBeforeMain; #else // Incremental poisoning is disabled, unpoison globals immediately. static constexpr bool allow_after_dynamic_init = true; #endif // SANITIZER_CAN_USE_PREINIT_ARRAY // This method runs immediately after dynamic initialization in each TU, when // all dynamically initialized globals except for those defined in the current // TU are poisoned. It simply unpoisons all dynamically initialized globals. void __asan_after_dynamic_init() { if (!flags()->check_initialization_order || !CanPoisonMemory()) return; CHECK(AsanInited()); Lock lock(&mu_for_globals); if (!allow_after_dynamic_init) return; if (!current_dynamic_init_module_name) return; if (flags()->report_globals >= 3) Printf("DynInitUnpoison\n"); DynInitGlobals().forEach([&](auto &kv) { UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false); return true; }); current_dynamic_init_module_name = nullptr; }