brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.0 KiB · c83b782 Raw
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