sparc64_set_context() emulates the kernel's `ta 0x6f` trap by calling
set_sigmask() to install the mask supplied via the user's ucontext_t.
The contract of set_sigmask() (see its comment in linux-user/signal.c)
is that the caller must have first called block_signals(), which sets
TaskState::signal_pending.
Without block_signals(), if a guest signal is pending-and-blocked at
the time setcontext is invoked and the new mask unblocks it,
signal_pending stays 0 and the post-trap process_pending_signals()
call in linux-user/sparc/cpu_loop.c never enters its while loop, so
the now-deliverable signal is left undelivered indefinitely.
This affects programs that use getcontext/setcontext to swap signal
masks, including libunwind's unw_resume() out of a signal handler:
without this fix, the test program below loops forever printing
"calling setcontext" instead of delivering the pending SIGUSR2.
#define _GNU_SOURCE
#include <ucontext.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static int got;
static void h(int s) { got = 1; }
int main(void) {
signal(SIGUSR2, h);
sigset_t m; sigemptyset(&m); sigaddset(&m, SIGUSR2);
sigprocmask(SIG_BLOCK, &m, NULL);
kill(getpid(), SIGUSR2);
ucontext_t uc;
getcontext(&uc);
if (got) return 0;
uc.uc_sigmask.__val[0] = 0;
setcontext(&uc);
return 1;
}
The 32-bit sparc do_sigreturn / do_rt_sigreturn paths already get
block_signals() from the rt_sigreturn syscall wrapper in
linux-user/syscall.c, so only sparc64_set_context (invoked directly
from cpu_loop) needs the addition.
Signed-off-by: Matt Turner <[email protected]>
---
linux-user/sparc/signal.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git ./linux-user/sparc/signal.c ./linux-user/sparc/signal.c
index fda5508c48..ba692c3123 100644
--- ./linux-user/sparc/signal.c
+++ ./linux-user/sparc/signal.c
@@ -619,6 +619,15 @@ void sparc64_set_context(CPUSPARCState *env)
}
}
target_to_host_sigset_internal(&set, &target_set);
+ /*
+ * set_sigmask() requires the caller to have first called
+ * block_signals() so that process_pending_signals() is guaranteed
+ * to run after the mask change. Without this, a guest signal that
+ * is pending-and-blocked at setcontext time is left undelivered
+ * even after its mask bit is cleared, because signal_pending stays
+ * 0 and the post-trap process_pending_signals() loop never enters.
+ */
+ block_signals();
set_sigmask(&set);
}
env->pc = pc;
--
2.53.0