Skip to content

[ASan][Windows] Honor asan config flags on windows when set through the user function #122990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

davidmrdavid
Copy link
Contributor

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.

@llvmbot
Copy link
Member

llvmbot commented Jan 15, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: David Justo (davidmrdavid)

Changes

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.

Full diff: https://github.com/llvm/llvm-project/pull/122990.diff

5 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_allocator.cpp (+11-1)
  • (modified) compiler-rt/lib/asan/asan_allocator.h (+1)
  • (modified) compiler-rt/lib/asan/asan_flags.cpp (+9-14)
  • (modified) compiler-rt/lib/asan/asan_internal.h (+1)
  • (modified) compiler-rt/lib/asan/asan_rtl.cpp (+44-10)
diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp
index 9e66f77217ec6b..cf041d0a62fc20 100644
--- a/compiler-rt/lib/asan/asan_allocator.cpp
+++ b/compiler-rt/lib/asan/asan_allocator.cpp
@@ -423,10 +423,15 @@ struct Allocator {
     PoisonShadow(chunk, allocated_size, kAsanHeapLeftRedzoneMagic);
   }
 
-  void ReInitialize(const AllocatorOptions &options) {
+  // Apply provided AllocatorOptions to an Allocator
+  void ApplyOptions(const AllocatorOptions &options) {
     SetAllocatorMayReturnNull(options.may_return_null);
     allocator.SetReleaseToOSIntervalMs(options.release_to_os_interval_ms);
     SharedInitCode(options);
+  }
+
+  void ReInitialize(const AllocatorOptions &options) {
+    ApplyOptions(options);
 
     // Poison all existing allocation's redzones.
     if (CanPoisonMemory()) {
@@ -975,6 +980,11 @@ void ReInitializeAllocator(const AllocatorOptions &options) {
   instance.ReInitialize(options);
 }
 
+// Apply provided AllocatorOptions to an Allocator
+void ApplyAllocatorOptions(const AllocatorOptions &options) {
+  instance.ApplyOptions(options);
+}
+
 void GetAllocatorOptions(AllocatorOptions *options) {
   instance.GetOptions(options);
 }
diff --git a/compiler-rt/lib/asan/asan_allocator.h b/compiler-rt/lib/asan/asan_allocator.h
index db8dc3bebfc620..a94ef958aa75ec 100644
--- a/compiler-rt/lib/asan/asan_allocator.h
+++ b/compiler-rt/lib/asan/asan_allocator.h
@@ -47,6 +47,7 @@ struct AllocatorOptions {
 void InitializeAllocator(const AllocatorOptions &options);
 void ReInitializeAllocator(const AllocatorOptions &options);
 void GetAllocatorOptions(AllocatorOptions *options);
+void ApplyAllocatorOptions(const AllocatorOptions &options);
 
 class AsanChunkView {
  public:
diff --git a/compiler-rt/lib/asan/asan_flags.cpp b/compiler-rt/lib/asan/asan_flags.cpp
index 9cfb70bd00c786..9be2273998e40a 100644
--- a/compiler-rt/lib/asan/asan_flags.cpp
+++ b/compiler-rt/lib/asan/asan_flags.cpp
@@ -144,7 +144,8 @@ static void InitializeDefaultFlags() {
   DisplayHelpMessages(&asan_parser);
 }
 
-static void ProcessFlags() {
+// Validate flags and report incompatible configurations
+static void ValidateFlags()s() {
   Flags *f = flags();
 
   // Flag validation:
@@ -214,11 +215,11 @@ static void ProcessFlags() {
 
 void InitializeFlags() {
   InitializeDefaultFlags();
-  ProcessFlags();
+  ValidateFlags();
 
 #if SANITIZER_WINDOWS
-  // On Windows, weak symbols are emulated by having the user program
-  // register which weak functions are defined.
+  // On Windows, weak symbols (such as the `__asan_default_options` function)
+  // are emulated by having the user program register which weak functions are defined.
   // The ASAN DLL will initialize flags prior to user module initialization,
   // so __asan_default_options will not point to the user definition yet.
   // We still want to ensure we capture when options are passed via
@@ -239,14 +240,8 @@ void InitializeFlags() {
         asan_parser.ParseString(__asan_default_options());
 
         DisplayHelpMessages(&asan_parser);
-        ProcessFlags();
-
-        // TODO: Update other globals and data structures that may need to change
-        // after initialization due to new flags potentially being set changing after
-        // `__asan_default_options` is registered.
-        // See GH issue 'https://github.com/llvm/llvm-project/issues/117925' for
-        // details.
-        SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
+        ValidateFlags();
+        ApplyFlags();
       });
 
 #  if CAN_SANITIZE_UB
@@ -259,7 +254,7 @@ void InitializeFlags() {
         ubsan_parser.ParseString(__ubsan_default_options());
 
         // To match normal behavior, do not print UBSan help.
-        ProcessFlags();
+        ValidateFlags();
       });
 #  endif
 
@@ -273,7 +268,7 @@ void InitializeFlags() {
         lsan_parser.ParseString(__lsan_default_options());
 
         // To match normal behavior, do not print LSan help.
-        ProcessFlags();
+        ValidateFlags();
       });
 #  endif
 
diff --git a/compiler-rt/lib/asan/asan_internal.h b/compiler-rt/lib/asan/asan_internal.h
index 06dfc4b1773397..464faad56f32d1 100644
--- a/compiler-rt/lib/asan/asan_internal.h
+++ b/compiler-rt/lib/asan/asan_internal.h
@@ -61,6 +61,7 @@ using __sanitizer::StackTrace;
 
 void AsanInitFromRtl();
 bool TryAsanInitFromRtl();
+void ApplyFlags();
 
 // asan_win.cpp
 void InitializePlatformExceptionHandlers();
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index 19c6c210b564c5..65c40d8bf8b13d 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -390,6 +390,39 @@ void PrintAddressSpaceLayout() {
           kHighShadowBeg > kMidMemEnd);
 }
 
+// Apply most options specified either through the ASAN_OPTIONS
+// environment variable, or through the `__asan_default_options` user function.
+//
+// This function may be called multiple times, once per weak reference callback on
+// Windows, so it needs to be idempotent.
+//
+// Context:
+// For maximum compatibility on Windows, it is necessary for ASan options to be
+// configured/registered/applied inside this method (instead of in ASanInitInternal,
+// for example). That's because, on Windows, the user-provided definition for `__asan_default_opts`
+// may not be bound when `ASanInitInternal` is invoked (it is bound later).
+//
+// To work around the late binding on windows, `ApplyOptions` will be called, again,
+// after binding to the user-provided `__asan_default_opts` function. Therefore,
+// any flags not configured here are not guaranteed to be configurable
+// through `__asan_default_opts` on Windows.
+//
+//
+// For more details on this issue, see: https://github.com/llvm/llvm-project/issues/117925
+void ApplyFlags() {
+  SetCanPoisonMemory(flags()->poison_heap);
+  SetMallocContextSize(common_flags()->malloc_context_size);
+
+  __sanitizer_set_report_path(common_flags()->log_path);
+
+  __asan_option_detect_stack_use_after_return =
+      flags()->detect_stack_use_after_return;
+
+  AllocatorOptions allocator_options;
+  allocator_options.SetFrom(flags(), common_flags());
+  ApplyAllocatorOptions(allocator_options);
+}
+
 static bool AsanInitInternal() {
   if (LIKELY(AsanInited()))
     return true;
@@ -397,10 +430,13 @@ static bool AsanInitInternal() {
 
   CacheBinaryName();
 
-  // Initialize flags. This must be done early, because most of the
-  // initialization steps look at flags().
+  // Initialize flags and register weak function callbacks for windows.
+  // This must be done early, because most of the initialization steps look at flags().
   InitializeFlags();
 
+  // NOTE: The sleep before/after init` flags will not work on Windows when set through
+  // `__asan_default_options`, because that function is not guaranteed to be bound
+  // this early in initialization.
   WaitForDebugger(flags()->sleep_before_init, "before init");
 
   // Stop performing init at this point if we are being loaded via
@@ -416,9 +452,6 @@ static bool AsanInitInternal() {
   AsanCheckDynamicRTPrereqs();
   AvoidCVE_2016_2143();
 
-  SetCanPoisonMemory(flags()->poison_heap);
-  SetMallocContextSize(common_flags()->malloc_context_size);
-
   InitializePlatformExceptionHandlers();
 
   InitializeHighMemEnd();
@@ -428,11 +461,6 @@ static bool AsanInitInternal() {
   SetCheckUnwindCallback(CheckUnwind);
   SetPrintfAndReportCallback(AppendToErrorMessageBuffer);
 
-  __sanitizer_set_report_path(common_flags()->log_path);
-
-  __asan_option_detect_stack_use_after_return =
-      flags()->detect_stack_use_after_return;
-
   __sanitizer::InitializePlatformEarly();
 
   // Setup internal allocator callback.
@@ -460,6 +488,12 @@ static bool AsanInitInternal() {
   allocator_options.SetFrom(flags(), common_flags());
   InitializeAllocator(allocator_options);
 
+  // Apply ASan flags.
+  // NOTE: In order for options specified through `__asan_default_options` to be honored on Windows,
+  // it is necessary for those options to be configured inside the `ApplyOptions` method.
+  // See the function-level comment for `ApplyFlags` for more details.
+  ApplyFlags();
+
   if (SANITIZER_START_BACKGROUND_THREAD_IN_ASAN_INTERNAL)
     MaybeStartBackgroudThread();
 

Copy link

github-actions bot commented Jan 15, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@davidmrdavid davidmrdavid marked this pull request as draft January 15, 2025 00:33
@davidmrdavid
Copy link
Contributor Author

davidmrdavid commented Jan 15, 2025

Converting to draft until clang-format is applied.
Update: done

@davidmrdavid davidmrdavid marked this pull request as ready for review January 15, 2025 01:56
@davidmrdavid
Copy link
Contributor Author

Just leaving a friendly "Ping" here. Thanks!

InitializeFlags();

// NOTE: The sleep before/after init` flags will not work on Windows when set
Copy link
Collaborator

@vitalybuka vitalybuka Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment does not belong here, maybe to WaitForDebugger implementation.
But it's irrelevant to the PR anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed: ba740aa

static bool AsanInitInternal() {
if (LIKELY(AsanInited()))
return true;
SanitizerToolName = "AddressSanitizer";

CacheBinaryName();

// Initialize flags. This must be done early, because most of the
// initialization steps look at flags().
// Initialize flags and register weak function callbacks for windows.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It reads as the function is windows specific.

Maybe something like:
Initialize flags. On Windows it also also register weak function callbacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I had tunnel vision on this one. Thanks! Incorporated: 0728fb8

@davidmrdavid
Copy link
Contributor Author

I'll mark this as 'draft until I add this test: #122990 (comment)

@davidmrdavid davidmrdavid marked this pull request as draft January 25, 2025 01:10
@davidmrdavid davidmrdavid marked this pull request as ready for review January 28, 2025 00:56
@davidmrdavid
Copy link
Contributor Author

Just leaving a friendly "Ping" here. Thank you :)

@davidmrdavid
Copy link
Contributor Author

Hi @vitalybuka, sorry to bother again - just wanted to keep this in your radar. Please let me know if there's a better channel/way to bump a PR as well, I want to work through the proper mechanisms here.

@vitalybuka
Copy link
Collaborator

Unless you click re-request review spinner in "reviewers" section, GitHub does shows this as waiting for review.

@vitalybuka
Copy link
Collaborator

I'll take a better look this week, and I've added folks who are more involved with Windows.

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a consequence of our choice to build the ASan RTL as a DLL? It seems like the simple case of a static executable (Chrome/Edge) with a static ASan runtime would've worked out of the box in the old model, but now that we've moved everything into a DLL, the ways that we emulate weak functions are not as transparent.

Regardless, this seems like a good change and a cost of doing business in the PE/COFF model.

@davidmrdavid
Copy link
Contributor Author

Is this a consequence of our choice to build the ASan RTL as a DLL?

I couldn't say with confidence one way or another, that decision predates me. But I think it's likely you're correct.

Regardless, this seems like a good change and a cost of doing business in the PE/COFF model.

Thanks. Long term - I think the initialization codepath on windows would benefit from the ability to delay until the user function is available for invocation. Today's model, where we leverage a callback to correct for missing configuration, is a bit complex and leaves the door open to bugs like this one. But anyways, I'm not ready to tackle something like that at this time, but it's on my radar as a possible improvement :) .

@davidmrdavid davidmrdavid requested a review from rnk February 11, 2025 21:58
@davidmrdavid
Copy link
Contributor Author

Just a friendly ping here (I hope I'm not being too noisy, trying to keep these roughly one week apart).
@rnk is this something you can help unblock? Thanks!

@davidmrdavid
Copy link
Contributor Author

Another friendly ping, @vitalybuka. Thanks, hope I'm not being too spammy here.

@davidmrdavid
Copy link
Contributor Author

Just leaving another ping, thanks a lot, and sorry for the spam. Won't ping again for another week.

@davidmrdavid
Copy link
Contributor Author

Another friendly ping here :-) . Thanks!

@davidmrdavid
Copy link
Contributor Author

@vitalybuka / @rnk just another ping here, thanks!

@davidmrdavid
Copy link
Contributor Author

ping @vitalybuka / @zmodem, my apologies if this is spammy, just not sure if this is falling under the radar. Thanks!

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good, but let @vitalybuka provide feedback. Thanks for the patch.

@davidmrdavid davidmrdavid requested a review from vitalybuka July 2, 2025 17:37
@vitalybuka vitalybuka merged commit 0d7e64f into llvm:main Jul 2, 2025
9 checks passed
@vitalybuka
Copy link
Collaborator

Thanks for the patch, and sorry for unnecessary delayed review.

@davidmrdavid davidmrdavid deleted the dev/dajusto/add-win-apply-allocator-options branch July 2, 2025 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants