From b4b63d3722c6decaf9bd9c2c6c072e3535198a0c Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Mon, 16 Feb 2015 10:21:03 +0100 Subject: [PATCH] Trap segfaults caused by service callbacks in separate process context This patch traps segfaults caused by 3rd party service callbacks in a separate process context. Effectively preventing a single programming mistake from taking down the entire system. Previously it was up to the callback coder to write error free code so that PID 1 did not crash. Signed-off-by: Joachim Nilsson --- sig.c | 14 +++++++++++++- svc.c | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/sig.c b/sig.c index a9e2b35b..95b74939 100644 --- a/sig.c +++ b/sig.c @@ -49,7 +49,7 @@ static int stopped = 0; static uev_t sigint_watcher, sigpwr_watcher; -static uev_t sigchld_watcher; +static uev_t sigchld_watcher, sigsegv_watcher; static uev_t sigstop_watcher, sigtstp_watcher, sigcont_watcher; @@ -103,6 +103,15 @@ static void sigchld_cb(uev_ctx_t *UNUSED(ctx), uev_t *UNUSED(w), void *UNUSED(ar svc_monitor(waitpid(-1, NULL, WNOHANG)); } +/* + * SIGSEGV: mostly if service callbacks segfault + */ +static void sigsegv_cb(uev_ctx_t *UNUSED(ctx), uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events)) +{ + _e("PID %d caused a segfault!\n", getpid()); + exit(-1); +} + /* * SIGSTOP/SIGTSTP: Paused by user or netflash */ @@ -204,6 +213,9 @@ void sig_setup(uev_ctx_t *ctx) /* After initial bootstrap of Finit we call the service monitor to reap children */ uev_signal_init(ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD); + /* Trap SIGSEGV in case service callbacks crash */ + uev_signal_init(ctx, &sigsegv_watcher, sigsegv_cb, NULL, SIGSEGV); + /* Stopping init is a bit tricky. */ uev_signal_init(ctx, &sigstop_watcher, sigstop_cb, NULL, SIGSTOP); uev_signal_init(ctx, &sigtstp_watcher, sigstop_cb, NULL, SIGTSTP); diff --git a/svc.c b/svc.c index 7d936d0b..477c3be6 100644 --- a/svc.c +++ b/svc.c @@ -395,8 +395,39 @@ svc_cmd_t svc_enabled(svc_t *svc, int event, void *arg) return SVC_STOP; /* Is there a service plugin registered? */ - if (svc->cb) - return svc->cb(svc, event, arg); + if (svc->cb) { + int status; + pid_t pid; + + /* Let callback run in separate process so it doesn't crash PID 1 */ + pid = fork(); + if (-1 == pid) { + _pe("Failed in %s callback", svc->cmd); + return SVC_STOP; + } + + if (!pid) { + status = svc->cb(svc, event, arg); + exit(status); + } + + if (waitpid(pid, &status, 0) == -1) { + _pe("Failed reading status from %s callback", svc->cmd); + return SVC_STOP; + } + + /* Callback normally exits here. */ + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + /* Check for SEGFAULT or other error ... */ + if (WCOREDUMP(status)) + _e("Callback to %s crashed!\n", svc->cmd); + else + _e("Callback to %s did not exit normally!\n", svc->cmd); + + return SVC_STOP; + } /* No service plugin, default to start, since listed in finit.conf */ return SVC_START;