blob: b027ec82d4e79ee879921d23b9677db292390eaa [file]
From: rive
Subject: [PATCH 0044] run the signal handler on the alt stack and put it back
The handler was installed without SA_ONSTACK, so it ran on the faulting
thread's own stack. When a host stack overflow faulted with a crash reporter
below wamr that had set up an alt stack for exactly that, wamr's handler had
no stack to run on and the process died without a report. It now runs on the
thread's alt stack when there is one, and passes the fault on from there.
Patch 0036 installs the handler once per process. A host that saved a
handler and later put it back, as Catch2 does around every test, removed
wamr's for good, and guard page faults then reached the host's handler as
crashes. When a thread sets up its signal state and finds the handler wamr
replaced, or the default, back in place, wamr's goes back on top and saves
what it found. A handler installed since wamr's is left alone: it may pass
faults on to wamr's, which would pass them back to it in a loop.
--- a/core/shared/platform/common/posix/posix_thread.c
+++ b/core/shared/platform/common/posix/posix_thread.c
@@ -656,23 +656,45 @@ signal_callback(int sig_num, siginfo_t *sig_info, void *sig_ucontext)
/* rive patch: dispositions are process wide, so they are installed and the
handlers they replace saved once, not again by every thread */
-static pthread_once_t signal_install_once = PTHREAD_ONCE_INIT;
+static pthread_mutex_t signal_install_lock = PTHREAD_MUTEX_INITIALIZER;
static bool signal_installed = false;
-static void
-install_signal_handler(void)
+static bool
+install_signal_handler(int sig_num, struct sigaction *prev_sig_act)
{
struct sigaction sig_act;
sig_act.sa_sigaction = signal_callback;
- sig_act.sa_flags = SA_SIGINFO | SA_NODEFER;
-#if WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
- sig_act.sa_flags |= SA_ONSTACK;
-#endif
+ /* rive patch: a host's alt stack is what lets a host stack overflow
+ reach the handlers wamr passes it on to */
+ sig_act.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
sigemptyset(&sig_act.sa_mask);
- signal_installed =
- sigaction(SIGSEGV, &sig_act, &prev_sig_act_SIGSEGV) == 0
- && sigaction(SIGBUS, &sig_act, &prev_sig_act_SIGBUS) == 0;
+ return sigaction(sig_num, &sig_act, prev_sig_act) == 0;
+}
+
+static void *
+handler_of(const struct sigaction *sig_act)
+{
+ return (sig_act->sa_flags & SA_SIGINFO) ? (void *)sig_act->sa_sigaction
+ : (void *)sig_act->sa_handler;
+}
+
+/* rive patch: a host that put back the handler wamr replaced, or the
+ default, removed wamr's for good, so it goes back on top. A handler
+ installed since stays, as it may pass faults back to wamr's. */
+static void
+reinstall_signal_handler(int sig_num, struct sigaction *prev_sig_act)
+{
+ struct sigaction cur_sig_act;
+ void *cur;
+
+ if (sigaction(sig_num, NULL, &cur_sig_act) != 0)
+ return;
+ cur = handler_of(&cur_sig_act);
+ if (cur != (void *)signal_callback
+ && (cur == (void *)SIG_DFL || cur == (void *)SIG_IGN
+ || cur == handler_of(prev_sig_act)))
+ install_signal_handler(sig_num, prev_sig_act);
}
int
@@ -713,7 +735,17 @@ os_thread_signal_init(os_signal_handler handler)
}
#endif
- pthread_once(&signal_install_once, install_signal_handler);
+ pthread_mutex_lock(&signal_install_lock);
+ if (!signal_installed) {
+ signal_installed =
+ install_signal_handler(SIGSEGV, &prev_sig_act_SIGSEGV)
+ && install_signal_handler(SIGBUS, &prev_sig_act_SIGBUS);
+ }
+ else {
+ reinstall_signal_handler(SIGSEGV, &prev_sig_act_SIGSEGV);
+ reinstall_signal_handler(SIGBUS, &prev_sig_act_SIGBUS);
+ }
+ pthread_mutex_unlock(&signal_install_lock);
if (!signal_installed) {
os_printf("Failed to register signal handler\n");
goto fail3;