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-exception |
6 | // |
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 | |
32 | namespace __asan { |
33 | |
34 | typedef __asan_global Global; |
35 | |
36 | struct GlobalListNode { |
37 | const Global *g = nullptr; |
38 | GlobalListNode *next = nullptr; |
39 | }; |
40 | typedef IntrusiveList<GlobalListNode> ListOfGlobals; |
41 | |
42 | static Mutex mu_for_globals; |
43 | static ListOfGlobals list_of_all_globals SANITIZER_GUARDED_BY(mu_for_globals); |
44 | |
45 | struct 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. |
52 | struct GlobalRegistrationSite { |
53 | u32 stack_id; |
54 | Global *g_first, *g_last; |
55 | }; |
56 | typedef InternalMmapVector<GlobalRegistrationSite> GlobalRegistrationSiteVector; |
57 | static GlobalRegistrationSiteVector *global_registration_site_vector; |
58 | |
59 | static 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 | |
73 | static const char *current_dynamic_init_module_name |
74 | SANITIZER_GUARDED_BY(mu_for_globals) = nullptr; |
75 | |
76 | using DynInitGlobalsByModule = |
77 | DenseMap<const char *, IntrusiveList<DynInitGlobal>>; |
78 | |
79 | // TODO: Add a NoDestroy helper, this patter is very common in sanitizers. |
80 | static 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 char |
85 | placeholder[sizeof(DynInitGlobalsByModule)]; |
86 | globals_by_module = new (placeholder) DynInitGlobalsByModule(); |
87 | } |
88 | |
89 | return *globals_by_module; |
90 | } |
91 | |
92 | ALWAYS_INLINE void PoisonShadowForGlobal(const Global *g, u8 value) { |
93 | FastPoisonShadow(aligned_beg: g->beg, aligned_size: g->size_with_redzone, value); |
94 | } |
95 | |
96 | ALWAYS_INLINE void PoisonRedZones(const Global &g) { |
97 | uptr aligned_size = RoundUpTo(size: g.size, ASAN_SHADOW_GRANULARITY); |
98 | FastPoisonShadow(aligned_beg: g.beg + aligned_size, aligned_size: g.size_with_redzone - aligned_size, |
99 | value: kAsanGlobalRedzoneMagic); |
100 | if (g.size != aligned_size) { |
101 | FastPoisonShadowPartialRightRedzone( |
102 | aligned_addr: g.beg + RoundDownTo(x: g.size, ASAN_SHADOW_GRANULARITY), |
103 | size: g.size % ASAN_SHADOW_GRANULARITY, ASAN_SHADOW_GRANULARITY, |
104 | value: kAsanGlobalRedzoneMagic); |
105 | } |
106 | } |
107 | |
108 | const uptr kMinimalDistanceFromAnotherGlobal = 64; |
109 | |
110 | static void AddGlobalToList(ListOfGlobals &list, const Global *g) { |
111 | list.push_front(x: new (GetGlobalLowLevelAllocator()) GlobalListNode{.g: g}); |
112 | } |
113 | |
114 | static 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, value: 0); |
122 | // Poison redzones back. |
123 | PoisonRedZones(g: *g); |
124 | if (mark_initialized) |
125 | dyn_g.initialized = true; |
126 | } |
127 | } |
128 | |
129 | static 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, value: kAsanInitializationOrderMagic); |
136 | } |
137 | } |
138 | |
139 | static 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 | |
145 | static void ReportGlobal(const Global &g, const char *prefix) { |
146 | DataInfo info; |
147 | bool symbolized = Symbolizer::GetOrInit()->SymbolizeData(address: g.beg, info: &info); |
148 | Report( |
149 | format: "%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(format: " location: name=%s, %d\n" , info.file, static_cast<int>(info.line)); |
158 | } else if (g.gcc_location != 0) { |
159 | // Fallback to Global::gcc_location |
160 | Report(format: " location: name=%s, %d\n" , g.gcc_location->filename, g.gcc_location->line_no); |
161 | } |
162 | } |
163 | |
164 | static 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 | |
175 | int 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, prefix: "Search" ); |
184 | if (IsAddressNearGlobal(addr, g)) { |
185 | internal_memcpy(dest: &globals[res], src: &g, n: sizeof(g)); |
186 | if (reg_sites) |
187 | reg_sites[res] = FindRegistrationSite(g: &g); |
188 | res++; |
189 | if (res == max_globals) |
190 | break; |
191 | } |
192 | } |
193 | return res; |
194 | } |
195 | |
196 | enum GlobalSymbolState { |
197 | UNREGISTERED = 0, |
198 | REGISTERED = 1 |
199 | }; |
200 | |
201 | // Check ODR violation for given global G via special ODR indicator. We use |
202 | // this method in case compiler instruments global variables through their |
203 | // local aliases. |
204 | static 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(odr_indicator: 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 registered |
215 | // 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(global_var_name: g->name)) |
219 | ReportODRViolation(g1: g, stack_id1: FindRegistrationSite(g), g2: l.g, |
220 | stack_id2: FindRegistrationSite(g: l.g)); |
221 | } |
222 | } else { // UNREGISTERED |
223 | *odr_indicator = REGISTERED; |
224 | } |
225 | |
226 | AddGlobalToList(list&: 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 global |
231 | // variables. |
232 | static void CheckODRViolationViaPoisoning(const Global *g) |
233 | SANITIZER_REQUIRES(mu_for_globals) { |
234 | if (__asan_region_is_poisoned(beg: g->beg, size: g->size_with_redzone)) { |
235 | // This check may not be enough: if the first global is much larger |
236 | // 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(global_var_name: g->name)) { |
241 | ReportODRViolation(g1: g, stack_id1: FindRegistrationSite(g), g2: l.g, |
242 | stack_id2: FindRegistrationSite(g: 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 former |
250 | // case we may poison same symbol multiple times, that can help us to |
251 | // cheaply detect ODR violation: if we try to poison an already poisoned |
252 | // global, we have ODR violation error. |
253 | // In latter case, we poison each symbol exactly once, so we use special |
254 | // indicator symbol to perform similar check. |
255 | // In either case, compiler provides a special odr_indicator field to Global |
256 | // structure, that can contain two kinds of values: |
257 | // 1) Non-zero value. In this case, odr_indicator is an address of |
258 | // corresponding indicator variable for given global. |
259 | // 2) Zero. This means that we don't use private aliases for global variables |
260 | // and can freely check ODR violation with the first method. |
261 | // |
262 | // This routine chooses between two different methods of ODR violation |
263 | // detection. |
264 | static 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 global |
270 | // so we store the globals in a map. |
271 | static void RegisterGlobal(const Global *g) SANITIZER_REQUIRES(mu_for_globals) { |
272 | CHECK(AsanInited()); |
273 | if (flags()->report_globals >= 2) |
274 | ReportGlobal(g: *g, prefix: "Added" ); |
275 | CHECK(flags()->report_globals); |
276 | CHECK(AddrIsInMem(g->beg)); |
277 | if (!AddrIsAlignedByGranularity(a: g->beg)) { |
278 | Report(format: "The following global variable is not properly aligned.\n" ); |
279 | Report(format: "This may happen if another global with the same name\n" ); |
280 | Report(format: "resides in another non-instrumented module.\n" ); |
281 | Report(format: "Or the global comes from a C file built w/o -fno-common.\n" ); |
282 | Report(format: "In either case this is likely an ODR violation bug,\n" ); |
283 | Report(format: "but AddressSanitizer can not provide more details.\n" ); |
284 | ReportODRViolation(g1: g, stack_id1: FindRegistrationSite(g), g2: g, stack_id2: 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 situation |
290 | // where two globals with the same name are defined in different modules. |
291 | if (UseODRIndicator(g)) |
292 | CheckODRViolationViaIndicator(g); |
293 | else |
294 | CheckODRViolationViaPoisoning(g); |
295 | } |
296 | if (CanPoisonMemory()) |
297 | PoisonRedZones(g: *g); |
298 | |
299 | AddGlobalToList(list&: list_of_all_globals, g); |
300 | |
301 | if (g->has_dynamic_init) { |
302 | DynInitGlobals()[g->module_name].push_back( |
303 | x: new (GetGlobalLowLevelAllocator()) DynInitGlobal{.g: *g, .initialized: false}); |
304 | } |
305 | } |
306 | |
307 | static void UnregisterGlobal(const Global *g) |
308 | SANITIZER_REQUIRES(mu_for_globals) { |
309 | CHECK(AsanInited()); |
310 | if (flags()->report_globals >= 2) |
311 | ReportGlobal(g: *g, prefix: "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, value: 0); |
318 | // We unpoison the shadow memory for the global but we do not remove it from |
319 | // the list because that would require O(n^2) time with the current list |
320 | // 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 | |
329 | void StopInitOrderChecking() { |
330 | if (!flags()->check_initialization_order) |
331 | return; |
332 | Lock lock(&mu_for_globals); |
333 | flags()->check_initialization_order = false; |
334 | DynInitGlobals().forEach(fn: [&](auto &kv) { |
335 | UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false); |
336 | return true; |
337 | }); |
338 | } |
339 | |
340 | static bool IsASCII(unsigned char c) { return /*0x00 <= c &&*/ c <= 0x7F; } |
341 | |
342 | const char *MaybeDemangleGlobalName(const char *name) { |
343 | // We can spoil names of globals with C linkage, so use an heuristic |
344 | // 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. |
355 | void 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(format: " '%s' is ascii string '%s'\n" , MaybeDemangleGlobalName(name: g.name), |
362 | (char *)g.beg); |
363 | } |
364 | |
365 | void PrintGlobalLocation(InternalScopedString *str, const __asan_global &g, |
366 | bool print_module_name) { |
367 | DataInfo info; |
368 | if (Symbolizer::GetOrInit()->SymbolizeData(address: g.beg, info: &info) && info.line != 0) { |
369 | str->AppendF(format: "%s:%d" , info.file, static_cast<int>(info.line)); |
370 | } else if (g.gcc_location != 0) { |
371 | // Fallback to Global::gcc_location |
372 | str->AppendF(format: "%s" , g.gcc_location->filename ? g.gcc_location->filename |
373 | : g.module_name); |
374 | if (g.gcc_location->line_no) |
375 | str->AppendF(format: ":%d" , g.gcc_location->line_no); |
376 | if (g.gcc_location->column_no) |
377 | str->AppendF(format: ":%d" , g.gcc_location->column_no); |
378 | } else { |
379 | str->AppendF(format: "%s" , g.module_name); |
380 | } |
381 | if (print_module_name && info.module) |
382 | str->AppendF(format: " in %s" , info.module); |
383 | } |
384 | |
385 | } // namespace __asan |
386 | |
387 | // ---------------------- Interface ---------------- {{{1 |
388 | using namespace __asan; |
389 | |
390 | // Apply __asan_register_globals to all globals found in the same loaded |
391 | // executable or shared library as `flag'. The flag tracks whether globals have |
392 | // already been registered or not for this image. |
393 | void __asan_register_image_globals(uptr *flag) { |
394 | if (*flag) |
395 | return; |
396 | AsanApplyToGlobals(op: __asan_register_globals, needle: flag); |
397 | *flag = 1; |
398 | } |
399 | |
400 | // This mirrors __asan_register_image_globals. |
401 | void __asan_unregister_image_globals(uptr *flag) { |
402 | if (!*flag) |
403 | return; |
404 | AsanApplyToGlobals(op: __asan_unregister_globals, needle: flag); |
405 | *flag = 0; |
406 | } |
407 | |
408 | void __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: globals_start, n: globals_stop - globals_start); |
415 | *flag = 1; |
416 | } |
417 | |
418 | void __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: globals_start, n: globals_stop - globals_start); |
425 | *flag = 0; |
426 | } |
427 | |
428 | // Register an array of globals. |
429 | void __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(new_size: 128); |
438 | } |
439 | GlobalRegistrationSite site = {.stack_id: stack_id, .g_first: &globals[0], .g_last: &globals[n - 1]}; |
440 | global_registration_site_vector->push_back(element: site); |
441 | if (flags()->report_globals >= 2) { |
442 | PRINT_CURRENT_STACK(); |
443 | Printf(format: "=== 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 long |
449 | // as __asan_global is less than 256 bytes large and its size is a power |
450 | // 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(g: &globals[i]); |
462 | } |
463 | |
464 | // Poison the metadata. It should not be accessible to user code. |
465 | PoisonShadow(addr: reinterpret_cast<uptr>(globals), size: n * sizeof(__asan_global), |
466 | value: kAsanGlobalRedzoneMagic); |
467 | } |
468 | |
469 | // Unregister an array of globals. |
470 | // We must do this when a shared objects gets dlclosed. |
471 | void __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(g: &globals[i]); |
481 | } |
482 | |
483 | // Unpoison the metadata. |
484 | PoisonShadow(addr: reinterpret_cast<uptr>(globals), size: n * sizeof(__asan_global), value: 0); |
485 | } |
486 | |
487 | // This method runs immediately prior to dynamic initialization in each TU, |
488 | // when all dynamically initialized globals are unpoisoned. This method |
489 | // poisons all global variables not defined in this TU, so that a dynamic |
490 | // initializer can only touch global variables in the same TU. |
491 | void __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(format: "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(fn: [&](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(dyn_globals: DynInitGlobals()[current_dynamic_init_module_name]); |
517 | UnpoisonDynamicGlobals(dyn_globals&: 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 not |
525 | // executed at all we will have false reports on globals. |
526 | #if SANITIZER_CAN_USE_PREINIT_ARRAY |
527 | // This optimization aims to reduce the overhead of `__asan_after_dynamic_init` |
528 | // calls by leveraging incremental unpoisoning/poisoning in |
529 | // `__asan_before_dynamic_init`. We expect most `__asan_after_dynamic_init |
530 | // calls` to be no-ops. However, to ensure all globals are unpoisoned before the |
531 | // `main`, we force `UnpoisonBeforeMain` to fully execute |
532 | // `__asan_after_dynamic_init`. |
533 | |
534 | // With lld, `UnpoisonBeforeMain` runs after standard `.init_array`, making it |
535 | // the final `__asan_after_dynamic_init` call for the static runtime. In |
536 | // contrast, GNU ld executes it earlier, causing subsequent |
537 | // `__asan_after_dynamic_init` calls to perform full unpoisoning, losing the |
538 | // optimization. |
539 | bool allow_after_dynamic_init SANITIZER_GUARDED_BY(mu_for_globals) = false; |
540 | |
541 | static 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(format: "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 | #else |
556 | // Incremental poisoning is disabled, unpoison globals immediately. |
557 | static constexpr bool allow_after_dynamic_init = true; |
558 | #endif // SANITIZER_CAN_USE_PREINIT_ARRAY |
559 | |
560 | // This method runs immediately after dynamic initialization in each TU, when |
561 | // all dynamically initialized globals except for those defined in the current |
562 | // TU are poisoned. It simply unpoisons all dynamically initialized globals. |
563 | void __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(format: "DynInitUnpoison\n" ); |
575 | |
576 | DynInitGlobals().forEach(fn: [&](auto &kv) { |
577 | UnpoisonDynamicGlobals(kv.second, /*mark_initialized=*/false); |
578 | return true; |
579 | }); |
580 | |
581 | current_dynamic_init_module_name = nullptr; |
582 | } |
583 | |