583 lines · cpp
1//===-- asan_globals.cpp --------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file is a part of AddressSanitizer, an address sanity checker.10//11// Handle globals.12//===----------------------------------------------------------------------===//13 14#include "asan_interceptors.h"15#include "asan_internal.h"16#include "asan_mapping.h"17#include "asan_poisoning.h"18#include "asan_report.h"19#include "asan_stack.h"20#include "asan_stats.h"21#include "asan_suppressions.h"22#include "asan_thread.h"23#include "sanitizer_common/sanitizer_common.h"24#include "sanitizer_common/sanitizer_dense_map.h"25#include "sanitizer_common/sanitizer_list.h"26#include "sanitizer_common/sanitizer_mutex.h"27#include "sanitizer_common/sanitizer_placement_new.h"28#include "sanitizer_common/sanitizer_stackdepot.h"29#include "sanitizer_common/sanitizer_symbolizer.h"30#include "sanitizer_common/sanitizer_thread_safety.h"31 32namespace __asan {33 34typedef __asan_global Global;35 36struct GlobalListNode {37 const Global *g = nullptr;38 GlobalListNode *next = nullptr;39};40typedef IntrusiveList<GlobalListNode> ListOfGlobals;41 42static Mutex mu_for_globals;43static ListOfGlobals list_of_all_globals SANITIZER_GUARDED_BY(mu_for_globals);44 45struct DynInitGlobal {46 Global g = {};47 bool initialized = false;48 DynInitGlobal *next = nullptr;49};50 51// We want to remember where a certain range of globals was registered.52struct GlobalRegistrationSite {53 u32 stack_id;54 Global *g_first, *g_last;55};56typedef InternalMmapVector<GlobalRegistrationSite> GlobalRegistrationSiteVector;57static GlobalRegistrationSiteVector *global_registration_site_vector;58 59static ListOfGlobals &GlobalsByIndicator(uptr odr_indicator)60 SANITIZER_REQUIRES(mu_for_globals) {61 using MapOfGlobals = DenseMap<uptr, ListOfGlobals>;62 63 static MapOfGlobals *globals_by_indicator = nullptr;64 if (!globals_by_indicator) {65 alignas(66 alignof(MapOfGlobals)) static char placeholder[sizeof(MapOfGlobals)];67 globals_by_indicator = new (placeholder) MapOfGlobals();68 }69 70 return (*globals_by_indicator)[odr_indicator];71}72 73static const char *current_dynamic_init_module_name74 SANITIZER_GUARDED_BY(mu_for_globals) = nullptr;75 76using DynInitGlobalsByModule =77 DenseMap<const char *, IntrusiveList<DynInitGlobal>>;78 79// TODO: Add a NoDestroy helper, this patter is very common in sanitizers.80static DynInitGlobalsByModule &DynInitGlobals()81 SANITIZER_REQUIRES(mu_for_globals) {82 static DynInitGlobalsByModule *globals_by_module = nullptr;83 if (!globals_by_module) {84 alignas(alignof(DynInitGlobalsByModule)) static char85 placeholder[sizeof(DynInitGlobalsByModule)];86 globals_by_module = new (placeholder) DynInitGlobalsByModule();87 }88 89 return *globals_by_module;90}91 92ALWAYS_INLINE void PoisonShadowForGlobal(const Global *g, u8 value) {93 FastPoisonShadow(g->beg, g->size_with_redzone, value);94}95 96ALWAYS_INLINE void PoisonRedZones(const Global &g) {97 uptr aligned_size = RoundUpTo(g.size, ASAN_SHADOW_GRANULARITY);98 FastPoisonShadow(g.beg + aligned_size, g.size_with_redzone - aligned_size,99 kAsanGlobalRedzoneMagic);100 if (g.size != aligned_size) {101 FastPoisonShadowPartialRightRedzone(102 g.beg + RoundDownTo(g.size, ASAN_SHADOW_GRANULARITY),103 g.size % ASAN_SHADOW_GRANULARITY, ASAN_SHADOW_GRANULARITY,104 kAsanGlobalRedzoneMagic);105 }106}107 108const uptr kMinimalDistanceFromAnotherGlobal = 64;109 110static void AddGlobalToList(ListOfGlobals &list, const Global *g) {111 list.push_front(new (GetGlobalLowLevelAllocator()) GlobalListNode{g});112}113 114static void UnpoisonDynamicGlobals(IntrusiveList<DynInitGlobal> &dyn_globals,115 bool mark_initialized) {116 for (auto &dyn_g : dyn_globals) {117 const Global *g = &dyn_g.g;118 if (dyn_g.initialized)119 continue;120 // Unpoison the whole global.121 PoisonShadowForGlobal(g, 0);122 // Poison redzones back.123 PoisonRedZones(*g);124 if (mark_initialized)125 dyn_g.initialized = true;126 }127}128 129static void PoisonDynamicGlobals(130 const IntrusiveList<DynInitGlobal> &dyn_globals) {131 for (auto &dyn_g : dyn_globals) {132 const Global *g = &dyn_g.g;133 if (dyn_g.initialized)134 continue;135 PoisonShadowForGlobal(g, kAsanInitializationOrderMagic);136 }137}138 139static bool IsAddressNearGlobal(uptr addr, const __asan_global &g) {140 if (addr <= g.beg - kMinimalDistanceFromAnotherGlobal) return false;141 if (addr >= g.beg + g.size_with_redzone) return false;142 return true;143}144 145static void ReportGlobal(const Global &g, const char *prefix) {146 DataInfo info;147 bool symbolized = Symbolizer::GetOrInit()->SymbolizeData(g.beg, &info);148 Report(149 "%s Global[%p]: beg=%p size=%zu/%zu name=%s source=%s module=%s "150 "dyn_init=%zu "151 "odr_indicator=%p\n",152 prefix, (void *)&g, (void *)g.beg, g.size, g.size_with_redzone, g.name,153 g.module_name, (symbolized ? info.module : "?"), g.has_dynamic_init,154 (void *)g.odr_indicator);155 156 if (symbolized && info.line != 0) {157 Report(" location: name=%s, %d\n", info.file, static_cast<int>(info.line));158 } else if (g.gcc_location != 0) {159 // Fallback to Global::gcc_location160 Report(" location: name=%s, %d\n", g.gcc_location->filename, g.gcc_location->line_no);161 }162}163 164static u32 FindRegistrationSite(const Global *g) {165 mu_for_globals.CheckLocked();166 CHECK(global_registration_site_vector);167 for (uptr i = 0, n = global_registration_site_vector->size(); i < n; i++) {168 GlobalRegistrationSite &grs = (*global_registration_site_vector)[i];169 if (g >= grs.g_first && g <= grs.g_last)170 return grs.stack_id;171 }172 return 0;173}174 175int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites,176 int max_globals) {177 if (!flags()->report_globals) return 0;178 Lock lock(&mu_for_globals);179 int res = 0;180 for (const auto &l : list_of_all_globals) {181 const Global &g = *l.g;182 if (flags()->report_globals >= 2)183 ReportGlobal(g, "Search");184 if (IsAddressNearGlobal(addr, g)) {185 internal_memcpy(&globals[res], &g, sizeof(g));186 if (reg_sites)187 reg_sites[res] = FindRegistrationSite(&g);188 res++;189 if (res == max_globals)190 break;191 }192 }193 return res;194}195 196enum GlobalSymbolState {197 UNREGISTERED = 0,198 REGISTERED = 1199};200 201// Check ODR violation for given global G via special ODR indicator. We use202// this method in case compiler instruments global variables through their203// local aliases.204static void CheckODRViolationViaIndicator(const Global *g)205 SANITIZER_REQUIRES(mu_for_globals) {206 // Instrumentation requests to skip ODR check.207 if (g->odr_indicator == UINTPTR_MAX)208 return;209 210 ListOfGlobals &relevant_globals = GlobalsByIndicator(g->odr_indicator);211 212 u8 *odr_indicator = reinterpret_cast<u8 *>(g->odr_indicator);213 if (*odr_indicator == REGISTERED) {214 // If *odr_indicator is REGISTERED, some module have already registered215 // externally visible symbol with the same name. This is an ODR violation.216 for (const auto &l : relevant_globals) {217 if ((flags()->detect_odr_violation >= 2 || g->size != l.g->size) &&218 !IsODRViolationSuppressed(g->name))219 ReportODRViolation(g, FindRegistrationSite(g), l.g,220 FindRegistrationSite(l.g));221 }222 } else { // UNREGISTERED223 *odr_indicator = REGISTERED;224 }225 226 AddGlobalToList(relevant_globals, g);227}228 229// Check ODR violation for given global G by checking if it's already poisoned.230// We use this method in case compiler doesn't use private aliases for global231// variables.232static void CheckODRViolationViaPoisoning(const Global *g)233 SANITIZER_REQUIRES(mu_for_globals) {234 if (__asan_region_is_poisoned(g->beg, g->size_with_redzone)) {235 // This check may not be enough: if the first global is much larger236 // the entire redzone of the second global may be within the first global.237 for (const auto &l : list_of_all_globals) {238 if (g->beg == l.g->beg &&239 (flags()->detect_odr_violation >= 2 || g->size != l.g->size) &&240 !IsODRViolationSuppressed(g->name)) {241 ReportODRViolation(g, FindRegistrationSite(g), l.g,242 FindRegistrationSite(l.g));243 }244 }245 }246}247 248// Clang provides two different ways for global variables protection:249// it can poison the global itself or its private alias. In former250// case we may poison same symbol multiple times, that can help us to251// cheaply detect ODR violation: if we try to poison an already poisoned252// global, we have ODR violation error.253// In latter case, we poison each symbol exactly once, so we use special254// indicator symbol to perform similar check.255// In either case, compiler provides a special odr_indicator field to Global256// structure, that can contain two kinds of values:257// 1) Non-zero value. In this case, odr_indicator is an address of258// corresponding indicator variable for given global.259// 2) Zero. This means that we don't use private aliases for global variables260// and can freely check ODR violation with the first method.261//262// This routine chooses between two different methods of ODR violation263// detection.264static inline bool UseODRIndicator(const Global *g) {265 return g->odr_indicator > 0;266}267 268// Register a global variable.269// This function may be called more than once for every global270// so we store the globals in a map.271static void RegisterGlobal(const Global *g) SANITIZER_REQUIRES(mu_for_globals) {272 CHECK(AsanInited());273 if (flags()->report_globals >= 2)274 ReportGlobal(*g, "Added");275 CHECK(flags()->report_globals);276 CHECK(AddrIsInMem(g->beg));277 if (!AddrIsAlignedByGranularity(g->beg)) {278 Report("The following global variable is not properly aligned.\n");279 Report("This may happen if another global with the same name\n");280 Report("resides in another non-instrumented module.\n");281 Report("Or the global comes from a C file built w/o -fno-common.\n");282 Report("In either case this is likely an ODR violation bug,\n");283 Report("but AddressSanitizer can not provide more details.\n");284 ReportODRViolation(g, FindRegistrationSite(g), g, FindRegistrationSite(g));285 CHECK(AddrIsAlignedByGranularity(g->beg));286 }287 CHECK(AddrIsAlignedByGranularity(g->size_with_redzone));288 if (flags()->detect_odr_violation) {289 // Try detecting ODR (One Definition Rule) violation, i.e. the situation290 // where two globals with the same name are defined in different modules.291 if (UseODRIndicator(g))292 CheckODRViolationViaIndicator(g);293 else294 CheckODRViolationViaPoisoning(g);295 }296 if (CanPoisonMemory())297 PoisonRedZones(*g);298 299 AddGlobalToList(list_of_all_globals, g);300 301 if (g->has_dynamic_init) {302 DynInitGlobals()[g->module_name].push_back(303 new (GetGlobalLowLevelAllocator()) DynInitGlobal{*g, false});304 }305}306 307static void UnregisterGlobal(const Global *g)308 SANITIZER_REQUIRES(mu_for_globals) {309 CHECK(AsanInited());310 if (flags()->report_globals >= 2)311 ReportGlobal(*g, "Removed");312 CHECK(flags()->report_globals);313 CHECK(AddrIsInMem(g->beg));314 CHECK(AddrIsAlignedByGranularity(g->beg));315 CHECK(AddrIsAlignedByGranularity(g->size_with_redzone));316 if (CanPoisonMemory())317 PoisonShadowForGlobal(g, 0);318 // We unpoison the shadow memory for the global but we do not remove it from319 // the list because that would require O(n^2) time with the current list320 // implementation. It might not be worth doing anyway.321 322 // Release ODR indicator.323 if (UseODRIndicator(g) && g->odr_indicator != UINTPTR_MAX) {324 u8 *odr_indicator = reinterpret_cast<u8 *>(g->odr_indicator);325 *odr_indicator = UNREGISTERED;326 }327}328 329void StopInitOrderChecking() {330 if (!flags()->check_initialization_order)331 return;332 Lock lock(&mu_for_globals);333 flags()->check_initialization_order = false;334 DynInitGlobals().forEach([&](auto &kv) {335 UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false);336 return true;337 });338}339 340static bool IsASCII(unsigned char c) { return /*0x00 <= c &&*/ c <= 0x7F; }341 342const char *MaybeDemangleGlobalName(const char *name) {343 // We can spoil names of globals with C linkage, so use an heuristic344 // approach to check if the name should be demangled.345 bool should_demangle = false;346 if (name[0] == '_' && name[1] == 'Z')347 should_demangle = true;348 else if (SANITIZER_WINDOWS && name[0] == '\01' && name[1] == '?')349 should_demangle = true;350 351 return should_demangle ? Symbolizer::GetOrInit()->Demangle(name) : name;352}353 354// Check if the global is a zero-terminated ASCII string. If so, print it.355void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g) {356 for (uptr p = g.beg; p < g.beg + g.size - 1; p++) {357 unsigned char c = *(unsigned char *)p;358 if (c == '\0' || !IsASCII(c)) return;359 }360 if (*(char *)(g.beg + g.size - 1) != '\0') return;361 str->AppendF(" '%s' is ascii string '%s'\n", MaybeDemangleGlobalName(g.name),362 (char *)g.beg);363}364 365void PrintGlobalLocation(InternalScopedString *str, const __asan_global &g,366 bool print_module_name) {367 DataInfo info;368 if (Symbolizer::GetOrInit()->SymbolizeData(g.beg, &info) && info.line != 0) {369 str->AppendF("%s:%d", info.file, static_cast<int>(info.line));370 } else if (g.gcc_location != 0) {371 // Fallback to Global::gcc_location372 str->AppendF("%s", g.gcc_location->filename ? g.gcc_location->filename373 : g.module_name);374 if (g.gcc_location->line_no)375 str->AppendF(":%d", g.gcc_location->line_no);376 if (g.gcc_location->column_no)377 str->AppendF(":%d", g.gcc_location->column_no);378 } else {379 str->AppendF("%s", g.module_name);380 }381 if (print_module_name && info.module)382 str->AppendF(" in %s", info.module);383}384 385} // namespace __asan386 387// ---------------------- Interface ---------------- {{{1388using namespace __asan;389 390// Apply __asan_register_globals to all globals found in the same loaded391// executable or shared library as `flag'. The flag tracks whether globals have392// already been registered or not for this image.393void __asan_register_image_globals(uptr *flag) {394 if (*flag)395 return;396 AsanApplyToGlobals(__asan_register_globals, flag);397 *flag = 1;398}399 400// This mirrors __asan_register_image_globals.401void __asan_unregister_image_globals(uptr *flag) {402 if (!*flag)403 return;404 AsanApplyToGlobals(__asan_unregister_globals, flag);405 *flag = 0;406}407 408void __asan_register_elf_globals(uptr *flag, void *start, void *stop) {409 if (*flag || start == stop)410 return;411 CHECK_EQ(0, ((uptr)stop - (uptr)start) % sizeof(__asan_global));412 __asan_global *globals_start = (__asan_global*)start;413 __asan_global *globals_stop = (__asan_global*)stop;414 __asan_register_globals(globals_start, globals_stop - globals_start);415 *flag = 1;416}417 418void __asan_unregister_elf_globals(uptr *flag, void *start, void *stop) {419 if (!*flag || start == stop)420 return;421 CHECK_EQ(0, ((uptr)stop - (uptr)start) % sizeof(__asan_global));422 __asan_global *globals_start = (__asan_global*)start;423 __asan_global *globals_stop = (__asan_global*)stop;424 __asan_unregister_globals(globals_start, globals_stop - globals_start);425 *flag = 0;426}427 428// Register an array of globals.429void __asan_register_globals(__asan_global *globals, uptr n) {430 if (!flags()->report_globals) return;431 GET_STACK_TRACE_MALLOC;432 u32 stack_id = StackDepotPut(stack);433 Lock lock(&mu_for_globals);434 if (!global_registration_site_vector) {435 global_registration_site_vector =436 new (GetGlobalLowLevelAllocator()) GlobalRegistrationSiteVector;437 global_registration_site_vector->reserve(128);438 }439 GlobalRegistrationSite site = {stack_id, &globals[0], &globals[n - 1]};440 global_registration_site_vector->push_back(site);441 if (flags()->report_globals >= 2) {442 PRINT_CURRENT_STACK();443 Printf("=== ID %d; %p %p\n", stack_id, (void *)&globals[0],444 (void *)&globals[n - 1]);445 }446 for (uptr i = 0; i < n; i++) {447 if (SANITIZER_WINDOWS && globals[i].beg == 0) {448 // The MSVC incremental linker may pad globals out to 256 bytes. As long449 // as __asan_global is less than 256 bytes large and its size is a power450 // of two, we can skip over the padding.451 static_assert(452 sizeof(__asan_global) < 256 &&453 (sizeof(__asan_global) & (sizeof(__asan_global) - 1)) == 0,454 "sizeof(__asan_global) incompatible with incremental linker padding");455 // If these are padding bytes, the rest of the global should be zero.456 CHECK(globals[i].size == 0 && globals[i].size_with_redzone == 0 &&457 globals[i].name == nullptr && globals[i].module_name == nullptr &&458 globals[i].odr_indicator == 0);459 continue;460 }461 RegisterGlobal(&globals[i]);462 }463 464 // Poison the metadata. It should not be accessible to user code.465 PoisonShadow(reinterpret_cast<uptr>(globals), n * sizeof(__asan_global),466 kAsanGlobalRedzoneMagic);467}468 469// Unregister an array of globals.470// We must do this when a shared objects gets dlclosed.471void __asan_unregister_globals(__asan_global *globals, uptr n) {472 if (!flags()->report_globals) return;473 Lock lock(&mu_for_globals);474 for (uptr i = 0; i < n; i++) {475 if (SANITIZER_WINDOWS && globals[i].beg == 0) {476 // Skip globals that look like padding from the MSVC incremental linker.477 // See comment in __asan_register_globals.478 continue;479 }480 UnregisterGlobal(&globals[i]);481 }482 483 // Unpoison the metadata.484 PoisonShadow(reinterpret_cast<uptr>(globals), n * sizeof(__asan_global), 0);485}486 487// This method runs immediately prior to dynamic initialization in each TU,488// when all dynamically initialized globals are unpoisoned. This method489// poisons all global variables not defined in this TU, so that a dynamic490// initializer can only touch global variables in the same TU.491void __asan_before_dynamic_init(const char *module_name) {492 if (!flags()->check_initialization_order || !CanPoisonMemory())493 return;494 bool strict_init_order = flags()->strict_init_order;495 CHECK(module_name);496 CHECK(AsanInited());497 Lock lock(&mu_for_globals);498 if (current_dynamic_init_module_name == module_name)499 return;500 if (flags()->report_globals >= 3)501 Printf("DynInitPoison module: %s\n", module_name);502 503 if (current_dynamic_init_module_name == nullptr) {504 // First call, poison all globals from other modules.505 DynInitGlobals().forEach([&](auto &kv) {506 if (kv.first != module_name) {507 PoisonDynamicGlobals(kv.second);508 } else {509 UnpoisonDynamicGlobals(kv.second,510 /*mark_initialized=*/!strict_init_order);511 }512 return true;513 });514 } else {515 // Module changed.516 PoisonDynamicGlobals(DynInitGlobals()[current_dynamic_init_module_name]);517 UnpoisonDynamicGlobals(DynInitGlobals()[module_name],518 /*mark_initialized=*/!strict_init_order);519 }520 current_dynamic_init_module_name = module_name;521}522 523// Maybe SANITIZER_CAN_USE_PREINIT_ARRAY is to conservative for `.init_array`,524// however we should not make mistake here. If `UnpoisonBeforeMain` was not525// executed at all we will have false reports on globals.526#if SANITIZER_CAN_USE_PREINIT_ARRAY527// This optimization aims to reduce the overhead of `__asan_after_dynamic_init`528// calls by leveraging incremental unpoisoning/poisoning in529// `__asan_before_dynamic_init`. We expect most `__asan_after_dynamic_init530// calls` to be no-ops. However, to ensure all globals are unpoisoned before the531// `main`, we force `UnpoisonBeforeMain` to fully execute532// `__asan_after_dynamic_init`.533 534// With lld, `UnpoisonBeforeMain` runs after standard `.init_array`, making it535// the final `__asan_after_dynamic_init` call for the static runtime. In536// contrast, GNU ld executes it earlier, causing subsequent537// `__asan_after_dynamic_init` calls to perform full unpoisoning, losing the538// optimization.539bool allow_after_dynamic_init SANITIZER_GUARDED_BY(mu_for_globals) = false;540 541static void UnpoisonBeforeMain(void) {542 {543 Lock lock(&mu_for_globals);544 if (allow_after_dynamic_init)545 return;546 allow_after_dynamic_init = true;547 }548 if (flags()->report_globals >= 3)549 Printf("UnpoisonBeforeMain\n");550 __asan_after_dynamic_init();551}552 553__attribute__((section(".init_array.65537"), used)) static void (554 *asan_after_init_array)(void) = UnpoisonBeforeMain;555#else556// Incremental poisoning is disabled, unpoison globals immediately.557static constexpr bool allow_after_dynamic_init = true;558#endif // SANITIZER_CAN_USE_PREINIT_ARRAY559 560// This method runs immediately after dynamic initialization in each TU, when561// all dynamically initialized globals except for those defined in the current562// TU are poisoned. It simply unpoisons all dynamically initialized globals.563void __asan_after_dynamic_init() {564 if (!flags()->check_initialization_order || !CanPoisonMemory())565 return;566 CHECK(AsanInited());567 Lock lock(&mu_for_globals);568 if (!allow_after_dynamic_init)569 return;570 if (!current_dynamic_init_module_name)571 return;572 573 if (flags()->report_globals >= 3)574 Printf("DynInitUnpoison\n");575 576 DynInitGlobals().forEach([&](auto &kv) {577 UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false);578 return true;579 });580 581 current_dynamic_init_module_name = nullptr;582}583