Skip to content
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

[asan][Windows] Synchronizing ASAN init on Windows #74086

Conversation

zacklj89
Copy link
Contributor

@zacklj89 zacklj89 commented Dec 1, 2023

This is an attempt to synchronize ASAN initialization using __sanitizer::atomics on Windows. One case in particular is a C# application that loads an ASan instrumented library.

This addresses:

  1. This check failure if a function is intercepted on T1 and called on T2 before T1 is done with ASAN initialization.
  2. This check failure can happen in parallel when two threads call ENSURE_ASAN_INITED.
  3. This check failure when thread is created before the main thread gets a chance to get created due to when CreateThread is intercepted.

Currently, everything is blocked by #if SANITIZER_WINDOWS because I'm not 100% sure about how ASan is loaded on other platforms and the implications.

SPR PRs one and two

Created using spr 1.3.4
@llvmbot
Copy link
Collaborator

llvmbot commented Dec 1, 2023

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

Author: Zack Johnson (zacklj89)

Changes

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

3 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_internal.h (+11)
  • (modified) compiler-rt/lib/asan/asan_rtl.cpp (+45-6)
  • (modified) compiler-rt/lib/asan/asan_thread.cpp (+24)
diff --git a/compiler-rt/lib/asan/asan_internal.h b/compiler-rt/lib/asan/asan_internal.h
index e2b1e9800f5be62..569f8aedc69a1d0 100644
--- a/compiler-rt/lib/asan/asan_internal.h
+++ b/compiler-rt/lib/asan/asan_internal.h
@@ -130,6 +130,17 @@ void InstallAtExitCheckLeaks();
   if (&__asan_on_error) \
   __asan_on_error()
 
+// Unless synchronization is used during initialization, 
+// race conditions can appear causing incorrect states or internal check
+// failures, depending on the loading thread and when ASAN is loaded on Windows.
+// From a multithreaded managed environment, if an ASAN instrumented dll
+// is loading on a spawned thread, an intercepted function may be called on
+// multiple threads while ASAN is still in the process of initialization. This
+// can also cause the ASAN thread registry to create the "main" thread after
+// another thread, resulting in a TID != 0.
+//
+// Two threads can also race to initialize ASAN, resulting in either incorrect
+// state or internal check failures for init already running.
 bool AsanInited();
 bool AsanInitIsRunning();  // Used to avoid infinite recursion in __asan_init().
 extern bool replace_intrin_cached;
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index d1e7856973b43b3..092ddf15d5a3db2 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -71,16 +71,54 @@ static void CheckUnwind() {
 }
 
 // -------------------------- Globals --------------------- {{{1
-static int asan_inited = 0;
-static int asan_init_is_running = 0;
+#if SANITIZER_WINDOWS
+atomic_uint8_t asan_inited{0};
+atomic_uint8_t asan_init_is_running{0};
+#else
+int asan_inited = 0;
+int asan_init_is_running = 0;
+#endif
 
-void SetAsanInited(u32 val) { asan_inited = val; }
+void SetAsanInited(u32 val) {
+#if SANITIZER_WINDOWS
+  atomic_store(&asan_inited, val, memory_order_release);
+#else
+  asan_inited = val;
+#endif
+}
 
-void SetAsanInitIsRunning(u32 val) { asan_init_is_running = val; }
+void SetAsanInitIsRunning(u32 val) {
+#if SANITIZER_WINDOWS
+  atomic_store(&asan_init_is_running, val, memory_order_release);
+#else
+  asan_init_is_running = val;
+#endif
+}
 
-bool AsanInited() { return asan_inited == 1; }
+bool AsanInited() {
+#if SANITIZER_WINDOWS
+  return atomic_load(&asan_inited, memory_order_acquire) == 1;
+#else
+  return asan_inited == 1;
+#endif
+}
 
-bool AsanInitIsRunning() { return asan_init_is_running == 1; }
+bool AsanInitIsRunning() {
+#if SANITIZER_WINDOWS
+  return atomic_load(&asan_init_is_running, memory_order_acquire) == 1;
+#else
+  return asan_init_is_running == 1;
+#endif
+}
+
+void CheckAsanInitRunning() {
+#if SANITIZER_WINDOWS
+  while (AsanInitIsRunning()) {
+    // If ASAN is initializing on another thread, wait for it to finish.
+    internal_sched_yield();
+  }
+#endif
+}
 
 bool replace_intrin_cached;
 
