llvmorg-21-init-17702-g0d7e64f5d2b4.patch (12525B)
1 From 7eb91b30bedfda404e7b68d94e7ab770b774846a Mon Sep 17 00:00:00 2001 2 From: David Justo <david.justo.1996@gmail.com> 3 Date: Wed, 2 Jul 2025 15:37:28 -0700 4 Subject: [PATCH] [ASan][Windows] Honor asan config flags on windows when set 5 through the user function (#122990) 6 7 **Related to:** https://github.com/llvm/llvm-project/issues/117925 8 **Follow up to:** https://github.com/llvm/llvm-project/pull/117929 9 10 **Context:** 11 As noted in the linked issue, some ASan configuration flags are not 12 honored on Windows when set through the `__asan_default_options` user 13 function. The reason for this is that `__asan_default_options` is not 14 available by the time `AsanInitInternal` executes, which is responsible 15 for applying the ASan flags. 16 17 To fix this properly, we'll probably need a deep re-design of ASan 18 initialization so that it is consistent across OS'es. 19 In the meantime, this PR offers a practical workaround. 20 21 **This PR:** refactors part of `AsanInitInternal` so that **idempotent** 22 flag-applying steps are extracted into a new function `ApplyOptions`. 23 This function is **also** invoked in the "weak function callback" on 24 Windows (which gets called when `__asan_default_options` is available) 25 so that, if any flags were set through the user-function, they are 26 safely applied _then_. 27 28 Today, `ApplyOptions` contains only a subset of flags. My hope is that 29 `ApplyOptions` will over time, through incremental refactorings 30 `AsanInitInternal` so that **all** flags are eventually honored. 31 32 Other minor changes: 33 * The introduction of a `ApplyAllocatorOptions` helper method, needed to 34 implement `ApplyOptions` for allocator options without re-initializing 35 the entire allocator. Reinitializing the entire allocator is expensive, 36 as it may do a whole pass over all the marked memory. To my knowledge, 37 this isn't needed for the options captured in `ApplyAllocatorOptions`. 38 * Rename `ProcessFlags` to `ValidateFlags`, which seems like a more 39 accurate name to what that function does, and prevents confusion when 40 compared to the new `ApplyOptions` function. 41 --- 42 compiler-rt/lib/asan/asan_allocator.cpp | 12 ++++- 43 compiler-rt/lib/asan/asan_allocator.h | 1 + 44 compiler-rt/lib/asan/asan_flags.cpp | 32 +++++------- 45 compiler-rt/lib/asan/asan_internal.h | 1 + 46 compiler-rt/lib/asan/asan_rtl.cpp | 52 +++++++++++++++---- 47 .../Windows/alloc_dealloc_mismatch.cpp | 29 +++++++++++ 48 6 files changed, 98 insertions(+), 29 deletions(-) 49 create mode 100644 compiler-rt/test/asan/TestCases/Windows/alloc_dealloc_mismatch.cpp 50 51 diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp 52 index 3a55c2af6565..d3c0288285b8 100644 53 --- a/compiler-rt/lib/asan/asan_allocator.cpp 54 +++ b/compiler-rt/lib/asan/asan_allocator.cpp 55 @@ -424,10 +424,15 @@ struct Allocator { 56 PoisonShadow(chunk, allocated_size, kAsanHeapLeftRedzoneMagic); 57 } 58 59 - void ReInitialize(const AllocatorOptions &options) { 60 + // Apply provided AllocatorOptions to an Allocator 61 + void ApplyOptions(const AllocatorOptions &options) { 62 SetAllocatorMayReturnNull(options.may_return_null); 63 allocator.SetReleaseToOSIntervalMs(options.release_to_os_interval_ms); 64 SharedInitCode(options); 65 + } 66 + 67 + void ReInitialize(const AllocatorOptions &options) { 68 + ApplyOptions(options); 69 70 // Poison all existing allocation's redzones. 71 if (CanPoisonMemory()) { 72 @@ -977,6 +982,11 @@ void ReInitializeAllocator(const AllocatorOptions &options) { 73 instance.ReInitialize(options); 74 } 75 76 +// Apply provided AllocatorOptions to an Allocator 77 +void ApplyAllocatorOptions(const AllocatorOptions &options) { 78 + instance.ApplyOptions(options); 79 +} 80 + 81 void GetAllocatorOptions(AllocatorOptions *options) { 82 instance.GetOptions(options); 83 } 84 diff --git a/compiler-rt/lib/asan/asan_allocator.h b/compiler-rt/lib/asan/asan_allocator.h 85 index db8dc3bebfc6..a94ef958aa75 100644 86 --- a/compiler-rt/lib/asan/asan_allocator.h 87 +++ b/compiler-rt/lib/asan/asan_allocator.h 88 @@ -47,6 +47,7 @@ struct AllocatorOptions { 89 void InitializeAllocator(const AllocatorOptions &options); 90 void ReInitializeAllocator(const AllocatorOptions &options); 91 void GetAllocatorOptions(AllocatorOptions *options); 92 +void ApplyAllocatorOptions(const AllocatorOptions &options); 93 94 class AsanChunkView { 95 public: 96 diff --git a/compiler-rt/lib/asan/asan_flags.cpp b/compiler-rt/lib/asan/asan_flags.cpp 97 index 9cfb70bd00c7..190a89345dd1 100644 98 --- a/compiler-rt/lib/asan/asan_flags.cpp 99 +++ b/compiler-rt/lib/asan/asan_flags.cpp 100 @@ -144,6 +144,7 @@ static void InitializeDefaultFlags() { 101 DisplayHelpMessages(&asan_parser); 102 } 103 104 +// Validate flags and report incompatible configurations 105 static void ProcessFlags() { 106 Flags *f = flags(); 107 108 @@ -217,11 +218,12 @@ void InitializeFlags() { 109 ProcessFlags(); 110 111 #if SANITIZER_WINDOWS 112 - // On Windows, weak symbols are emulated by having the user program 113 - // register which weak functions are defined. 114 - // The ASAN DLL will initialize flags prior to user module initialization, 115 - // so __asan_default_options will not point to the user definition yet. 116 - // We still want to ensure we capture when options are passed via 117 + // On Windows, weak symbols (such as the `__asan_default_options` function) 118 + // are emulated by having the user program register which weak functions are 119 + // defined. The ASAN DLL will initialize flags prior to user module 120 + // initialization, so __asan_default_options will not point to the user 121 + // definition yet. We still want to ensure we capture when options are passed 122 + // via 123 // __asan_default_options, so we add a callback to be run 124 // when it is registered with the runtime. 125 126 @@ -232,21 +234,13 @@ void InitializeFlags() { 127 // __sanitizer_register_weak_function. 128 AddRegisterWeakFunctionCallback( 129 reinterpret_cast<uptr>(__asan_default_options), []() { 130 - FlagParser asan_parser; 131 - 132 - RegisterAsanFlags(&asan_parser, flags()); 133 - RegisterCommonFlags(&asan_parser); 134 - asan_parser.ParseString(__asan_default_options()); 135 - 136 - DisplayHelpMessages(&asan_parser); 137 + // We call `InitializeDefaultFlags` again, instead of just parsing 138 + // `__asan_default_options` directly, to ensure that flags set through 139 + // `ASAN_OPTS` take precedence over those set through 140 + // `__asan_default_options`. 141 + InitializeDefaultFlags(); 142 ProcessFlags(); 143 - 144 - // TODO: Update other globals and data structures that may need to change 145 - // after initialization due to new flags potentially being set changing after 146 - // `__asan_default_options` is registered. 147 - // See GH issue 'https://github.com/llvm/llvm-project/issues/117925' for 148 - // details. 149 - SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null); 150 + ApplyFlags(); 151 }); 152 153 # if CAN_SANITIZE_UB 154 diff --git a/compiler-rt/lib/asan/asan_internal.h b/compiler-rt/lib/asan/asan_internal.h 155 index 06dfc4b17733..464faad56f32 100644 156 --- a/compiler-rt/lib/asan/asan_internal.h 157 +++ b/compiler-rt/lib/asan/asan_internal.h 158 @@ -61,6 +61,7 @@ using __sanitizer::StackTrace; 159 160 void AsanInitFromRtl(); 161 bool TryAsanInitFromRtl(); 162 +void ApplyFlags(); 163 164 // asan_win.cpp 165 void InitializePlatformExceptionHandlers(); 166 diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp 167 index 19c6c210b564..1564a8f5164c 100644 168 --- a/compiler-rt/lib/asan/asan_rtl.cpp 169 +++ b/compiler-rt/lib/asan/asan_rtl.cpp 170 @@ -390,6 +390,39 @@ void PrintAddressSpaceLayout() { 171 kHighShadowBeg > kMidMemEnd); 172 } 173 174 +// Apply most options specified either through the ASAN_OPTIONS 175 +// environment variable, or through the `__asan_default_options` user function. 176 +// 177 +// This function may be called multiple times, once per weak reference callback 178 +// on Windows, so it needs to be idempotent. 179 +// 180 +// Context: 181 +// For maximum compatibility on Windows, it is necessary for ASan options to be 182 +// configured/registered/applied inside this method (instead of in 183 +// ASanInitInternal, for example). That's because, on Windows, the user-provided 184 +// definition for `__asan_default_opts` may not be bound when `ASanInitInternal` 185 +// is invoked (it is bound later). 186 +// 187 +// To work around the late binding on windows, `ApplyOptions` will be called, 188 +// again, after binding to the user-provided `__asan_default_opts` function. 189 +// Therefore, any flags not configured here are not guaranteed to be 190 +// configurable through `__asan_default_opts` on Windows. 191 +// 192 +// 193 +// For more details on this issue, see: 194 +// https://github.com/llvm/llvm-project/issues/117925 195 +void ApplyFlags() { 196 + SetCanPoisonMemory(flags()->poison_heap); 197 + SetMallocContextSize(common_flags()->malloc_context_size); 198 + 199 + __asan_option_detect_stack_use_after_return = 200 + flags()->detect_stack_use_after_return; 201 + 202 + AllocatorOptions allocator_options; 203 + allocator_options.SetFrom(flags(), common_flags()); 204 + ApplyAllocatorOptions(allocator_options); 205 +} 206 + 207 static bool AsanInitInternal() { 208 if (LIKELY(AsanInited())) 209 return true; 210 @@ -397,8 +430,9 @@ static bool AsanInitInternal() { 211 212 CacheBinaryName(); 213 214 - // Initialize flags. This must be done early, because most of the 215 - // initialization steps look at flags(). 216 + // Initialize flags. On Windows it also also register weak function callbacks. 217 + // This must be done early, because most of the initialization steps look at 218 + // flags(). 219 InitializeFlags(); 220 221 WaitForDebugger(flags()->sleep_before_init, "before init"); 222 @@ -416,9 +450,6 @@ static bool AsanInitInternal() { 223 AsanCheckDynamicRTPrereqs(); 224 AvoidCVE_2016_2143(); 225 226 - SetCanPoisonMemory(flags()->poison_heap); 227 - SetMallocContextSize(common_flags()->malloc_context_size); 228 - 229 InitializePlatformExceptionHandlers(); 230 231 InitializeHighMemEnd(); 232 @@ -429,10 +460,6 @@ static bool AsanInitInternal() { 233 SetPrintfAndReportCallback(AppendToErrorMessageBuffer); 234 235 __sanitizer_set_report_path(common_flags()->log_path); 236 - 237 - __asan_option_detect_stack_use_after_return = 238 - flags()->detect_stack_use_after_return; 239 - 240 __sanitizer::InitializePlatformEarly(); 241 242 // Setup internal allocator callback. 243 @@ -460,6 +487,13 @@ static bool AsanInitInternal() { 244 allocator_options.SetFrom(flags(), common_flags()); 245 InitializeAllocator(allocator_options); 246 247 + // Apply ASan flags. 248 + // NOTE: In order for options specified through `__asan_default_options` to be 249 + // honored on Windows, it is necessary for those options to be configured 250 + // inside the `ApplyOptions` method. See the function-level comment for 251 + // `ApplyFlags` for more details. 252 + ApplyFlags(); 253 + 254 if (SANITIZER_START_BACKGROUND_THREAD_IN_ASAN_INTERNAL) 255 MaybeStartBackgroudThread(); 256 257 diff --git a/compiler-rt/test/asan/TestCases/Windows/alloc_dealloc_mismatch.cpp b/compiler-rt/test/asan/TestCases/Windows/alloc_dealloc_mismatch.cpp 258 new file mode 100644 259 index 000000000000..c7d62f15c3c3 260 --- /dev/null 261 +++ b/compiler-rt/test/asan/TestCases/Windows/alloc_dealloc_mismatch.cpp 262 @@ -0,0 +1,29 @@ 263 +// RUN: %clangxx_asan -O0 %s -o %t 264 +// RUN: %env_asan_opts=alloc_dealloc_mismatch=1 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISMATCH 265 +// RUN: %env_asan_opts=alloc_dealloc_mismatch=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS 266 + 267 +// RUN: %clangxx_asan -O0 %s -o %t -DUSER_FUNCTION 268 +// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISMATCH 269 + 270 +// It is expected that ASAN_OPTS will override the value set through the user function. 271 +// RUN: %env_asan_opts=alloc_dealloc_mismatch=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS 272 + 273 +#if USER_FUNCTION 274 +// It's important to test the `alloc_dealloc_mismatch` flag set through the user function because, on Windows, 275 +// flags configured through the user-defined function `__asan_default_options` are not always be honored. 276 +// See: https://github.com/llvm/llvm-project/issues/117925 277 +extern "C" __declspec(dllexport) extern const char *__asan_default_options() { 278 + return "alloc_dealloc_mismatch=1"; 279 +} 280 +#endif 281 + 282 +#include <cstdio> 283 +#include <cstdlib> 284 + 285 +// Tests the `alloc_dealloc_mismatch` flag set both via user function and through the environment variable. 286 +int main() { 287 + // In the 'CHECK-MISMATCH' case, we simply check that the AddressSanitizer reports an error. 288 + delete (new int[10]); // CHECK-MISMATCH: AddressSanitizer: 289 + printf("Success"); // CHECK-SUCCESS: Success 290 + return 0; 291 +} 292 \ No newline at end of file 293 -- 294 2.51.0.1.g7a422dac74