diff --git a/libc/signal.cc b/libc/signal.cc index 059505344..788c82a84 100644 --- a/libc/signal.cc +++ b/libc/signal.cc @@ -413,6 +413,15 @@ int kill(pid_t pid, int sig) } unsigned sigidx = sig - 1; if (is_sig_dfl(signal_actions[sigidx])) { + // Per POSIX, the default disposition of SIGCHLD, SIGURG and SIGWINCH is + // to IGNORE (not to terminate the process). OSv treats an unhandled + // SIG_DFL signal as fatal and powers the VM off, which is wrong for + // these three: e.g. delivering SIGCHLD to a process with no handler + // installed must be silently ignored, not fatal. Honor the ignore + // default for them instead of powering off. + if (sig == SIGCHLD || sig == SIGURG || sig == SIGWINCH) { + return 0; + } // Our default is to power off. debugf("Uncaught signal %d (\"%s\"). Powering off.\n", sig, strsignal(sig)); diff --git a/modules/tests/Makefile b/modules/tests/Makefile index ff66fcd93..56ebe97f5 100644 --- a/modules/tests/Makefile +++ b/modules/tests/Makefile @@ -130,6 +130,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-mq-smoke.so tst-bsd-evh. tst-queue-mpsc.so tst-af-local.so tst-pipe.so tst-yield.so \ misc-ctxsw.so tst-read.so tst-symlink.so tst-openat.so \ tst-eventfd.so tst-remove.so misc-wake.so tst-epoll.so misc-lfring.so \ + tst-sig-dfl-ignore.so \ misc-fsx.so tst-sleep.so tst-resolve.so tst-except.so \ misc-tcp-sendonly.so tst-tcp-nbwrite.so misc-tcp-hash-srv.so \ misc-loadbalance.so misc-scheduler.so tst-console.so tst-app.so \ diff --git a/tests/tst-sig-dfl-ignore.cc b/tests/tst-sig-dfl-ignore.cc new file mode 100644 index 000000000..d6eb865da --- /dev/null +++ b/tests/tst-sig-dfl-ignore.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 Greg Burd + * + * This work is open source software, licensed under the terms of the + * BSD license as described in the LICENSE file in the top-level directory. + * + * SIGCHLD, SIGURG and SIGWINCH have a POSIX default disposition of "ignore". + * Delivering one of them to a process that has installed no handler must be a + * no-op, NOT fatal. Before the fix, OSv's kill() treated any SIG_DFL signal as + * uncaught-and-fatal and powered the VM off, so raising SIGCHLD with no handler + * would kill the guest. This test raises each of the three with the default + * disposition and asserts the process simply survives. + */ + +#include +#include +#include +#include +#include + +int main() +{ + printf("=== tst-sig-dfl-ignore ===\n"); + + // No handlers installed => default disposition. Per POSIX these three + // default to ignore, so raise() must return 0 and we must keep running. + for (int sig : {SIGCHLD, SIGURG, SIGWINCH}) { + int r = raise(sig); + printf("raise(%d) -> %d\n", sig, r); + assert(r == 0); + } + + // kill(getpid(), ...) of the same signals must also be a no-op survivor. + for (int sig : {SIGCHLD, SIGURG, SIGWINCH}) { + int r = kill(getpid(), sig); + printf("kill(self, %d) -> %d\n", sig, r); + assert(r == 0); + } + + // If we reached here, the process was not terminated by any of them. + printf("=== tst-sig-dfl-ignore: PASS (survived SIGCHLD/SIGURG/SIGWINCH) ===\n"); + return 0; +}