@@ -391,6 +429,7 @@ void PrintAddressSpaceLayout() {
 }
 
 static void AsanInitInternal() {
+  CheckAsanInitRunning();
   if (LIKELY(AsanInited()))
     return;
   SanitizerToolName = "AddressSanitizer";
diff --git a/compiler-rt/lib/asan/asan_thread.cpp b/compiler-rt/lib/asan/asan_thread.cpp
index 8798968947e82e6..dc0ad2caf3bbd10 100644
--- a/compiler-rt/lib/asan/asan_thread.cpp
+++ b/compiler-rt/lib/asan/asan_thread.cpp
@@ -27,6 +27,10 @@ namespace __asan {
 
 // AsanThreadContext implementation.
 
+#if SANITIZER_WINDOWS
+static atomic_uint8_t main_thread_created{0};
+#endif
+
 void AsanThreadContext::OnCreated(void *arg) {
   CreateThreadContextArgs *args = static_cast<CreateThreadContextArgs *>(arg);
   if (args->stack)
@@ -93,6 +97,12 @@ AsanThreadContext *GetThreadContextByTidLocked(u32 tid) {
 AsanThread *AsanThread::Create(const void *start_data, uptr data_size,
                                u32 parent_tid, StackTrace *stack,
                                bool detached) {
+#if SANITIZER_WINDOWS
+  while (atomic_load(&main_thread_created, memory_order_acquire) == 0) {
+    // If another thread is trying to be created before the main thread, wait.
+    internal_sched_yield();
+  }
+#endif
   uptr PageSize = GetPageSizeCached();
   uptr size = RoundUpTo(sizeof(AsanThread), PageSize);
   AsanThread *thread = (AsanThread *)MmapOrDie(size, __func__);
@@ -288,11 +298,25 @@ void AsanThread::ThreadStart(tid_t os_id) {
 }
 
 AsanThread *CreateMainThread() {
+// Depending on the loading thread, specifically in managed scenarios, the main
+// thread can be created after other threads on Windows. This ensures we start
+// the main thread before those threads.
+#  if SANITIZER_WINDOWS
+  uptr PageSize = GetPageSizeCached();
+  uptr size = RoundUpTo(sizeof(AsanThread), PageSize);
+  AsanThread *main_thread = (AsanThread *)MmapOrDie(size, __func__);
+  AsanThreadContext::CreateThreadContextArgs args = {main_thread, nullptr};
+  asanThreadRegistry().CreateThread(0, true, kMainTid, &args);
+  SetCurrentThread(main_thread);
+  main_thread->ThreadStart(internal_getpid());
+  atomic_store(&main_thread_created, 1, memory_order_release);
+#  else
   AsanThread *main_thread = AsanThread::Create(
       /* parent_tid */ kMainTid,
       /* stack */ nullptr, /* detached */ true);
   SetCurrentThread(main_thread);
   main_thread->ThreadStart(internal_getpid());
+#  endif
   return main_thread;
 }
 

Copy link

github-actions bot commented Dec 1, 2023

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

@vitalybuka vitalybuka self-requested a review December 2, 2023 01:05
@@ -93,6 +97,12 @@ AsanThreadContext *GetThreadContextByTidLocked(u32 tid) {
AsanThread *AsanThread::Create(const void *start_data, uptr data_size,
u32 parent_tid, StackTrace *stack,
bool detached) {
#if SANITIZER_WINDOWS
Copy link
Collaborator

Choose a reason for hiding this comment

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

This block can be moved before AsanThread *t = AsanThread::Create(params, current_tid, &stack, detached); in INTERCEPTOR_WINAPI(HANDLE, CreateThread

@@ -288,11 +298,25 @@ void AsanThread::ThreadStart(tid_t os_id) {
}

AsanThread *CreateMainThread() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

#ifdef // !SANITIZER_WINDOWS
AsanThread *CreateMainThread() {
...
#endif

And put windows version into asan_win.cpp

#endif
}

void CheckAsanInitRunning() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably not needed If you rebase on #74387

@vitalybuka
Copy link
Collaborator

Could you please if #74387 works for you?

@zacklj89
Copy link
Contributor Author

zacklj89 commented Dec 5, 2023

I cherry-picked the change from #74387 for testing (alongside the change here in asan_thread.cpp). It appears sufficient after testing. Thanks!

vitalybuka added a commit that referenced this pull request Dec 12, 2023
This allows to remove `asan_init_is_running` which likely had a data
race.

Simplifies #74086 and reduces a difference between platforms.

Reviewers: zacklj89, eugenis, dvyukov

Reviewed By: zacklj89, dvyukov

Pull Request: #74387
@zacklj89 zacklj89 closed this Dec 19, 2023
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.

None yet

3 participants