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 2/2] selftests/landlock: cover TIOCSIG signal scoping
Date: Sun, 13 Sep 2026 18:19:58 -0400 [thread overview]
Message-ID: <20260913221958.839429-3-clusk@northecho.dev> (raw)
In-Reply-To: <20260913221958.839429-1-clusk@northecho.dev>
Add a focused regression test for a sandboxed PTY master holder using
TIOCSIG to signal an out-of-domain slave foreground process group.
The test observes both the ioctl result and the target's signal-handler
effect. It fails on the unpatched base because TIOCSIG succeeds and
SIGTSTP is delivered. It passes with the preceding RFC prototype because
the ioctl fails with EPERM and the target observes no signal.
The identical test binary and initramfs were booted against both kernels
under QEMU. TAP reported one failing test on the affected image and one
passing test on the patched image.
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Christopher Lusk <clusk@northecho.dev>
---
.../selftests/landlock/scoped_signal_test.c | 142 ++++++++++++++++++
1 file changed, 142 insertions(+)
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index 259cdcc8aa5c..9e1dbcfa07c2 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -12,6 +12,8 @@
#include <pthread.h>
#include <sched.h>
#include <signal.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/types.h>
@@ -681,6 +683,146 @@ TEST(sigio_to_pgid_members)
_metadata->exit_code = KSFT_FAIL;
}
+struct tiocsig_result {
+ int ret;
+ int error;
+};
+
+static void handle_tiocsig(int sig)
+{
+ if (sig == SIGTSTP)
+ signal_received = 1;
+}
+
+static int setup_tiocsig_handler(void)
+{
+ struct sigaction action = {
+ .sa_handler = handle_tiocsig,
+ .sa_flags = SA_RESTART,
+ };
+
+ if (sigemptyset(&action.sa_mask))
+ return -1;
+ return sigaction(SIGTSTP, &action, NULL);
+}
+
+static int create_pty_master(char *const slave_path,
+ const size_t slave_path_size)
+{
+ int master_fd, pty_number, unlock = 0;
+
+ master_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_CLOEXEC);
+ if (master_fd < 0)
+ return -1;
+ if (ioctl(master_fd, TIOCSPTLCK, &unlock) < 0 ||
+ ioctl(master_fd, TIOCGPTN, &pty_number) < 0) {
+ const int saved_errno = errno;
+
+ close(master_fd);
+ errno = saved_errno;
+ return -1;
+ }
+ if (snprintf(slave_path, slave_path_size, "/dev/pts/%d", pty_number) >=
+ (int)slave_path_size) {
+ close(master_fd);
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ return master_fd;
+}
+
+/*
+ * Checks that TIOCSIG cannot bypass LANDLOCK_SCOPE_SIGNAL when a sandboxed
+ * holder of a PTY master targets an out-of-domain foreground process group.
+ */
+TEST(tiocsig_to_foreground_pgrp)
+{
+ struct tiocsig_result result = {};
+ char slave_path[64], byte;
+ int ready[2], release[2], effect[2], report[2];
+ int master_fd, status, target_effect = -1;
+ pid_t attacker, target;
+
+ drop_caps(_metadata);
+ master_fd = create_pty_master(slave_path, sizeof(slave_path));
+ if (master_fd < 0 && errno == ENOENT)
+ SKIP(return, "Unix98 PTY not available");
+ ASSERT_LE(0, master_fd);
+ ASSERT_EQ(0, pipe2(ready, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(release, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(effect, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(report, O_CLOEXEC));
+
+ target = fork();
+ ASSERT_LE(0, target);
+ if (target == 0) {
+ int slave_fd;
+
+ EXPECT_EQ(0, close(master_fd));
+ EXPECT_EQ(0, close(ready[0]));
+ EXPECT_EQ(0, close(release[1]));
+ EXPECT_EQ(0, close(effect[0]));
+ EXPECT_EQ(0, close(report[0]));
+ EXPECT_EQ(0, close(report[1]));
+ ASSERT_LE(0, setsid());
+ slave_fd = open(slave_path, O_RDWR | O_CLOEXEC);
+ ASSERT_LE(0, slave_fd);
+ ASSERT_NE(SIG_ERR, signal(SIGTTOU, SIG_IGN));
+ ASSERT_EQ(0, setup_tiocsig_handler());
+ signal_received = 0;
+ ASSERT_EQ(0, tcsetpgrp(slave_fd, getpgrp()));
+ ASSERT_EQ(1, write(ready[1], ".", 1));
+ ASSERT_EQ(1, read(release[0], &byte, 1));
+ target_effect = signal_received;
+ ASSERT_EQ((ssize_t)sizeof(target_effect),
+ write(effect[1], &target_effect,
+ sizeof(target_effect)));
+ EXPECT_EQ(0, close(slave_fd));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(ready[1]));
+ EXPECT_EQ(0, close(release[0]));
+ EXPECT_EQ(0, close(effect[1]));
+ ASSERT_EQ(1, read(ready[0], &byte, 1));
+
+ attacker = fork();
+ ASSERT_LE(0, attacker);
+ if (attacker == 0) {
+ EXPECT_EQ(0, close(ready[0]));
+ EXPECT_EQ(0, close(release[1]));
+ EXPECT_EQ(0, close(effect[0]));
+ EXPECT_EQ(0, close(report[0]));
+ create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+ errno = 0;
+ result.ret = ioctl(master_fd, TIOCSIG, SIGTSTP);
+ result.error = errno;
+ ASSERT_EQ((ssize_t)sizeof(result),
+ write(report[1], &result, sizeof(result)));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(0, close(report[1]));
+ ASSERT_EQ((ssize_t)sizeof(result),
+ read(report[0], &result, sizeof(result)));
+ ASSERT_EQ(attacker, waitpid(attacker, &status, 0));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* Release the target only after the signal has either fired or failed. */
+ ASSERT_EQ(1, write(release[1], ".", 1));
+ ASSERT_EQ((ssize_t)sizeof(target_effect),
+ read(effect[0], &target_effect, sizeof(target_effect)));
+ ASSERT_EQ(target, waitpid(target, &status, 0));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ EXPECT_EQ(-1, result.ret);
+ EXPECT_EQ(EPERM, result.error);
+ EXPECT_EQ(0, target_effect);
+ EXPECT_EQ(0, close(master_fd));
+}
+
static void *thread_setown_scoped(void *arg)
{
const int fd = *(int *)arg;
--
2.55.0
next prev parent reply other threads:[~2026-09-13 22:20 UTC|newest]
Thread overview: 8+ 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 ` [RFC PATCH 1/2] tty: mediate TIOCSIG through task_kill LSM hooks Christopher Lusk
2026-09-13 23:49 ` Christopher Lusk
2026-09-13 22:19 ` Christopher Lusk [this message]
2026-09-14 9:34 ` [RFC PATCH 0/2] Landlock signal scope and TIOCSIG Günther Noack
2026-09-14 13:40 ` Christopher Lusk
2026-09-14 17:13 ` Günther Noack
2026-09-14 18:09 ` [PATCH] docs: landlock: clarify TTY signal scoping Christopher Lusk
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-3-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®