* [PATCH v3 0/2] landlock: clarify TTY signal scoping @ 2026-09-23 16:33 Christopher Lusk 2026-09-23 16:33 ` [PATCH v3 1/2] docs: " Christopher Lusk 2026-09-23 16:33 ` [PATCH v3 2/2] selftests/landlock: cover " Christopher Lusk 0 siblings, 2 replies; 5+ messages in thread From: Christopher Lusk @ 2026-09-23 16:33 UTC (permalink / raw) To: Mickaël Salaün, Günther Noack Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, linux-security-module, linux-doc, linux-kselftest, linux-kernel Document that a PTY master retains control over processes attached to the terminal, and add a selftest covering this boundary alongside the direct signal restriction. Since v2, the documentation follows Mickaël's capability framing, the UAPI description refers to arbitrary signal targets, and the series includes the requested selftest. Link: https://lore.kernel.org/r/20260916152336.1589383-1-clusk@northecho.dev Christopher Lusk (2): docs: landlock: clarify TTY signal scoping selftests/landlock: cover TTY signal scoping Documentation/userspace-api/landlock.rst | 6 + include/uapi/linux/landlock.h | 2 +- .../selftests/landlock/scoped_signal_test.c | 175 ++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) -- 2.55.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/2] docs: landlock: clarify TTY signal scoping 2026-09-23 16:33 [PATCH v3 0/2] landlock: clarify TTY signal scoping Christopher Lusk @ 2026-09-23 16:33 ` Christopher Lusk 2026-09-23 16:39 ` sashiko-bot 2026-09-23 16:33 ` [PATCH v3 2/2] selftests/landlock: cover " Christopher Lusk 1 sibling, 1 reply; 5+ messages in thread From: Christopher Lusk @ 2026-09-23 16:33 UTC (permalink / raw) To: Mickaël Salaün, Günther Noack Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, linux-security-module, linux-doc, linux-kselftest, linux-kernel LANDLOCK_SCOPE_SIGNAL limits signals sent to arbitrary processes outside a domain. A PTY master differs because its holder controls attached processes through input and signal injection. Clarify this boundary in the userspace guide and narrow the UAPI description to arbitrary target processes. Built the userspace-api docs; no new warnings. Link: https://lore.kernel.org/r/20260916152336.1589383-1-clusk@northecho.dev Suggested-by: Günther Noack <gnoack@google.com> Suggested-by: Mickaël Salaün <mic@digikod.net> Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Christopher Lusk <clusk@northecho.dev> --- Documentation/userspace-api/landlock.rst | 6 ++++++ include/uapi/linux/landlock.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst index 84cb7bf6b3ed..4d9fb1f1588c 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -430,6 +430,12 @@ The operations which can be scoped are: This limits the sending of signals to target processes which run within the same or a nested Landlock domain. + Holding a PTY master FD grants control over the processes running under that + terminal, including the ability to inject input and signals. A process able + to write to the master should therefore be considered as privileged as those + processes, and this capability is not restricted by + ``LANDLOCK_SCOPE_SIGNAL``. + ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` This limits the set of abstract :manpage:`unix(7)` sockets to which we can :manpage:`connect(2)` to socket addresses which were created by a process in diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index cceda3b3b961..ad7b1ebd62ed 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -500,7 +500,7 @@ struct landlock_net_port_attr { * connecting to an abstract UNIX socket created by a process outside the * related Landlock domain (e.g., a parent domain or a non-sandboxed process). * - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal - * to another process outside the domain. + * to an arbitrary process outside the domain. */ /* clang-format off */ #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0) -- 2.55.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/2] docs: landlock: clarify TTY signal scoping 2026-09-23 16:33 ` [PATCH v3 1/2] docs: " Christopher Lusk @ 2026-09-23 16:39 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-09-23 16:39 UTC (permalink / raw) Cc: linux-security-module > LANDLOCK_SCOPE_SIGNAL limits signals sent to arbitrary processes outside > a domain. A PTY master differs because its holder controls attached > processes through input and signal injection. > > Clarify this boundary in the userspace guide and narrow the UAPI > description to arbitrary target processes. > > Built the userspace-api docs; no new warnings. > > Link: https://lore.kernel.org/r/20260916152336.1589383-1-clusk@northecho.dev > Suggested-by: Günther Noack <gnoack@google.com> > Suggested-by: Mickaël Salaün <mic@digikod.net> > Assisted-by: Codex:gpt-5.6-sol > Signed-off-by: Christopher Lusk <clusk@northecho.dev> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260923163321.79770-1-clusk@northecho.dev?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 2/2] selftests/landlock: cover TTY signal scoping 2026-09-23 16:33 [PATCH v3 0/2] landlock: clarify TTY signal scoping Christopher Lusk 2026-09-23 16:33 ` [PATCH v3 1/2] docs: " Christopher Lusk @ 2026-09-23 16:33 ` Christopher Lusk 2026-09-23 16:42 ` sashiko-bot 1 sibling, 1 reply; 5+ messages in thread From: Christopher Lusk @ 2026-09-23 16:33 UTC (permalink / raw) To: Mickaël Salaün, Günther Noack Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, linux-security-module, linux-doc, linux-kselftest, linux-kernel LANDLOCK_SCOPE_SIGNAL blocks signaling an arbitrary out-of-domain process, but a PTY master retains signal delivery to its terminal foreground process group. Add a test that enters a signal-scoped domain, checks that kill(SIGUSR1) is denied with EPERM, and checks that TIOCSIG(SIGTSTP) succeeds and reaches the target's signal handler. Tested the focused case in three QEMU boots and ran the complete Landlock selftest suite (12 programs, no failures). Built all Landlock selftests with W=1. Link: https://lore.kernel.org/r/20260916152336.1589383-1-clusk@northecho.dev Suggested-by: Günther Noack <gnoack@google.com> Suggested-by: Mickaël Salaün <mic@digikod.net> Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Christopher Lusk <clusk@northecho.dev> --- .../selftests/landlock/scoped_signal_test.c | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c index 259cdcc8aa5c..9a50817f33eb 100644 --- a/tools/testing/selftests/landlock/scoped_signal_test.c +++ b/tools/testing/selftests/landlock/scoped_signal_test.c @@ -9,9 +9,12 @@ #include <errno.h> #include <fcntl.h> #include <linux/landlock.h> +#include <poll.h> #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> @@ -767,6 +770,178 @@ TEST(sigio_to_pgid_self) EXPECT_EQ(0, close(trigger[1])); } +struct tiocsig_result { + int kill_ret; + int kill_errno; + int ioctl_ret; + int ioctl_errno; +}; + +static int tty_effect_fd = -1; + +static void handle_tty_signal(int sig) +{ + const char effect = sig; + + if (tty_effect_fd >= 0) + (void)write(tty_effect_fd, &effect, sizeof(effect)); +} + +static int setup_tty_signal_handler(int sig) +{ + struct sigaction action = { + .sa_handler = handle_tty_signal, + .sa_flags = SA_RESTART, + }; + + if (sigemptyset(&action.sa_mask)) + return -1; + return sigaction(sig, &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; +} + +/* + * A PTY master grants control over its attached terminal, including signal + * delivery to the foreground process group. LANDLOCK_SCOPE_SIGNAL blocks + * arbitrary signal targets, but it does not restrict this terminal capability. + */ +TEST(tiocsig_to_foreground_pgrp) +{ + struct tiocsig_result result = {}; + struct pollfd poll_fd = { + .events = POLLIN, + }; + char slave_path[64], byte, effect_signal = 0; + int ready[2], release[2], effect[2], report[2]; + int master_fd, poll_ret, status; + ssize_t report_size; + 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)); + tty_effect_fd = effect[1]; + ASSERT_EQ(0, setup_tty_signal_handler(SIGUSR1)); + ASSERT_EQ(0, setup_tty_signal_handler(SIGTSTP)); + ASSERT_EQ(0, tcsetpgrp(slave_fd, getpgrp())); + ASSERT_EQ(1, write(ready[1], ".", 1)); + ASSERT_EQ(1, read(release[0], &byte, 1)); + EXPECT_EQ(0, close(slave_fd)); + EXPECT_EQ(0, close(effect[1])); + _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.kill_ret = kill(target, SIGUSR1); + result.kill_errno = errno; + errno = 0; + result.ioctl_ret = ioctl(master_fd, TIOCSIG, SIGTSTP); + result.ioctl_errno = errno; + ASSERT_EQ((ssize_t)sizeof(result), + write(report[1], &result, sizeof(result))); + EXPECT_EQ(0, close(report[1])); + EXPECT_EQ(0, close(master_fd)); + _exit(_metadata->exit_code); + return; + } + EXPECT_EQ(0, close(report[1])); + report_size = read(report[0], &result, sizeof(result)); + EXPECT_EQ((ssize_t)sizeof(result), report_size); + EXPECT_EQ(0, close(report[0])); + EXPECT_EQ(attacker, waitpid(attacker, &status, 0)); + EXPECT_TRUE(WIFEXITED(status)); + if (WIFEXITED(status)) + EXPECT_EQ(0, WEXITSTATUS(status)); + + EXPECT_EQ(-1, result.kill_ret); + EXPECT_EQ(EPERM, result.kill_errno); + EXPECT_EQ(0, result.ioctl_ret); + EXPECT_EQ(0, result.ioctl_errno); + + poll_fd.fd = effect[0]; + poll_ret = poll(&poll_fd, 1, 1000); + EXPECT_EQ(1, poll_ret); + if (poll_ret == 1 && (poll_fd.revents & POLLIN)) { + EXPECT_EQ((ssize_t)sizeof(effect_signal), + read(effect[0], &effect_signal, + sizeof(effect_signal))); + EXPECT_EQ(SIGTSTP, effect_signal); + } else { + EXPECT_TRUE(poll_ret == 1 && (poll_fd.revents & POLLIN)); + } + + ASSERT_EQ(1, write(release[1], ".", 1)); + EXPECT_EQ(target, waitpid(target, &status, 0)); + EXPECT_TRUE(WIFEXITED(status)); + if (WIFEXITED(status)) + EXPECT_EQ(0, WEXITSTATUS(status)); + + EXPECT_EQ(0, close(ready[0])); + EXPECT_EQ(0, close(release[1])); + EXPECT_EQ(0, close(effect[0])); + EXPECT_EQ(0, close(master_fd)); +} + /* Trace tests */ /* clang-format off */ -- 2.55.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 2/2] selftests/landlock: cover TTY signal scoping 2026-09-23 16:33 ` [PATCH v3 2/2] selftests/landlock: cover " Christopher Lusk @ 2026-09-23 16:42 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-09-23 16:42 UTC (permalink / raw) Cc: linux-security-module > LANDLOCK_SCOPE_SIGNAL blocks signaling an arbitrary out-of-domain process, > but a PTY master retains signal delivery to its terminal foreground process > group. > > Add a test that enters a signal-scoped domain, checks that kill(SIGUSR1) > is denied with EPERM, and checks that TIOCSIG(SIGTSTP) succeeds and reaches > the target's signal handler. > > Tested the focused case in three QEMU boots and ran the complete Landlock > selftest suite (12 programs, no failures). Built all Landlock selftests > with W=1. > > Link: https://lore.kernel.org/r/20260916152336.1589383-1-clusk@northecho.dev > Suggested-by: Günther Noack <gnoack@google.com> > Suggested-by: Mickaël Salaün <mic@digikod.net> > Assisted-by: Codex:gpt-5.6-sol > Signed-off-by: Christopher Lusk <clusk@northecho.dev> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260923163321.79770-1-clusk@northecho.dev?part=2 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-23 16:42 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-23 16:33 [PATCH v3 0/2] landlock: clarify TTY signal scoping Christopher Lusk 2026-09-23 16:33 ` [PATCH v3 1/2] docs: " Christopher Lusk 2026-09-23 16:39 ` sashiko-bot 2026-09-23 16:33 ` [PATCH v3 2/2] selftests/landlock: cover " Christopher Lusk 2026-09-23 16:42 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox