From 208331f31740568fddc881190981e36e6338ba18 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Tue, 2 Jun 2026 12:17:06 -0400 Subject: [PATCH] deps: uv: restore prior signal disposition instead of SIG_DFL (DCP-4748) libuv's uv__signal_unregister_handler() reset the disposition of a signal to SIG_DFL once its last watcher was removed. On Android this is intercepted by ART's libsigchain, which logs an ERROR-level stack trace ("Setting SIGSEGV to SIG_DFL ...") and can destabilise ART's signal handling, because ART claims SIGSEGV/SIGBUS/SIGFPE/SIGILL/SIGTRAP/SIGABRT for implicit null checks, stack overflow detection, etc. On an embedded worker this fires on every node::FreeEnvironment() during CleanupHandles -> uv_close on a UV_SIGNAL handle (DCP-4748). Resolve libuv's own long-standing "XXX save old action so we can restore it later on?" TODO: uv__signal_register_handler() now captures the previous disposition on the genuine none->installed transition (gated by a `save` flag so the oneshot re-registration paths don't clobber it), and uv__signal_unregister_handler() restores that instead of forcing SIG_DFL, falling back to SIG_DFL only when nothing was captured. Platform-agnostic and strictly more correct than a blind reset; on Android it restores ART's handler so no SIG_DFL reset (and no libsigchain report) occurs. Signed-off-by: Philippe Laporte --- deps/uv/src/unix/signal.c | 60 ++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/deps/uv/src/unix/signal.c b/deps/uv/src/unix/signal.c index ccaa72db457c59..4896a2217f867b 100644 --- a/deps/uv/src/unix/signal.c +++ b/deps/uv/src/unix/signal.c @@ -32,6 +32,27 @@ # define SA_RESTART 0 #endif +/* Some libcs don't expose NSIG; 65 covers all standard and realtime signals on + * the platforms libuv supports. An out-of-range signum simply skips the + * save/restore path below and keeps the historical SIG_DFL behaviour. + */ +#ifndef NSIG +# define NSIG 65 +#endif + +/* Remembers the signal disposition that was in effect before libuv first + * installed uv__signal_handler() for a given signum, so that + * uv__signal_unregister_handler() can restore it instead of blindly forcing + * SIG_DFL. Restoring the prior handler is strictly more correct than a reset, + * and on platforms where another runtime owns these signals -- notably + * Android's ART, whose libsigchain logs (and can be destabilised by) any reset + * of a claimed signal to SIG_DFL -- it avoids clobbering that handler. + * Indexed by signum and only touched while the global signal lock is held, so + * it needs no synchronisation of its own. + */ +static struct sigaction uv__signal_saved_sa[NSIG]; +static unsigned char uv__signal_saved[NSIG]; + typedef struct { uv_signal_t* handle; int signum; @@ -221,9 +242,10 @@ static void uv__signal_handler(int signum) { } -static int uv__signal_register_handler(int signum, int oneshot) { +static int uv__signal_register_handler(int signum, int oneshot, int save) { /* When this function is called, the signal lock must be held. */ struct sigaction sa; + struct sigaction* oldp; /* XXX use a separate signal stack? */ memset(&sa, 0, sizeof(sa)); @@ -234,10 +256,23 @@ static int uv__signal_register_handler(int signum, int oneshot) { if (oneshot) sa.sa_flags |= SA_RESETHAND; - /* XXX save old action so we can restore it later on? */ - if (sigaction(signum, &sa, NULL)) + /* Resolve the historical "save old action so we can restore it later on" + * TODO by capturing the previous disposition straight into the per-signum + * slot. `save` is set only on the transition from "no libuv handler" to + * "libuv handler installed" for this signum (see uv__signal_start); the + * oneshot re-registration paths pass save=0 so they never overwrite the + * captured disposition with libuv's own handler. + */ + oldp = NULL; + if (save && signum > 0 && signum < NSIG) + oldp = &uv__signal_saved_sa[signum]; + + if (sigaction(signum, &sa, oldp)) return UV__ERR(errno); + if (oldp != NULL) + uv__signal_saved[signum] = 1; + return 0; } @@ -246,8 +281,19 @@ static void uv__signal_unregister_handler(int signum) { /* When this function is called, the signal lock must be held. */ struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; + /* Restore the disposition captured when uv__signal_register_handler() first + * installed our handler for this signum, if we have it; otherwise fall back + * to the historical SIG_DFL reset. Restoring a non-default handler (e.g. + * ART's on Android) avoids the libsigchain "Setting SIGxxx to SIG_DFL" + * report. + */ + if (signum > 0 && signum < NSIG && uv__signal_saved[signum]) { + sa = uv__signal_saved_sa[signum]; + uv__signal_saved[signum] = 0; + } else { + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + } /* sigaction can only fail with EINVAL or EFAULT; an attempt to deregister a * signal implies that it was successfully registered earlier, so EINVAL @@ -413,7 +459,7 @@ static int uv__signal_start(uv_signal_t* handle, first_handle = uv__signal_first_handle(signum); if (first_handle == NULL || (!oneshot && (first_handle->flags & UV_SIGNAL_ONE_SHOT))) { - err = uv__signal_register_handler(signum, oneshot); + err = uv__signal_register_handler(signum, oneshot, first_handle == NULL); if (err) { /* Registering the signal handler failed. Must be an invalid signal. */ uv__signal_unlock_and_unblock(&saved_sigmask); @@ -568,7 +614,7 @@ static void uv__signal_stop(uv_signal_t* handle) { rem_oneshot = handle->flags & UV_SIGNAL_ONE_SHOT; first_oneshot = first_handle->flags & UV_SIGNAL_ONE_SHOT; if (first_oneshot && !rem_oneshot) { - ret = uv__signal_register_handler(handle->signum, 1); + ret = uv__signal_register_handler(handle->signum, 1, 0); assert(ret == 0); (void)ret; }