Skip to content

Commit dcededb

Browse files
committed
deps: uv: restore prior signal disposition on Android (DCP-4748)
On Android, ART claims SIGSEGV/SIGBUS/SIGFPE/SIGILL/SIGTRAP/SIGABRT for implicit null-checks and stack-overflow detection, routed through libsigchain. libuv's uv__signal_unregister_handler() resets a signal to SIG_DFL once its last watcher is removed; on Android that clobbers ART's handler and emits an ERROR-level libsigchain stack trace on every embedded node::FreeEnvironment() (DCP-4748). Gated to __ANDROID__: uv__signal_register_handler() captures the previous disposition on the no-handler -> handler transition and uv__signal_unregister_handler() restores it instead of forcing SIG_DFL. bionic round-trips the sigaction flags correctly, so this is safe; it is deliberately NOT applied to other platforms (notably macOS, which drops SA_RESETHAND in the old action -- see upstream libuv nodejs#4299, which caused PR nodejs#4216 to be reverted). Off Android the code path and behaviour are unchanged. The saved table is accessed only under the global signal lock.
1 parent 812226b commit dcededb

1 file changed

Lines changed: 59 additions & 5 deletions

File tree

deps/uv/src/unix/signal.c

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@
3232
# define SA_RESTART 0
3333
#endif
3434

35+
/* DCP-4748: Android-only. On Android, ART claims SIGSEGV/SIGBUS/SIGFPE/etc. via
36+
* libsigchain; libuv's usual reset-to-SIG_DFL on the last unwatch clobbers that
37+
* handler and emits an ERROR-level libsigchain log on every embedded
38+
* node::FreeEnvironment(). On Android we instead save the disposition that was
39+
* in effect before libuv installed its handler and restore it on the last
40+
* unwatch. This is deliberately gated to __ANDROID__: bionic round-trips the
41+
* sigaction flags correctly (unlike macOS, which drops SA_RESETHAND -- see
42+
* upstream libuv #4299 / reverted #4216), so no other platform's behaviour is
43+
* touched. The table is indexed by signum and accessed only under the global
44+
* signal lock.
45+
*/
46+
#ifdef __ANDROID__
47+
#ifndef NSIG
48+
# define NSIG 65
49+
#endif
50+
static struct sigaction uv__signal_saved_sa[NSIG];
51+
static unsigned char uv__signal_saved[NSIG];
52+
#endif
53+
3554
typedef struct {
3655
uv_signal_t* handle;
3756
int signum;
@@ -221,9 +240,10 @@ static void uv__signal_handler(int signum) {
221240
}
222241

223242

224-
static int uv__signal_register_handler(int signum, int oneshot) {
243+
static int uv__signal_register_handler(int signum, int oneshot, int save) {
225244
/* When this function is called, the signal lock must be held. */
226245
struct sigaction sa;
246+
struct sigaction* oldp;
227247

228248
/* XXX use a separate signal stack? */
229249
memset(&sa, 0, sizeof(sa));
@@ -234,10 +254,29 @@ static int uv__signal_register_handler(int signum, int oneshot) {
234254
if (oneshot)
235255
sa.sa_flags |= SA_RESETHAND;
236256

237-
/* XXX save old action so we can restore it later on? */
238-
if (sigaction(signum, &sa, NULL))
257+
/* On Android, capture the previous disposition so uv__signal_unregister_handler()
258+
* can restore it instead of resetting to SIG_DFL. `save` is set only on the
259+
* transition from "no libuv handler" to "libuv handler installed" for this
260+
* signum (see uv__signal_start); the oneshot re-registration paths pass
261+
* save=0 so they never overwrite the captured disposition with libuv's own
262+
* handler. Off Android this is a no-op and behaviour is unchanged.
263+
*/
264+
oldp = NULL;
265+
#ifdef __ANDROID__
266+
if (save && signum > 0 && signum < NSIG)
267+
oldp = &uv__signal_saved_sa[signum];
268+
#else
269+
(void) save;
270+
#endif
271+
272+
if (sigaction(signum, &sa, oldp))
239273
return UV__ERR(errno);
240274

275+
#ifdef __ANDROID__
276+
if (oldp != NULL)
277+
uv__signal_saved[signum] = 1;
278+
#endif
279+
241280
return 0;
242281
}
243282

@@ -246,8 +285,23 @@ static void uv__signal_unregister_handler(int signum) {
246285
/* When this function is called, the signal lock must be held. */
247286
struct sigaction sa;
248287

288+
/* On Android, restore the disposition captured when uv__signal_register_handler()
289+
* first installed our handler for this signum, if we have it; this avoids the
290+
* libsigchain "Setting SIGxxx to SIG_DFL" report and clobbering ART's handler.
291+
* Off Android, reset to SIG_DFL as upstream does.
292+
*/
293+
#ifdef __ANDROID__
294+
if (signum > 0 && signum < NSIG && uv__signal_saved[signum]) {
295+
sa = uv__signal_saved_sa[signum];
296+
uv__signal_saved[signum] = 0;
297+
} else {
298+
memset(&sa, 0, sizeof(sa));
299+
sa.sa_handler = SIG_DFL;
300+
}
301+
#else
249302
memset(&sa, 0, sizeof(sa));
250303
sa.sa_handler = SIG_DFL;
304+
#endif
251305

252306
/* sigaction can only fail with EINVAL or EFAULT; an attempt to deregister a
253307
* signal implies that it was successfully registered earlier, so EINVAL
@@ -413,7 +467,7 @@ static int uv__signal_start(uv_signal_t* handle,
413467
first_handle = uv__signal_first_handle(signum);
414468
if (first_handle == NULL ||
415469
(!oneshot && (first_handle->flags & UV_SIGNAL_ONE_SHOT))) {
416-
err = uv__signal_register_handler(signum, oneshot);
470+
err = uv__signal_register_handler(signum, oneshot, first_handle == NULL);
417471
if (err) {
418472
/* Registering the signal handler failed. Must be an invalid signal. */
419473
uv__signal_unlock_and_unblock(&saved_sigmask);
@@ -568,7 +622,7 @@ static void uv__signal_stop(uv_signal_t* handle) {
568622
rem_oneshot = handle->flags & UV_SIGNAL_ONE_SHOT;
569623
first_oneshot = first_handle->flags & UV_SIGNAL_ONE_SHOT;
570624
if (first_oneshot && !rem_oneshot) {
571-
ret = uv__signal_register_handler(handle->signum, 1);
625+
ret = uv__signal_register_handler(handle->signum, 1, 0);
572626
assert(ret == 0);
573627
(void)ret;
574628
}

0 commit comments

Comments
 (0)