drivers/tty/pty.c | 8 +- include/linux/sched/signal.h | 1 + kernel/signal.c | 30 +++- .../selftests/landlock/scoped_signal_test.c | 142 ++++++++++++++++++ 4 files changed, 177 insertions(+), 4 deletions(-)
Landlock documents LANDLOCK_SCOPE_SIGNAL as limiting signal delivery to
processes in the same or a nested Landlock domain. A retained PTY master
can currently use TIOCSIG to deliver SIGINT, SIGQUIT, or SIGTSTP to an
out-of-domain slave foreground process group because the privileged TTY
signal path never reaches security_task_kill().
This RFC asks two questions before proposing a final interface.
First, should this be classified as SCOPE_SIGNAL under-enforcement, or as
part of Landlock's documented inherited-TTY limitation? The "Current
limitations / IOCTL support" section says that IOCTL_DEV does not affect
pre-existing descriptors, names TIOCSTI and TIOCLINUX, and recommends
closing inherited TTY descriptors. That text discusses the filesystem
IOCTL_DEV right rather than SCOPE_SIGNAL, and unlike the two named ioctls,
TIOCSIG is not CAP_SYS_ADMIN-gated. Commit 4b80320ca7ed fixed the same
effect-level class for SIGIO rather than treating the retained signal
source as exempt.
Second, if this is a bug, should TIOCSIG use the existing task_kill hook as
patch 1 demonstrates, or should it gain a dedicated TTY-signal hook which
Landlock can implement without changing other LSM policies? The prototype
is atomic with process-group delivery and behaviorally narrow to TIOCSIG,
but calling task_kill means SELinux, Smack, AppArmor, BPF LSM programs, and
future implementations also mediate this operation. The series does not
claim that cross-LSM policy change is settled.
The demonstrated generic impact is low:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:L = 3.8. Scope is changed
because the effect reaches a process outside the sandbox authority, but
the primitive is limited to three job-control signals and no independent
integrity impact has been reproduced.
Patch 1 is the behaviorally validated proof-of-concept fix. Patch 2 is a
minimal regression test; further test polishing should follow the chosen
interface direction.
Validation used the same userspace image against the affected and patched
kernels. Across three boots per image and 32 iterations per cell:
affected: 96/96 cross-domain TIOCSIG deliveries
patched: 96/96 cross-domain TIOCSIG denials
both: 96/96 unconfined deliveries
96/96 same-domain deliveries
96/96 scoped direct-kill denials
The regression test separately fails on the affected image and passes on
the patched image, with exactly one TAP test executed in each run.
No external report or patch has been sent before this RFC. Guidance on
both classification and hook direction would be appreciated.
Christopher Lusk (2):
tty: mediate TIOCSIG through task_kill LSM hooks
selftests/landlock: cover TIOCSIG signal scoping
drivers/tty/pty.c | 8 +-
include/linux/sched/signal.h | 1 +
kernel/signal.c | 30 +++-
.../selftests/landlock/scoped_signal_test.c | 142 ++++++++++++++++++
4 files changed, 177 insertions(+), 4 deletions(-)
--
2.55.0
Hello Christopher!
On Sun, Sep 13, 2026 at 06:19:56PM -0400, Christopher Lusk wrote:
> Landlock documents LANDLOCK_SCOPE_SIGNAL as limiting signal delivery to
> processes in the same or a nested Landlock domain. A retained PTY master
> can currently use TIOCSIG to deliver SIGINT, SIGQUIT, or SIGTSTP to an
> out-of-domain slave foreground process group because the privileged TTY
> signal path never reaches security_task_kill().
>
> This RFC asks two questions before proposing a final interface.
>
> First, should this be classified as SCOPE_SIGNAL under-enforcement, or as
> part of Landlock's documented inherited-TTY limitation? The "Current
> limitations / IOCTL support" section says that IOCTL_DEV does not affect
> pre-existing descriptors, names TIOCSTI and TIOCLINUX, and recommends
> closing inherited TTY descriptors. That text discusses the filesystem
> IOCTL_DEV right rather than SCOPE_SIGNAL, and unlike the two named ioctls,
> TIOCSIG is not CAP_SYS_ADMIN-gated. Commit 4b80320ca7ed fixed the same
> effect-level class for SIGIO rather than treating the retained signal
> source as exempt.
>
> Second, if this is a bug, should TIOCSIG use the existing task_kill hook as
> patch 1 demonstrates, or should it gain a dedicated TTY-signal hook which
> Landlock can implement without changing other LSM policies? The prototype
> is atomic with process-group delivery and behaviorally narrow to TIOCSIG,
> but calling task_kill means SELinux, Smack, AppArmor, BPF LSM programs, and
> future implementations also mediate this operation. The series does not
> claim that cross-LSM policy change is settled.
Thank you for bringing this up; I was not aware of this code path and
researched it a bit.
Let me try to paraphrase the issue to make sure I understand:
1. A process creates a PTY device and acquires the PTY master FD.
2. The process then restricts itself into a signal-scoped Landlock domain.
3. Processes outside of the domain are attached to the terminal
4. Through the PTY master FD, the master process emulates a terminal
to the attached processes. One of the commands it can issue is
TIOCSIG, allowing the PTY master process to send SIGINT ("Ctrl-C"),
SIGQUIT ("Ctrl-\") or SIGTSTP ("Ctrl-Z") to TTY-attached processes,
which may live *outside* the Landlock domain. (source:
pty_signal() in drivers/tty/pty.c)
TIOCSIG was introduced in 2010 in Linux [1] and in 1989 in BSD (quoted
in the Linux patch). According to the patch, it is only required in a
special terminal mode where the mapping of signals is disabled.
In more normal operation modes, the terminal interprets these keyboard
shortcuts sent as characters. This is implemented in
n_tty_receive_char_special() when you write() the characters '\x03'
(Ctrl-C), '\x1c' (Ctrl-\) or '\x1a' (Ctrl-Z) to the PTY master FD.
(Your proposed patch does not fix this either, even though it sends
the same signals.)
Additionally, PTYs can send:
* SIGWINCH (when you do ioctl(TIOCSWINSZ) on the master FD)
* SIGHUP and SIGCONT
[1] https://lore.kernel.org/all/E1OR73h-0004VN-JE@lirone.symas.net/
In summary:
* Sending signals to attached processes is a very normal way how PTYs
interact with the attached processes, and TIOCSIG is not the only
cause for it.
* Processes are attached to a PTY because they were started on that
PTY or they have voluntarily attached to it.
With these two points in mind, I am leaning towards treating access to
the PTY master FD as a "capability" whose acquisition can already be
adequately restricted with existing Landlock controls. Like
socketpair(), which Landlock also don't restrict, the creation of a
new PTY always returns a new master and client side TTY FD and it
feels to me more effective to control who attaches to these than to
control the TTY-internal communication protocols itself.
Maybe the way to think about this is to say that it is the *TTY
driver* which is sending these signals in response to the PTY master
FD receiving a TIOCSIG or having a Ctrl-C written to it. This is
similar to a SSH or Telnet daemon which can also trigger signals for
the attached processes on the other end of the terminal by sending the
right commands over the wire, and I also don't see an issue with that,
because in the same way as here, the client programs have voluntarily
attached to the TTY. 🤔
Does that seem reasonable? I am happy to be corrected if this
analysis is wrong.
If you agree, I think the best path forward might be to document it
more clearly that TTY interactions are not part of the SCOPE_SIGNAL
guarantees.
–Günther
> The demonstrated generic impact is low:
> CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:L = 3.8. Scope is changed
> because the effect reaches a process outside the sandbox authority, but
> the primitive is limited to three job-control signals and no independent
> integrity impact has been reproduced.
>
> Patch 1 is the behaviorally validated proof-of-concept fix. Patch 2 is a
> minimal regression test; further test polishing should follow the chosen
> interface direction.
>
> Validation used the same userspace image against the affected and patched
> kernels. Across three boots per image and 32 iterations per cell:
>
> affected: 96/96 cross-domain TIOCSIG deliveries
> patched: 96/96 cross-domain TIOCSIG denials
> both: 96/96 unconfined deliveries
> 96/96 same-domain deliveries
> 96/96 scoped direct-kill denials
>
> The regression test separately fails on the affected image and passes on
> the patched image, with exactly one TAP test executed in each run.
>
> No external report or patch has been sent before this RFC. Guidance on
> both classification and hook direction would be appreciated.
>
> Christopher Lusk (2):
> tty: mediate TIOCSIG through task_kill LSM hooks
> selftests/landlock: cover TIOCSIG signal scoping
>
> drivers/tty/pty.c | 8 +-
> include/linux/sched/signal.h | 1 +
> kernel/signal.c | 30 +++-
> .../selftests/landlock/scoped_signal_test.c | 142 ++++++++++++++++++
> 4 files changed, 177 insertions(+), 4 deletions(-)
>
> --
> 2.55.0
Hello Günther, Thanks for digging into this so carefully, and for the clear write-up. Your capability framing convinces me. Controlling who may attach to (or open a master for) the PTY is the right layer, and the master FD is best thought of as the capability, the same way socketpair() is. You are also right that the series is incomplete as a fix: the same three signals arrive through the n_tty control-character path (Ctrl-C / Ctrl-\ / Ctrl-Z) that my patch does not touch, and PTYs additionally raise SIGWINCH, SIGHUP and SIGCONT. That reinforces your point rather than mine. Chasing individual signal-delivery paths inside the TTY layer is the wrong layer, and a per-ioctl hook would only paper over one entry into a mechanism that is working as designed. One question, mostly so I have the line right in my own notes rather than to relitigate: how do you see this relative to the SIGIO/fowner path that 4b80320ca7ed brought under SCOPE_SIGNAL? My read of the distinction is that in the SIGIO case the sandboxed process unilaterally selects the target by arming the owner, whereas here the recipients have voluntarily attached to the terminal and the TTY driver delivers job-control signals over that attachment. If that is the intended boundary, it is a clean one, and I am happy to treat TTY-driven signals as outside the guarantee. If it is useful, I would be glad to send a small documentation patch making that explicit: a note in the SCOPE_SIGNAL / IPC-scoping section of landlock.rst that TTY-driver signal delivery (TIOCSIG and the control-character path) is not mediated by SCOPE_SIGNAL, with the practical guidance to control PTY attachment instead. I will drop the task_kill approach. Thanks again for the thorough look. Christopher
Hi Christopher, I agree with Günther and I'd like to bring, from my point of view, the most important thing about TTY master FDs: they are mean to send arbitrary data/signals to TTY slaves, and in most cases this mean inject keys and then commands. So TTY master FDs should be seen as a (legitimate) way to control another process, even if that might not always be the case. A case in point would be an SSH or sudo/su command attached to a slave TTY. The process able to write on the related master TTY FD should be considered as privileged as the slave command. This illustrates why human-computer interfaces (including TTYs, desktop interfaces...) are so critical. Also, just adding these LSM hook changes would break exisiting LSM's security policies. Updating the Landlock doc with this rationale is defenitely a good idea. Testing this invariant is also valiable, so it would be nice to tweak a bit your test and include it with the doc update. Regards, Mickaël On Mon, Sep 14, 2026 at 09:40:13AM -0400, Christopher Lusk wrote: > Hello Günther, > > Thanks for digging into this so carefully, and for the clear write-up. > > Your capability framing convinces me. Controlling who may attach to (or > open a master for) the PTY is the right layer, and the master FD is best > thought of as the capability, the same way socketpair() is. > > You are also right that the series is incomplete as a fix: the same three > signals arrive through the n_tty control-character path (Ctrl-C / Ctrl-\ / > Ctrl-Z) that my patch does not touch, and PTYs additionally raise SIGWINCH, > SIGHUP and SIGCONT. That reinforces your point rather than mine. Chasing > individual signal-delivery paths inside the TTY layer is the wrong layer, > and a per-ioctl hook would only paper over one entry into a mechanism that > is working as designed. > > One question, mostly so I have the line right in my own notes rather than > to relitigate: how do you see this relative to the SIGIO/fowner path that > 4b80320ca7ed brought under SCOPE_SIGNAL? My read of the distinction is > that in the SIGIO case the sandboxed process unilaterally selects the target > by arming the owner, whereas here the recipients have voluntarily attached > to the terminal and the TTY driver delivers job-control signals over that > attachment. If that is the intended boundary, it is a clean one, and I am > happy to treat TTY-driven signals as outside the guarantee. > > If it is useful, I would be glad to send a small documentation patch making > that explicit: a note in the SCOPE_SIGNAL / IPC-scoping section of > landlock.rst that TTY-driver signal delivery (TIOCSIG and the > control-character path) is not mediated by SCOPE_SIGNAL, with the practical > guidance to control PTY attachment instead. I will drop the task_kill > approach. > > Thanks again for the thorough look. > > Christopher >
Hello! On Mon, Sep 14, 2026 at 09:40:13AM -0400, Christopher Lusk wrote: > Your capability framing convinces me. Controlling who may attach to (or > open a master for) the PTY is the right layer, and the master FD is best > thought of as the capability, the same way socketpair() is. > > You are also right that the series is incomplete as a fix: the same three > signals arrive through the n_tty control-character path (Ctrl-C / Ctrl-\ / > Ctrl-Z) that my patch does not touch, and PTYs additionally raise SIGWINCH, > SIGHUP and SIGCONT. That reinforces your point rather than mine. Chasing > individual signal-delivery paths inside the TTY layer is the wrong layer, > and a per-ioctl hook would only paper over one entry into a mechanism that > is working as designed. > > One question, mostly so I have the line right in my own notes rather than > to relitigate: how do you see this relative to the SIGIO/fowner path that > 4b80320ca7ed brought under SCOPE_SIGNAL? My read of the distinction is > that in the SIGIO case the sandboxed process unilaterally selects the target > by arming the owner, whereas here the recipients have voluntarily attached > to the terminal and the TTY driver delivers job-control signals over that > attachment. If that is the intended boundary, it is a clean one, and I am > happy to treat TTY-driven signals as outside the guarantee. Yes, that is the difference why SIGIO had to be protected -- in the SIGIO case, it was the already landlocked process which could itself select the signal recipients through fcntl(fd, F_SETOWN, ...). In the terminal case, it is the TTY-client-side processes that select which processes are attached to the terminal. With the PTY master FD alone, it is not possible to signal processes that aren't already attached to the terminal. > If it is useful, I would be glad to send a small documentation patch making > that explicit: a note in the SCOPE_SIGNAL / IPC-scoping section of > landlock.rst that TTY-driver signal delivery (TIOCSIG and the > control-character path) is not mediated by SCOPE_SIGNAL, with the practical > guidance to control PTY attachment instead. I will drop the task_kill > approach. Thank you, I would appreciate that! Thanks, –Günther
LANDLOCK_SCOPE_SIGNAL mediates signal delivery when a sandboxed process
selects the recipient, including SIGIO through fowner. It does not mediate
signals directed by the TTY layer to processes attached to a terminal in
response to terminal activity. This distinction was clarified while
discussing TIOCSIG handling because the PTY master acts as a capability and
the signal recipients have attached to the terminal.
Document the TTY-driven signal paths which are outside the scope and advise
controlling access to the terminal or PTY master instead. This records the
outcome of the RFC discussion and avoids implying that
LANDLOCK_SCOPE_SIGNAL covers every signal-delivery mechanism.
The documentation text and changelog were drafted with assistance from
Claude (claude-opus-4-8) and Codex (gpt-5.6-sol).
The userspace API documentation builds successfully with the kernel-pinned
Sphinx dependencies. The remaining warnings are unrelated to the changed
Landlock text.
Link: https://lore.kernel.org/r/20260914.b8a029f9abb8@gnoack.org
Suggested-by: Günther Noack <gnoack@google.com>
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Christopher Lusk <clusk@northecho.dev>
---
Documentation/userspace-api/landlock.rst | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 84cb7bf6b3ed..64418b09840d 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -430,6 +430,21 @@ 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.
+ This scope does not cover signals delivered by the TTY layer. A process
+ holding a PTY master, or otherwise driving a terminal, can cause the TTY
+ driver to deliver signals to processes attached to that terminal across
+ Landlock domain boundaries. This includes ``SIGINT``, ``SIGQUIT``, and
+ ``SIGTSTP`` via the ``TIOCSIG`` :manpage:`ioctl(2)` command or the
+ corresponding control characters. The TTY layer may also deliver
+ ``SIGWINCH``, ``SIGHUP``, and ``SIGCONT``.
+
+ These signals originate from the TTY driver in response to terminal
+ activity rather than from a :manpage:`kill(2)`-style request. They can
+ only reach processes attached to the terminal. To restrict this
+ interaction, control possession of the PTY master and terminal attachment.
+ For example, do not pass a PTY master to a sandboxed process if its slave
+ has processes from outside the Landlock domain attached to it.
+
``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
--
2.55.0
Hello!
Thanks for the review!
On Mon, Sep 14, 2026 at 02:09:46PM -0400, Christopher Lusk wrote:
> LANDLOCK_SCOPE_SIGNAL mediates signal delivery when a sandboxed process
> selects the recipient, including SIGIO through fowner. It does not mediate
> signals directed by the TTY layer to processes attached to a terminal in
> response to terminal activity. This distinction was clarified while
> discussing TIOCSIG handling because the PTY master acts as a capability and
> the signal recipients have attached to the terminal.
>
> Document the TTY-driven signal paths which are outside the scope and advise
> controlling access to the terminal or PTY master instead. This records the
> outcome of the RFC discussion and avoids implying that
> LANDLOCK_SCOPE_SIGNAL covers every signal-delivery mechanism.
>
> The documentation text and changelog were drafted with assistance from
> Claude (claude-opus-4-8) and Codex (gpt-5.6-sol).
>
> The userspace API documentation builds successfully with the kernel-pinned
> Sphinx dependencies. The remaining warnings are unrelated to the changed
> Landlock text.
>
> Link: https://lore.kernel.org/r/20260914.b8a029f9abb8@gnoack.org
> Suggested-by: Günther Noack <gnoack@google.com>
> Assisted-by: Claude:claude-opus-4-8
> Assisted-by: Codex:gpt-5.6-sol
> Signed-off-by: Christopher Lusk <clusk@northecho.dev>
> ---
> Documentation/userspace-api/landlock.rst | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index 84cb7bf6b3ed..64418b09840d 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -430,6 +430,21 @@ 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.
>
> + This scope does not cover signals delivered by the TTY layer. A process
> + holding a PTY master, or otherwise driving a terminal, can cause the TTY
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
What other ways of driving a terminal are you alluding to? (I assume
the LLM wrote that? Did it make that up, or does it have another trick
up its sleeve for sending signals that we are overlooking?)
> + driver to deliver signals to processes attached to that terminal across
> + Landlock domain boundaries. This includes ``SIGINT``, ``SIGQUIT``, and
> + ``SIGTSTP`` via the ``TIOCSIG`` :manpage:`ioctl(2)` command or the
> + corresponding control characters. The TTY layer may also deliver
> + ``SIGWINCH``, ``SIGHUP``, and ``SIGCONT``.
> +
> + These signals originate from the TTY driver in response to terminal
> + activity rather than from a :manpage:`kill(2)`-style request. They can
> + only reach processes attached to the terminal. To restrict this
> + interaction, control possession of the PTY master and terminal attachment.
> + For example, do not pass a PTY master to a sandboxed process if its slave
> + has processes from outside the Landlock domain attached to it.
> +
LANDLOCK_SCOPE_SIGNAL was previously described in two lines here.
Now we have 15 lines, 13 of which are talking exclusively about the PTY corner
case. I am afraid this will water down the main message here.
I understand that LLMs can help in writing good English, but they also
have a tendency to be much more verbose than the existing text and can
direct the reader's attention away from the main points with that.
Suggested replacement:
Holding a PTY master FD still grants the capability to issue signals
through that PTY to the processes running under that terminal.
Does that seem reasonable?
Listing the full list of signals that can be sent through a PTY seems like an
excessive level of detail here. But if you find a reference listing
the same signals in kernel doc or a stable URL, we could link it from
the docs.
Please also add an (even shorter) remark to the landlock.h header
file's description of LANDLOCK_SCOPE_SIGNAL, similar in length to the
one we added for the whiteout objects for LANDLOCK_ACCESS_FS_MAKE_REG
recently.
> ``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
> --
> 2.55.0
>
Some meta-level remarks:
* Please send new patch sets as top-level emails rather than as
responses to existing mail threads. (It is not technically wrong to
do that, but they do get overlooked within mail threads more often.
To connect the dots, you can link the original mail on lore as
you've already done here as well.)
* *If you want*, a thing that would still be worthwhile having in code
would be a regression selftest for this signal sending path. This
is similar to the tests you've already created in your initial
patchset, but would now check that signal sending *works* despite
the sender being in the scoped domain.
(For transparency, I should remark that the final decision on this
code review is still up to Mickaël in the end. I do believe that this
approach is the best solution, but the reasoning is less clear-cut
than in other bugs we had before.)
Thanks,
—Günther
Hello Günther, Thank you for the careful review. The phrase "or otherwise driving a terminal" was LLM over-generalization, not a separate mechanism. I checked the paths I had in mind against the source, and none justifies that broader wording. I am dropping it. You are also right about the verbosity. The generated text expanded a two-line distinction into 15 lines and obscured the main point. I will use your suggested sentence and remove the signal enumeration. I do not have a stable kernel documentation URL for that list, so I will not add a citation. v2 will contain your sentence and a short LANDLOCK_SCOPE_SIGNAL remark in landlock.h, matched to the recent MAKE_REG whiteout remark. I will post it as a new top-level patch with a Link back to this discussion. I will keep the positive selftest you suggested as a separate follow-up so the documentation clarification can be reviewed independently. Christopher
On Wed, Sep 16, 2026 at 10:52:55AM -0400, Christopher Lusk wrote:
> Hello Günther,
>
> Thank you for the careful review.
>
> The phrase "or otherwise driving a terminal" was LLM over-generalization,
> not a separate mechanism. I checked the paths I had in mind against the
> source, and none justifies that broader wording. I am dropping it.
Thanks for double checking the wording, sounds good.
> You are also right about the verbosity. The generated text expanded a
> two-line distinction into 15 lines and obscured the main point. I will use
> your suggested sentence and remove the signal enumeration. I do not have a
> stable kernel documentation URL for that list, so I will not add a citation.
>
> v2 will contain your sentence and a short LANDLOCK_SCOPE_SIGNAL remark in
> landlock.h, matched to the recent MAKE_REG whiteout remark. I will post it
> as a new top-level patch with a Link back to this discussion.
>
> I will keep the positive selftest you suggested as a separate follow-up so
> the documentation clarification can be reviewed independently.
Sounds good as well.
—Günther
© 2016 - 2026 Red Hat, Inc.