https://github.com/kenballus created https://github.com/llvm/llvm-project/pull/172086
# SignalSanitizer (`sigsan`) ## Summary This PR adds a new sanitizer to detect async-signal-unsafe function calls made from signal handlers, to catch bugs like [RegreSSHion](https://www.qualys.com/2024/07/01/cve-2024-6387/regresshion.txt). ## Background >From signal-safety(7): > An async-signal-safe function is one that can be safely called from within a > signal handler. Many functions are not async-signal-safe. In particular, > nonreentrant functions are generally unsafe to call from a signal handler. > ... > To avoid problems with unsafe functions, there are two possible choices: > (a) Ensure that (1) the signal handler calls only async-signal-safe > functions, and (2) the signal handler itself is reentrant with respect to > global variables in the main program. > (b) Block signal delivery in the main program when calling functions that > are unsafe or operating on global data that is also accessed by the signal > handler. > Generally, the second choice is difficult in programs of any complexity, so > the first choice is taken. Consider the following C program: ```C #include <signal.h> // for signal, SIGINT #include <stdlib.h> // for malloc, free void handler(int) { free(malloc(128)); } #define ARRAY_LEN(a) (sizeof(a) / sizeof(*a)) int main(void) { signal(SIGINT, handler); while (1) { void *p[128]; for (size_t i = 0; i < ARRAY_LEN(p); i++) { p[i] = malloc(128); } for (size_t i = 0; i < ARRAY_LEN(p); i++) { free(p[i]); } } } ``` This program just `malloc`s and `free`s in a loop. When it receives `SIGINT`, it `malloc`s once and `free`s it immediately. Because `malloc` and `free` are not async-signal-safe, `handler` can corrupt the heap. For a demo, try building the above program at `-O0` and send `SIGINT` in a loop; the program should crash with errors like this: ``` Fatal glibc error: malloc.c:2610 (sysmalloc): assertion failed: (old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0) ``` or this: ``` malloc(): mismatching next->prev_size (unsorted) ``` or this: ``` double free or corruption (!prev) ``` or this: ``` free(): double free detected in tcache 2 ``` On my amd64 GNU/Linux system, it takes ~30 `SIGINT`s before one of them causes detectable heap corruption. ## Mechanism To catch these bugs at runtime, a sanitizer might employ the following high-level strategy: - Whenever someone tries to install a signal handler, instead install a wrapper that increments a thread-local counter, calls the provided handler, then decrements the counter. - For each async-signal-unsafe function that tends to cause problems, intercept it with a wrapper that fails with a backtrace if the counter is nonzero, and calls the function otherwise. This draft PR implements these ideas, and intercepts a few async-signal-unsafe libc functions as a PoC. It also ensures that signal handlers do not clobber `errno`, because doing so opens the door to `errno`-checking race conditions. ## Demo To see this catch a real bug (RegreSSHion, which enabled RCE in i386 OpenSSH), do the following (on a computer you don't really care about): ```bash # Download the OpenSSH 9.7p1 source code wget https://github.com/openssh/openssh-portable/archive/refs/tags/V_9_7_P1.tar.gz tar xf V_9_7_P1.tar.gz cd openssh-portable-V_9_7_P1 # Build it export CC=/the/path/to/this/build/of/clang CFLAGS='-O0 -g -fsanitize=signal' LDFLAGS='-fsanitize=signal' autoreconf -fi ./configure --without-openssl make -j$(nproc) # Install it sudo make install # Run it on port 9999 with a 1 second LoginGracePeriod sudo /usr/local/sbin/sshd -g 1 -eD -p 9999 # From another shell, talk to it nc localhost 9999 ``` A second after getting the OpenSSH banner, you should be kicked off and see a backtrace on the `sshd`'s `stderr`: ``` ==2338676==ERROR: SignalSanitizer: async-signal-unsafe function vsnprintf called from a signal handler. #0 0x5d96f00828d5 (/usr/local/sbin/sshd+0x3e8d5) #1 0x5d96f00829a5 (/usr/local/sbin/sshd+0x3e9a5) #2 0x5d96f0083238 (/usr/local/sbin/sshd+0x3f238) #3 0x5d96f00f5eef (/usr/local/sbin/sshd+0xb1eef) #4 0x5d96f00f5a33 (/usr/local/sbin/sshd+0xb1a33) #5 0x5d96f00f5c23 (/usr/local/sbin/sshd+0xb1c23) #6 0x5d96f00896b8 (/usr/local/sbin/sshd+0x456b8) #7 0x5d96f0082a5f (/usr/local/sbin/sshd+0x3ea5f) #8 0x7b8f8fc3e4cf (/usr/lib/libc.so.6+0x3e4cf) (BuildId: 2f722da304c0a508c891285e6840199c35019c8d) #9 0x7b8f8fc9318d (/usr/lib/libc.so.6+0x9318d) (BuildId: 2f722da304c0a508c891285e6840199c35019c8d) #10 0x7b8f8fc931b3 (/usr/lib/libc.so.6+0x931b3) (BuildId: 2f722da304c0a508c891285e6840199c35019c8d) #11 0x7b8f8fd0da2d (/usr/lib/libc.so.6+0x10da2d) (BuildId: 2f722da304c0a508c891285e6840199c35019c8d) #12 0x5d96f01070fc (/usr/local/sbin/sshd+0xc30fc) #13 0x5d96f0107272 (/usr/local/sbin/sshd+0xc3272) #14 0x5d96f01248dd (/usr/local/sbin/sshd+0xe08dd) #15 0x5d96f00871dc (/usr/local/sbin/sshd+0x431dc) #16 0x7b8f8fc27634 (/usr/lib/libc.so.6+0x27634) (BuildId: 2f722da304c0a508c891285e6840199c35019c8d) #17 0x7b8f8fc276e8 (/usr/lib/libc.so.6+0x276e8) (BuildId: 2f722da304c0a508c891285e6840199c35019c8d) #18 0x5d96f005d114 (/usr/local/sbin/sshd+0x19114) ``` ## Future Directions I'd be happy to make large changes to this PR to help it better fit the LLVM style and structure. This whole thing might belong in UBSan instead of being its own separate part of compiler-rt. It would be interesting to try to statically classify functions in IR as async-signal-safe or unsafe and insert the counter checks in a pass, but for now I went with a simple `INTERCEPTOR`-based scheme because it's easy and doesn't require recompiling the world :) >From 92afc2af6c11b89425c038a2e722cbd496fb80d6 Mon Sep 17 00:00:00 2001 From: Ben Kallus <[email protected]> Date: Thu, 11 Dec 2025 23:17:53 -0500 Subject: [PATCH 1/4] Add SigSan --- clang/include/clang/Basic/Sanitizers.def | 2 + clang/include/clang/Driver/SanitizerArgs.h | 1 + clang/lib/Driver/ToolChains/CommonArgs.cpp | 7 + clang/lib/Driver/ToolChains/Linux.cpp | 1 + compiler-rt/lib/CMakeLists.txt | 1 + compiler-rt/lib/sigsan/CMakeLists.txt | 18 ++ compiler-rt/lib/sigsan/sigsan.cpp | 200 +++++++++++++++++++++ 7 files changed, 230 insertions(+) create mode 100644 compiler-rt/lib/sigsan/CMakeLists.txt create mode 100644 compiler-rt/lib/sigsan/sigsan.cpp diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def index da85431625026..9594e0166c780 100644 --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -88,6 +88,8 @@ SANITIZER("realtime", Realtime) // LeakSanitizer SANITIZER("leak", Leak) +SANITIZER("signal", Signal) + // UndefinedBehaviorSanitizer SANITIZER("alignment", Alignment) SANITIZER("array-bounds", ArrayBounds) diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index eea7897e96afd..7ae1f202eb41d 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -83,6 +83,7 @@ class SanitizerArgs { SanitizerArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, bool DiagnoseErrors = true); + bool needsSigsanRt() const { return Sanitizers.has(SanitizerKind::Signal); } bool needsSharedRt() const { return SharedRuntime; } bool needsStableAbi() const { return StableABI; } diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp index ec8dcdc81db56..605a4a32aa276 100644 --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -1574,6 +1574,9 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, const SanitizerArgs &SanArgs = TC.getSanitizerArgs(Args); // Collect shared runtimes. if (SanArgs.needsSharedRt()) { + if (SanArgs.needsSigsanRt()) { + SharedRuntimes.push_back("sigsan"); + } if (SanArgs.needsAsanRt()) { SharedRuntimes.push_back("asan"); if (!Args.hasArg(options::OPT_shared) && !TC.getTriple().isAndroid()) @@ -1628,6 +1631,10 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, // Each static runtime that has a DSO counterpart above is excluded below, // but runtimes that exist only as static are not affected by needsSharedRt. + if (!SanArgs.needsSharedRt() && SanArgs.needsSigsanRt()) { + StaticRuntimes.push_back("sigsan"); + } + if (!SanArgs.needsSharedRt() && SanArgs.needsAsanRt()) { StaticRuntimes.push_back("asan"); if (SanArgs.linkCXXRuntimes()) diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp index 94a9fe8b1a63f..745a005aed286 100644 --- a/clang/lib/Driver/ToolChains/Linux.cpp +++ b/clang/lib/Driver/ToolChains/Linux.cpp @@ -914,6 +914,7 @@ SanitizerMask Linux::getSupportedSanitizers() const { Res |= SanitizerKind::KernelAddress; Res |= SanitizerKind::Vptr; Res |= SanitizerKind::SafeStack; + Res |= SanitizerKind::Signal; if (IsX86_64 || IsMIPS64 || IsAArch64 || IsLoongArch64) Res |= SanitizerKind::DataFlow; if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 || diff --git a/compiler-rt/lib/CMakeLists.txt b/compiler-rt/lib/CMakeLists.txt index e6158ec408895..e9960bb237fe1 100644 --- a/compiler-rt/lib/CMakeLists.txt +++ b/compiler-rt/lib/CMakeLists.txt @@ -42,6 +42,7 @@ if(COMPILER_RT_BUILD_SANITIZERS) add_subdirectory(lsan) # Contains RTUbsan used even without COMPILER_RT_HAS_UBSAN. add_subdirectory(ubsan) + add_subdirectory(sigsan) endif() foreach(sanitizer ${COMPILER_RT_SANITIZERS_TO_BUILD}) diff --git a/compiler-rt/lib/sigsan/CMakeLists.txt b/compiler-rt/lib/sigsan/CMakeLists.txt new file mode 100644 index 0000000000000..1168e0fcc9973 --- /dev/null +++ b/compiler-rt/lib/sigsan/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories(..) + +# Runtime library sources and build flags. +set(SIGSAN_SOURCES + sigsan.cpp +) + +add_compiler_rt_component(sigsan) +add_compiler_rt_runtime(clang_rt.sigsan + STATIC + ARCHS x86_64 + SOURCES ${SIGSAN_SOURCES} + OBJECT_LIBS RTInterception + RTSanitizerCommon + RTSanitizerCommonLibc + RTSanitizerCommonSymbolizer + PARENT_TARGET sigsan +) diff --git a/compiler-rt/lib/sigsan/sigsan.cpp b/compiler-rt/lib/sigsan/sigsan.cpp new file mode 100644 index 0000000000000..34099dcdbd405 --- /dev/null +++ b/compiler-rt/lib/sigsan/sigsan.cpp @@ -0,0 +1,200 @@ +#include "interception/interception.h" // for INTERCEPTOR, INTERCEPT_FUNCTION, REAL +#include "sanitizer_common/sanitizer_stacktrace.h" // for GET_CURRENT_PC_BP_SP, BufferedStackTrace +#include "sanitizer_common/sanitizer_report_decorator.h" // for SanitizerCommonDecorator +#include "sanitizer_common/sanitizer_common.h" // for Printf, Die +// TODO: figure out the include for Report + +#include <signal.h> // for siginfo_t, NSIG, struct sigaction, SIG_IGN, SIG_DFL +#include <cstdint> // for uintptr_t +#include <cstdarg> // for va_list, va_start, va_end +#include <stdio.h> // for FILE + +using namespace __sanitizer; + +using signal_handler = void (*)(int); +using extended_signal_handler = void (*)(int, siginfo_t *, void *); + +thread_local unsigned int __sigsan_signal_depth = 0; + +// TODO: handle sigvec and the other deprecated sighandler installation functions + +// TODO: Make these atomic so concurrent calls to signal for the same signum don't cause problems +static uintptr_t __sigsan_handlers[NSIG]; + +void __sigsan_handler(int signum) { + __sigsan_signal_depth++; + ((signal_handler)(__sigsan_handlers[signum]))(signum); + __sigsan_signal_depth--; +} + +void __sigsan_extended_handler(int signum, siginfo_t *si, void *arg) { + __sigsan_signal_depth++; + ((extended_signal_handler)(__sigsan_handlers[signum]))(signum, si, arg); + __sigsan_signal_depth--; +} + +INTERCEPTOR(signal_handler, signal, int signum, signal_handler handler) { + // Adapted from llvm-project/libc/src/signal/linux/signal.cpp + struct sigaction action, old; + action.sa_handler = handler; + action.sa_flags = SA_RESTART; + return sigaction(signum, &action, &old) < 0 ? SIG_ERR : old.sa_handler; +} + +INTERCEPTOR(int, sigaction, int sig, struct sigaction const *__restrict act, struct sigaction *oldact) { + auto old_handler = __sigsan_handlers[sig]; + + int result; + if (!act || act->sa_handler == SIG_IGN || act->sa_handler == SIG_DFL) { + result = REAL(sigaction)(sig, act, oldact); + } else { + // Pass in act, but replace the sa_handler with our middleman + struct sigaction act_copy = *act; + act_copy.sa_handler = act_copy.sa_flags & SA_SIGINFO ? (signal_handler)(uintptr_t)__sigsan_extended_handler : __sigsan_handler; + result = REAL(sigaction)(sig, &act_copy, oldact); + } + + if (result == 0) { + if (act) { + // TODO: Fix race condition. + // (sig gets delievered right here, causing old sighandler to be called) + __sigsan_handlers[sig] = (uintptr_t)act->sa_handler; + } + + if (oldact) { + // TODO: figure out if oldact gets written to even when result != 0 + + // Stick in the handler from __sigsan_handlers, so the caller isn't aware of our trickery :) + oldact->sa_handler = (signal_handler)old_handler; + } + } + + return result; +} + +void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context, bool request_fast, u32 max_depth) { + uptr top = 0; + uptr bottom = 0; + GetThreadStackTopAndBottom(false, &top, &bottom); + Unwind(max_depth, pc, bp, context, top, bottom, request_fast); +} + +#define SIGSAN_INTERCEPTOR(ret_type, func, args, ...) \ + INTERCEPTOR(ret_type, func, ##__VA_ARGS__) { \ + if (__sigsan_signal_depth > 0) { \ + __sigsan_signal_depth = 0; /* To avoid having to make this function async-signal-safe :) */ \ + SanitizerCommonDecorator d; \ + Printf("%s", d.Warning()); \ + Report("ERROR: SignalSanitizer: Async-signal unsafe function " #func " called from a signal handler.\n"); \ + Printf("%s", d.Default()); \ + BufferedStackTrace stack; \ + GET_CURRENT_PC_BP_SP; \ + (void)sp; \ + stack.Unwind(pc, bp, nullptr, false); \ + stack.Print(); \ + Die(); \ + } \ + return REAL(func)args; \ + } + +SIGSAN_INTERCEPTOR(void *, malloc, (size), size_t size) +SIGSAN_INTERCEPTOR(void *, calloc, (n, size), size_t n, size_t size) +SIGSAN_INTERCEPTOR(void *, realloc, (p, size), void *p, size_t size) +SIGSAN_INTERCEPTOR(void *, reallocarray, (p, n, size), void *p, size_t n, size_t size) +SIGSAN_INTERCEPTOR(void, free, (p), void *p) + +SIGSAN_INTERCEPTOR(int, fputc, (c, stream), int c, FILE *stream) +SIGSAN_INTERCEPTOR(int, putc, (c, stream), int c, FILE *stream) +SIGSAN_INTERCEPTOR(int, putchar, (c), int c); +SIGSAN_INTERCEPTOR(int, fputs, (s, stream), const char *s, FILE *stream) +SIGSAN_INTERCEPTOR(int, puts, (s), const char *s) + +SIGSAN_INTERCEPTOR(int, vprintf, (format, ap), const char *format, va_list ap) +SIGSAN_INTERCEPTOR(int, vfprintf, (stream, format, ap), FILE *stream, const char *format, va_list ap) +SIGSAN_INTERCEPTOR(int, vdprintf, (fd, format, ap), int fd, const char *format, va_list ap) +SIGSAN_INTERCEPTOR(int, vsprintf, (str, format, ap), char *str, const char *format, va_list ap) +SIGSAN_INTERCEPTOR(int, vsnprintf, (str, size, format, ap), char *str, size_t size, const char *format, va_list ap) + +INTERCEPTOR(int, printf, const char *format, ...) { + va_list ap; + va_start(ap, format); + auto result = REAL(vprintf)(format, ap); + va_end(ap); + return result; +} +INTERCEPTOR(int, fprintf, FILE *stream, const char *format, ...) { + va_list ap; + va_start(ap, format); + auto result = REAL(vfprintf)(stream, format, ap); + va_end(ap); + return result; +} +INTERCEPTOR(int, dprintf, int fd, const char *format, ...) { + va_list ap; + va_start(ap, format); + auto result = REAL(vdprintf)(fd, format, ap); + va_end(ap); + return result; +} +INTERCEPTOR(int, sprintf, char *str, const char *format, ...) { + va_list ap; + va_start(ap, format); + auto result = REAL(vsprintf)(str, format, ap); + va_end(ap); + return result; +} +INTERCEPTOR(int, snprintf, char *str, size_t size, const char *format, ...) { + va_list ap; + va_start(ap, format); + auto result = REAL(vsnprintf)(str, size, format, ap); + va_end(ap); + return result; +} + +__attribute__((constructor)) void __sigsan_init() { + SetCommonFlagsDefaults(); + InitializeCommonFlags(); + + INTERCEPT_FUNCTION(signal); + INTERCEPT_FUNCTION(sigaction); + + INTERCEPT_FUNCTION(malloc); + INTERCEPT_FUNCTION(calloc); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(reallocarray); + INTERCEPT_FUNCTION(free); + INTERCEPT_FUNCTION(fputc); + INTERCEPT_FUNCTION(putc); + INTERCEPT_FUNCTION(putchar); + INTERCEPT_FUNCTION(fputs); + INTERCEPT_FUNCTION(puts); + + INTERCEPT_FUNCTION(vprintf); + INTERCEPT_FUNCTION(vfprintf); + INTERCEPT_FUNCTION(vdprintf); + INTERCEPT_FUNCTION(vsprintf); + INTERCEPT_FUNCTION(vsnprintf); + + INTERCEPT_FUNCTION(printf); + INTERCEPT_FUNCTION(fprintf); + INTERCEPT_FUNCTION(dprintf); + INTERCEPT_FUNCTION(sprintf); + INTERCEPT_FUNCTION(snprintf); + + // TODO: Fix race conditions. + // (signal/sigaction called while this loop is running) + for (int i = 0; i < NSIG; i++) { + struct sigaction existing_sa; + if (REAL(sigaction)(i, NULL, &existing_sa) == 0) { + auto const existing_handler = existing_sa.sa_handler; + if (existing_handler != SIG_IGN && existing_handler != SIG_DFL) { + __sigsan_handlers[i] = (uintptr_t)existing_handler; + if (existing_sa.sa_flags & SA_SIGINFO) { + existing_sa.sa_handler = __sigsan_handler; + } else { + existing_sa.sa_handler = (signal_handler)(uintptr_t)__sigsan_extended_handler; + } + } + } + } +} >From 8d4d9f9bd7cebb3e24da28b5aed52a4f8e3bf6c8 Mon Sep 17 00:00:00 2001 From: Ben Kallus <[email protected]> Date: Fri, 12 Dec 2025 09:25:33 -0500 Subject: [PATCH 2/4] format and add a few more functions --- compiler-rt/lib/sigsan/sigsan.cpp | 339 ++++++++++++++++++------------ 1 file changed, 208 insertions(+), 131 deletions(-) diff --git a/compiler-rt/lib/sigsan/sigsan.cpp b/compiler-rt/lib/sigsan/sigsan.cpp index 34099dcdbd405..b48583740e2b2 100644 --- a/compiler-rt/lib/sigsan/sigsan.cpp +++ b/compiler-rt/lib/sigsan/sigsan.cpp @@ -1,13 +1,14 @@ #include "interception/interception.h" // for INTERCEPTOR, INTERCEPT_FUNCTION, REAL -#include "sanitizer_common/sanitizer_stacktrace.h" // for GET_CURRENT_PC_BP_SP, BufferedStackTrace +#include "sanitizer_common/sanitizer_common.h" // for Printf, Die #include "sanitizer_common/sanitizer_report_decorator.h" // for SanitizerCommonDecorator -#include "sanitizer_common/sanitizer_common.h" // for Printf, Die +#include "sanitizer_common/sanitizer_stacktrace.h" // for GET_CURRENT_PC_BP_SP, BufferedStackTrace // TODO: figure out the include for Report +#include <cstdarg> // for va_list, va_start, va_end +#include <cstdint> // for uintptr_t +#include <errno.h> // for errno #include <signal.h> // for siginfo_t, NSIG, struct sigaction, SIG_IGN, SIG_DFL -#include <cstdint> // for uintptr_t -#include <cstdarg> // for va_list, va_start, va_end -#include <stdio.h> // for FILE +#include <stdio.h> // for FILE using namespace __sanitizer; @@ -16,21 +17,63 @@ using extended_signal_handler = void (*)(int, siginfo_t *, void *); thread_local unsigned int __sigsan_signal_depth = 0; -// TODO: handle sigvec and the other deprecated sighandler installation functions +// TODO: handle sigvec and the other deprecated sighandler installation +// functions -// TODO: Make these atomic so concurrent calls to signal for the same signum don't cause problems +// TODO: Make these atomic so concurrent calls to signal for the same signum +// don't cause problems static uintptr_t __sigsan_handlers[NSIG]; +[[noreturn]] void __sigsan_print_backtrace_and_die() { + BufferedStackTrace stack; + GET_CURRENT_PC_BP_SP; + (void)sp; + stack.Unwind(pc, bp, nullptr, false); + stack.Print(); + Die(); +} + +[[noreturn]] void +__sigsan_die_from_unsafe_function_call(char const *func_name) { + __sigsan_signal_depth = + 0; /* To avoid having to make this function async-signal-safe :) */ + SanitizerCommonDecorator d; + Printf("%s", d.Warning()); + Report("ERROR: SignalSanitizer: async-signal-unsafe function %s called from " + "a signal handler.\n", + func_name); + Printf("%s", d.Default()); + __sigsan_print_backtrace_and_die(); +} + +[[noreturn]] void __sigsan_die_from_modified_errno() { + __sigsan_signal_depth = + 0; /* To avoid having to make this function async-signal-safe :) */ + SanitizerCommonDecorator d; + Printf("%s", d.Warning()); + Report("ERROR: SignalSanitizer: errno modified from a signal handler.\n"); + Printf("%s", d.Default()); + __sigsan_print_backtrace_and_die(); +} + void __sigsan_handler(int signum) { __sigsan_signal_depth++; + int saved_errno = errno; ((signal_handler)(__sigsan_handlers[signum]))(signum); + if (errno != saved_errno) { + __sigsan_die_from_modified_errno(); + } __sigsan_signal_depth--; } void __sigsan_extended_handler(int signum, siginfo_t *si, void *arg) { - __sigsan_signal_depth++; - ((extended_signal_handler)(__sigsan_handlers[signum]))(signum, si, arg); - __sigsan_signal_depth--; + __sigsan_signal_depth++; + int saved_errno = errno; + ((extended_signal_handler)(__sigsan_handlers[signum]))(signum, si, arg); + if (errno != saved_errno) { + __sigsan_die_from_modified_errno(); + } + __sigsan_signal_depth--; } INTERCEPTOR(signal_handler, signal, int signum, signal_handler handler) { @@ -41,160 +84,194 @@ INTERCEPTOR(signal_handler, signal, int signum, signal_handler handler) { return sigaction(signum, &action, &old) < 0 ? SIG_ERR : old.sa_handler; } -INTERCEPTOR(int, sigaction, int sig, struct sigaction const *__restrict act, struct sigaction *oldact) { - auto old_handler = __sigsan_handlers[sig]; - - int result; - if (!act || act->sa_handler == SIG_IGN || act->sa_handler == SIG_DFL) { - result = REAL(sigaction)(sig, act, oldact); - } else { - // Pass in act, but replace the sa_handler with our middleman - struct sigaction act_copy = *act; - act_copy.sa_handler = act_copy.sa_flags & SA_SIGINFO ? (signal_handler)(uintptr_t)__sigsan_extended_handler : __sigsan_handler; - result = REAL(sigaction)(sig, &act_copy, oldact); - } +INTERCEPTOR(int, sigaction, int sig, struct sigaction const *__restrict act, + struct sigaction *oldact) { + auto old_handler = __sigsan_handlers[sig]; - if (result == 0) { - if (act) { - // TODO: Fix race condition. - // (sig gets delievered right here, causing old sighandler to be called) - __sigsan_handlers[sig] = (uintptr_t)act->sa_handler; - } + int result; + if (!act || act->sa_handler == SIG_IGN || act->sa_handler == SIG_DFL) { + result = REAL(sigaction)(sig, act, oldact); + } else { + // Pass in act, but replace the sa_handler with our middleman + struct sigaction act_copy = *act; + act_copy.sa_handler = + act_copy.sa_flags & SA_SIGINFO + ? (signal_handler)(uintptr_t)__sigsan_extended_handler + : __sigsan_handler; + result = REAL(sigaction)(sig, &act_copy, oldact); + } - if (oldact) { - // TODO: figure out if oldact gets written to even when result != 0 + if (result == 0) { + if (act) { + // TODO: Fix race condition. + // (sig gets delievered right here, causing old sighandler to be called) + __sigsan_handlers[sig] = (uintptr_t)act->sa_handler; + } - // Stick in the handler from __sigsan_handlers, so the caller isn't aware of our trickery :) - oldact->sa_handler = (signal_handler)old_handler; - } + if (oldact) { + // TODO: figure out if oldact gets written to even when result != 0 + + // Stick in the handler from __sigsan_handlers, so the caller isn't aware + // of our trickery :) + oldact->sa_handler = (signal_handler)old_handler; } + } - return result; + return result; } -void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context, bool request_fast, u32 max_depth) { - uptr top = 0; - uptr bottom = 0; - GetThreadStackTopAndBottom(false, &top, &bottom); - Unwind(max_depth, pc, bp, context, top, bottom, request_fast); +void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, + void *context, + bool request_fast, + u32 max_depth) { + uptr top = 0; + uptr bottom = 0; + GetThreadStackTopAndBottom(false, &top, &bottom); + Unwind(max_depth, pc, bp, context, top, bottom, request_fast); } -#define SIGSAN_INTERCEPTOR(ret_type, func, args, ...) \ - INTERCEPTOR(ret_type, func, ##__VA_ARGS__) { \ - if (__sigsan_signal_depth > 0) { \ - __sigsan_signal_depth = 0; /* To avoid having to make this function async-signal-safe :) */ \ - SanitizerCommonDecorator d; \ - Printf("%s", d.Warning()); \ - Report("ERROR: SignalSanitizer: Async-signal unsafe function " #func " called from a signal handler.\n"); \ - Printf("%s", d.Default()); \ - BufferedStackTrace stack; \ - GET_CURRENT_PC_BP_SP; \ - (void)sp; \ - stack.Unwind(pc, bp, nullptr, false); \ - stack.Print(); \ - Die(); \ - } \ - return REAL(func)args; \ - } +#define SIGSAN_INTERCEPTOR(ret_type, func, args, ...) \ + INTERCEPTOR(ret_type, func, ##__VA_ARGS__) { \ + if (__sigsan_signal_depth > 0) { \ + __sigsan_die_from_unsafe_function_call(#func); \ + } \ + return REAL(func) args; \ + } +// malloc SIGSAN_INTERCEPTOR(void *, malloc, (size), size_t size) SIGSAN_INTERCEPTOR(void *, calloc, (n, size), size_t n, size_t size) SIGSAN_INTERCEPTOR(void *, realloc, (p, size), void *p, size_t size) -SIGSAN_INTERCEPTOR(void *, reallocarray, (p, n, size), void *p, size_t n, size_t size) +SIGSAN_INTERCEPTOR(void *, reallocarray, (p, n, size), void *p, size_t n, + size_t size) SIGSAN_INTERCEPTOR(void, free, (p), void *p) +// stdio SIGSAN_INTERCEPTOR(int, fputc, (c, stream), int c, FILE *stream) SIGSAN_INTERCEPTOR(int, putc, (c, stream), int c, FILE *stream) SIGSAN_INTERCEPTOR(int, putchar, (c), int c); SIGSAN_INTERCEPTOR(int, fputs, (s, stream), const char *s, FILE *stream) SIGSAN_INTERCEPTOR(int, puts, (s), const char *s) - +SIGSAN_INTERCEPTOR(int, fflush, (stream), FILE *stream) SIGSAN_INTERCEPTOR(int, vprintf, (format, ap), const char *format, va_list ap) -SIGSAN_INTERCEPTOR(int, vfprintf, (stream, format, ap), FILE *stream, const char *format, va_list ap) -SIGSAN_INTERCEPTOR(int, vdprintf, (fd, format, ap), int fd, const char *format, va_list ap) -SIGSAN_INTERCEPTOR(int, vsprintf, (str, format, ap), char *str, const char *format, va_list ap) -SIGSAN_INTERCEPTOR(int, vsnprintf, (str, size, format, ap), char *str, size_t size, const char *format, va_list ap) - +SIGSAN_INTERCEPTOR(int, vfprintf, (stream, format, ap), FILE *stream, + const char *format, va_list ap) +SIGSAN_INTERCEPTOR(int, vdprintf, (fd, format, ap), int fd, const char *format, + va_list ap) +SIGSAN_INTERCEPTOR(int, vsprintf, (str, format, ap), char *str, + const char *format, va_list ap) +SIGSAN_INTERCEPTOR(int, vsnprintf, (str, size, format, ap), char *str, + size_t size, const char *format, va_list ap) INTERCEPTOR(int, printf, const char *format, ...) { - va_list ap; - va_start(ap, format); - auto result = REAL(vprintf)(format, ap); - va_end(ap); - return result; + va_list ap; + va_start(ap, format); + auto const result = REAL(vprintf)(format, ap); + va_end(ap); + return result; } INTERCEPTOR(int, fprintf, FILE *stream, const char *format, ...) { - va_list ap; - va_start(ap, format); - auto result = REAL(vfprintf)(stream, format, ap); - va_end(ap); - return result; + va_list ap; + va_start(ap, format); + auto const result = REAL(vfprintf)(stream, format, ap); + va_end(ap); + return result; } INTERCEPTOR(int, dprintf, int fd, const char *format, ...) { - va_list ap; - va_start(ap, format); - auto result = REAL(vdprintf)(fd, format, ap); - va_end(ap); - return result; + va_list ap; + va_start(ap, format); + auto const result = REAL(vdprintf)(fd, format, ap); + va_end(ap); + return result; } INTERCEPTOR(int, sprintf, char *str, const char *format, ...) { - va_list ap; - va_start(ap, format); - auto result = REAL(vsprintf)(str, format, ap); - va_end(ap); - return result; + va_list ap; + va_start(ap, format); + auto const result = REAL(vsprintf)(str, format, ap); + va_end(ap); + return result; } INTERCEPTOR(int, snprintf, char *str, size_t size, const char *format, ...) { - va_list ap; - va_start(ap, format); - auto result = REAL(vsnprintf)(str, size, format, ap); - va_end(ap); - return result; + va_list ap; + va_start(ap, format); + auto const result = REAL(vsnprintf)(str, size, format, ap); + va_end(ap); + return result; +} +SIGSAN_INTERCEPTOR(FILE *, fopen, (path, mode), const char *path, + const char *mode) +SIGSAN_INTERCEPTOR(FILE *, fdopen, (fd, mode), int fd, const char *mode) +SIGSAN_INTERCEPTOR(FILE *, freopen, (path, mode, stream), const char *path, + const char *mode, FILE *stream) + +// syslog +SIGSAN_INTERCEPTOR(void, openlog, (ident, option, facility), const char *ident, + int option, int facility) +SIGSAN_INTERCEPTOR(void, closelog, ()) +SIGSAN_INTERCEPTOR(void, vsyslog, (priority, format, ap), int priority, + const char *format, va_list ap) +INTERCEPTOR(void, syslog, int priority, const char *format, ...) { + va_list ap; + va_start(ap, format); + REAL(vsyslog)(priority, format, ap); + va_end(ap); } __attribute__((constructor)) void __sigsan_init() { - SetCommonFlagsDefaults(); - InitializeCommonFlags(); - - INTERCEPT_FUNCTION(signal); - INTERCEPT_FUNCTION(sigaction); - - INTERCEPT_FUNCTION(malloc); - INTERCEPT_FUNCTION(calloc); - INTERCEPT_FUNCTION(realloc); - INTERCEPT_FUNCTION(reallocarray); - INTERCEPT_FUNCTION(free); - INTERCEPT_FUNCTION(fputc); - INTERCEPT_FUNCTION(putc); - INTERCEPT_FUNCTION(putchar); - INTERCEPT_FUNCTION(fputs); - INTERCEPT_FUNCTION(puts); - - INTERCEPT_FUNCTION(vprintf); - INTERCEPT_FUNCTION(vfprintf); - INTERCEPT_FUNCTION(vdprintf); - INTERCEPT_FUNCTION(vsprintf); - INTERCEPT_FUNCTION(vsnprintf); - - INTERCEPT_FUNCTION(printf); - INTERCEPT_FUNCTION(fprintf); - INTERCEPT_FUNCTION(dprintf); - INTERCEPT_FUNCTION(sprintf); - INTERCEPT_FUNCTION(snprintf); - - // TODO: Fix race conditions. - // (signal/sigaction called while this loop is running) - for (int i = 0; i < NSIG; i++) { - struct sigaction existing_sa; - if (REAL(sigaction)(i, NULL, &existing_sa) == 0) { - auto const existing_handler = existing_sa.sa_handler; - if (existing_handler != SIG_IGN && existing_handler != SIG_DFL) { - __sigsan_handlers[i] = (uintptr_t)existing_handler; - if (existing_sa.sa_flags & SA_SIGINFO) { - existing_sa.sa_handler = __sigsan_handler; - } else { - existing_sa.sa_handler = (signal_handler)(uintptr_t)__sigsan_extended_handler; - } - } + SetCommonFlagsDefaults(); + InitializeCommonFlags(); + + INTERCEPT_FUNCTION(signal); + INTERCEPT_FUNCTION(sigaction); + + // malloc + INTERCEPT_FUNCTION(malloc); + INTERCEPT_FUNCTION(calloc); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(reallocarray); + INTERCEPT_FUNCTION(free); + + // stdio + INTERCEPT_FUNCTION(fputc); + INTERCEPT_FUNCTION(putc); + INTERCEPT_FUNCTION(putchar); + INTERCEPT_FUNCTION(fputs); + INTERCEPT_FUNCTION(puts); + INTERCEPT_FUNCTION(fflush); + INTERCEPT_FUNCTION(vprintf); + INTERCEPT_FUNCTION(vfprintf); + INTERCEPT_FUNCTION(vdprintf); + INTERCEPT_FUNCTION(vsprintf); + INTERCEPT_FUNCTION(vsnprintf); + INTERCEPT_FUNCTION(printf); + INTERCEPT_FUNCTION(fprintf); + INTERCEPT_FUNCTION(dprintf); + INTERCEPT_FUNCTION(sprintf); + INTERCEPT_FUNCTION(snprintf); + INTERCEPT_FUNCTION(fopen); + INTERCEPT_FUNCTION(fdopen); + INTERCEPT_FUNCTION(freopen); + + // syslog + INTERCEPT_FUNCTION(openlog); + INTERCEPT_FUNCTION(closelog); + INTERCEPT_FUNCTION(vsyslog); + INTERCEPT_FUNCTION(syslog); + + // TODO: Fix race conditions. + // (signal/sigaction called while this loop is running) + for (int i = 0; i < NSIG; i++) { + struct sigaction existing_sa; + if (REAL(sigaction)(i, NULL, &existing_sa) == 0) { + auto const existing_handler = existing_sa.sa_handler; + if (existing_handler != SIG_IGN && existing_handler != SIG_DFL) { + __sigsan_handlers[i] = (uintptr_t)existing_handler; + if (existing_sa.sa_flags & SA_SIGINFO) { + existing_sa.sa_handler = __sigsan_handler; + } else { + existing_sa.sa_handler = + (signal_handler)(uintptr_t)__sigsan_extended_handler; } + } } + } } >From c5db568b858ea325042238942a322c7e7b7c4a03 Mon Sep 17 00:00:00 2001 From: Ben Kallus <[email protected]> Date: Fri, 12 Dec 2025 09:32:35 -0500 Subject: [PATCH 3/4] add pthread_mutex_whatever --- compiler-rt/lib/sigsan/sigsan.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/compiler-rt/lib/sigsan/sigsan.cpp b/compiler-rt/lib/sigsan/sigsan.cpp index b48583740e2b2..39d4bd44e8ea7 100644 --- a/compiler-rt/lib/sigsan/sigsan.cpp +++ b/compiler-rt/lib/sigsan/sigsan.cpp @@ -216,6 +216,12 @@ INTERCEPTOR(void, syslog, int priority, const char *format, ...) { va_end(ap); } +SIGSAN_INTERCEPTOR(int, pthread_mutex_init, (mutex, mutexattr), pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) +SIGSAN_INTERCEPTOR(int, pthread_mutex_lock, (mutex), pthread_mutex_t *mutex) +SIGSAN_INTERCEPTOR(int, pthread_mutex_trylock, (mutex), pthread_mutex_t *mutex) +SIGSAN_INTERCEPTOR(int, pthread_mutex_unlock, (mutex), pthread_mutex_t *mutex) +SIGSAN_INTERCEPTOR(int, pthread_mutex_destroy, (mutex), pthread_mutex_t *mutex) + __attribute__((constructor)) void __sigsan_init() { SetCommonFlagsDefaults(); InitializeCommonFlags(); @@ -257,6 +263,13 @@ __attribute__((constructor)) void __sigsan_init() { INTERCEPT_FUNCTION(vsyslog); INTERCEPT_FUNCTION(syslog); + // pthreads + INTERCEPT_FUNCTION(pthread_mutex_init); + INTERCEPT_FUNCTION(pthread_mutex_lock); + INTERCEPT_FUNCTION(pthread_mutex_trylock); + INTERCEPT_FUNCTION(pthread_mutex_unlock); + INTERCEPT_FUNCTION(pthread_mutex_destroy); + // TODO: Fix race conditions. // (signal/sigaction called while this loop is running) for (int i = 0; i < NSIG; i++) { >From 174017af9ab3ce563f402b71d244f6c09d30194a Mon Sep 17 00:00:00 2001 From: Ben Kallus <[email protected]> Date: Fri, 12 Dec 2025 16:29:00 -0500 Subject: [PATCH 4/4] Add vasprintf and saprintf checks --- compiler-rt/lib/sigsan/CMakeLists.txt | 2 +- compiler-rt/lib/sigsan/sigsan.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler-rt/lib/sigsan/CMakeLists.txt b/compiler-rt/lib/sigsan/CMakeLists.txt index 1168e0fcc9973..9c446ae5227db 100644 --- a/compiler-rt/lib/sigsan/CMakeLists.txt +++ b/compiler-rt/lib/sigsan/CMakeLists.txt @@ -1,10 +1,10 @@ include_directories(..) -# Runtime library sources and build flags. set(SIGSAN_SOURCES sigsan.cpp ) +# TODO: support other architectures add_compiler_rt_component(sigsan) add_compiler_rt_runtime(clang_rt.sigsan STATIC diff --git a/compiler-rt/lib/sigsan/sigsan.cpp b/compiler-rt/lib/sigsan/sigsan.cpp index 39d4bd44e8ea7..f14ed9357636c 100644 --- a/compiler-rt/lib/sigsan/sigsan.cpp +++ b/compiler-rt/lib/sigsan/sigsan.cpp @@ -203,6 +203,16 @@ SIGSAN_INTERCEPTOR(FILE *, fdopen, (fd, mode), int fd, const char *mode) SIGSAN_INTERCEPTOR(FILE *, freopen, (path, mode, stream), const char *path, const char *mode, FILE *stream) +// GNU stdio +SIGSAN_INTERCEPTOR(int, vasprintf, (strp, fmt, ap), char **strp, const char *fmt, va_list ap) +INTERCEPTOR(int, asprintf, char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + auto const result = REAL(vasprintf)(strp, fmt, ap); + va_end(ap); + return result; +} + // syslog SIGSAN_INTERCEPTOR(void, openlog, (ident, option, facility), const char *ident, int option, int facility) @@ -257,6 +267,10 @@ __attribute__((constructor)) void __sigsan_init() { INTERCEPT_FUNCTION(fdopen); INTERCEPT_FUNCTION(freopen); + // GNU stdio + INTERCEPT_FUNCTION(vasprintf); + INTERCEPT_FUNCTION(asprintf); + // syslog INTERCEPT_FUNCTION(openlog); INTERCEPT_FUNCTION(closelog); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
