Hello Shi!
On Sat, Nov 07, 2026 at 12:23:35PM +0800, Shi Lecun wrote:
While investigating LANDLOCK_SCOPE_SIGNAL, I noticed that the current
documentation does not describe how signal scoping interacts with
asynchronous notifications registered before the scope is enforced.
A minimal reproducer performs the following steps as an unprivileged user:
1. Create a pipe and use fcntl(F_SETOWN) to select a same-UID process
outside the future Landlock domain as the notification recipient.
2. Configure F_SETSIG and O_ASYNC.
3. Add a Landlock layer handling LANDLOCK_SCOPE_SIGNAL.
4. Write to the pipe.
The configured signal is still delivered to the out-of-domain process.
Using F_SETSIG(SIGKILL) makes this especially visible by terminating the
recipient. In contrast:
- kill(2) after enforcing LANDLOCK_SCOPE_SIGNAL fails with EPERM;
- registering f_owner after enforcing the scope does not result in
out-of-domain signal delivery.
I reproduced this matrix on 7.2.14-arch1-1 with Landlock ABI 10 as UID
1000:
direct kill after scope victim survives
post-scope F_SETOWN victim survives
pre-scope F_SETOWN victim receives SIGKILL
FS layer, F_SETOWN, scope victim receives SIGKILL
I also inspected a local kernel tree at commit d68ce4e71248.
From security/landlock/fs.c and security/landlock/task.c, my
understanding is that hook_file_set_fowner() saves the applicable
Landlock subject when F_SETOWN is called, and
hook_file_send_sigiotask() later checks delivery using that saved
subject. If no signal-scoped domain applied at registration time, the
saved domain is NULL and later delivery is not checked against the
triggering task's newly added domain. The usual UID/EUID checks in
sigio_perm() still apply.
This also appears to be intentional and covered by the existing
fown/sandbox_after_setown variant in
tools/testing/selftests/landlock/scoped_signal_test.c, which expects a
signal to be received when F_SETOWN happened before sandboxing. I
therefore do not consider this behavior itself to be a signal-scope
bypass.
However, Documentation/userspace-api/landlock.rst currently says:
"This limits the sending of signals to target processes which run
within the same or a nested Landlock domain."
Thanks for the investigation!
The F_SETOWN signaling restriction was not initially covered correctly
by Landlock's signal scoping support, and was discovered and fixed by
Bryam Vargas in [1].
Another signaling-related report we received was about pseudoterminals,
with a documentation patch in the same place, in [2].
It feels that in both the F_SETOWN and the PTY case, the underlying
rationale is the same though -- a signaling mechanism that was set up
prior or independent to Landlock enforcement continues to work.
Without a lifecycle qualification, this can be read as covering every
signal delivered after the scope is enforced, including notifications
whose f_owner was registered earlier. The same section explicitly
documents the lifetime exception for previously connected UNIX sockets,
but there is no corresponding explanation for asynchronous signals.
Would it make sense to add a paragraph along these lines?
Asynchronous signal notifications associated with a file owner, such
as SIGIO and SIGURG, are checked using the Landlock context saved
when fcntl(F_SETOWN) sets the notification recipient. Adding a
LANDLOCK_SCOPE_SIGNAL restriction later does not apply that new
restriction to notifications using the previously saved context.
Sandboxes that require signal isolation should therefore enforce
the scope before untrusted code can register asynchronous
notification recipients, and account for any pre-existing
registrations on retained file descriptors.
Does this accurately describe the intended userspace semantics?
If so, I would be happy to send a documentation patch for the IPC
scoping section.
Your writeup matches my understanding as well, but I am a bit worried
that these corner cases start taking more room in the documentation
than the main point of what that feature does. How about something
shorter, maybe:
For signaling of I/O availability (F_GETSIG(2const)), Landlock
checks are done with respect to the Landlock domain at the time of
fcntl(F_SETOWN).
?
It feels better to link to the existing documentation for such
features instead of trying to describe them correctly.
–Günther
[1] https://lore.kernel.org/all/cover.1780614610.git.hexlabsecurity@proton.me/ (local)
[2] https://lore.kernel.org/all/20260923163321.79770-2-clusk@northecho.dev/ (local)