[compiler-rt] [SafeStack] Allocate unsafe sigaltstack (PR #206463)

Jakob Koschel via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 29 04:18:21 PDT 2026


https://github.com/jakos-sec created https://github.com/llvm/llvm-project/pull/206463

PR https://github.com/llvm/llvm-project/pull/196969 was approved and merged but with `spr` the wrong base branch was set.

This merges the approved changes into `main`

>From 162b90a376a070d9dfed8dde83db32221a2bf65a Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Mon, 11 May 2026 15:07:40 +0000
Subject: [PATCH 1/2] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20ch?=
 =?UTF-8?q?anges=20to=20main=20this=20commit=20is=20based=20on?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.7

[skip ci]
---
 compiler-rt/lib/safestack/safestack.cpp  |  21 +++-
 compiler-rt/test/safestack/sigaltstack.c | 123 +++++++++++++++++++++++
 2 files changed, 140 insertions(+), 4 deletions(-)
 create mode 100644 compiler-rt/test/safestack/sigaltstack.c

diff --git a/compiler-rt/lib/safestack/safestack.cpp b/compiler-rt/lib/safestack/safestack.cpp
index f01a642987646..ec8c1dd6c8acb 100644
--- a/compiler-rt/lib/safestack/safestack.cpp
+++ b/compiler-rt/lib/safestack/safestack.cpp
@@ -15,15 +15,14 @@
 
 #define SANITIZER_COMMON_NO_REDEFINE_BUILTINS
 
-#include "safestack_platform.h"
-#include "safestack_util.h"
-#include "sanitizer_common/sanitizer_internal_defs.h"
-
 #include <errno.h>
 #include <string.h>
 #include <sys/resource.h>
 
 #include "interception/interception.h"
+#include "safestack_platform.h"
+#include "safestack_util.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
 
 // interception.h drags in sanitizer_redefine_builtins.h, which in turn
 // creates references to __sanitizer_internal_memcpy etc.  The interceptors
@@ -277,6 +276,16 @@ INTERCEPTOR(int, pthread_create, pthread_t *thread,
   return REAL(pthread_create)(thread, attr, thread_start, tinfo);
 }
 
+// We are intercepting sigaction in order to keep note of the set sigaction and
+// overwrite it our own function to execute the switching if the unsafe stack
+// pointer before and after the signal is handled.
+// In this version, we are simply making sure the interceptor is functional.
+// sigaction is required to be async-signal-safe.
+INTERCEPTOR(int, sigaction, int sig, const struct sigaction* act,
+            struct sigaction* oldact) {
+  return REAL(sigaction)(sig, act, oldact);
+}
+
 pthread_mutex_t interceptor_init_mutex = PTHREAD_MUTEX_INITIALIZER;
 bool interceptors_inited = false;
 
@@ -287,6 +296,8 @@ void EnsureInterceptorsInitialized() {
 
   // Initialize pthread interceptors for thread allocation
   INTERCEPT_FUNCTION(pthread_create);
+  // Initialize sigaction interceptor to overwrite the signal handler.
+  INTERCEPT_FUNCTION(sigaction);
 
   interceptors_inited = true;
 }
@@ -313,6 +324,8 @@ void __safestack_init() {
 
   // Setup the cleanup handler
   pthread_key_create(&thread_cleanup_key, thread_cleanup_handler);
+
+  EnsureInterceptorsInitialized();
 }
 
 #if SANITIZER_CAN_USE_PREINIT_ARRAY
diff --git a/compiler-rt/test/safestack/sigaltstack.c b/compiler-rt/test/safestack/sigaltstack.c
new file mode 100644
index 0000000000000..062f86c1a36ee
--- /dev/null
+++ b/compiler-rt/test/safestack/sigaltstack.c
@@ -0,0 +1,123 @@
+// RUN: %clang_safestack %s -pthread -o %t
+// RUN: %run %t
+
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/mman.h>
+
+// Test that safe stack works with sigaltstack.
+int puts(const char *);
+
+extern void *__get_unsafe_stack_ptr();
+extern void *__get_unsafe_stack_top();
+extern void *__get_unsafe_stack_bottom();
+
+__thread int signal_handlers_called = 0;
+
+void signal_handler(int signo) {
+  signal_handlers_called += 1;
+  assert(__get_unsafe_stack_ptr() <= __get_unsafe_stack_top() &&
+         __get_unsafe_stack_ptr() >= __get_unsafe_stack_bottom());
+}
+
+void signal_sigaction(int signo, siginfo_t *si, void *uc) {
+  signal_handlers_called += 1;
+  assert(__get_unsafe_stack_ptr() <= __get_unsafe_stack_top() &&
+         __get_unsafe_stack_ptr() >= __get_unsafe_stack_bottom());
+}
+
+void *t1_start(void *ptr) {
+  // Test that since we didn't allocate a sigaltstack yet for this thread but
+  // successfully call the correct signal handler.
+  raise(SIGUSR1);
+  raise(SIGUSR2);
+
+  stack_t sigstk = {};
+  size_t ss_size = 4096 * 4;
+  void *ss_sp = mmap(NULL, ss_size, PROT_READ | PROT_WRITE,
+                     MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+  assert(ss_sp);
+  sigstk.ss_size = ss_size;
+  sigstk.ss_sp = ss_sp;
+
+  sigaltstack(&sigstk, NULL);
+
+  // Test that after sigaltstack is set, it signal handling still works.
+  raise(SIGUSR1);
+  raise(SIGUSR2);
+
+  assert(signal_handlers_called == 4);
+
+  return NULL;
+}
+
+int main() {
+  char c[] = "hello world";
+  puts(c);
+
+  stack_t sigstk = {};
+  size_t ss_size = 4096 * 4;
+  void *ss_sp = mmap(NULL, sigstk.ss_size, PROT_READ | PROT_WRITE,
+                     MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+  sigstk.ss_size = ss_size;
+  sigstk.ss_sp = ss_sp;
+
+  sigaltstack(&sigstk, NULL);
+
+  // Make sure retrieving the sigaltstack works without problems.
+  sigaltstack(NULL, &sigstk);
+
+  // Make sure updating the size of the sigaltstack works.
+  stack_t new_sigstk = {};
+  new_sigstk.ss_size = 4096 * 8;
+  new_sigstk.ss_sp = mmap(NULL, new_sigstk.ss_size, PROT_READ | PROT_WRITE,
+                          MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+
+  sigaltstack(&new_sigstk, NULL);
+  munmap(ss_sp, ss_size);
+
+  struct sigaction sa;
+  sa.sa_handler = signal_handler;
+  sigemptyset(&sa.sa_mask);
+  assert(sigaction(SIGUSR1, &sa, NULL) != -1);
+
+  sa.sa_sigaction = signal_sigaction;
+  sa.sa_flags = SA_SIGINFO;
+  sigemptyset(&sa.sa_mask);
+  assert(sigaction(SIGUSR2, &sa, NULL) != -1);
+
+  // Test that we do not use the unsafe sigaltstack if SA_ONSTACK is not set.
+  raise(SIGUSR1);
+  raise(SIGUSR2);
+
+  sa.sa_handler = signal_handler;
+  sa.sa_flags = SA_ONSTACK;
+  sigemptyset(&sa.sa_mask);
+  assert(sigaction(SIGUSR1, &sa, NULL) != -1);
+
+  sa.sa_sigaction = signal_sigaction;
+  sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
+  sigemptyset(&sa.sa_mask);
+  assert(sigaction(SIGUSR2, &sa, NULL) != -1);
+
+  // Test that we do not crash when SA_ONSTACK is set but currently still use
+  // the normal unsafe sigaltstack.
+  raise(SIGUSR1);
+  raise(SIGUSR2);
+
+  // Check that unsafe stack is set to the normal unsafe stack.
+  assert(__get_unsafe_stack_ptr() <= __get_unsafe_stack_top() &&
+         __get_unsafe_stack_ptr() >= __get_unsafe_stack_bottom());
+
+  assert(signal_handlers_called == 4);
+
+  // Now check if a sigaction set to use sigaltstack works on a thread that did
+  // not call sigaltstack() yet.
+  pthread_t t1;
+  assert(!pthread_create(&t1, NULL, t1_start, NULL));
+  assert(!pthread_join(t1, NULL));
+
+  return 0;
+}

>From ae1d9848edc7f94f106a50a75006f315fe00ac5b Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Mon, 29 Jun 2026 10:15:20 +0200
Subject: [PATCH 2/2] [SafeStack] Allocate unsafe sigaltstack (#196969)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This introduces a new function `unsafe_sigaltstack(size_t ss_size)` that
can be used by a user to allocate the unsafe sigaltstack.
We cannot intercept sigaltstack to do that automatically, since
allocating memory for the unsafe stack would not be async-signal-safe.

* https://github.com/llvm/llvm-project/pull/196968
* ➤ https://github.com/llvm/llvm-project/pull/196969
* https://github.com/llvm/llvm-project/pull/196970
* https://github.com/llvm/llvm-project/pull/196971
---
 .../include/sanitizer/safestack_interface.h   | 15 ++++
 compiler-rt/lib/safestack/safestack.cpp       | 70 +++++++++++++++++++
 compiler-rt/test/safestack/sigaltstack.c      | 20 ++++++
 3 files changed, 105 insertions(+)

diff --git a/compiler-rt/include/sanitizer/safestack_interface.h b/compiler-rt/include/sanitizer/safestack_interface.h
index 68d2fed6ccef6..f4827c6a0128e 100644
--- a/compiler-rt/include/sanitizer/safestack_interface.h
+++ b/compiler-rt/include/sanitizer/safestack_interface.h
@@ -28,6 +28,21 @@ const void *SANITIZER_CDECL __safestack_get_unsafe_stack_bottom(void);
 /// Returns a pointer to the top of the unsafe stack of the current thread.
 const void *SANITIZER_CDECL __safestack_get_unsafe_stack_top(void);
 
+/// Returns a pointer to the top of the unsafe sigalt stack of the current
+/// thread.
+const void *SANITIZER_CDECL __safestack_get_unsafe_sigalt_stack_ptr(void);
+
+/// Returns a pointer to the bottom of the unsafe sigalt stack of the current
+/// thread.
+const void *SANITIZER_CDECL __safestack_get_unsafe_sigalt_stack_bottom(void);
+
+/// Returns a pointer to the top of the unsafe sigalt stack of the current
+/// thread.
+const void *SANITIZER_CDECL __safestack_get_unsafe_sigalt_stack_top(void);
+
+/// Set a new unsafe signal stack context to be used if SA_ONSTACK is set.
+int SANITIZER_CDECL __safestack_unsafe_sigaltstack(size_t ss_size);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/compiler-rt/lib/safestack/safestack.cpp b/compiler-rt/lib/safestack/safestack.cpp
index 739bac59c5d3f..1724e6d6ae08e 100644
--- a/compiler-rt/lib/safestack/safestack.cpp
+++ b/compiler-rt/lib/safestack/safestack.cpp
@@ -113,6 +113,14 @@ __thread void *unsafe_stack_start = nullptr;
 __thread size_t unsafe_stack_size = 0;
 __thread size_t unsafe_stack_guard = 0;
 
+// Per-thread unsafe stack information used for the unsafe stack during signal
+// handling if sigaltstack is used. Without, only the safe stack is switched by
+// the operating system. When the program indicates to use a separate stack for
+// signal handling, this should also include the unsafe stack component.
+__thread void* unsafe_sigalt_stack_ptr = nullptr;
+__thread void* unsafe_sigalt_stack_start = nullptr;
+__thread size_t unsafe_sigalt_stack_size = 0;
+
 inline void *unsafe_stack_alloc(size_t size, size_t guard) {
   SFS_CHECK(size + guard >= size);
   void *addr = Mmap(nullptr, size + guard, PROT_READ | PROT_WRITE,
@@ -134,6 +142,16 @@ inline void unsafe_stack_setup(void *start, size_t size, size_t guard) {
   unsafe_stack_guard = guard;
 }
 
+inline void unsafe_sigalt_stack_setup(void* start, size_t size) {
+  SFS_CHECK((uintptr_t)start + size >= (uintptr_t)start);
+  void* stack_ptr = (void*)((uintptr_t)start + size);
+  SFS_CHECK((((uintptr_t)stack_ptr) & (kStackAlign - 1)) == 0);
+
+  unsafe_sigalt_stack_ptr = stack_ptr;
+  unsafe_sigalt_stack_start = start;
+  unsafe_sigalt_stack_size = size;
+}
+
 /// Thread data for the cleanup handler
 pthread_key_t thread_cleanup_key;
 
@@ -225,6 +243,14 @@ void thread_cleanup_handler(void *_iter) {
   pthread_mutex_unlock(&thread_stacks_mutex);
 
   unsafe_stack_start = nullptr;
+
+  // In case the sigalt stack was allocated, we need to unmap the used memory.
+  if (unsafe_sigalt_stack_start) {
+    unsafe_sigalt_stack_ptr = nullptr;
+    Munmap(unsafe_sigalt_stack_start, unsafe_sigalt_stack_size);
+    unsafe_sigalt_stack_start = nullptr;
+    unsafe_sigalt_stack_size = 0;
+  }
 }
 
 void EnsureInterceptorsInitialized();
@@ -287,6 +313,30 @@ INTERCEPTOR(int, sigaction, int sig, const struct sigaction* act,
   return REAL(sigaction)(sig, act, oldact);
 }
 
+// Since sigaltstack is required to be async-signal-safe, we cannot simply
+// intercept it to allocate the the unsafe stack. Instead if the users wishes to
+// setup an unsafe sigalt stack can call unsafe_sigaltstack(ss_size size)
+// explicitliy.
+int setup_unsafe_sigaltstack(size_t ss_size) {
+  EnsureInterceptorsInitialized();
+
+  SFS_CHECK(ss_size);
+  ss_size = RoundUpTo(ss_size, kStackAlign);
+
+  // For now always map a new unsafe sigaltstack when setting a new
+  // sigaltstack. Potentially if the size is identical, this step can be
+  // skipped.
+  void* prev_sigalt_stack_start = unsafe_sigalt_stack_start;
+  size_t prev_sigalt_stack_size = unsafe_sigalt_stack_size;
+  void* sigalt_addr = unsafe_stack_alloc(ss_size, 0);
+  unsafe_sigalt_stack_setup(sigalt_addr, ss_size);
+  if (prev_sigalt_stack_start != nullptr) {
+    Munmap(prev_sigalt_stack_start, prev_sigalt_stack_size);
+  }
+
+  return 0;
+}
+
 pthread_mutex_t interceptor_init_mutex = PTHREAD_MUTEX_INITIALIZER;
 bool interceptors_inited = false;
 
@@ -354,6 +404,26 @@ __safestack_get_unsafe_stack_ptr() {
   return __safestack_unsafe_stack_ptr;
 }
 
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE const void*
+__safestack_get_unsafe_sigalt_stack_bottom() {
+  return unsafe_sigalt_stack_start;
+}
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE const void*
+__safestack_get_unsafe_sigalt_stack_top() {
+  return (char*)unsafe_sigalt_stack_start + unsafe_sigalt_stack_size;
+}
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE const void*
+__safestack_get_unsafe_sigalt_stack_ptr() {
+  return unsafe_sigalt_stack_ptr;
+}
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE int __safestack_unsafe_sigaltstack(
+    size_t ss_size) {
+  return setup_unsafe_sigaltstack(ss_size);
+}
+
 // Compatibility aliases
 extern "C" SANITIZER_INTERFACE_ATTRIBUTE void* __get_unsafe_stack_bottom() {
   return const_cast<void*>(__safestack_get_unsafe_stack_bottom());
diff --git a/compiler-rt/test/safestack/sigaltstack.c b/compiler-rt/test/safestack/sigaltstack.c
index 3c9fd61acff91..5432ca70ff04b 100644
--- a/compiler-rt/test/safestack/sigaltstack.c
+++ b/compiler-rt/test/safestack/sigaltstack.c
@@ -49,6 +49,7 @@ void *t1_start(void *ptr) {
   sigstk.ss_size = ss_size;
   sigstk.ss_sp = ss_sp;
 
+  __safestack_unsafe_sigaltstack(sigstk.ss_size);
   sigaltstack(&sigstk, NULL);
 
   // Test that after sigaltstack is set, it signal handling still works.
@@ -64,6 +65,9 @@ int main() {
   char c[] = "hello world";
   puts(c);
 
+  // Make sure no sigaltstack is allocated by default.
+  assert(!__safestack_get_unsafe_sigalt_stack_ptr());
+
   stack_t sigstk = {};
   size_t ss_size = 4096 * 4;
   void *ss_sp = mmap(NULL, sigstk.ss_size, PROT_READ | PROT_WRITE,
@@ -71,8 +75,17 @@ int main() {
   sigstk.ss_size = ss_size;
   sigstk.ss_sp = ss_sp;
 
+  __safestack_unsafe_sigaltstack(sigstk.ss_size);
   sigaltstack(&sigstk, NULL);
 
+  // Make sure __safestack_unsafe_sigaltstack allocated the unsafe sigaltstack with the
+  // correct size.
+  assert(__safestack_get_unsafe_sigalt_stack_ptr());
+  assert(__safestack_get_unsafe_sigalt_stack_ptr() ==
+         __safestack_get_unsafe_sigalt_stack_top());
+  assert((__safestack_get_unsafe_sigalt_stack_top() -
+          __safestack_get_unsafe_sigalt_stack_bottom()) == sigstk.ss_size);
+
   // Make sure retrieving the sigaltstack works without problems.
   sigaltstack(NULL, &sigstk);
 
@@ -82,9 +95,16 @@ int main() {
   new_sigstk.ss_sp = mmap(NULL, new_sigstk.ss_size, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
 
+  __safestack_unsafe_sigaltstack(new_sigstk.ss_size);
   sigaltstack(&new_sigstk, NULL);
   munmap(ss_sp, ss_size);
 
+  // Make sure updating the size of the unsafe sigaltstack also updates when
+  // setting a new sigaltstack.
+  assert(__safestack_get_unsafe_sigalt_stack_ptr());
+  assert((__safestack_get_unsafe_sigalt_stack_top() -
+          __safestack_get_unsafe_sigalt_stack_bottom()) == new_sigstk.ss_size);
+
   struct sigaction sa;
   sa.sa_handler = signal_handler;
   sigemptyset(&sa.sa_mask);



More information about the llvm-commits mailing list