From: Oleg Nesterov <oleg@redhat.com>
To: Andrew Morton <akpm@linux-foundation.org>
Cc: Andy Lutomirski <luto@kernel.org>, Kees Cook <kees@kernel.org>,
Kusaram Devineni <kusaram@devineni.in>,
Peter Zijlstra <peterz@infradead.org>,
Thomas Gleixner <tglx@kernel.org>, Will Drewry <wad@chromium.org>,
linux-kernel@vger.kernel.org
Subject: [PATCH v2 2/2] signal: prevent evasion of SA_IMMUTABLE signals
Date: Sat, 2 May 2026 16:13:44 +0200 [thread overview]
Message-ID: <afYGmDYa51NJsM-H@redhat.com> (raw)
In-Reply-To: <afYGeHSWkLzXDlvC@redhat.com>
force_sig_info_to_task(HANDLER_EXIT) sets SA_IMMUTABLE to ensure a forced
fatal signal cannot be ignored or caught by userspace; it must always
terminate the target. However, if get_signal() dequeues another synchronous
signal first, and that signal has a handler and its sa_mask includes the
fatal SA_IMMUTABLE signal, the task can return to userspace and survive.
So dequeue_synchronous_signal() must always dequeue an SA_IMMUTABLE signal
first. But it relies on the SI_FROMKERNEL() check and picks the first one
it sees in pending->list, and thus we have the following problems:
- If the same signal was already pending and blocked, the new siginfo
with .si_code > 0 will be lost.
Change __send_signal_locked() to bypass the legacy_queue() check in
this case.
- If force_sig_info_to_task() races with another synchronous/SI_FROMKERNEL
signal, that signal can be picked first.
Change __send_signal_locked() to add an SA_IMMUTABLE signal at the start
of pending->list.
- SA_IMMUTABLE implies override_rlimit == true, but GFP_ATOMIC can fail
anyway.
Change __send_signal_locked() to escalate to SIGKILL in this (very
unlikely) case.
Not perfect and perhaps deserves WARN() or pr_warn_ratelimited(), but
better than nothing.
However, unlike get_signal(), __send_signal_locked() can not rely on the
k_sigaction.sa.sa_flags & SA_IMMUTABLE check; another signal with the same
.si_signo can come before dequeue_synchronous_signal() dequeues the signal
sent by force(HANDLER_EXIT). Say, send_sig_perf() from task_work_run(),
and this signal is SI_FROMKERNEL() too.
So this patch adds 2 helpers, mark_si_immutable() and unmark_si_immutable()
for force_sig_info_to_task() and __send_signal_locked() to pass/check the
"immutable" state of siginfo sent by HANDLER_EXIT.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
---
kernel/signal.c | 35 ++++++++++++++++++++++++++++++-----
1 file changed, 30 insertions(+), 5 deletions(-)
diff --git a/kernel/signal.c b/kernel/signal.c
index 75e277e24b52..5a5eeb04ac7a 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1034,6 +1034,19 @@ static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
return;
}
+static inline void mark_si_immutable(struct kernel_siginfo *info)
+{
+ info->si_signo |= INT_MIN; /* sets MSB */
+}
+
+static inline bool unmark_si_immutable(struct kernel_siginfo *info)
+{
+ bool ret = !is_si_special(info) && (info->si_signo & INT_MIN);
+ if (ret)
+ info->si_signo &= ~INT_MIN;
+ return ret;
+}
+
static inline bool legacy_queue(struct sigpending *signals, int sig)
{
return (sig < SIGRTMIN) && sigismember(&signals->signal, sig);
@@ -1042,6 +1055,7 @@ static inline bool legacy_queue(struct sigpending *signals, int sig)
static int __send_signal_locked(int sig, struct kernel_siginfo *info,
struct task_struct *t, enum pid_type type, bool force)
{
+ bool immutable = unmark_si_immutable(info);
struct sigpending *pending;
struct sigqueue *q;
int override_rlimit;
@@ -1055,12 +1069,12 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info,
pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
/*
- * Short-circuit ignored signals and support queuing
- * exactly one non-rt signal, so that we can get more
- * detailed information about the cause of the signal.
+ * Queue exactly one non-rt signal so that we can get more
+ * detailed information about the cause. But we must never
+ * lose the siginfo for an SA_IMMUTABLE signal.
*/
result = TRACE_SIGNAL_ALREADY_PENDING;
- if (legacy_queue(pending, sig))
+ if (legacy_queue(pending, sig) && !immutable)
goto ret;
result = TRACE_SIGNAL_DELIVERED;
@@ -1087,7 +1101,12 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info,
q = sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
if (q) {
- list_add_tail(&q->list, &pending->list);
+ /* Ensure dequeue_synchronous_signal() sees SA_IMMUTABLE first */
+ if (immutable)
+ list_add(&q->list, &pending->list);
+ else
+ list_add_tail(&q->list, &pending->list);
+
switch ((unsigned long) info) {
case (unsigned long) SEND_SIG_NOINFO:
clear_siginfo(&q->info);
@@ -1130,6 +1149,9 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info,
* send the signal, but the *info bits are lost.
*/
result = TRACE_SIGNAL_LOSE_INFO;
+ /* The task must not escape SA_IMMUTABLE; escalate to SIGKILL */
+ if (immutable)
+ sig = SIGKILL;
}
out_set:
@@ -1317,6 +1339,9 @@ force_sig_info_to_task(struct kernel_siginfo *info, struct task_struct *t,
if (action->sa.sa_handler == SIG_DFL &&
(!t->ptrace || (handler == HANDLER_EXIT)))
t->signal->flags &= ~SIGNAL_UNKILLABLE;
+
+ if (handler == HANDLER_EXIT)
+ mark_si_immutable(info);
ret = __send_signal_locked(sig, info, t, PIDTYPE_PID, false);
/* This can happen if the signal was already pending and blocked */
if (!task_sigpending(t))
--
2.52.0
next prev parent reply other threads:[~2026-05-02 14:13 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-02 14:13 [PATCH v2 1/2] signal: change force_sig_info_to_task() to call __send_signal_locked() Oleg Nesterov
2026-05-02 14:13 ` Oleg Nesterov [this message]
2026-05-04 9:27 ` Oleg Nesterov
2026-05-13 23:52 ` Andrew Morton
2026-05-14 8:18 ` Oleg Nesterov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=afYGmDYa51NJsM-H@redhat.com \
--to=oleg@redhat.com \
--cc=akpm@linux-foundation.org \
--cc=kees@kernel.org \
--cc=kusaram@devineni.in \
--cc=linux-kernel@vger.kernel.org \
--cc=luto@kernel.org \
--cc=peterz@infradead.org \
--cc=tglx@kernel.org \
--cc=wad@chromium.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.