[clang] [compiler-rt] [safestack] Support statically linked executables (PR #190126)

via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 2 00:52:55 PDT 2026


https://github.com/edcsnt updated https://github.com/llvm/llvm-project/pull/190126

>From 6394b7ffcec9f30ec991dfbcd4b1a39a2cd25c5e Mon Sep 17 00:00:00 2001
From: Eduardo Santos <33993373+edcsnt at users.noreply.github.com>
Date: Thu, 2 Apr 2026 04:32:17 -0300
Subject: [PATCH] [safestack] Support statically linked executables

cat >a.c <<EOF && clang -fsanitize=safe-stack -static a.c && ./a.out

void *f(void *p)
{
	return NULL;
}

int main(void)
{
	pthread_t t;
	(void)pthread_create(&t, NULL, f, NULL);
	return 0;
}
EOF

causes an invalid memory reference at 0x0 that generates SIGSEGV, as
lldb -b -o r -Q ./a.out will attest.

On the first call to pthread_create, the SafeStack interceptor calls
dlsym(RTLD_NEXT) to obtain the address of pthread_create. In a
statically linked executable file, there is no next executable object
file to search in. A null pointer is dereferenced when the interceptor
attempts to call REAL(pthread_create).

Declare a weak symbol for the implementation of pthread_create and fall
back to it if dlsym returns a null pointer. The Clang driver instructs
the link editor to extract the defining archive member if linking
statically.

illumos and Solaris do not provide libc.a as [1] and [2] document.

The SafeStack runtime obtains the addresses of libc symbols for NetBSD
almost exclusively via dlsym [3]. Even if refactored to avoid dlsym,
[4] itself discourages static linking.

[1] https://www.illumos.org/issues/5247#note-7
[2] http://www.linker-aliens.org/blogs/rie/entry/static_linking_where_did_it/
[3] https://github.com/llvm/llvm-project/blob/e3cbd9984a78422c3799629eb6b5f7f7818c1a11/compiler-rt/lib/safestack/safestack_platform.h#L66-L86
[4] https://man.netbsd.org/pthread.3#DESCRIPTION
---
 clang/lib/Driver/ToolChains/CommonArgs.cpp  |  9 +++++
 compiler-rt/lib/safestack/safestack.cpp     | 45 +++++++++++++++++++++
 compiler-rt/test/safestack/pthread-static.c | 20 +++++++++
 3 files changed, 74 insertions(+)
 create mode 100644 compiler-rt/test/safestack/pthread-static.c

diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index c04e2c2b5b8c5..094675fee1632 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1707,6 +1707,15 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
   if (SanArgs.needsSafeStackRt()) {
     NonWholeStaticRuntimes.push_back("safestack");
     RequiredSymbols.push_back("__safestack_init");
+    if (Args.hasArg(options::OPT_static) ||
+        Args.hasArg(options::OPT_static_pie)) {
+      if (TC.getTriple().isGNUEnvironment())
+        RequiredSymbols.push_back("__pthread_create_2_1");
+      else if (TC.getTriple().isOSLinux())
+        RequiredSymbols.push_back("__pthread_create");
+      else if (TC.getTriple().isOSFreeBSD())
+        RequiredSymbols.push_back("_pthread_create");
+    }
   }
   if (!(SanArgs.needsSharedRt() && SanArgs.needsUbsanRt())) {
     if (SanArgs.needsCfiCrossDsoRt())
diff --git a/compiler-rt/lib/safestack/safestack.cpp b/compiler-rt/lib/safestack/safestack.cpp
index f01a642987646..05cee3289bff1 100644
--- a/compiler-rt/lib/safestack/safestack.cpp
+++ b/compiler-rt/lib/safestack/safestack.cpp
@@ -62,6 +62,26 @@ __attribute__((visibility(
     "default"))) __thread void *__safestack_unsafe_stack_ptr = nullptr;
 }
 
+// These symbols may have no definition within the component being linked,
+// either because the archive library is not passed to the link editor or
+// because the shared object does not include them in its dynamic linking
+// symbol table. Give them an STB_WEAK binding so that, if left unresolved,
+// they have a zero value.
+#if SANITIZER_GLIBC
+extern "C"
+    __attribute__((weak)) int __pthread_create_2_1(pthread_t*,
+                                                   const pthread_attr_t*,
+                                                   void* (*)(void*), void*);
+#elif SANITIZER_LINUX
+extern "C" __attribute__((weak)) int __pthread_create(pthread_t*,
+                                                      const pthread_attr_t*,
+                                                      void* (*)(void*), void*);
+#elif SANITIZER_FREEBSD
+extern "C"
+    __attribute__((weak)) int _pthread_create(pthread_t*, const pthread_attr_t*,
+                                              void* (*)(void*), void*);
+#endif
+
 namespace {
 
 // TODO: The runtime library does not currently protect the safe stack beyond
@@ -288,6 +308,24 @@ void EnsureInterceptorsInitialized() {
   // Initialize pthread interceptors for thread allocation
   INTERCEPT_FUNCTION(pthread_create);
 
+#if SANITIZER_GLIBC
+  // REAL(pthread_create) is null after INTERCEPT_FUNCTION(pthread_create) in
+  // statically linked executable files. Fall back to the weak symbol we
+  // declare.
+  //
+  // diet libc and uClibc-ng are not supported because interception requires a
+  // distinct symbol to call after replacing pthread_create; neither provides
+  // such a symbol.
+  if (!REAL(pthread_create))
+    REAL(pthread_create) = __pthread_create_2_1;
+#elif SANITIZER_LINUX
+  if (!REAL(pthread_create))
+    REAL(pthread_create) = __pthread_create;
+#elif SANITIZER_FREEBSD
+  if (!REAL(pthread_create))
+    REAL(pthread_create) = _pthread_create;
+#endif
+
   interceptors_inited = true;
 }
 
@@ -320,8 +358,15 @@ void __safestack_init() {
 // On other platforms we use the constructor attribute to arrange to run our
 // initialization early.
 extern "C" {
+#  if !SANITIZER_MUSL
 __attribute__((section(".preinit_array"),
                used)) void (*__safestack_preinit)(void) = __safestack_init;
+#  else
+// musl does not execute .preinit_array functions.
+__attribute__((constructor(0))) void __safestack_init_fallback() {
+  __safestack_init();
+}
+#  endif
 }
 #endif
 
diff --git a/compiler-rt/test/safestack/pthread-static.c b/compiler-rt/test/safestack/pthread-static.c
new file mode 100644
index 0000000000000..fde4b174aaff2
--- /dev/null
+++ b/compiler-rt/test/safestack/pthread-static.c
@@ -0,0 +1,20 @@
+// RUN: %clang_safestack %s -pthread -static -o %t
+// RUN: %run %t
+
+// REQUIRES: linux || freebsd
+
+// Smoke test for pthread_create in a statically linked executable.
+
+#include <pthread.h>
+
+void *f(void *p)
+{
+  return NULL;
+}
+
+int main(void)
+{
+  pthread_t t;
+  (void)pthread_create(&t, NULL, f, NULL);
+  return 0;
+}



More information about the llvm-commits mailing list