Skip to content

Commit 0d7e64f

Browse files
authored
[ASan][Windows] Honor asan config flags on windows when set through the user function (#122990)
**Related to:** #117925 **Follow up to:** #117929 **Context:** As noted in the linked issue, some ASan configuration flags are not honored on Windows when set through the `__asan_default_options` user function. The reason for this is that `__asan_default_options` is not available by the time `AsanInitInternal` executes, which is responsible for applying the ASan flags. To fix this properly, we'll probably need a deep re-design of ASan initialization so that it is consistent across OS'es. In the meantime, this PR offers a practical workaround. **This PR:** refactors part of `AsanInitInternal` so that **idempotent** flag-applying steps are extracted into a new function `ApplyOptions`. This function is **also** invoked in the "weak function callback" on Windows (which gets called when `__asan_default_options` is available) so that, if any flags were set through the user-function, they are safely applied _then_. Today, `ApplyOptions` contains only a subset of flags. My hope is that `ApplyOptions` will over time, through incremental refactorings `AsanInitInternal` so that **all** flags are eventually honored. Other minor changes: * The introduction of a `ApplyAllocatorOptions` helper method, needed to implement `ApplyOptions` for allocator options without re-initializing the entire allocator. Reinitializing the entire allocator is expensive, as it may do a whole pass over all the marked memory. To my knowledge, this isn't needed for the options captured in `ApplyAllocatorOptions`. * Rename `ProcessFlags` to `ValidateFlags`, which seems like a more accurate name to what that function does, and prevents confusion when compared to the new `ApplyOptions` function.
1 parent 6550f28 commit 0d7e64f

File tree

6 files changed

+98
-29
lines changed

6 files changed

+98
-29
lines changed

compiler-rt/lib/asan/asan_allocator.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,15 @@ struct Allocator {
424424
PoisonShadow(chunk, allocated_size, kAsanHeapLeftRedzoneMagic);
425425
}
426426

427-
void ReInitialize(const AllocatorOptions &options) {
427+
// Apply provided AllocatorOptions to an Allocator
428+
void ApplyOptions(const AllocatorOptions &options) {
428429
SetAllocatorMayReturnNull(options.may_return_null);
429430
allocator.SetReleaseToOSIntervalMs(options.release_to_os_interval_ms);
430431
SharedInitCode(options);
432+
}
433+
434+
void ReInitialize(const AllocatorOptions &options) {
435+
ApplyOptions(options);
431436

432437
// Poison all existing allocation's redzones.
433438
if (CanPoisonMemory()) {
@@ -977,6 +982,11 @@ void ReInitializeAllocator(const AllocatorOptions &options) {
977982
instance.ReInitialize(options);
978983
}
979984

985+
// Apply provided AllocatorOptions to an Allocator
986+
void ApplyAllocatorOptions(const AllocatorOptions &options) {
987+
instance.ApplyOptions(options);
988+
}
989+
980990
void GetAllocatorOptions(AllocatorOptions *options) {
981991
instance.GetOptions(options);
982992
}

compiler-rt/lib/asan/asan_allocator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct AllocatorOptions {
4747
void InitializeAllocator(const AllocatorOptions &options);
4848
void ReInitializeAllocator(const AllocatorOptions &options);
4949
void GetAllocatorOptions(AllocatorOptions *options);
50+
void ApplyAllocatorOptions(const AllocatorOptions &options);
5051

5152
class AsanChunkView {
5253
public:

compiler-rt/lib/asan/asan_flags.cpp

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ static void InitializeDefaultFlags() {
144144
DisplayHelpMessages(&asan_parser);
145145
}
146146

147+
// Validate flags and report incompatible configurations
147148
static void ProcessFlags() {
148149
Flags *f = flags();
149150

@@ -217,11 +218,12 @@ void InitializeFlags() {
217218
ProcessFlags();
218219

219220
#if SANITIZER_WINDOWS
220-
// On Windows, weak symbols are emulated by having the user program
221-
// register which weak functions are defined.
222-
// The ASAN DLL will initialize flags prior to user module initialization,
223-
// so __asan_default_options will not point to the user definition yet.
224-
// We still want to ensure we capture when options are passed via
221+
// On Windows, weak symbols (such as the `__asan_default_options` function)
222+
// are emulated by having the user program register which weak functions are
223+
// defined. The ASAN DLL will initialize flags prior to user module
224+
// initialization, so __asan_default_options will not point to the user
225+
// definition yet. We still want to ensure we capture when options are passed
226+
// via
225227
// __asan_default_options, so we add a callback to be run
226228
// when it is registered with the runtime.
227229

@@ -232,21 +234,13 @@ void InitializeFlags() {
232234
// __sanitizer_register_weak_function.
233235
AddRegisterWeakFunctionCallback(
234236
reinterpret_cast<uptr>(__asan_default_options), []() {
235-
FlagParser asan_parser;
236-
237-
RegisterAsanFlags(&asan_parser, flags());
238-
RegisterCommonFlags(&asan_parser);
239-
asan_parser.ParseString(__asan_default_options());
240-
241-
DisplayHelpMessages(&asan_parser);
237+
// We call `InitializeDefaultFlags` again, instead of just parsing
238+
// `__asan_default_options` directly, to ensure that flags set through
239+
// `ASAN_OPTS` take precedence over those set through
240+
// `__asan_default_options`.
241+
InitializeDefaultFlags();
242242
ProcessFlags();
243-
244-
// TODO: Update other globals and data structures that may need to change
245-
// after initialization due to new flags potentially being set changing after
246-
// `__asan_default_options` is registered.
247-
// See GH issue 'https://github.com/llvm/llvm-project/issues/117925' for
248-
// details.
249-
SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
243+
ApplyFlags();
250244
});
251245

252246
# if CAN_SANITIZE_UB

compiler-rt/lib/asan/asan_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ using __sanitizer::StackTrace;
6161

6262
void AsanInitFromRtl();
6363
bool TryAsanInitFromRtl();
64+
void ApplyFlags();
6465

6566
// asan_win.cpp
6667
void InitializePlatformExceptionHandlers();

compiler-rt/lib/asan/asan_rtl.cpp

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -390,15 +390,49 @@ void PrintAddressSpaceLayout() {
390390
kHighShadowBeg > kMidMemEnd);
391391
}
392392

393+
// Apply most options specified either through the ASAN_OPTIONS
394+
// environment variable, or through the `__asan_default_options` user function.
395+
//
396+
// This function may be called multiple times, once per weak reference callback
397+
// on Windows, so it needs to be idempotent.
398+
//
399+
// Context:
400+
// For maximum compatibility on Windows, it is necessary for ASan options to be
401+
// configured/registered/applied inside this method (instead of in
402+
// ASanInitInternal, for example). That's because, on Windows, the user-provided
403+
// definition for `__asan_default_opts` may not be bound when `ASanInitInternal`
404+
// is invoked (it is bound later).
405+
//
406+
// To work around the late binding on windows, `ApplyOptions` will be called,
407+
// again, after binding to the user-provided `__asan_default_opts` function.
408+
// Therefore, any flags not configured here are not guaranteed to be
409+
// configurable through `__asan_default_opts` on Windows.
410+
//
411+
//
412+
// For more details on this issue, see:
413+
// https://github.com/llvm/llvm-project/issues/117925
414+
void ApplyFlags() {
415+
SetCanPoisonMemory(flags()->poison_heap);
416+
SetMallocContextSize(common_flags()->malloc_context_size);
417+
418+
__asan_option_detect_stack_use_after_return =
419+
flags()->detect_stack_use_after_return;
420+
421+
AllocatorOptions allocator_options;
422+
allocator_options.SetFrom(flags(), common_flags());
423+
ApplyAllocatorOptions(allocator_options);
424+
}
425+
393426
static bool AsanInitInternal() {
394427
if (LIKELY(AsanInited()))
395428
return true;
396429
SanitizerToolName = "AddressSanitizer";
397430

398431
CacheBinaryName();
399432

400-
// Initialize flags. This must be done early, because most of the
401-
// initialization steps look at flags().
433+
// Initialize flags. On Windows it also also register weak function callbacks.
434+
// This must be done early, because most of the initialization steps look at
435+
// flags().
402436
InitializeFlags();
403437

404438
WaitForDebugger(flags()->sleep_before_init, "before init");
@@ -416,9 +450,6 @@ static bool AsanInitInternal() {
416450
AsanCheckDynamicRTPrereqs();
417451
AvoidCVE_2016_2143();
418452

419-
SetCanPoisonMemory(flags()->poison_heap);
420-
SetMallocContextSize(common_flags()->malloc_context_size);
421-
422453
InitializePlatformExceptionHandlers();
423454

424455
InitializeHighMemEnd();
@@ -429,10 +460,6 @@ static bool AsanInitInternal() {
429460
SetPrintfAndReportCallback(AppendToErrorMessageBuffer);
430461

431462
__sanitizer_set_report_path(common_flags()->log_path);
432-
433-
__asan_option_detect_stack_use_after_return =
434-
flags()->detect_stack_use_after_return;
435-
436463
__sanitizer::InitializePlatformEarly();
437464

438465
// Setup internal allocator callback.
@@ -472,6 +499,13 @@ static bool AsanInitInternal() {
472499
allocator_options.SetFrom(flags(), common_flags());
473500
InitializeAllocator(allocator_options);
474501

502+
// Apply ASan flags.
503+
// NOTE: In order for options specified through `__asan_default_options` to be
504+
// honored on Windows, it is necessary for those options to be configured
505+
// inside the `ApplyOptions` method. See the function-level comment for
506+
// `ApplyFlags` for more details.
507+
ApplyFlags();
508+
475509
if (SANITIZER_START_BACKGROUND_THREAD_IN_ASAN_INTERNAL)
476510
MaybeStartBackgroudThread();
477511

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %clangxx_asan -O0 %s -o %t
2+
// RUN: %env_asan_opts=alloc_dealloc_mismatch=1 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISMATCH
3+
// RUN: %env_asan_opts=alloc_dealloc_mismatch=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
4+
5+
// RUN: %clangxx_asan -O0 %s -o %t -DUSER_FUNCTION
6+
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISMATCH
7+
8+
// It is expected that ASAN_OPTS will override the value set through the user function.
9+
// RUN: %env_asan_opts=alloc_dealloc_mismatch=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
10+
11+
#if USER_FUNCTION
12+
// It's important to test the `alloc_dealloc_mismatch` flag set through the user function because, on Windows,
13+
// flags configured through the user-defined function `__asan_default_options` are not always be honored.
14+
// See: https://github.com/llvm/llvm-project/issues/117925
15+
extern "C" __declspec(dllexport) extern const char *__asan_default_options() {
16+
return "alloc_dealloc_mismatch=1";
17+
}
18+
#endif
19+
20+
#include <cstdio>
21+
#include <cstdlib>
22+
23+
// Tests the `alloc_dealloc_mismatch` flag set both via user function and through the environment variable.
24+
int main() {
25+
// In the 'CHECK-MISMATCH' case, we simply check that the AddressSanitizer reports an error.
26+
delete (new int[10]); // CHECK-MISMATCH: AddressSanitizer:
27+
printf("Success"); // CHECK-SUCCESS: Success
28+
return 0;
29+
}

0 commit comments

Comments
 (0)