[compiler-rt] [compiler-rt] Remember alternate signal stack pointer (PR #194868)

via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 29 07:06:03 PDT 2026


https://github.com/accauble created https://github.com/llvm/llvm-project/pull/194868

When unsetting the alternate signal stack, asan incorrectly assumed that the registered stack was the one it had allocated. This caused problems when another component would register another alternate signal stack (like CreateSigAltStack in LLVM), like crashes with attempting to unmap the other component's memory.

The fix is to just remember the stack we allocated. If the registered stack is the one we allocated, then we can continue unsetting it. Either way, we unmap the saved stack.

Also includes a test Claude helped write which reproduced the error I was seeing when another component registered a new alt sig stack.

I did not find a relevant Issue but I did find a PR making the same fix.

>From 2feb5203f9924e9bdd7fd7150f00c3d71c9d9411 Mon Sep 17 00:00:00 2001
From: Allyson Cauble-Chantrenne <acaublec at amd.com>
Date: Wed, 29 Apr 2026 08:36:21 -0500
Subject: [PATCH] [Asan] When unsetting the alternate signal stack, asan
 incorrectly assumed that the registered stack was the one it had allocated.
 This caused problems when another component would register another alternate
 signal stack (like CreateSigAltStack in LLVM), like crashes with attempting
 to unmap the other component's memory.

The fix is to just remember the stack we allocated. If the
registered stack is the one we allocated, then we can continue
unsetting it. Either way, we unmap the saved stack.

Also includes a test Claude helped write which reproduced the error
I was seeing when another component registered a new alt sig stack.
---
 .../sanitizer_posix_libcdep.cpp               | 38 +++++++--
 .../Posix/sigaltstack-replaced-by-app.cpp     | 79 +++++++++++++++++++
 2 files changed, 111 insertions(+), 6 deletions(-)
 create mode 100644 compiler-rt/test/asan/TestCases/Posix/sigaltstack-replaced-by-app.cpp

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp
index 35b596de30fff..eb08705094bd5 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp
@@ -188,6 +188,11 @@ static uptr GetAltStackSize() {
   return SIGSTKSZ * 4;
 }
 
+// Remember the stack we register. We cannot assume that the registered one is
+// the one we allocated
+static THREADLOCAL void* registered_alt_stack_sp;
+static THREADLOCAL uptr registered_alt_stack_size;
+
 void SetAlternateSignalStack() {
   stack_t altstack, oldstack;
   CHECK_EQ(0, sigaltstack(nullptr, &oldstack));
@@ -201,15 +206,36 @@ void SetAlternateSignalStack() {
   altstack.ss_sp = (char *)MmapOrDie(altstack.ss_size, __func__);
   altstack.ss_flags = 0;
   CHECK_EQ(0, sigaltstack(&altstack, nullptr));
+  registered_alt_stack_sp = altstack.ss_sp;
+  registered_alt_stack_size = altstack.ss_size;
 }
 
 void UnsetAlternateSignalStack() {
-  stack_t altstack, oldstack;
-  altstack.ss_sp = nullptr;
-  altstack.ss_flags = SS_DISABLE;
-  altstack.ss_size = GetAltStackSize();  // Some sane value required on Darwin.
-  CHECK_EQ(0, sigaltstack(&altstack, &oldstack));
-  UnmapOrDie(oldstack.ss_sp, oldstack.ss_size);
+  if (!registered_alt_stack_sp)
+    return;
+  void* sp = registered_alt_stack_sp;
+  uptr size = registered_alt_stack_size;
+  registered_alt_stack_sp = nullptr;
+  registered_alt_stack_size = 0;
+
+  // Only un-register the alt stack with the kernel if it still points to the
+  // one we created. Another component (e.g. CreateSigAltStack in llvm) could
+  // have replaced our registration and we will leave them to disable it so
+  // we don't break their signal handling.
+  stack_t current;
+  CHECK_EQ(0, sigaltstack(nullptr, &current));
+  bool still_ours = !(current.ss_flags & SS_DISABLE) && current.ss_sp == sp &&
+                    current.ss_size == size;
+  if (still_ours) {
+    stack_t altstack;
+    altstack.ss_sp = nullptr;
+    altstack.ss_flags = SS_DISABLE;
+    altstack.ss_size = GetAltStackSize();  // Sane value required on Darwin.
+    CHECK_EQ(0, sigaltstack(&altstack, nullptr));
+  }
+  // Even if another stack has been registered, we still need to unmap the
+  // one we allocated
+  UnmapOrDie(sp, size);
 }
 
 bool IsSignalHandlerFromSanitizer(int signum) {
diff --git a/compiler-rt/test/asan/TestCases/Posix/sigaltstack-replaced-by-app.cpp b/compiler-rt/test/asan/TestCases/Posix/sigaltstack-replaced-by-app.cpp
new file mode 100644
index 0000000000000..c782762aa3f1d
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Posix/sigaltstack-replaced-by-app.cpp
@@ -0,0 +1,79 @@
+// Regression test for UnsetAlternateSignalStack: when another component
+// installs its own alternate signal stack on top of ASan's (e.g.
+// llvm/lib/Support/Unix/Signals.inc CreateSigAltStack does this when it
+// finds ASan's stack too small), ASan's per-thread destructor must not
+// try to free the replacement. ASan does not own that buffer; calling
+// UnmapOrDie on it crashes the process at thread teardown:
+//
+//   AsanThread::Destroy
+//     -> UnsetAlternateSignalStack
+//          sigaltstack(SS_DISABLE, &oldstack)  // returns *current* stack
+//          UnmapOrDie(oldstack.ss_sp, oldstack.ss_size)
+//            munmap fails (the pointer was not allocated by mmap, is
+//            mid-VMA, or is not page aligned)
+//            -> ReportMunmapFailureAndDie -> CHECK fail -> abort
+//
+// Reproducer:
+//   * A worker thread replaces ASan's alt-stack registration with an
+//     mmap'd buffer whose ss_sp is intentionally not page aligned.
+//     sigaltstack() accepts misaligned pointers; munmap() does not, so
+//     ASan's eventual munmap fails deterministically with EINVAL.
+//     (Without the misalignment, whether the bug fires depends on VMA
+//     fragmentation, which is not reliable in a self-contained test.)
+//   * Returning from the worker triggers __nptl_deallocate_tsd, which
+//     runs ASan's per-thread destructor and reaches the bad path.
+//     main()'s thread does not go through __nptl_deallocate_tsd on
+//     process exit, which is why this only manifests with a spawned
+//     thread.
+//
+// LeakSanitizer doesn't track raw mmap regions, so the intentionally
+// leaked alt-stack mapping does not require detect_leaks=0.
+//
+// RUN: %clangxx_asan -O0 %s -pthread -o %t && %run %t
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+static void *worker(void *) {
+  size_t pagesize = sysconf(_SC_PAGESIZE);
+  size_t mapped_size = 32 * pagesize;
+  char *region =
+      static_cast<char *>(mmap(nullptr, mapped_size, PROT_READ | PROT_WRITE,
+                               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+  if (region == MAP_FAILED) {
+    perror("mmap");
+    abort();
+  }
+
+  // ss_sp deliberately not page aligned: see file header.
+  stack_t new_stack = {};
+  new_stack.ss_sp = region + 1;
+  new_stack.ss_size = mapped_size - 1;
+  new_stack.ss_flags = 0;
+  if (sigaltstack(&new_stack, nullptr) != 0) {
+    perror("sigaltstack");
+    abort();
+  }
+
+  // Returning here drives ASan's per-thread destructor, which is where
+  // the bug used to abort.
+  return nullptr;
+}
+
+int main() {
+  pthread_t t;
+  if (pthread_create(&t, nullptr, worker, nullptr) != 0) {
+    perror("pthread_create");
+    return 1;
+  }
+  if (pthread_join(t, nullptr) != 0) {
+    perror("pthread_join");
+    return 1;
+  }
+  printf("OK\n");
+  return 0;
+}



More information about the llvm-commits mailing list