From: Christopher Lusk <clusk@northecho.dev>
To: "Mickaël Salaün" <mic@digikod.net>
Cc: "Günther Noack" <gnoack@google.com>,
"Oleg Nesterov" <oleg@redhat.com>,
"Jiri Slaby" <jirislaby@kernel.org>,
"Shuah Khan" <shuah@kernel.org>,
"Tahera Fahimi" <fahimitahera@gmail.com>,
"Paul Moore" <paul@paul-moore.com>,
"Casey Schaufler" <casey@schaufler-ca.com>,
"John Johansen" <john.johansen@canonical.com>,
linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
linux-kselftest@vger.kernel.org
Subject: [RFC PATCH 1/2] tty: mediate TIOCSIG through task_kill LSM hooks
Date: Sun, 13 Sep 2026 18:19:57 -0400 [thread overview]
Message-ID: <20260913221958.839429-2-clusk@northecho.dev> (raw)
In-Reply-To: <20260913221958.839429-1-clusk@northecho.dev>
TIOCSIG lets a PTY master holder send SIGINT, SIGQUIT, or SIGTSTP to
the slave's foreground process group. pty_signal() currently uses
kill_pgrp(..., priv=1), which represents the signal as SEND_SIG_PRIV.
check_kill_permission() consequently returns before security_task_kill().
This leaves the operation outside every task_kill LSM policy. In
particular, a task restricted with LANDLOCK_SCOPE_SIGNAL can use a
retained PTY master to signal an out-of-domain foreground process group.
Add a kill_pgrp_lsm() variant selected only by pty_signal(). It invokes
security_task_kill() for each process-group member immediately before
delivery while tasklist_lock remains held. This preserves the existing
per-recipient and partial-success semantics without a separate pre-check
race. Ordinary privileged process-group signals continue to use the
unchanged kill_pgrp() path.
This is an RFC because the policy boundary is not settled. Landlock's
IOCTL documentation warns that pre-existing TTY file descriptors remain
dangerous, while LANDLOCK_SCOPE_SIGNAL separately promises to restrict
signals to processes outside the domain hierarchy. The proposed helper
also makes SELinux, Smack, AppArmor, and other task_kill LSMs mediate
TIOCSIG for the first time. Maintainer guidance is requested on whether
this should instead use a dedicated, opt-in TTY signal hook.
Tested on x86-64 QEMU with a held-constant four-cell effect oracle.
Across three boots per image, the unpatched kernel
delivered 96/96 cross-domain scoped TIOCSIG attempts; the patched kernel
denied 96/96. Both images delivered 96/96 unconfined and 96/96
same-domain TIOCSIG controls, and denied 96/96 scoped direct-kill
anchors. There were no indeterminate cases or kernel diagnostics.
Fixes: 54a6e6bbf3be ("landlock: Add signal scoping")
Link: https://lore.kernel.org/r/56bffc24f3d0d08b45a686a48e99766b0a0821fa.1780614610.git.hexlabsecurity@proton.me
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Christopher Lusk <clusk@northecho.dev>
---
drivers/tty/pty.c | 8 ++++++--
include/linux/sched/signal.h | 1 +
kernel/signal.c | 30 ++++++++++++++++++++++++++++--
3 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index cc7f7091ed9a..8f5eea156ce4 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -187,6 +187,7 @@ static int pty_get_pktmode(struct tty_struct *tty, int __user *arg)
/* Send a signal to the slave */
static int pty_signal(struct tty_struct *tty, int sig)
{
+ int ret = 0;
struct pid *pgrp;
if (sig != SIGINT && sig != SIGQUIT && sig != SIGTSTP)
@@ -195,10 +196,13 @@ static int pty_signal(struct tty_struct *tty, int sig)
if (tty->link) {
pgrp = tty_get_pgrp(tty->link);
if (pgrp)
- kill_pgrp(pgrp, sig, 1);
+ ret = kill_pgrp_lsm(pgrp, sig, 1);
put_pid(pgrp);
}
- return 0;
+ /* Preserve the historical success result for an empty process group. */
+ if (ret == -ESRCH)
+ return 0;
+ return ret;
}
static void pty_flush_buffer(struct tty_struct *tty)
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index 584ae88b435e..7d6aee7256a3 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -337,6 +337,7 @@ extern int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid);
extern int kill_pid_usb_asyncio(int sig, int errno, sigval_t addr, struct pid *,
const struct cred *);
extern int kill_pgrp(struct pid *pid, int sig, int priv);
+int kill_pgrp_lsm(struct pid *pid, int sig, int priv);
extern int kill_pid(struct pid *pid, int sig, int priv);
extern __must_check bool do_notify_parent(struct task_struct *, int);
extern void __wake_up_parent(struct task_struct *p, struct task_struct *parent);
diff --git a/kernel/signal.c b/kernel/signal.c
index a5e15bf09d31..758393b7257d 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1426,13 +1426,22 @@ int group_send_sig_info(int sig, struct kernel_siginfo *info,
* control characters do (^C, ^Z etc)
* - the caller must hold at least a readlock on tasklist_lock
*/
-int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp)
+static int __kill_pgrp_info_filtered(int sig, struct kernel_siginfo *info,
+ struct pid *pgrp, bool check_lsm)
{
struct task_struct *p = NULL;
int ret = -ESRCH;
do_each_pid_task(pgrp, PIDTYPE_PGID, p) {
- int err = group_send_sig_info(sig, info, p, PIDTYPE_PGID);
+ int err = 0;
+
+ if (check_lsm) {
+ rcu_read_lock();
+ err = security_task_kill(p, info, sig, NULL);
+ rcu_read_unlock();
+ }
+ if (!err)
+ err = group_send_sig_info(sig, info, p, PIDTYPE_PGID);
/*
* If group_send_sig_info() succeeds at least once ret
* becomes 0 and after that the code below has no effect.
@@ -1446,6 +1455,11 @@ int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp)
return ret;
}
+int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp)
+{
+ return __kill_pgrp_info_filtered(sig, info, pgrp, false);
+}
+
static int kill_pid_info_type(int sig, struct kernel_siginfo *info,
struct pid *pid, enum pid_type type)
{
@@ -1886,6 +1900,18 @@ int kill_pgrp(struct pid *pid, int sig, int priv)
}
EXPORT_SYMBOL(kill_pgrp);
+int kill_pgrp_lsm(struct pid *pid, int sig, int priv)
+{
+ int ret;
+
+ read_lock(&tasklist_lock);
+ ret = __kill_pgrp_info_filtered(sig, __si_special(priv), pid, true);
+ read_unlock(&tasklist_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(kill_pgrp_lsm);
+
int kill_pid(struct pid *pid, int sig, int priv)
{
return kill_pid_info(sig, __si_special(priv), pid);
--
2.55.0
next prev parent reply other threads:[~2026-09-13 22:20 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-13 22:19 [RFC PATCH 0/2] Landlock signal scope and TIOCSIG Christopher Lusk
2026-09-13 22:19 ` Christopher Lusk [this message]
2026-09-13 22:37 ` [RFC PATCH 1/2] tty: mediate TIOCSIG through task_kill LSM hooks sashiko-bot
2026-09-13 22:19 ` [RFC PATCH 2/2] selftests/landlock: cover TIOCSIG signal scoping Christopher Lusk
2026-09-13 22:26 ` sashiko-bot
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=20260913221958.839429-2-clusk@northecho.dev \
--to=clusk@northecho.dev \
--cc=casey@schaufler-ca.com \
--cc=fahimitahera@gmail.com \
--cc=gnoack@google.com \
--cc=jirislaby@kernel.org \
--cc=john.johansen@canonical.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=mic@digikod.net \
--cc=oleg@redhat.com \
--cc=paul@paul-moore.com \
--cc=shuah@kernel.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.