diff options
| author | Christian Brauner <brauner@kernel.org> | 2026-09-21 15:45:05 +0200 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-09-22 15:33:21 +0200 |
| commit | 2586c1ecbc749f37673f02df9ed65b6a4ad28601 (patch) | |
| tree | 57c1724cae9409034bebf4c8254dce8276c635a7 /kernel | |
| parent | c7de3d02e678490b3aa0796d9be7dfe5f259374a (diff) | |
| download | linux-next-2586c1ecbc749f37673f02df9ed65b6a4ad28601.tar.gz linux-next-2586c1ecbc749f37673f02df9ed65b6a4ad28601.zip | |
signal: enforce the user worker signal mask in __set_task_blocked()
User workers block every signal except for SIGKILL and SIGSTOP in
copy_process(). So complete_signal() never picks a thread that blocks
the signal and get_signal() never dequeues such a thread. Net effect is
that user workers only exit on SIGKILL or stop on SIGSTOP.
Before we fixed it a tracer could unblock a signal. For example, by
unblocking SIGHUP a installing a handler complete_signal() could end up
picking a user worker for a process-directed SIGHUP. Which effectively
means the handler never runs.
After blocking PTRACE_SETSIGMASK what remains is the in-kernel
sigprocmask() and force_sig_info_to_task() changes.
The in-kernel sigprocmask() users block more signals for a critical
section and restore the saved mask afterwards. cifs used to do that in
__smb_send_rqst() and ocfs2 in ocfs2_block_signals(). Both were
reachable from an io-wq worker. Both only add SIGKILL and SIGSTOP to the
user workers's and then put the fork-time mask back.
force_sig_info_to_task() unblocks a signal so a target can't hide from
the signal. A synchronous signal forced onto a user worker is accepted
currently so let's leave that alone.
Make it a rule that a user worker's signal mask can never drop below
the copy_process() deafult. SIGKILL and SIGSTOP have the same rule in
the other direction. rt_sigprocmask(), set_current_blocked() and
PTRACE_SETSIGMASK strip them from whatever mask userspace asks for.
Do the same for user worker mask in __set_task_blocked(). Add the
fork-time mask back into the new set for a user worker and warn if that
changed anything. Warn when a caller unblocks a signal for a user worker.
No functional changes.
Link: https://lore.kernel.org/r/aq_4fY6GVtK47Njq@redhat.com
Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-16-8e4adb1619e6@kernel.org
Acked-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/signal.c | 10 |
1 files changed, 10 insertions, 0 deletions
diff --git a/kernel/signal.c b/kernel/signal.c index 51e43e17d1c6..12be8ef85d82 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3192,6 +3192,16 @@ long do_no_restart_syscall(struct restart_block *param) static void __set_task_blocked(struct task_struct *tsk, const sigset_t *newset) { + sigset_t floor, floored; + + /* A user worker never unblocks anything but SIGKILL and SIGSTOP. */ + if (unlikely(tsk->flags & PF_USER_WORKER)) { + siginitsetinv(&floor, SIG_KERNEL_ONLY_MASK); + sigorsets(&floored, newset, &floor); + WARN_ON_ONCE(!sigequalsets(&floored, newset)); + newset = &floored; + } + if (task_sigpending(tsk) && !thread_group_empty(tsk)) { sigset_t newblocked; /* A set of now blocked but previously unblocked signals. */ |
