* [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope
@ 2026-03-15 22:21 Günther Noack
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
` (8 more replies)
0 siblings, 9 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen, Paul Moore, James Morris,
Serge E . Hallyn
Cc: Günther Noack, linux-security-module, Tingmao Wang,
Justin Suess, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
Hello!
This patch set introduces a filesystem-based Landlock restriction
mechanism for connecting to UNIX domain sockets (or addressing them
with sendmsg(2)). It introduces the filesystem access right
LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
For the connection-oriented SOCK_STREAM and SOCK_SEQPACKET type
sockets, the access right makes the connect(2) operation fail with
EACCES, if denied.
SOCK_DGRAM-type UNIX sockets can be used both with connect(2), or by
passing an explicit recipient address with every sendmsg(2)
invocation. In the latter case, the Landlock check is done when an
explicit recipient address is passed to sendmsg(2) and can make
sendmsg(2) return EACCES. When UNIX datagram sockets are connected
with connect(2), a fixed recipient address is associated with the
socket and the check happens during connect(2) and may return EACCES.
When LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled within a Landlock
domain, this domain will only allow connect(2) and sendmsg(2) to
server sockets that were created within the same domain. Or, to
phrase it the other way around: Unless it is allow-listed with a
LANDLOCK_PATH_BENEATH rule, the newly created domain denies connect(2)
and sendmsg(2) actions that are directed *outwards* of that domain.
In that regard, LANDLOCK_ACCESS_FS_RESOLVE_UNIX has the same semantics
as one of the "scoped" access rights.
== Motivation
Currently, landlocked processes can connect to named UNIX sockets
through the BSD socket API described in unix(7), by invoking socket(2)
followed by connect(2) with a suitable struct sockname_un holding the
socket's filename. This is a surprising gap in Landlock's sandboxing
capabilities for users (e.g. in [1]) and it can be used to escape a
sandbox when a Unix service offers command execution (various such
scenarios were listed by Tingmao Wang in [2]).
The original feature request is at [4].
== Alternatives and Related Work
=== Alternative: Use existing LSM hooks
We have carefully and seriously considered the use of existing LSM
hooks, but still came to the conclusion that a new LSM hook is better
suited in this case:
The existing hooks security_unix_stream_connect(),
security_unix_may_send() and security_socket_connect() do not give
access to the resolved filesystem path.
* Resolving the filesystem path in the struct sockaddr_un again within
a Landlock would produce a TOCTOU race, so this is not an option.
* We would therefore need to wire through the resolved struct path
from unix_find_bsd() to one of the existing LSM hooks which get
called later. This would be a more substantial change to af_unix.c.
The struct path that is available in the listening-side struct sock is
can be read through the existing hooks, but it is not an option to use
this information: As the listening socket may have been bound from
within a different namespace, the path that was used for that can is
in the general case not meaningful for a sandboxed process. In
particular, it is not possible to use this path (or prefixes thereof)
when constructing a sandbox policy in the client-side process.
Paul Moore also chimed in in support of adding a new hook, with the
rationale that the simplest change to the LSM hook interface has
traditionally proven to be the most robust. [11]
More details are on the Github issue at [6] and on the LKML at [9].
In a the discussion of the V2 review, started by Christian Brauner
[10], we have further explored the approach of reusing the existing
LSM hooks but still ended up leaning on the side of introducing a new
hook, with Paul Moore and me (gnoack) arguing for that option.
Further insights about the LSM hook were shared in the V3 review by
Tingmao Wang [12], who spotted additional requirements due to the two
approaches being merged into one patch set. The summary of that
discussion is in [13].
=== Related work: Scope Control for Pathname Unix Sockets
The motivation for this patch is the same as in Tingmao Wang's patch
set for "scoped" control for pathname Unix sockets [2], originally
proposed in the Github feature request [5].
In [14], we have settled on the decision to merge the two patch sets
into this one, whose primary way of controlling connect(2) is
LANDLOCK_ACCESS_FS_RESOLVE_UNIX, but where this flag additionally has
the semantics of only restricting this unix(7) IPC *outwards* of the
created Landlock domain, in line with the logic that exists for the
existing "scoped" flags already.
By having LANDLOCK_ACCESS_FS_RESOLVE_UNIX implement "scoping"
semantics, we can avoid introducing two separate interacting flags for
now, but we retain the option of introducing
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET at a later point in time, should
such a flag be needed to express additional rules.
== Credits
The feature was originally suggested by Jann Horn in [7].
Tingmao Wang and Demi Marie Obenour have taken the initiative to
revive this discussion again in [1], [4] and [5].
Tingmao Wang has sent the patch set for the scoped access control for
pathname Unix sockets [2] and has contributed substantial insights
during the code review, shaping the form of the LSM hook and agreeing
to merge the pathname and scoped-flag patch sets.
Justin Suess has sent the patch for the LSM hook in [8] and
subsequently through this patch set.
Christian Brauner and Paul Moore have contributed to the design of the
new LSM hook, discussing the tradeoffs in [10].
Sebastian Andrzej Siewior and Kuniyuki Iwashima have helped with
locking questions in the networking subsystem. [15] [16]
Ryan Sullivan has started on an initial implementation and has brought
up relevant discussion points on the Github issue at [4].
As maintainer of Landlock, Mickaël Salaün has done the main review so
far and particularly pointed out ways in which the UNIX connect()
patch sets interact with each other and what we need to look for with
regards to UAPI consistency as Landlock evolves.
[1] https://lore.kernel.org/landlock/515ff0f4-2ab3-46de-8d1e-5c66a93c6ede@gmail.com/
[2] Tingmao Wang's "Implement scope control for pathname Unix sockets"
https://lore.kernel.org/all/cover.1767115163.git.m@maowtm.org/
[3] https://lore.kernel.org/all/20251230.bcae69888454@gnoack.org/
[4] Github issue for FS-based control for named Unix sockets:
https://github.com/landlock-lsm/linux/issues/36
[5] Github issue for scope-based restriction of named Unix sockets:
https://github.com/landlock-lsm/linux/issues/51
[6] https://github.com/landlock-lsm/linux/issues/36#issuecomment-2950632277
[7] https://lore.kernel.org/linux-security-module/CAG48ez3NvVnonOqKH4oRwRqbSOLO0p9djBqgvxVwn6gtGQBPcw@mail.gmail.com/
[8] Patch for the LSM hook:
https://lore.kernel.org/all/20251231213314.2979118-1-utilityemal77@gmail.com/
[9] https://lore.kernel.org/all/20260108.64bd7391e1ae@gnoack.org/
[10] https://lore.kernel.org/all/20260113-kerngesund-etage-86de4a21da24@brauner/
[11] https://lore.kernel.org/all/CAHC9VhQHZCe0LMx4xzSo-h1SWY489U4frKYnxu4YVrcJN3x7nA@mail.gmail.com/
[12] https://lore.kernel.org/all/e6b6b069-384c-4c45-a56b-fa54b26bc72a@maowtm.org/
[13] https://lore.kernel.org/all/aYMenaSmBkAsFowd@google.com/
[14] https://lore.kernel.org/all/20260205.Kiech3gupee1@digikod.net/
[15] https://lore.kernel.org/all/20260310151907.VYySCtJp@linutronix.de/
[16] https://lore.kernel.org/all/CAAVpQUC95mSjX1vRK===pubHofcYqbkNE7goYKiu6vha5GYAFw@mail.gmail.com/
---
== Patch set history
V1: https://lore.kernel.org/all/20260101134102.25938-1-gnoack3000@gmail.com/
V2: https://lore.kernel.org/all/20260110143300.71048-2-gnoack3000@gmail.com/
V3: https://lore.kernel.org/all/20260119203457.97676-2-gnoack3000@gmail.com/
V4: https://lore.kernel.org/all/20260208231017.114343-1-gnoack3000@gmail.com/
V5: https://lore.kernel.org/all/20260215105158.28132-1-gnoack3000@gmail.com/
Changes in V6:
* Implementation:
* Move the LSM hook call after the check that checks for the other
end's matching socket type. (Justin Suess)
* Lock with unix_state_lock() and check SOCK_DEAD.
* Remove unnecessary layer_access_masks_empty() call (and its
implementation).
* Documentation:
* Squash docs with design rationale into main implementation commit,
and cross-referece it from the header docs.
* Clarify that denials result in EACCES and that this is consistent
with other filesystem access rights.
* Minor:
* Use mem_is_zero() in is_layer_masks_allowed() (minor cleanup)
* Omit unnecessary __attribute__((fallthrough)) usages
* Remove comment at the end of a line in a place.
* Selftests:
* sun_path population fixes
* coredump test: Set EUID to 0 (needed for UML-based selftests)
Link[1]: https://lore.kernel.org/all/20260218.ohth8theu8Yi@digikod.net/
Changes in V5:
This change primarily adds tests, changing the testing approach for
the main test to use the scoped_domains fixture as in Tingmao's patch
set [2], and adding tests for the audit and coredump cases.
* Selftests:
* Replaced the main selftest with one based on scoped_domains
* Added audit test
* Added test for the coredump case
* Added a follow-up commit that simplifies ruleset enforcement
* Kernel code:
* Mark coredump check as unlikely (per Justin's review)
* Drop check for socket type (per Mickaël's review)
Changes in V4:
Since this version, this patch set subsumes the scoping semantics from
Tingmao Wang's "Scope Control" patch set [2], per discussion with
Tingmao Wang and Mickaël Salaün in [14] and in the thread leading up
to it.
Now, LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET only restricts connect(2) and
sendmsg(2) *outwards* of the domain where it is restricted, *with the
same semantics as a "scoped" flag*.
* Implement a layer-mask based version of domain_is_scoped():
unmask_scoped_access(). Rationale: domain_is_scoped() returns
early, which we can't do in the layer masks based variant. The two
variants are similar enough.
* LSM hook: Replace 'type' argument with 'sk' argument,
per discussion in [12] and [13].
* Bump ABI version to 9 (pessimistically assuming that we won't make
it for 7.0)
* Documentation fixes in header file and in Documentation/
* selftests: more test variants, now also parameterizing whether the
server socket gets created within the Landlock domain or before that
* selftests: use EXPECT_EQ() for test cleanup
Changes in V3:
* LSM hook: rename it to security_unix_find() (Justin Suess)
(resolving the previously open question about the LSM hook name)
Related discussions:
https://lore.kernel.org/all/20260112.Wufar9coosoo@digikod.net/
https://lore.kernel.org/all/CAHC9VhSRiHwLEWfFkQdPEwgB4AXKbXzw_+3u=9hPpvUTnu02Bg@mail.gmail.com/
* Reunite the three UNIX resolving access rights back into one
(resolving the previously open question about the access right
structuring) Related discussion:
https://lore.kernel.org/all/20260112.Wufar9coosoo@digikod.net/)
* Sample tool: Add new UNIX lookup access rights to ACCESS_FILE
Changes in V2:
* Send Justin Suess's LSM hook patch together with the Landlock
implementation
* LSM hook: Pass type and flags parameters to the hook, to make the
access right more generally usable across LSMs, per suggestion from
Paul Moore (Implemented by Justin)
* Split the access right into the three types of UNIX domain sockets:
SOCK_STREAM, SOCK_DGRAM and SOCK_SEQPACKET.
* selftests: More exhaustive tests.
* Removed a minor commit from V1 which adds a missing close(fd) to a
test (it is already in the mic-next branch)
Günther Noack (8):
landlock: use mem_is_zero() in is_layer_masks_allowed()
landlock: Control pathname UNIX domain socket resolution by path
samples/landlock: Add support for named UNIX domain socket
restrictions
landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX
landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX
landlock/selftests: Check that coredump sockets stay unrestricted
landlock/selftests: fs_test: Simplify ruleset creation and enforcement
landlock: Document FS access right for pathname UNIX sockets
Justin Suess (1):
lsm: Add LSM hook security_unix_find
Documentation/security/landlock.rst | 40 +
Documentation/userspace-api/landlock.rst | 15 +-
include/linux/lsm_hook_defs.h | 5 +
include/linux/security.h | 11 +
include/uapi/linux/landlock.h | 19 +
net/unix/af_unix.c | 13 +-
samples/landlock/sandboxer.c | 12 +-
security/landlock/access.h | 2 +-
security/landlock/audit.c | 1 +
security/landlock/fs.c | 112 +-
security/landlock/limits.h | 2 +-
security/landlock/syscalls.c | 2 +-
security/security.c | 20 +
tools/testing/selftests/landlock/base_test.c | 2 +-
tools/testing/selftests/landlock/fs_test.c | 1329 ++++++++++--------
15 files changed, 979 insertions(+), 606 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-17 21:14 ` Mickaël Salaün
` (2 more replies)
2026-03-15 22:21 ` [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed() Günther Noack
` (7 subsequent siblings)
8 siblings, 3 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen, Paul Moore, James Morris,
Serge E . Hallyn
Cc: Günther Noack, Tingmao Wang, Justin Suess,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
From: Justin Suess <utilityemal77@gmail.com>
Add a LSM hook security_unix_find.
This hook is called to check the path of a named unix socket before a
connection is initiated. The peer socket may be inspected as well.
Why existing hooks are unsuitable:
Existing socket hooks, security_unix_stream_connect(),
security_unix_may_send(), and security_socket_connect() don't provide
TOCTOU-free / namespace independent access to the paths of sockets.
(1) We cannot resolve the path from the struct sockaddr in existing hooks.
This requires another path lookup. A change in the path between the
two lookups will cause a TOCTOU bug.
(2) We cannot use the struct path from the listening socket, because it
may be bound to a path in a different namespace than the caller,
resulting in a path that cannot be referenced at policy creation time.
Cc: Günther Noack <gnoack3000@gmail.com>
Cc: Tingmao Wang <m@maowtm.org>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
include/linux/lsm_hook_defs.h | 5 +++++
include/linux/security.h | 11 +++++++++++
net/unix/af_unix.c | 13 ++++++++++---
security/security.c | 20 ++++++++++++++++++++
4 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c..7a0fd3dbfa29 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -317,6 +317,11 @@ LSM_HOOK(int, 0, post_notification, const struct cred *w_cred,
LSM_HOOK(int, 0, watch_key, struct key *key)
#endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
+#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
+LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
+ int flags)
+#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
+
#ifdef CONFIG_SECURITY_NETWORK
LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
struct sock *newsk)
diff --git a/include/linux/security.h b/include/linux/security.h
index 83a646d72f6f..99a33d8eb28d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1931,6 +1931,17 @@ static inline int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
}
#endif /* CONFIG_SECURITY_NETWORK */
+#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
+
+int security_unix_find(const struct path *path, struct sock *other, int flags);
+
+#else /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
+static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+ return 0;
+}
+#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
+
#ifdef CONFIG_SECURITY_INFINIBAND
int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey);
int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 3756a93dc63a..aced28179bac 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1231,11 +1231,18 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
goto path_put;
err = -EPROTOTYPE;
- if (sk->sk_type == type)
- touch_atime(&path);
- else
+ if (sk->sk_type != type)
goto sock_put;
+ /*
+ * We call the hook because we know that the inode is a socket and we
+ * hold a valid reference to it via the path.
+ */
+ err = security_unix_find(&path, sk, flags);
+ if (err)
+ goto sock_put;
+ touch_atime(&path);
+
path_put(&path);
return sk;
diff --git a/security/security.c b/security/security.c
index 67af9228c4e9..c73196b8db4b 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4731,6 +4731,26 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
#endif /* CONFIG_SECURITY_NETWORK */
+#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
+/**
+ * security_unix_find() - Check if a named AF_UNIX socket can connect
+ * @path: path of the socket being connected to
+ * @other: peer sock
+ * @flags: flags associated with the socket
+ *
+ * This hook is called to check permissions before connecting to a named
+ * AF_UNIX socket.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+ return call_int_hook(unix_find, path, other, flags);
+}
+EXPORT_SYMBOL(security_unix_find);
+
+#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
+
#ifdef CONFIG_SECURITY_INFINIBAND
/**
* security_ib_pkey_access() - Check if access to an IB pkey is allowed
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed()
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-18 16:52 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path Günther Noack
` (6 subsequent siblings)
8 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, linux-security-module, Tingmao Wang,
Justin Suess, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
This is equivalent, but expresses the intent a bit clearer.
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
security/landlock/fs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index a03ec664c78e..97065d51685a 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -564,7 +564,7 @@ static void test_no_more_access(struct kunit *const test)
static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
{
- return !memchr_inv(&masks->access, 0, sizeof(masks->access));
+ return mem_is_zero(&masks->access, sizeof(masks->access));
}
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
2026-03-15 22:21 ` [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed() Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-18 11:15 ` Sebastian Andrzej Siewior
2026-03-18 16:52 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 4/9] samples/landlock: Add support for named UNIX domain socket restrictions Günther Noack
` (5 subsequent siblings)
8 siblings, 2 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi
* Add a new access right LANDLOCK_ACCESS_FS_RESOLVE_UNIX, which
controls the look up operations for named UNIX domain sockets. The
resolution happens during connect() and sendmsg() (depending on
socket type).
* Hook into the path lookup in unix_find_bsd() in af_unix.c, using a
LSM hook. Make policy decisions based on the new access rights
* Increment the Landlock ABI version.
* Minor test adaptions to keep the tests working.
* Document the design rationale for scoped access rights,
and cross-reference it from the header documentation.
With this access right, access is granted if either of the following
conditions is met:
* The target socket's filesystem path was allow-listed using a
LANDLOCK_RULE_PATH_BENEATH rule, *or*:
* The target socket was created in the same Landlock domain in which
LANDLOCK_ACCESS_FS_RESOLVE_UNIX was restricted.
In case of a denial, connect() and sendmsg() return EACCES, which is
the same error as it is returned if the user does not have the write
bit in the traditional Unix file system permissions of that file.
Document the (possible future) interaction between scoped flags and
other access rights in struct landlock_ruleset_attr, and summarize the
rationale, as discussed in code review leading up to [2].
This feature was created with substantial discussion and input from
Justin Suess, Tingmao Wang and Mickaël Salaün.
Cc: Tingmao Wang <m@maowtm.org>
Cc: Justin Suess <utilityemal77@gmail.com>
Cc: Mickaël Salaün <mic@digikod.net>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Suggested-by: Jann Horn <jannh@google.com>
Link[1]: https://github.com/landlock-lsm/linux/issues/36
Link[2]: https://lore.kernel.org/all/20260205.8531e4005118@gnoack.org/
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
Documentation/security/landlock.rst | 40 +++++++
include/uapi/linux/landlock.h | 19 ++++
security/landlock/access.h | 2 +-
security/landlock/audit.c | 1 +
security/landlock/fs.c | 110 ++++++++++++++++++-
security/landlock/limits.h | 2 +-
security/landlock/syscalls.c | 2 +-
tools/testing/selftests/landlock/base_test.c | 2 +-
tools/testing/selftests/landlock/fs_test.c | 5 +-
9 files changed, 176 insertions(+), 7 deletions(-)
diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
index 3e4d4d04cfae..4bbe250a6829 100644
--- a/Documentation/security/landlock.rst
+++ b/Documentation/security/landlock.rst
@@ -89,6 +89,46 @@ this is required to keep access controls consistent over the whole system, and
this avoids unattended bypasses through file descriptor passing (i.e. confused
deputy attack).
+.. _scoped-flags-interaction:
+
+Interaction between scoped flags and other access rights
+--------------------------------------------------------
+
+The ``scoped`` flags in ``struct landlock_ruleset_attr`` restrict the
+use of *outgoing* IPC from the created Landlock domain, while they
+permit reaching out to IPC endpoints *within* the created Landlock
+domain.
+
+In the future, scoped flags *may* interact with other access rights,
+e.g. so that abstract UNIX sockets can be allow-listed by name, or so
+that signals can be allow-listed by signal number or target process.
+
+When introducing ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``, we defined it to
+implicitly have the same scoping semantics as a
+``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` flag would have: connecting to
+UNIX sockets within the same domain (where
+``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` is used) is unconditionally
+allowed.
+
+The reasoning is:
+
+* Like other IPC mechanisms, connecting to named UNIX sockets in the
+ same domain should be expected and harmless. (If needed, users can
+ further refine their Landlock policies with nested domains or by
+ restricting ``LANDLOCK_ACCESS_FS_MAKE_SOCK``.)
+* We reserve the option to still introduce
+ ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the future. (This would
+ be useful if we wanted to have a Landlock rule to permit IPC access
+ to other Landlock domains.)
+* But we can postpone the point in time when users have to deal with
+ two interacting flags visible in the userspace API. (In particular,
+ it is possible that it won't be needed in practice, in which case we
+ can avoid the second flag altogether.)
+* If we *do* introduce ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the
+ future, setting this scoped flag in a ruleset does *not reduce* the
+ restrictions, because access within the same scope is already
+ allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.
+
Tests
=====
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f88fa1f68b77..751e3c143cba 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -248,6 +248,24 @@ struct landlock_net_port_attr {
*
* This access right is available since the fifth version of the Landlock
* ABI.
+ * - %LANDLOCK_ACCESS_FS_RESOLVE_UNIX: Look up pathname UNIX domain sockets
+ * (:manpage:`unix(7)`). On UNIX domain sockets, this restricts both calls to
+ * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an
+ * explicit recipient address.
+ *
+ * This access right only applies to connections to UNIX server sockets which
+ * were created outside of the newly created Landlock domain (e.g. from within
+ * a parent domain or from an unrestricted process). Newly created UNIX
+ * servers within the same Landlock domain continue to be accessible. In this
+ * regard, %LANDLOCK_ACCESS_RESOLVE_UNIX has the same semantics as the
+ * ``LANDLOCK_SCOPE_*`` flags.
+ *
+ * If a resolve attempt is denied, the operation returns an ``EACCES`` error,
+ * in line with other filesystem access rights (but different to denials for
+ * abstract UNIX domain sockets).
+ *
+ * The rationale for this design is described in
+ * :ref:`Documentation/security/landlock.rst <scoped-flags-interaction>`.
*
* Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
* with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
@@ -333,6 +351,7 @@ struct landlock_net_port_attr {
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
#define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
+#define LANDLOCK_ACCESS_FS_RESOLVE_UNIX (1ULL << 16)
/* clang-format on */
/**
diff --git a/security/landlock/access.h b/security/landlock/access.h
index 42c95747d7bd..89dc8e7b93da 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -34,7 +34,7 @@
LANDLOCK_ACCESS_FS_IOCTL_DEV)
/* clang-format on */
-typedef u16 access_mask_t;
+typedef u32 access_mask_t;
/* Makes sure all filesystem access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 60ff217ab95b..8d0edf94037d 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -37,6 +37,7 @@ static const char *const fs_access_strings[] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
+ [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix",
};
static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 97065d51685a..0486f5ab06c9 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -27,6 +27,7 @@
#include <linux/lsm_hooks.h>
#include <linux/mount.h>
#include <linux/namei.h>
+#include <linux/net.h>
#include <linux/path.h>
#include <linux/pid.h>
#include <linux/rcupdate.h>
@@ -36,6 +37,7 @@
#include <linux/types.h>
#include <linux/wait_bit.h>
#include <linux/workqueue.h>
+#include <net/af_unix.h>
#include <uapi/linux/fiemap.h>
#include <uapi/linux/landlock.h>
@@ -314,7 +316,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_READ_FILE | \
LANDLOCK_ACCESS_FS_TRUNCATE | \
- LANDLOCK_ACCESS_FS_IOCTL_DEV)
+ LANDLOCK_ACCESS_FS_IOCTL_DEV | \
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
/* clang-format on */
/*
@@ -1557,6 +1560,110 @@ static int hook_path_truncate(const struct path *const path)
return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
}
+/**
+ * unmask_scoped_access - Remove access right bits in @masks in all layers
+ * where @client and @server have the same domain
+ *
+ * This does the same as domain_is_scoped(), but unmasks bits in @masks.
+ * It can not return early as domain_is_scoped() does.
+ *
+ * @client: Client domain
+ * @server: Server domain
+ * @masks: Layer access masks to unmask
+ * @access: Access bit that controls scoping
+ */
+static void unmask_scoped_access(const struct landlock_ruleset *const client,
+ const struct landlock_ruleset *const server,
+ struct layer_access_masks *const masks,
+ const access_mask_t access)
+{
+ int client_layer, server_layer;
+ const struct landlock_hierarchy *client_walker, *server_walker;
+
+ /* This should not happen. */
+ if (WARN_ON_ONCE(!client))
+ return;
+
+ /* Server has no Landlock domain; nothing to clear. */
+ if (!server)
+ return;
+
+ client_layer = client->num_layers - 1;
+ client_walker = client->hierarchy;
+ server_layer = server->num_layers - 1;
+ server_walker = server->hierarchy;
+
+ /*
+ * Clears the access bits at all layers where the client domain is the
+ * same as the server domain. We start the walk at min(client_layer,
+ * server_layer). The layer bits until there can not be cleared because
+ * either the client or the server domain is missing.
+ */
+ for (; client_layer > server_layer; client_layer--)
+ client_walker = client_walker->parent;
+
+ for (; server_layer > client_layer; server_layer--)
+ server_walker = server_walker->parent;
+
+ for (; client_layer >= 0; client_layer--) {
+ if (masks->access[client_layer] & access &&
+ client_walker == server_walker)
+ masks->access[client_layer] &= ~access;
+
+ client_walker = client_walker->parent;
+ server_walker = server_walker->parent;
+ }
+}
+
+static int hook_unix_find(const struct path *const path, struct sock *other,
+ int flags)
+{
+ const struct landlock_ruleset *dom_other;
+ const struct landlock_cred_security *subject;
+ struct layer_access_masks layer_masks;
+ struct landlock_request request = {};
+ static const struct access_masks fs_resolve_unix = {
+ .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ };
+
+ /* Lookup for the purpose of saving coredumps is OK. */
+ if (unlikely(flags & SOCK_COREDUMP))
+ return 0;
+
+ /* Access to the same (or a lower) domain is always allowed. */
+ subject = landlock_get_applicable_subject(current_cred(),
+ fs_resolve_unix, NULL);
+
+ if (!subject)
+ return 0;
+
+ if (!landlock_init_layer_masks(subject->domain, fs_resolve_unix.fs,
+ &layer_masks, LANDLOCK_KEY_INODE))
+ return 0;
+
+ /* Checks the layers in which we are connecting within the same domain. */
+ unix_state_lock(other);
+ if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket ||
+ !other->sk_socket->file)) {
+ unix_state_unlock(other);
+ return 0;
+ }
+ dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
+ unix_state_unlock(other);
+
+ unmask_scoped_access(subject->domain, dom_other, &layer_masks,
+ fs_resolve_unix.fs);
+
+ /* Checks the connections to allow-listed paths. */
+ if (is_access_to_paths_allowed(subject->domain, path,
+ fs_resolve_unix.fs, &layer_masks,
+ &request, NULL, 0, NULL, NULL, NULL))
+ return 0;
+
+ landlock_log_denial(subject, &request);
+ return -EACCES;
+}
+
/* File hooks */
/**
@@ -1834,6 +1941,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(path_unlink, hook_path_unlink),
LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
LSM_HOOK_INIT(path_truncate, hook_path_truncate),
+ LSM_HOOK_INIT(unix_find, hook_unix_find),
LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
LSM_HOOK_INIT(file_open, hook_file_open),
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index eb584f47288d..b454ad73b15e 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -19,7 +19,7 @@
#define LANDLOCK_MAX_NUM_LAYERS 16
#define LANDLOCK_MAX_NUM_RULES U32_MAX
-#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
+#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_RESOLVE_UNIX
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 3b33839b80c7..a6e23657f3ce 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
* If the change involves a fix that requires userspace awareness, also update
* the errata documentation in Documentation/userspace-api/landlock.rst .
*/
-const int landlock_abi_version = 8;
+const int landlock_abi_version = 9;
/**
* sys_landlock_create_ruleset - Create a new ruleset
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 0fea236ef4bd..30d37234086c 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
- ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 968a91c927a4..b318627e7561 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -575,9 +575,10 @@ TEST_F_FORK(layout1, inval)
LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_READ_FILE | \
LANDLOCK_ACCESS_FS_TRUNCATE | \
- LANDLOCK_ACCESS_FS_IOCTL_DEV)
+ LANDLOCK_ACCESS_FS_IOCTL_DEV | \
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
-#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV
+#define ACCESS_LAST LANDLOCK_ACCESS_FS_RESOLVE_UNIX
#define ACCESS_ALL ( \
ACCESS_FILE | \
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 4/9] samples/landlock: Add support for named UNIX domain socket restrictions
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
` (2 preceding siblings ...)
2026-03-15 22:21 ` [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-15 22:21 ` [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
` (4 subsequent siblings)
8 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, Justin Suess, linux-security-module,
Tingmao Wang, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
The access right for UNIX domain socket lookups is grouped with the
read-write rights in the sample tool. Rationale: In the general case,
any operations are possible through a UNIX domain socket, including
data-mutating operations.
Cc: Justin Suess <utilityemal77@gmail.com>
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
samples/landlock/sandboxer.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 9f21088c0855..66e56ae275c6 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -111,7 +111,8 @@ static int parse_path(char *env_path, const char ***const path_list)
LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_READ_FILE | \
LANDLOCK_ACCESS_FS_TRUNCATE | \
- LANDLOCK_ACCESS_FS_IOCTL_DEV)
+ LANDLOCK_ACCESS_FS_IOCTL_DEV | \
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
/* clang-format on */
@@ -295,11 +296,12 @@ static bool check_ruleset_scope(const char *const env_var,
LANDLOCK_ACCESS_FS_MAKE_SYM | \
LANDLOCK_ACCESS_FS_REFER | \
LANDLOCK_ACCESS_FS_TRUNCATE | \
- LANDLOCK_ACCESS_FS_IOCTL_DEV)
+ LANDLOCK_ACCESS_FS_IOCTL_DEV | \
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
/* clang-format on */
-#define LANDLOCK_ABI_LAST 8
+#define LANDLOCK_ABI_LAST 9
#define XSTR(s) #s
#define STR(s) XSTR(s)
@@ -438,6 +440,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
~LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
__attribute__((fallthrough));
case 7:
+ case 8:
+ /* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
+ ruleset_attr.handled_access_fs &=
+ ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
"Hint: You should update the running kernel "
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
` (3 preceding siblings ...)
2026-03-15 22:21 ` [PATCH v6 4/9] samples/landlock: Add support for named UNIX domain socket restrictions Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-18 16:53 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 6/9] landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
` (3 subsequent siblings)
8 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, Justin Suess, Tingmao Wang,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
* Extract common helpers from an existing IOCTL test that
also uses pathname unix(7) sockets.
* These tests use the common scoped domains fixture which is also used
in other Landlock scoping tests and which was used in Tingmao Wang's
earlier patch set in [1].
These tests exercise the cross product of the following scenarios:
* Stream connect(), Datagram connect(), Datagram sendmsg() and
Seqpacket connect().
* Child-to-parent and parent-to-child communication
* The Landlock policy configuration as listed in the scoped_domains
fixture.
* In the default variant, Landlock domains are only placed where
prescribed in the fixture.
* In the "ALL_DOMAINS" variant, Landlock domains are also placed in
the places where the fixture says to omit them, but with a
LANDLOCK_RULE_PATH_BENEATH that allows connection.
Cc: Justin Suess <utilityemal77@gmail.com>
Cc: Tingmao Wang <m@maowtm.org>
Cc: Mickaël Salaün <mic@digikod.net>
Link[1]: https://lore.kernel.org/all/53b9883648225d5a08e82d2636ab0b4fda003bc9.1767115163.git.m@maowtm.org/
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
tools/testing/selftests/landlock/fs_test.c | 392 ++++++++++++++++++++-
1 file changed, 376 insertions(+), 16 deletions(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index b318627e7561..fdbb024da774 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -4358,30 +4358,66 @@ TEST_F_FORK(layout1, named_pipe_ioctl)
ASSERT_EQ(child_pid, waitpid(child_pid, NULL, 0));
}
+/*
+ * set_up_named_unix_server - Create a pathname unix socket
+ *
+ * If the socket type is not SOCK_DGRAM, also invoke listen(2).
+ *
+ * Return: The listening FD - it is the caller responsibility to close it.
+ */
+static int set_up_named_unix_server(struct __test_metadata *const _metadata,
+ int type, const char *const path)
+{
+ int fd;
+ struct sockaddr_un addr = {
+ .sun_family = AF_UNIX,
+ };
+
+ fd = socket(AF_UNIX, type, 0);
+ ASSERT_LE(0, fd);
+
+ ASSERT_LT(strlen(path), sizeof(addr.sun_path));
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ ASSERT_EQ(0, bind(fd, (struct sockaddr *)&addr, sizeof(addr)));
+
+ if (type != SOCK_DGRAM)
+ ASSERT_EQ(0, listen(fd, 10 /* qlen */));
+ return fd;
+}
+
+/*
+ * test_connect_named_unix - connect to the given named UNIX socket
+ *
+ * Return: The errno from connect(), or 0
+ */
+static int test_connect_named_unix(struct __test_metadata *const _metadata,
+ int fd, const char *const path)
+{
+ struct sockaddr_un addr = {
+ .sun_family = AF_UNIX,
+ };
+
+ ASSERT_LT(strlen(path), sizeof(addr.sun_path));
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+ return errno;
+ return 0;
+}
+
/* For named UNIX domain sockets, no IOCTL restrictions apply. */
TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
{
const char *const path = file1_s1d1;
int srv_fd, cli_fd, ruleset_fd;
- struct sockaddr_un srv_un = {
- .sun_family = AF_UNIX,
- };
- struct sockaddr_un cli_un = {
- .sun_family = AF_UNIX,
- };
const struct landlock_ruleset_attr attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
};
/* Sets up a server */
ASSERT_EQ(0, unlink(path));
- srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- ASSERT_LE(0, srv_fd);
-
- strncpy(srv_un.sun_path, path, sizeof(srv_un.sun_path));
- ASSERT_EQ(0, bind(srv_fd, (struct sockaddr *)&srv_un, sizeof(srv_un)));
-
- ASSERT_EQ(0, listen(srv_fd, 10 /* qlen */));
+ srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path);
/* Enables Landlock. */
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
@@ -4393,9 +4429,7 @@ TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, cli_fd);
- strncpy(cli_un.sun_path, path, sizeof(cli_un.sun_path));
- ASSERT_EQ(0,
- connect(cli_fd, (struct sockaddr *)&cli_un, sizeof(cli_un)));
+ ASSERT_EQ(0, test_connect_named_unix(_metadata, cli_fd, path));
/* FIONREAD and other IOCTLs should not be forbidden. */
EXPECT_EQ(0, test_fionread_ioctl(cli_fd));
@@ -4570,6 +4604,332 @@ TEST_F_FORK(ioctl, handle_file_access_file)
ASSERT_EQ(0, close(file_fd));
}
+/*
+ * test_sendto_named_unix - sendto to the given named UNIX socket
+ *
+ * sendto() is equivalent to sendmsg() in this respect.
+ *
+ * Return: The errno from sendto(), or 0
+ */
+static int test_sendto_named_unix(struct __test_metadata *const _metadata,
+ int fd, const char *const path)
+{
+ static const char buf[] = "dummy";
+ struct sockaddr_un addr = {
+ .sun_family = AF_UNIX,
+ };
+
+ ASSERT_LT(strlen(path), sizeof(addr.sun_path));
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ if (sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&addr,
+ sizeof(addr)) == -1)
+ return errno;
+ return 0;
+}
+
+/* clang-format off */
+FIXTURE(scoped_domains) {};
+/* clang-format on */
+
+#include "scoped_base_variants.h"
+
+FIXTURE_SETUP(scoped_domains)
+{
+ drop_caps(_metadata);
+};
+
+FIXTURE_TEARDOWN(scoped_domains)
+{
+}
+
+static void enforce_fs_resolve_unix(struct __test_metadata *const _metadata,
+ const struct rule rules[])
+{
+ if (rules) {
+ int fd = create_ruleset(_metadata,
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+ enforce_ruleset(_metadata, fd);
+ EXPECT_EQ(0, close(fd));
+ } else {
+ drop_access_rights(
+ _metadata,
+ &(struct landlock_ruleset_attr){
+ .handled_access_fs =
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ });
+ }
+}
+
+/*
+ * Flags for test_connect_to_parent and test_connect_to_child:
+ *
+ * USE_SENDTO: Use sendto() instead of connect() (for SOCK_DGRAM only)
+ * ENFORCE_ALL: Enforce a Landlock domain even when the variant says
+ * we shouldn't. We enforce a domain where the path is allow-listed,
+ * and expect the behavior to be the same as if none was used.
+ */
+#define USE_SENDTO (1 << 0)
+#define ENFORCE_ALL (1 << 1)
+
+static void test_connect_to_parent(struct __test_metadata *const _metadata,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ int sock_type, int flags)
+{
+ const char *const path = "sock";
+ const struct rule rules[] = {
+ {
+ .path = ".",
+ .access = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ },
+ {},
+ };
+ int cli_fd, srv_fd, res, status;
+ pid_t child_pid;
+ int readiness_pipe[2];
+ char buf[1];
+
+ if (variant->domain_both)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ unlink(path);
+ ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
+
+ child_pid = fork();
+ ASSERT_LE(0, child_pid);
+
+ if (child_pid == 0) {
+ if (variant->domain_child)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ /* Wait for server to be available. */
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+ EXPECT_EQ(1, read(readiness_pipe[0], &buf, 1));
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+
+ /* Talk to server. */
+ cli_fd = socket(AF_UNIX, sock_type, 0);
+ ASSERT_LE(0, cli_fd);
+
+ if (flags & USE_SENDTO)
+ res = test_sendto_named_unix(_metadata, cli_fd, path);
+ else
+ res = test_connect_named_unix(_metadata, cli_fd, path);
+
+ EXPECT_EQ(variant->domain_child ? EACCES : 0, res);
+
+ /* Clean up. */
+ EXPECT_EQ(0, close(cli_fd));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ if (variant->domain_parent)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
+
+ /* Tell the child that it can connect. */
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+ EXPECT_EQ(sizeof(buf), write(readiness_pipe[1], buf, sizeof(buf)));
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+
+ /* Wait for child. */
+ ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /* Clean up. */
+ EXPECT_EQ(0, close(srv_fd));
+ EXPECT_EQ(0, unlink(path));
+}
+
+static void test_connect_to_child(struct __test_metadata *const _metadata,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ int sock_type, int flags)
+{
+ const char *const path = "sock";
+ const struct rule rules[] = {
+ {
+ .path = ".",
+ .access = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ },
+ {},
+ };
+ int readiness_pipe[2];
+ int shutdown_pipe[2];
+ int cli_fd, srv_fd, res, status;
+ pid_t child_pid;
+ char buf[1];
+
+ if (variant->domain_both)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ unlink(path);
+ ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(shutdown_pipe, O_CLOEXEC));
+
+ child_pid = fork();
+ ASSERT_LE(0, child_pid);
+
+ if (child_pid == 0) {
+ if (variant->domain_child)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
+
+ /* Tell the parent that it can connect. */
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+ EXPECT_EQ(sizeof(buf),
+ write(readiness_pipe[1], buf, sizeof(buf)));
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+
+ /* Wait until it is time to shut down. */
+ EXPECT_EQ(0, close(shutdown_pipe[1]));
+ EXPECT_EQ(1, read(shutdown_pipe[0], &buf, 1));
+ EXPECT_EQ(0, close(shutdown_pipe[0]));
+
+ /* Cleanup */
+ EXPECT_EQ(0, close(srv_fd));
+ EXPECT_EQ(0, unlink(path));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ if (variant->domain_parent)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ /* Wait for server to be available. */
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+ EXPECT_EQ(1, read(readiness_pipe[0], &buf, 1));
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+
+ /* Talk to server. */
+ cli_fd = socket(AF_UNIX, sock_type, 0);
+ ASSERT_LE(0, cli_fd);
+
+ if (flags & USE_SENDTO)
+ res = test_sendto_named_unix(_metadata, cli_fd, path);
+ else
+ res = test_connect_named_unix(_metadata, cli_fd, path);
+
+ EXPECT_EQ(variant->domain_parent ? EACCES : 0, res);
+
+ /* Clean up. */
+ EXPECT_EQ(0, close(cli_fd));
+
+ /* Tell the server to shut down. */
+ EXPECT_EQ(0, close(shutdown_pipe[0]));
+ EXPECT_EQ(sizeof(buf), write(shutdown_pipe[1], buf, sizeof(buf)));
+ EXPECT_EQ(0, close(shutdown_pipe[1]));
+
+ /* Wait for child. */
+ ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_STREAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM, USE_SENDTO);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_SEQPACKET, 0);
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_STREAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM,
+ USE_SENDTO | ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_SEQPACKET, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_STREAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM, USE_SENDTO);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_SEQPACKET, 0);
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_STREAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM,
+ USE_SENDTO | ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_SEQPACKET, ENFORCE_ALL);
+}
+
+#undef USE_SENDTO
+#undef ENFORCE_ALL
+
/* clang-format off */
FIXTURE(layout1_bind) {};
/* clang-format on */
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 6/9] landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
` (4 preceding siblings ...)
2026-03-15 22:21 ` [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-18 16:53 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted Günther Noack
` (2 subsequent siblings)
8 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, linux-security-module, Tingmao Wang,
Justin Suess, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
Add an audit test to check that Landlock denials from
LANDLOCK_ACCESS_FS_RESOLVE_UNIX result in audit logs in the expected
format. (There is one audit test for each filesystem access right, so
we should add one for LANDLOCK_ACCESS_FS_RESOLVE_UNIX as well.)
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
tools/testing/selftests/landlock/fs_test.c | 43 +++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index fdbb024da774..4198148e172f 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -7538,7 +7538,8 @@ static const __u64 access_fs_16 =
LANDLOCK_ACCESS_FS_MAKE_SYM |
LANDLOCK_ACCESS_FS_REFER |
LANDLOCK_ACCESS_FS_TRUNCATE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ LANDLOCK_ACCESS_FS_IOCTL_DEV |
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
/* clang-format on */
TEST_F(audit_layout1, execute_read)
@@ -7983,6 +7984,46 @@ TEST_F(audit_layout1, ioctl_dev)
EXPECT_EQ(1, records.domain);
}
+TEST_F(audit_layout1, resolve_unix)
+{
+ struct audit_records records;
+ const char *const path = "sock";
+ int srv_fd, cli_fd, status;
+ pid_t child_pid;
+
+ srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path);
+
+ child_pid = fork();
+ ASSERT_LE(0, child_pid);
+ if (!child_pid) {
+ drop_access_rights(_metadata,
+ &(struct landlock_ruleset_attr){
+ .handled_access_fs = access_fs_16,
+ });
+
+ cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, cli_fd);
+ EXPECT_EQ(EACCES,
+ test_connect_named_unix(_metadata, cli_fd, path));
+
+ EXPECT_EQ(0, close(cli_fd));
+ _exit(_metadata->exit_code);
+ }
+
+ ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd,
+ "fs\\.resolve_unix", path, NULL));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(1, records.domain);
+
+ EXPECT_EQ(0, close(srv_fd));
+}
+
TEST_F(audit_layout1, mount)
{
struct audit_records records;
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
` (5 preceding siblings ...)
2026-03-15 22:21 ` [PATCH v6 6/9] landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-18 16:53 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 8/9] landlock/selftests: fs_test: Simplify ruleset creation and enforcement Günther Noack
2026-03-15 22:21 ` [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets Günther Noack
8 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, linux-security-module, Tingmao Wang,
Justin Suess, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
Even when a process is restricted with the new
LANDLOCK_ACCESS_FS_RESOLVE_SOCKET right, the kernel can continue
writing its coredump to the configured coredump socket.
In the test, we create a local server and rewire the system to write
coredumps into it. We then create a child process within a Landlock
domain where LANDLOCK_ACCESS_FS_RESOLVE_SOCKET is restricted and make
the process crash. The test uses SO_PEERCRED to check that the
connecting client process is the expected one.
Includes a fix by Mickaël Salaün for setting the EUID to 0 (see [1]).
Link[1]: https://lore.kernel.org/all/20260218.ohth8theu8Yi@digikod.net/
Suggested-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
tools/testing/selftests/landlock/fs_test.c | 141 +++++++++++++++++++++
1 file changed, 141 insertions(+)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 4198148e172f..a12a4b8105d2 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -22,6 +22,7 @@
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
+#include <sys/resource.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -4930,6 +4931,146 @@ TEST_F(scoped_domains, unix_seqpacket_connect_to_child_full)
#undef USE_SENDTO
#undef ENFORCE_ALL
+static void read_core_pattern(struct __test_metadata *const _metadata,
+ char *buf, size_t buf_size)
+{
+ int fd;
+ ssize_t ret;
+
+ fd = open("/proc/sys/kernel/core_pattern", O_RDONLY | O_CLOEXEC);
+ ASSERT_LE(0, fd);
+
+ ret = read(fd, buf, buf_size - 1);
+ ASSERT_LE(0, ret);
+ EXPECT_EQ(0, close(fd));
+
+ buf[ret] = '\0';
+}
+
+static void set_core_pattern(struct __test_metadata *const _metadata,
+ const char *pattern)
+{
+ int fd;
+ size_t len = strlen(pattern);
+
+ /*
+ * Writing to /proc/sys/kernel/core_pattern requires EUID 0 because
+ * sysctl_perm() checks that, ignoring capabilities like
+ * CAP_SYS_ADMIN or CAP_DAC_OVERRIDE.
+ *
+ * Switching EUID clears the dumpable flag, which must be restored
+ * afterwards to allow coredumps.
+ */
+ set_cap(_metadata, CAP_SETUID);
+ ASSERT_EQ(0, seteuid(0));
+ clear_cap(_metadata, CAP_SETUID);
+
+ fd = open("/proc/sys/kernel/core_pattern", O_WRONLY | O_CLOEXEC);
+ ASSERT_LE(0, fd)
+ {
+ TH_LOG("Failed to open core_pattern for writing: %s",
+ strerror(errno));
+ }
+
+ ASSERT_EQ(len, write(fd, pattern, len));
+ EXPECT_EQ(0, close(fd));
+
+ set_cap(_metadata, CAP_SETUID);
+ ASSERT_EQ(0, seteuid(getuid()));
+ clear_cap(_metadata, CAP_SETUID);
+
+ /* Restore dumpable flag cleared by seteuid(). */
+ ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0));
+}
+
+FIXTURE(coredump)
+{
+ char original_core_pattern[256];
+};
+
+FIXTURE_SETUP(coredump)
+{
+ disable_caps(_metadata);
+ read_core_pattern(_metadata, self->original_core_pattern,
+ sizeof(self->original_core_pattern));
+}
+
+FIXTURE_TEARDOWN_PARENT(coredump)
+{
+ set_core_pattern(_metadata, self->original_core_pattern);
+}
+
+/*
+ * Test that even when a process is restricted with
+ * LANDLOCK_ACCESS_FS_RESOLVE_UNIX, the kernel can still initiate a connection
+ * to the coredump socket on the processes' behalf.
+ */
+TEST_F_FORK(coredump, socket_not_restricted)
+{
+ static const char core_pattern[] = "@/tmp/landlock_coredump_test.sock";
+ const char *const sock_path = core_pattern + 1;
+ int srv_fd, conn_fd, status;
+ pid_t child_pid;
+ struct ucred cred;
+ socklen_t cred_len = sizeof(cred);
+ char buf[4096];
+
+ /* Set up the coredump server socket. */
+ unlink(sock_path);
+ srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, sock_path);
+
+ /* Point coredumps at our socket. */
+ set_core_pattern(_metadata, core_pattern);
+
+ /* Restrict LANDLOCK_ACCESS_FS_RESOLVE_UNIX. */
+ drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
+ .handled_access_fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ });
+
+ /* Fork a child that crashes. */
+ child_pid = fork();
+ ASSERT_LE(0, child_pid);
+ if (child_pid == 0) {
+ struct rlimit rl = {
+ .rlim_cur = RLIM_INFINITY,
+ .rlim_max = RLIM_INFINITY,
+ };
+
+ ASSERT_EQ(0, setrlimit(RLIMIT_CORE, &rl));
+
+ /* Crash on purpose. */
+ kill(getpid(), SIGSEGV);
+ _exit(1);
+ }
+
+ /*
+ * Accept the coredump connection. If Landlock incorrectly denies the
+ * kernel's coredump connect, accept() will block forever, so the test
+ * would time out.
+ */
+ conn_fd = accept(srv_fd, NULL, NULL);
+ ASSERT_LE(0, conn_fd);
+
+ /* Check that the connection came from the crashing child. */
+ ASSERT_EQ(0, getsockopt(conn_fd, SOL_SOCKET, SO_PEERCRED, &cred,
+ &cred_len));
+ EXPECT_EQ(child_pid, cred.pid);
+
+ /* Drain the coredump data so the kernel can finish. */
+ while (read(conn_fd, buf, sizeof(buf)) > 0)
+ ;
+
+ EXPECT_EQ(0, close(conn_fd));
+
+ /* Wait for the child and verify it coredumped. */
+ ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_TRUE(WCOREDUMP(status));
+
+ EXPECT_EQ(0, close(srv_fd));
+ EXPECT_EQ(0, unlink(sock_path));
+}
+
/* clang-format off */
FIXTURE(layout1_bind) {};
/* clang-format on */
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 8/9] landlock/selftests: fs_test: Simplify ruleset creation and enforcement
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
` (6 preceding siblings ...)
2026-03-15 22:21 ` [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-15 22:21 ` [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets Günther Noack
8 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, linux-security-module, Tingmao Wang,
Justin Suess, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
* Add enforce_fs() for defining and enforcing a ruleset in one step
* In some places, dropped "ASSERT_LE(0, fd)" checks after
create_ruleset() call -- create_ruleset() already checks that.
* In some places, rename "file_fd" to "fd" if it is not needed to
disambiguate any more.
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
tools/testing/selftests/landlock/fs_test.c | 822 ++++++---------------
1 file changed, 211 insertions(+), 611 deletions(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index a12a4b8105d2..d61957af170f 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -767,15 +767,6 @@ static int create_ruleset(struct __test_metadata *const _metadata,
.handled_access_fs = handled_access_fs,
};
- ASSERT_NE(NULL, rules)
- {
- TH_LOG("No rule list");
- }
- ASSERT_NE(NULL, rules[0].path)
- {
- TH_LOG("Empty rule list");
- }
-
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd)
@@ -783,16 +774,26 @@ static int create_ruleset(struct __test_metadata *const _metadata,
TH_LOG("Failed to create a ruleset: %s", strerror(errno));
}
- for (i = 0; rules[i].path; i++) {
- if (!rules[i].access)
- continue;
+ if (rules)
+ for (i = 0; rules[i].path; i++) {
+ if (!rules[i].access)
+ continue;
- add_path_beneath(_metadata, ruleset_fd, rules[i].access,
- rules[i].path);
- }
+ add_path_beneath(_metadata, ruleset_fd, rules[i].access,
+ rules[i].path);
+ }
return ruleset_fd;
}
+static void enforce_fs(struct __test_metadata *const _metadata,
+ const __u64 access_fs, const struct rule rules[])
+{
+ const int ruleset_fd = create_ruleset(_metadata, access_fs, rules);
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+}
+
TEST_F_FORK(layout0, proc_nsfs)
{
const struct rule rules[] = {
@@ -879,13 +880,10 @@ TEST_F_FORK(layout1, effective_access)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
char buf;
int reg_fd;
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
/* Tests on a directory (with or without O_PATH). */
ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
@@ -932,12 +930,9 @@ TEST_F_FORK(layout1, unhandled_access)
},
{},
};
- /* Here, we only handle read accesses, not write accesses. */
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ /* Here, we only handle read accesses, not write accesses. */
+ enforce_fs(_metadata, ACCESS_RO, rules);
/*
* Because the policy does not handle LANDLOCK_ACCESS_FS_WRITE_FILE,
@@ -966,11 +961,8 @@ TEST_F_FORK(layout1, ruleset_overlap)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
/* Checks s1d1 hierarchy. */
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
@@ -1022,11 +1014,8 @@ TEST_F_FORK(layout1, layer_rule_unions)
},
{},
};
- int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer1);
/* Checks s1d1 hierarchy with layer1. */
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
@@ -1048,10 +1037,7 @@ TEST_F_FORK(layout1, layer_rule_unions)
ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
/* Doesn't change anything from layer1. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer2);
/* Checks s1d1 hierarchy with layer2. */
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
@@ -1073,10 +1059,7 @@ TEST_F_FORK(layout1, layer_rule_unions)
ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY));
/* Only allows write (but not read) to dir_s1d3. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer3);
/* Checks s1d1 hierarchy with layer3. */
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
@@ -1114,27 +1097,18 @@ TEST_F_FORK(layout1, non_overlapping_accesses)
},
{},
};
- int ruleset_fd;
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file1_s1d2));
- ruleset_fd =
- create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1);
ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0));
ASSERT_EQ(EACCES, errno);
ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0));
ASSERT_EQ(0, unlink(file1_s1d2));
- ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE,
- layer2);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, layer2);
/* Unchanged accesses for file creation. */
ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0));
@@ -1238,37 +1212,24 @@ TEST_F_FORK(layout1, interleaved_masked_accesses)
},
{},
};
- int ruleset_fd;
- ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
- layer1_read);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer1_read);
/* Checks that read access is granted for file1_s1d3 with layer 1. */
ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY));
- ruleset_fd = create_ruleset(_metadata,
- LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_WRITE_FILE,
- layer2_read_write);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
+ layer2_read_write);
/* Checks that previous access rights are unchanged with layer 2. */
ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY));
- ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
- layer3_read);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer3_read);
/* Checks that previous access rights are unchanged with layer 3. */
ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR));
@@ -1276,13 +1237,9 @@ TEST_F_FORK(layout1, interleaved_masked_accesses)
ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY));
/* This time, denies write access for the file hierarchy. */
- ruleset_fd = create_ruleset(_metadata,
- LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_WRITE_FILE,
- layer4_read_write);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
+ layer4_read_write);
/*
* Checks that the only change with layer 4 is that write access is
@@ -1293,11 +1250,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses)
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
- ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
- layer5_read);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer5_read);
/* Checks that previous access rights are unchanged with layer 5. */
ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
@@ -1305,11 +1258,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses)
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
- ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE,
- layer6_execute);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, layer6_execute);
/* Checks that previous access rights are unchanged with layer 6. */
ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY));
@@ -1317,13 +1266,9 @@ TEST_F_FORK(layout1, interleaved_masked_accesses)
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY));
ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY));
- ruleset_fd = create_ruleset(_metadata,
- LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_WRITE_FILE,
- layer7_read_write);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
+ layer7_read_write);
/* Checks read access is now denied with layer 7. */
ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY));
@@ -1344,7 +1289,6 @@ TEST_F_FORK(layout1, inherit_subset)
};
const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
@@ -1460,7 +1404,6 @@ TEST_F_FORK(layout1, inherit_superset)
};
const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
enforce_ruleset(_metadata, ruleset_fd);
/* Readdir access is denied for dir_s1d2. */
@@ -1476,7 +1419,7 @@ TEST_F_FORK(layout1, inherit_superset)
LANDLOCK_ACCESS_FS_READ_DIR,
dir_s1d2);
enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ EXPECT_EQ(0, close(ruleset_fd));
/* Readdir access is still denied for dir_s1d2. */
ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY));
@@ -1498,7 +1441,6 @@ TEST_F_FORK(layout0, max_layers)
};
const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
for (i = 0; i < 16; i++)
enforce_ruleset(_metadata, ruleset_fd);
@@ -1507,7 +1449,7 @@ TEST_F_FORK(layout0, max_layers)
ASSERT_EQ(-1, err);
ASSERT_EQ(E2BIG, errno);
}
- ASSERT_EQ(0, close(ruleset_fd));
+ EXPECT_EQ(0, close(ruleset_fd));
}
TEST_F_FORK(layout1, empty_or_same_ruleset)
@@ -1521,20 +1463,15 @@ TEST_F_FORK(layout1, empty_or_same_ruleset)
ASSERT_LE(-1, ruleset_fd);
ASSERT_EQ(ENOMSG, errno);
- /* Enforces policy which deny read access to all files. */
- ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE;
- ruleset_fd =
- landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
+ /* Enforces policy which denies read access to all files. */
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, NULL);
+
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
- /* Nests a policy which deny read access to all directories. */
- ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR;
+ /* Nests a policy which denies read access to all directories. */
ruleset_fd =
- landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
- ASSERT_LE(0, ruleset_fd);
+ create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, NULL);
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY));
@@ -1558,11 +1495,8 @@ TEST_F_FORK(layout1, rule_on_mountpoint)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
@@ -1587,11 +1521,8 @@ TEST_F_FORK(layout1, rule_over_mountpoint)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
@@ -1615,21 +1546,15 @@ TEST_F_FORK(layout1, rule_over_root_allow_then_deny)
},
{},
};
- int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
/* Checks allowed access. */
ASSERT_EQ(0, test_open("/", O_RDONLY));
ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
rules[0].access = LANDLOCK_ACCESS_FS_READ_FILE;
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
/* Checks denied access (on a directory). */
ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
@@ -1645,11 +1570,8 @@ TEST_F_FORK(layout1, rule_over_root_deny)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
/* Checks denied access (on a directory). */
ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
@@ -1665,7 +1587,6 @@ TEST_F_FORK(layout1, rule_inside_mount_ns)
},
{},
};
- int ruleset_fd;
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3))
@@ -1675,10 +1596,7 @@ TEST_F_FORK(layout1, rule_inside_mount_ns)
ASSERT_EQ(0, chdir("/"));
clear_cap(_metadata, CAP_SYS_ADMIN);
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
ASSERT_EQ(0, test_open("s3d3", O_RDONLY));
ASSERT_EQ(EACCES, test_open("/", O_RDONLY));
@@ -1693,11 +1611,8 @@ TEST_F_FORK(layout1, mount_and_pivot)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL));
@@ -1716,9 +1631,6 @@ TEST_F_FORK(layout1, move_mount)
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
-
- ASSERT_LE(0, ruleset_fd);
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
@@ -1731,8 +1643,7 @@ TEST_F_FORK(layout1, move_mount)
dir_s3d2, 0));
clear_cap(_metadata, CAP_SYS_ADMIN);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, rules);
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD,
@@ -1747,14 +1658,9 @@ TEST_F_FORK(layout1, topology_changes_with_net_only)
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
};
- int ruleset_fd;
/* Add network restrictions. */
- ruleset_fd =
- landlock_create_ruleset(&ruleset_net, sizeof(ruleset_net), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ drop_access_rights(_metadata, &ruleset_net);
/* Mount, remount, move_mount, umount, and pivot_root checks. */
set_cap(_metadata, CAP_SYS_ADMIN);
@@ -1775,14 +1681,9 @@ TEST_F_FORK(layout1, topology_changes_with_net_and_fs)
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
};
- int ruleset_fd;
/* Add network and filesystem restrictions. */
- ruleset_fd = landlock_create_ruleset(&ruleset_net_fs,
- sizeof(ruleset_net_fs), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ drop_access_rights(_metadata, &ruleset_net_fs);
/* Mount, remount, move_mount, umount, and pivot_root checks. */
set_cap(_metadata, CAP_SYS_ADMIN);
@@ -1819,14 +1720,13 @@ TEST_F_FORK(layout1, release_inodes)
};
const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- ASSERT_LE(0, ruleset_fd);
/* Unmount a file hierarchy while it is being used by a ruleset. */
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, umount(dir_s3d2));
clear_cap(_metadata, CAP_SYS_ADMIN);
enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ EXPECT_EQ(0, close(ruleset_fd));
ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY));
@@ -1858,7 +1758,6 @@ TEST_F_FORK(layout1, covered_rule)
/* Creates a ruleset with the future hidden directory. */
ruleset_fd =
create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
- ASSERT_LE(0, ruleset_fd);
/* Covers with a new mount point. */
set_cap(_metadata, CAP_SYS_ADMIN);
@@ -1908,10 +1807,7 @@ static void test_relative_path(struct __test_metadata *const _metadata,
};
int dirfd, ruleset_fd;
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer1_base);
ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_subs);
@@ -2092,10 +1988,7 @@ TEST_F_FORK(layout1, execute)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
- ASSERT_LE(0, ruleset_fd);
copy_file(_metadata, bin_true, file1_s1d1);
copy_file(_metadata, bin_true, file1_s1d2);
copy_file(_metadata, bin_true, file1_s1d3);
@@ -2104,8 +1997,7 @@ TEST_F_FORK(layout1, execute)
test_execute(_metadata, 0, file1_s1d1);
test_check_exec(_metadata, 0, file1_s1d1);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY));
ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
@@ -2216,16 +2108,12 @@ TEST_F_FORK(layout1, link)
},
{},
};
- int ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1);
-
- ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file1_s1d2));
ASSERT_EQ(0, unlink(file1_s1d3));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, layer1[0].access, layer1);
ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
ASSERT_EQ(EACCES, errno);
@@ -2245,10 +2133,7 @@ TEST_F_FORK(layout1, link)
ASSERT_EQ(0, unlink(file2_s1d2));
ASSERT_EQ(0, unlink(file2_s1d3));
- ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, layer2[0].access, layer2);
/* Checks that linkind doesn't require the ability to delete a file. */
ASSERT_EQ(0, link(file1_s1d2, file2_s1d2));
@@ -2298,15 +2183,10 @@ TEST_F_FORK(layout1, rename_file)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
-
- ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, unlink(file1_s1d2));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
/*
* Tries to replace a file, from a directory that allows file removal,
@@ -2380,17 +2260,12 @@ TEST_F_FORK(layout1, rename_dir)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
-
- ASSERT_LE(0, ruleset_fd);
/* Empties dir_s1d3 to allow renaming. */
ASSERT_EQ(0, unlink(file1_s1d3));
ASSERT_EQ(0, unlink(file2_s1d3));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
/* Exchanges and renames directory to a different parent. */
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
@@ -2444,12 +2319,8 @@ TEST_F_FORK(layout1, reparent_refer)
},
{},
};
- int ruleset_fd =
- create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1));
ASSERT_EQ(EXDEV, errno);
@@ -2479,14 +2350,9 @@ static void refer_denied_by_default(struct __test_metadata *const _metadata,
const int layer1_err,
const struct rule layer2[])
{
- int ruleset_fd;
-
ASSERT_EQ(0, unlink(file1_s1d2));
- ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, layer1[0].access, layer1);
/*
* If the first layer handles LANDLOCK_ACCESS_FS_REFER (according to
@@ -2498,10 +2364,7 @@ static void refer_denied_by_default(struct __test_metadata *const _metadata,
ASSERT_EQ(layer1_err, test_exchange(file2_s1d1, file2_s1d2));
ASSERT_EQ(layer1_err, test_exchange(file2_s1d2, file2_s1d1));
- ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, layer2[0].access, layer2);
/*
* Now, either the first or the second layer does not handle
@@ -2587,10 +2450,7 @@ TEST_F_FORK(layout1, refer_denied_by_default4)
*/
TEST_F_FORK(layout1, refer_mount_root_deny)
{
- const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_DIR,
- };
- int root_fd, ruleset_fd;
+ int root_fd;
/* Creates a mount object from a non-mount point. */
set_cap(_metadata, CAP_SYS_ADMIN);
@@ -2600,13 +2460,7 @@ TEST_F_FORK(layout1, refer_mount_root_deny)
clear_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_LE(0, root_fd);
- ruleset_fd =
- landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
- ASSERT_LE(0, ruleset_fd);
-
- ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
- ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
- EXPECT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, NULL);
/* Link denied by Landlock: EACCES. */
EXPECT_EQ(-1, linkat(root_fd, ".", root_fd, "does_not_exist", 0));
@@ -2641,18 +2495,12 @@ TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed)
},
{},
};
- int ruleset_fd;
ASSERT_EQ(0, unlink(file1_s3d3));
- ruleset_fd = create_ruleset(_metadata,
- LANDLOCK_ACCESS_FS_REFER |
- LANDLOCK_ACCESS_FS_MAKE_REG |
- LANDLOCK_ACCESS_FS_REMOVE_FILE,
- layer1);
-
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ layer1);
ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3));
}
@@ -2678,13 +2526,10 @@ TEST_F_FORK(layout1, reparent_link)
},
{},
};
- const int ruleset_fd = create_ruleset(
- _metadata,
- LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER,
+ layer1);
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file1_s1d2));
@@ -2756,13 +2601,10 @@ TEST_F_FORK(layout1, reparent_rename)
},
{},
};
- const int ruleset_fd = create_ruleset(
- _metadata,
- LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER,
+ layer1);
ASSERT_EQ(0, unlink(file1_s1d2));
ASSERT_EQ(0, unlink(file1_s1d3));
@@ -2902,13 +2744,9 @@ reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata)
},
{},
};
- const int ruleset_fd = create_ruleset(
- _metadata,
- LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
-
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER,
+ layer1);
}
static void
@@ -2925,12 +2763,7 @@ reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata)
* Same checks as before but with a second layer and a new MAKE_DIR
* rule (and no explicit handling of REFER).
*/
- const int ruleset_fd =
- create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2);
-
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2);
}
TEST_F_FORK(layout1, reparent_exdev_layers_rename1)
@@ -3199,15 +3032,11 @@ TEST_F_FORK(layout1, reparent_remove)
},
{},
};
- const int ruleset_fd = create_ruleset(
- _metadata,
- LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR |
- LANDLOCK_ACCESS_FS_REMOVE_FILE,
- layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ layer1);
/* Access denied because of wrong/swapped remove file/dir. */
ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2));
@@ -3271,17 +3100,13 @@ TEST_F_FORK(layout1, reparent_dom_superset)
},
{},
};
- int ruleset_fd = create_ruleset(_metadata,
- LANDLOCK_ACCESS_FS_REFER |
- LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_MAKE_SOCK |
- LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_MAKE_FIFO,
- layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_MAKE_SOCK |
+ LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_MAKE_FIFO,
+ layer1);
ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1));
ASSERT_EQ(EXDEV, errno);
@@ -3344,18 +3169,13 @@ TEST_F_FORK(layout1, remove_dir)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
-
- ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file1_s1d2));
ASSERT_EQ(0, unlink(file1_s1d3));
ASSERT_EQ(0, unlink(file2_s1d3));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
ASSERT_EQ(0, rmdir(dir_s1d3));
ASSERT_EQ(0, mkdir(dir_s1d3, 0700));
@@ -3381,12 +3201,8 @@ TEST_F_FORK(layout1, remove_file)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
ASSERT_EQ(-1, unlink(file1_s1d1));
ASSERT_EQ(EACCES, errno);
@@ -3407,9 +3223,6 @@ static void test_make_file(struct __test_metadata *const _metadata,
},
{},
};
- const int ruleset_fd = create_ruleset(_metadata, access, rules);
-
- ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file2_s1d1));
@@ -3425,8 +3238,7 @@ static void test_make_file(struct __test_metadata *const _metadata,
ASSERT_EQ(0, unlink(file1_s1d3));
ASSERT_EQ(0, unlink(file2_s1d3));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, access, rules);
ASSERT_EQ(-1, mknod(file1_s1d1, mode | 0400, dev));
ASSERT_EQ(EACCES, errno);
@@ -3495,10 +3307,6 @@ TEST_F_FORK(layout1, make_sym)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
-
- ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file2_s1d1));
@@ -3510,8 +3318,7 @@ TEST_F_FORK(layout1, make_sym)
ASSERT_EQ(0, unlink(file1_s1d3));
ASSERT_EQ(0, unlink(file2_s1d3));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
ASSERT_EQ(-1, symlink("none", file1_s1d1));
ASSERT_EQ(EACCES, errno);
@@ -3540,17 +3347,12 @@ TEST_F_FORK(layout1, make_dir)
},
{},
};
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
-
- ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, unlink(file1_s1d1));
ASSERT_EQ(0, unlink(file1_s1d2));
ASSERT_EQ(0, unlink(file1_s1d3));
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, rules[0].access, rules);
/* Uses file_* as directory names. */
ASSERT_EQ(-1, mkdir(file1_s1d1, 0700));
@@ -3581,14 +3383,10 @@ TEST_F_FORK(layout1, proc_unlinked_file)
{},
};
int reg_fd, proc_fd;
- const int ruleset_fd = create_ruleset(
- _metadata,
- LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
- rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
+ rules);
ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR));
ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
@@ -3624,13 +3422,9 @@ TEST_F_FORK(layout1, proc_pipe)
},
{},
};
- /* Limits read and write access to files tied to the filesystem. */
- const int ruleset_fd =
- create_ruleset(_metadata, rules[0].access, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ /* Limits read and write access to files tied to the filesystem. */
+ enforce_fs(_metadata, rules[0].access, rules);
/* Checks enforcement for normal files. */
ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR));
@@ -3720,16 +3514,10 @@ TEST_F_FORK(layout1, truncate_unhandled)
{},
};
- const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_WRITE_FILE;
- int ruleset_fd;
-
/* Enables Landlock. */
- ruleset_fd = create_ruleset(_metadata, handled, rules);
-
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
+ rules);
/*
* Checks read right: truncate and open with O_TRUNC work, unless the
@@ -3802,17 +3590,13 @@ TEST_F_FORK(layout1, truncate)
},
{},
};
- const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_WRITE_FILE |
- LANDLOCK_ACCESS_FS_TRUNCATE;
- int ruleset_fd;
/* Enables Landlock. */
- ruleset_fd = create_ruleset(_metadata, handled, rules);
-
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_WRITE_FILE |
+ LANDLOCK_ACCESS_FS_TRUNCATE,
+ rules);
/* Checks read, write and truncate rights: truncation works. */
EXPECT_EQ(0, test_truncate(file_rwt));
@@ -3912,34 +3696,25 @@ TEST_F_FORK(layout1, ftruncate)
},
{},
};
- int fd_layer0, fd_layer1, fd_layer2, fd_layer3, ruleset_fd;
+ int fd_layer0, fd_layer1, fd_layer2, fd_layer3;
fd_layer0 = open(path, O_WRONLY);
EXPECT_EQ(0, test_ftruncate(fd_layer0));
- ruleset_fd = create_ruleset(_metadata, handled1, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, handled1, layer1);
fd_layer1 = open(path, O_WRONLY);
EXPECT_EQ(0, test_ftruncate(fd_layer0));
EXPECT_EQ(0, test_ftruncate(fd_layer1));
- ruleset_fd = create_ruleset(_metadata, handled2, layer2);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, handled2, layer2);
fd_layer2 = open(path, O_WRONLY);
EXPECT_EQ(0, test_ftruncate(fd_layer0));
EXPECT_EQ(0, test_ftruncate(fd_layer1));
EXPECT_EQ(0, test_ftruncate(fd_layer2));
- ruleset_fd = create_ruleset(_metadata, handled3, layer3);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, handled3, layer3);
fd_layer3 = open(path, O_WRONLY);
EXPECT_EQ(0, test_ftruncate(fd_layer0));
@@ -4031,13 +3806,10 @@ TEST_F_FORK(ftruncate, open_and_ftruncate)
},
{},
};
- int fd, ruleset_fd;
+ int fd;
/* Enables Landlock. */
- ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, variant->handled, rules);
fd = open(path, O_WRONLY);
EXPECT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0));
@@ -4072,12 +3844,9 @@ TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes)
},
{},
};
- int fd, ruleset_fd;
+ int fd;
- ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, variant->handled, rules);
fd = open(path, O_WRONLY);
ASSERT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0));
@@ -4122,10 +3891,7 @@ static int test_fs_ioc_getflags_ioctl(int fd)
TEST(memfd_ftruncate_and_ioctl)
{
- const struct landlock_ruleset_attr attr = {
- .handled_access_fs = ACCESS_ALL,
- };
- int ruleset_fd, fd, i;
+ int fd, i;
/*
* We exercise the same test both with and without Landlock enabled, to
@@ -4147,10 +3913,7 @@ TEST(memfd_ftruncate_and_ioctl)
ASSERT_EQ(0, close(fd));
/* Enables Landlock. */
- ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_ALL, NULL);
}
}
@@ -4165,10 +3928,7 @@ static int test_fionread_ioctl(int fd)
TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl)
{
- const struct landlock_ruleset_attr attr = {
- .handled_access_fs = ACCESS_ALL,
- };
- int ruleset_fd, fd;
+ int fd;
/*
* Checks that for files opened with O_PATH, both ioctl(2) and
@@ -4184,10 +3944,7 @@ TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl)
ASSERT_EQ(0, close(fd));
/* Enables Landlock. */
- ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_ALL, NULL);
/*
* Checks that after enabling Landlock,
@@ -4261,16 +4018,10 @@ struct space_resv {
*/
TEST_F_FORK(layout1, blanket_permitted_ioctls)
{
- const struct landlock_ruleset_attr attr = {
- .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
- };
- int ruleset_fd, fd;
+ int fd;
/* Enables Landlock. */
- ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_IOCTL_DEV, NULL);
fd = open("/dev/null", O_RDWR | O_CLOEXEC);
ASSERT_LE(0, fd);
@@ -4323,20 +4074,14 @@ TEST_F_FORK(layout1, blanket_permitted_ioctls)
TEST_F_FORK(layout1, named_pipe_ioctl)
{
pid_t child_pid;
- int fd, ruleset_fd;
+ int fd;
const char *const path = file1_s1d1;
- const struct landlock_ruleset_attr attr = {
- .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
- };
ASSERT_EQ(0, unlink(path));
ASSERT_EQ(0, mkfifo(path, 0600));
/* Enables Landlock. */
- ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_IOCTL_DEV, NULL);
/* The child process opens the pipe for writing. */
child_pid = fork();
@@ -4411,20 +4156,14 @@ static int test_connect_named_unix(struct __test_metadata *const _metadata,
TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
{
const char *const path = file1_s1d1;
- int srv_fd, cli_fd, ruleset_fd;
- const struct landlock_ruleset_attr attr = {
- .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
- };
+ int srv_fd, cli_fd;
/* Sets up a server */
ASSERT_EQ(0, unlink(path));
srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path);
/* Enables Landlock. */
- ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_IOCTL_DEV, NULL);
/* Sets up a client connection to it */
cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
@@ -4497,29 +4236,25 @@ TEST_F_FORK(ioctl, handle_dir_access_file)
},
{},
};
- int file_fd, ruleset_fd;
+ int fd;
/* Enables Landlock. */
- ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, variant->handled, rules);
- file_fd = open("/dev/zero", variant->open_mode);
- ASSERT_LE(0, file_fd);
+ fd = open("/dev/zero", variant->open_mode);
+ ASSERT_LE(0, fd);
/* Checks that IOCTL commands return the expected errors. */
- EXPECT_EQ(variant->expected_fionread_result,
- test_fionread_ioctl(file_fd));
+ EXPECT_EQ(variant->expected_fionread_result, test_fionread_ioctl(fd));
/* Checks that unrestrictable commands are unrestricted. */
- EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
- EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
- EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
- EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
- EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));
+ EXPECT_EQ(0, ioctl(fd, FIOCLEX));
+ EXPECT_EQ(0, ioctl(fd, FIONCLEX));
+ EXPECT_EQ(0, ioctl(fd, FIONBIO, &flag));
+ EXPECT_EQ(0, ioctl(fd, FIOASYNC, &flag));
+ EXPECT_EQ(0, ioctl(fd, FIGETBSZ, &flag));
- ASSERT_EQ(0, close(file_fd));
+ ASSERT_EQ(0, close(fd));
}
TEST_F_FORK(ioctl, handle_dir_access_dir)
@@ -4532,13 +4267,10 @@ TEST_F_FORK(ioctl, handle_dir_access_dir)
},
{},
};
- int dir_fd, ruleset_fd;
+ int dir_fd;
/* Enables Landlock. */
- ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, variant->handled, rules);
/*
* Ignore variant->open_mode for this test, as we intend to open a
@@ -4577,32 +4309,28 @@ TEST_F_FORK(ioctl, handle_file_access_file)
},
{},
};
- int file_fd, ruleset_fd;
+ int fd;
/* Enables Landlock. */
- ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, variant->handled, rules);
- file_fd = open("/dev/zero", variant->open_mode);
- ASSERT_LE(0, file_fd)
+ fd = open("/dev/zero", variant->open_mode);
+ ASSERT_LE(0, fd)
{
TH_LOG("Failed to open /dev/zero: %s", strerror(errno));
}
/* Checks that IOCTL commands return the expected errors. */
- EXPECT_EQ(variant->expected_fionread_result,
- test_fionread_ioctl(file_fd));
+ EXPECT_EQ(variant->expected_fionread_result, test_fionread_ioctl(fd));
/* Checks that unrestrictable commands are unrestricted. */
- EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
- EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
- EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
- EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
- EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));
+ EXPECT_EQ(0, ioctl(fd, FIOCLEX));
+ EXPECT_EQ(0, ioctl(fd, FIONCLEX));
+ EXPECT_EQ(0, ioctl(fd, FIONBIO, &flag));
+ EXPECT_EQ(0, ioctl(fd, FIOASYNC, &flag));
+ EXPECT_EQ(0, ioctl(fd, FIGETBSZ, &flag));
- ASSERT_EQ(0, close(file_fd));
+ ASSERT_EQ(0, close(fd));
}
/*
@@ -4644,24 +4372,6 @@ FIXTURE_TEARDOWN(scoped_domains)
{
}
-static void enforce_fs_resolve_unix(struct __test_metadata *const _metadata,
- const struct rule rules[])
-{
- if (rules) {
- int fd = create_ruleset(_metadata,
- LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
- enforce_ruleset(_metadata, fd);
- EXPECT_EQ(0, close(fd));
- } else {
- drop_access_rights(
- _metadata,
- &(struct landlock_ruleset_attr){
- .handled_access_fs =
- LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
- });
- }
-}
-
/*
* Flags for test_connect_to_parent and test_connect_to_child:
*
@@ -4692,9 +4402,9 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata,
char buf[1];
if (variant->domain_both)
- enforce_fs_resolve_unix(_metadata, NULL);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
else if (flags & ENFORCE_ALL)
- enforce_fs_resolve_unix(_metadata, rules);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
unlink(path);
ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
@@ -4704,9 +4414,11 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata,
if (child_pid == 0) {
if (variant->domain_child)
- enforce_fs_resolve_unix(_metadata, NULL);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ NULL);
else if (flags & ENFORCE_ALL)
- enforce_fs_resolve_unix(_metadata, rules);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ rules);
/* Wait for server to be available. */
EXPECT_EQ(0, close(readiness_pipe[1]));
@@ -4732,9 +4444,9 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata,
}
if (variant->domain_parent)
- enforce_fs_resolve_unix(_metadata, NULL);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
else if (flags & ENFORCE_ALL)
- enforce_fs_resolve_unix(_metadata, rules);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
@@ -4773,9 +4485,9 @@ static void test_connect_to_child(struct __test_metadata *const _metadata,
char buf[1];
if (variant->domain_both)
- enforce_fs_resolve_unix(_metadata, NULL);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
else if (flags & ENFORCE_ALL)
- enforce_fs_resolve_unix(_metadata, rules);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
unlink(path);
ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
@@ -4786,9 +4498,11 @@ static void test_connect_to_child(struct __test_metadata *const _metadata,
if (child_pid == 0) {
if (variant->domain_child)
- enforce_fs_resolve_unix(_metadata, NULL);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ NULL);
else if (flags & ENFORCE_ALL)
- enforce_fs_resolve_unix(_metadata, rules);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ rules);
srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
@@ -4812,9 +4526,9 @@ static void test_connect_to_child(struct __test_metadata *const _metadata,
}
if (variant->domain_parent)
- enforce_fs_resolve_unix(_metadata, NULL);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
else if (flags & ENFORCE_ALL)
- enforce_fs_resolve_unix(_metadata, rules);
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
/* Wait for server to be available. */
EXPECT_EQ(0, close(readiness_pipe[1]));
@@ -5023,9 +4737,7 @@ TEST_F_FORK(coredump, socket_not_restricted)
set_core_pattern(_metadata, core_pattern);
/* Restrict LANDLOCK_ACCESS_FS_RESOLVE_UNIX. */
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
- });
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL);
/* Fork a child that crashes. */
child_pid = fork();
@@ -5212,13 +4924,9 @@ TEST_F_FORK(layout1_bind, same_content_same_file)
},
{},
};
- int ruleset_fd;
/* Sets rules for the parent directories. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_parent);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer1_parent);
/* Checks source hierarchy. */
ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY));
@@ -5237,10 +4945,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file)
ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY));
/* Sets rules for the mount points. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_mount_point);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer2_mount_point);
/* Checks source hierarchy. */
ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
@@ -5261,10 +4966,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file)
ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY));
/* Sets a (shared) rule only on the source. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_source);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer3_source);
/* Checks source hierarchy. */
ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY));
@@ -5285,10 +4987,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file)
ASSERT_EQ(EACCES, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY));
/* Sets a (shared) rule only on the destination. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_destination);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer4_destination);
/* Checks source hierarchy. */
ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY));
@@ -5313,13 +5012,10 @@ TEST_F_FORK(layout1_bind, reparent_cross_mount)
},
{},
};
- int ruleset_fd = create_ruleset(
- _metadata,
- LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE,
+ layer1);
/* Checks basic denied move. */
ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2));
@@ -5376,10 +5072,6 @@ TEST_F_FORK(layout1_bind, path_disconnected)
create_ruleset(_metadata, ACCESS_RW, layer3_only_s1d2);
int bind_s1d3_fd;
- ASSERT_LE(0, ruleset_fd_l1);
- ASSERT_LE(0, ruleset_fd_l2);
- ASSERT_LE(0, ruleset_fd_l3);
-
enforce_ruleset(_metadata, ruleset_fd_l1);
EXPECT_EQ(0, close(ruleset_fd_l1));
@@ -5483,8 +5175,6 @@ TEST_F_FORK(layout1_bind, path_disconnected_rename)
ruleset_fd_l1 = create_ruleset(_metadata, ACCESS_ALL, layer1);
ruleset_fd_l2 = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
layer2_only_s1d2);
- ASSERT_LE(0, ruleset_fd_l1);
- ASSERT_LE(0, ruleset_fd_l2);
enforce_ruleset(_metadata, ruleset_fd_l1);
EXPECT_EQ(0, close(ruleset_fd_l1));
@@ -5630,7 +5320,7 @@ TEST_F_FORK(layout1_bind, path_disconnected_link)
},
{}
};
- int ruleset_fd, bind_s1d3_fd;
+ int bind_s1d3_fd;
/* Removes unneeded files created by layout1, otherwise it will EEXIST. */
ASSERT_EQ(0, unlink(file1_s1d2));
@@ -5653,10 +5343,7 @@ TEST_F_FORK(layout1_bind, path_disconnected_link)
TH_LOG("Failed to create %s: %s", dir_s4d2, strerror(errno));
}
- ruleset_fd = create_ruleset(_metadata, ACCESS_ALL, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- EXPECT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_ALL, layer1);
/* From disconnected to connected. */
ASSERT_EQ(0, linkat(bind_s1d3_fd, file1_name, AT_FDCWD, file1_s2d2, 0))
@@ -6194,7 +5881,6 @@ TEST_F_FORK(layout4_disconnected_leafs, read_rename_exchange)
int ruleset_fd, s1d41_bind_fd, s1d42_bind_fd;
ruleset_fd = create_ruleset(_metadata, handled_access, rules);
- ASSERT_LE(0, ruleset_fd);
/* Adds rule for the covered directory. */
if (variant->allowed_s2d2) {
@@ -7127,7 +6813,6 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
},
{},
};
- int ruleset_fd;
size_t i;
const char *path_entry;
@@ -7135,10 +6820,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
SKIP(return, "overlayfs is not supported (test)");
/* Sets rules on base directories (i.e. outside overlay scope). */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer1_base);
/* Checks lower layer. */
for_each_path(lower_base_files, path_entry, i) {
@@ -7183,10 +6865,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
}
/* Sets rules on data directories (i.e. inside overlay scope). */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_data);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer2_data);
/* Checks merge. */
for_each_path(merge_base_files, path_entry, i) {
@@ -7200,10 +6879,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
}
/* Same checks with tighter rules. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_subdirs);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer3_subdirs);
/* Checks changes for lower layer. */
for_each_path(lower_base_files, path_entry, i) {
@@ -7225,10 +6901,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
}
/* Sets rules directly on overlayed files. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_files);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer4_files);
/* Checks unchanged accesses on lower layer. */
for_each_path(lower_sub_files, path_entry, i) {
@@ -7253,10 +6926,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
}
/* Only allowes access to the merge hierarchy. */
- ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer5_merge_only);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, ACCESS_RW, layer5_merge_only);
/* Checks new accesses on lower layer. */
for_each_path(lower_sub_files, path_entry, i) {
@@ -7442,11 +7112,7 @@ static void layer3_fs_tag_inode(struct __test_metadata *const _metadata,
},
{},
};
- const struct landlock_ruleset_attr layer2_deny_everything_attr = {
- .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
- };
const char *const dev_null_path = "/dev/null";
- int ruleset_fd;
if (self->skip_test)
SKIP(return, "this filesystem is not supported (test)");
@@ -7455,22 +7121,14 @@ static void layer3_fs_tag_inode(struct __test_metadata *const _metadata,
EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
- ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
- layer1_allow_read_file);
- EXPECT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- EXPECT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE,
+ layer1_allow_read_file);
EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC));
/* Forbids directory reading. */
- ruleset_fd =
- landlock_create_ruleset(&layer2_deny_everything_attr,
- sizeof(layer2_deny_everything_attr), 0);
- EXPECT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- EXPECT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, NULL);
/* Checks with Landlock and forbidden access. */
EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC));
@@ -7532,7 +7190,6 @@ TEST_F_FORK(layout3_fs, release_inodes)
ruleset_fd =
create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1);
- ASSERT_LE(0, ruleset_fd);
/* Unmount the filesystem while it is being used by a ruleset. */
set_cap(_metadata, CAP_SYS_ADMIN);
@@ -7639,11 +7296,7 @@ TEST_F(audit_layout1, execute_make)
test_execute(_metadata, 0, file1_s1d1);
test_check_exec(_metadata, 0, file1_s1d1);
- drop_access_rights(_metadata,
- &(struct landlock_ruleset_attr){
- .handled_access_fs =
- LANDLOCK_ACCESS_FS_EXECUTE,
- });
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, NULL);
test_execute(_metadata, EACCES, file1_s1d1);
EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.execute",
@@ -7691,9 +7344,7 @@ TEST_F(audit_layout1, execute_read)
test_execute(_metadata, 0, file1_s1d1);
test_check_exec(_metadata, 0, file1_s1d1);
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
/*
* The only difference with the previous audit_layout1.execute_read test is
@@ -7715,9 +7366,7 @@ TEST_F(audit_layout1, write_file)
{
struct audit_records records;
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY));
EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
@@ -7732,9 +7381,7 @@ TEST_F(audit_layout1, read_file)
{
struct audit_records records;
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY));
EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_file",
@@ -7749,9 +7396,7 @@ TEST_F(audit_layout1, read_dir)
{
struct audit_records records;
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(EACCES, test_open(dir_s1d1, O_DIRECTORY));
EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_dir",
@@ -7769,9 +7414,7 @@ TEST_F(audit_layout1, remove_dir)
EXPECT_EQ(0, unlink(file1_s1d3));
EXPECT_EQ(0, unlink(file2_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, rmdir(dir_s1d3));
EXPECT_EQ(EACCES, errno);
@@ -7792,9 +7435,7 @@ TEST_F(audit_layout1, remove_file)
{
struct audit_records records;
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, unlink(file1_s1d3));
EXPECT_EQ(EACCES, errno);
@@ -7812,9 +7453,7 @@ TEST_F(audit_layout1, make_char)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, 0));
EXPECT_EQ(EACCES, errno);
@@ -7832,9 +7471,7 @@ TEST_F(audit_layout1, make_dir)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, mkdir(file1_s1d3, 0755));
EXPECT_EQ(EACCES, errno);
@@ -7852,9 +7489,7 @@ TEST_F(audit_layout1, make_reg)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, mknod(file1_s1d3, S_IFREG | 0644, 0));
EXPECT_EQ(EACCES, errno);
@@ -7872,9 +7507,7 @@ TEST_F(audit_layout1, make_sock)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, mknod(file1_s1d3, S_IFSOCK | 0644, 0));
EXPECT_EQ(EACCES, errno);
@@ -7892,9 +7525,7 @@ TEST_F(audit_layout1, make_fifo)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, mknod(file1_s1d3, S_IFIFO | 0644, 0));
EXPECT_EQ(EACCES, errno);
@@ -7912,9 +7543,7 @@ TEST_F(audit_layout1, make_block)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, mknod(file1_s1d3, S_IFBLK | 0644, 0));
EXPECT_EQ(EACCES, errno);
@@ -7932,9 +7561,7 @@ TEST_F(audit_layout1, make_sym)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, symlink("target", file1_s1d3));
EXPECT_EQ(EACCES, errno);
@@ -7952,10 +7579,7 @@ TEST_F(audit_layout1, refer_handled)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs =
- LANDLOCK_ACCESS_FS_REFER,
- });
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REFER, NULL);
EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3));
EXPECT_EQ(EXDEV, errno);
@@ -7977,12 +7601,9 @@ TEST_F(audit_layout1, refer_make)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata,
- &(struct landlock_ruleset_attr){
- .handled_access_fs =
- LANDLOCK_ACCESS_FS_MAKE_REG |
- LANDLOCK_ACCESS_FS_REFER,
- });
+ enforce_fs(_metadata,
+ LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER,
+ NULL);
EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3));
EXPECT_EQ(EACCES, errno);
@@ -8002,9 +7623,7 @@ TEST_F(audit_layout1, refer_rename)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(EACCES, test_rename(file1_s1d2, file1_s2d3));
EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
@@ -8024,9 +7643,7 @@ TEST_F(audit_layout1, refer_exchange)
EXPECT_EQ(0, unlink(file1_s1d3));
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
/*
* The only difference with the previous audit_layout1.refer_rename test is
@@ -8064,12 +7681,8 @@ TEST_F(audit_layout1, refer_rename_half)
},
{},
};
- int ruleset_fd =
- create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
- ASSERT_LE(0, ruleset_fd);
- enforce_ruleset(_metadata, ruleset_fd);
- ASSERT_EQ(0, close(ruleset_fd));
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3));
ASSERT_EQ(EXDEV, errno);
@@ -8087,9 +7700,7 @@ TEST_F(audit_layout1, truncate)
{
struct audit_records records;
- drop_access_rights(_metadata, &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
EXPECT_EQ(-1, truncate(file1_s1d3, 0));
EXPECT_EQ(EACCES, errno);
@@ -8106,12 +7717,8 @@ TEST_F(audit_layout1, ioctl_dev)
struct audit_records records;
int fd;
- drop_access_rights(_metadata,
- &(struct landlock_ruleset_attr){
- .handled_access_fs =
- access_fs_16 &
- ~LANDLOCK_ACCESS_FS_READ_FILE,
- });
+ enforce_fs(_metadata, access_fs_16 & ~LANDLOCK_ACCESS_FS_READ_FILE,
+ NULL);
fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
ASSERT_LE(0, fd);
@@ -8137,10 +7744,7 @@ TEST_F(audit_layout1, resolve_unix)
child_pid = fork();
ASSERT_LE(0, child_pid);
if (!child_pid) {
- drop_access_rights(_metadata,
- &(struct landlock_ruleset_attr){
- .handled_access_fs = access_fs_16,
- });
+ enforce_fs(_metadata, access_fs_16, NULL);
cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, cli_fd);
@@ -8169,11 +7773,7 @@ TEST_F(audit_layout1, mount)
{
struct audit_records records;
- drop_access_rights(_metadata,
- &(struct landlock_ruleset_attr){
- .handled_access_fs =
- LANDLOCK_ACCESS_FS_EXECUTE,
- });
+ enforce_fs(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, NULL);
set_cap(_metadata, CAP_SYS_ADMIN);
EXPECT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL));
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
` (7 preceding siblings ...)
2026-03-15 22:21 ` [PATCH v6 8/9] landlock/selftests: fs_test: Simplify ruleset creation and enforcement Günther Noack
@ 2026-03-15 22:21 ` Günther Noack
2026-03-18 16:54 ` Mickaël Salaün
8 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-15 22:21 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Günther Noack, Justin Suess, linux-security-module,
Tingmao Wang, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima
Cc: Justin Suess <utilityemal77@gmail.com>
Cc: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Günther Noack <gnoack3000@gmail.com>
---
Documentation/userspace-api/landlock.rst | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 13134bccdd39..e60ebd07c5cc 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -77,7 +77,8 @@ to be explicit about the denied-by-default access rights.
LANDLOCK_ACCESS_FS_MAKE_SYM |
LANDLOCK_ACCESS_FS_REFER |
LANDLOCK_ACCESS_FS_TRUNCATE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ LANDLOCK_ACCESS_FS_IOCTL_DEV |
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
.handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
@@ -127,6 +128,11 @@ version, and only use the available subset of access rights:
/* Removes LANDLOCK_SCOPE_* for ABI < 6 */
ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL);
+ __attribute__((fallthrough));
+ case 7:
+ case 8:
+ /* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
+ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
}
This enables the creation of an inclusive ruleset that will contain our rules.
@@ -685,6 +691,13 @@ enforce Landlock rulesets across all threads of the calling process
using the ``LANDLOCK_RESTRICT_SELF_TSYNC`` flag passed to
sys_landlock_restrict_self().
+Pathname UNIX sockets (ABI < 9)
+-------------------------------
+
+Starting with the Landlock ABI version 9, it is possible to restrict
+connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
+the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
+
.. _kernel_support:
Kernel support
--
2.53.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
@ 2026-03-17 21:14 ` Mickaël Salaün
2026-03-17 21:34 ` Paul Moore
2026-03-18 16:51 ` Mickaël Salaün
2 siblings, 0 replies; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-17 21:14 UTC (permalink / raw)
To: Paul Moore, Sebastian Andrzej Siewior, Kuniyuki Iwashima,
Simon Horman, Jakub Kicinski, netdev
Cc: Günther Noack, John Johansen, James Morris, Serge E . Hallyn,
Tingmao Wang, Justin Suess, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Alexander Viro, Christian Brauner
Paul and netdev folks, I think this new hook is now good. I'd like to
push this series to -next this week. Please let us know if you find any
issue, otherwise I guess we'll get more eyes and bots in the -next tree.
On Sun, Mar 15, 2026 at 11:21:42PM +0100, Günther Noack wrote:
> From: Justin Suess <utilityemal77@gmail.com>
>
> Add a LSM hook security_unix_find.
>
> This hook is called to check the path of a named unix socket before a
> connection is initiated. The peer socket may be inspected as well.
>
> Why existing hooks are unsuitable:
>
> Existing socket hooks, security_unix_stream_connect(),
> security_unix_may_send(), and security_socket_connect() don't provide
> TOCTOU-free / namespace independent access to the paths of sockets.
>
> (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> This requires another path lookup. A change in the path between the
> two lookups will cause a TOCTOU bug.
>
> (2) We cannot use the struct path from the listening socket, because it
> may be bound to a path in a different namespace than the caller,
> resulting in a path that cannot be referenced at policy creation time.
>
> Cc: Günther Noack <gnoack3000@gmail.com>
> Cc: Tingmao Wang <m@maowtm.org>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
> include/linux/lsm_hook_defs.h | 5 +++++
> include/linux/security.h | 11 +++++++++++
> net/unix/af_unix.c | 13 ++++++++++---
> security/security.c | 20 ++++++++++++++++++++
> 4 files changed, 46 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..7a0fd3dbfa29 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -317,6 +317,11 @@ LSM_HOOK(int, 0, post_notification, const struct cred *w_cred,
> LSM_HOOK(int, 0, watch_key, struct key *key)
> #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
>
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
> + int flags)
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +
> #ifdef CONFIG_SECURITY_NETWORK
> LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
> struct sock *newsk)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..99a33d8eb28d 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1931,6 +1931,17 @@ static inline int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
> }
> #endif /* CONFIG_SECURITY_NETWORK */
>
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +
> +int security_unix_find(const struct path *path, struct sock *other, int flags);
> +
> +#else /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> + return 0;
> +}
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +
> #ifdef CONFIG_SECURITY_INFINIBAND
> int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey);
> int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num);
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 3756a93dc63a..aced28179bac 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1231,11 +1231,18 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
> goto path_put;
>
> err = -EPROTOTYPE;
> - if (sk->sk_type == type)
> - touch_atime(&path);
> - else
> + if (sk->sk_type != type)
> goto sock_put;
>
> + /*
> + * We call the hook because we know that the inode is a socket and we
> + * hold a valid reference to it via the path.
> + */
> + err = security_unix_find(&path, sk, flags);
> + if (err)
> + goto sock_put;
> + touch_atime(&path);
> +
> path_put(&path);
>
> return sk;
> diff --git a/security/security.c b/security/security.c
> index 67af9228c4e9..c73196b8db4b 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -4731,6 +4731,26 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
>
> #endif /* CONFIG_SECURITY_NETWORK */
>
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +/**
> + * security_unix_find() - Check if a named AF_UNIX socket can connect
> + * @path: path of the socket being connected to
> + * @other: peer sock
> + * @flags: flags associated with the socket
> + *
> + * This hook is called to check permissions before connecting to a named
> + * AF_UNIX socket.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> + return call_int_hook(unix_find, path, other, flags);
> +}
> +EXPORT_SYMBOL(security_unix_find);
> +
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +
> #ifdef CONFIG_SECURITY_INFINIBAND
> /**
> * security_ib_pkey_access() - Check if access to an IB pkey is allowed
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
2026-03-17 21:14 ` Mickaël Salaün
@ 2026-03-17 21:34 ` Paul Moore
2026-03-17 23:20 ` [PATCH v7 " Justin Suess
2026-03-18 8:48 ` [PATCH v6 " Mickaël Salaün
2026-03-18 16:51 ` Mickaël Salaün
2 siblings, 2 replies; 43+ messages in thread
From: Paul Moore @ 2026-03-17 21:34 UTC (permalink / raw)
To: Günther Noack, Mickaël Salaün, John Johansen,
James Morris, Serge E . Hallyn
Cc: Günther Noack, Tingmao Wang, Justin Suess,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
>
> Add a LSM hook security_unix_find.
>
> This hook is called to check the path of a named unix socket before a
> connection is initiated. The peer socket may be inspected as well.
>
> Why existing hooks are unsuitable:
>
> Existing socket hooks, security_unix_stream_connect(),
> security_unix_may_send(), and security_socket_connect() don't provide
> TOCTOU-free / namespace independent access to the paths of sockets.
>
> (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> This requires another path lookup. A change in the path between the
> two lookups will cause a TOCTOU bug.
>
> (2) We cannot use the struct path from the listening socket, because it
> may be bound to a path in a different namespace than the caller,
> resulting in a path that cannot be referenced at policy creation time.
>
> Cc: Günther Noack <gnoack3000@gmail.com>
> Cc: Tingmao Wang <m@maowtm.org>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
> include/linux/lsm_hook_defs.h | 5 +++++
> include/linux/security.h | 11 +++++++++++
> net/unix/af_unix.c | 13 ++++++++++---
> security/security.c | 20 ++++++++++++++++++++
> 4 files changed, 46 insertions(+), 3 deletions(-)
Some really minor nitpicky things (below), but nothing critical.
However, as we discussed, I would like to see the AppArmor folks comment
on the new hook before we merge anything as I know they have an interest
here.
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..7a0fd3dbfa29 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -317,6 +317,11 @@ LSM_HOOK(int, 0, post_notification, const struct cred *w_cred,
> LSM_HOOK(int, 0, watch_key, struct key *key)
> #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
>
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
> + int flags)
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
I'd suggest moving this into the CONFIG_SECURITY_NETWORK that is directly
below this block so you only have to check the CONFIG_SECURITY_PATH
state. You can place it directly after the existing security_unix*()
hooks.
> #ifdef CONFIG_SECURITY_NETWORK
> LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
> struct sock *newsk)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..99a33d8eb28d 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1931,6 +1931,17 @@ static inline int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
> }
> #endif /* CONFIG_SECURITY_NETWORK */
>
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +
> +int security_unix_find(const struct path *path, struct sock *other, int flags);
> +
> +#else /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> + return 0;
> +}
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
Similar to above, I would suggest moving this into the
CONFIG_SECURITY_NETWORK block directly above this so you only need to
check for CONFIG_SECURITY_PATH when declaring the security_unix_find()
hook.
Extra bonus points if you locate it next to the existing security_unix*()
hooks.
> #ifdef CONFIG_SECURITY_INFINIBAND
> int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey);
> int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num);
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 3756a93dc63a..aced28179bac 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1231,11 +1231,18 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
> goto path_put;
>
> err = -EPROTOTYPE;
> - if (sk->sk_type == type)
> - touch_atime(&path);
> - else
> + if (sk->sk_type != type)
> goto sock_put;
>
> + /*
> + * We call the hook because we know that the inode is a socket and we
> + * hold a valid reference to it via the path.
> + */
I'm not entirely sure that this comment is necessary as it doesn't tell
us anything we don't already know from a quick glance at the code. Is
there something sneaky, or hard to see, that we should know about?
> + err = security_unix_find(&path, sk, flags);
> + if (err)
> + goto sock_put;
> + touch_atime(&path);
> +
This is hyper nitpicky, but I'd probably put one line of vertical
whitespace before the touch_atime() call as it has nothing to do with
the LSM hook being called.
> path_put(&path);
>
> return sk;
> diff --git a/security/security.c b/security/security.c
> index 67af9228c4e9..c73196b8db4b 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -4731,6 +4731,26 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
>
> #endif /* CONFIG_SECURITY_NETWORK */
>
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +/**
> + * security_unix_find() - Check if a named AF_UNIX socket can connect
> + * @path: path of the socket being connected to
> + * @other: peer sock
> + * @flags: flags associated with the socket
> + *
> + * This hook is called to check permissions before connecting to a named
> + * AF_UNIX socket.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> + return call_int_hook(unix_find, path, other, flags);
> +}
> +EXPORT_SYMBOL(security_unix_find);
> +
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
You can probably guess that I'm going to suggest placing this inside the
existing CONFIG_SECURITY_NETWORK block, right after the existing UNIX
hooks :)
--
paul-moore.com
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v7 1/9] lsm: Add LSM hook security_unix_find
2026-03-17 21:34 ` Paul Moore
@ 2026-03-17 23:20 ` Justin Suess
2026-03-18 1:28 ` Paul Moore
2026-03-18 8:48 ` [PATCH v6 " Mickaël Salaün
1 sibling, 1 reply; 43+ messages in thread
From: Justin Suess @ 2026-03-17 23:20 UTC (permalink / raw)
To: paul
Cc: bigeasy, brauner, demiobenour, fahimitahera, gnoack3000, hi,
horms, ivanov.mikhail1, jannh, jmorris, john.johansen,
konstantin.meskhidze, kuniyu, linux-security-module, m, matthieu,
mic, netdev, samasth.norway.ananda, serge, utilityemal77, viro
Add a LSM hook security_unix_find.
This hook is called to check the path of a named unix socket before a
connection is initiated. The peer socket may be inspected as well.
Why existing hooks are unsuitable:
Existing socket hooks, security_unix_stream_connect(),
security_unix_may_send(), and security_socket_connect() don't provide
TOCTOU-free / namespace independent access to the paths of sockets.
(1) We cannot resolve the path from the struct sockaddr in existing hooks.
This requires another path lookup. A change in the path between the
two lookups will cause a TOCTOU bug.
(2) We cannot use the struct path from the listening socket, because it
may be bound to a path in a different namespace than the caller,
resulting in a path that cannot be referenced at policy creation time.
Consumers of the hook wishing to reference @other are responsible
for acquiring the unix_state_lock and checking for the SOCK_DEAD flag
therein, ensuring the socket hasn't died since lookup.
Cc: Günther Noack <gnoack3000@gmail.com>
Cc: Tingmao Wang <m@maowtm.org>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
Paul,
I updated the hook placement as per your suggestions. Moving the hook into
the block does require duplicate stubs, but I don't see another way to move the
stub into that block and properly handle the case where CONFIG_SECURITY_PATH is
defined but CONFIG_SECURITY_NETWORK isn't. If the stub is moved into that #else
block it will never be defined in that case.
I removed the self-evident comment as well from security_unix_find and added
the whitespace.
I also updated the comments and commit message with respect to locking.
include/linux/lsm_hook_defs.h | 6 ++++++
include/linux/security.h | 13 +++++++++++++
net/unix/af_unix.c | 10 +++++++---
security/security.c | 19 +++++++++++++++++++
4 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c..0017a540c2fb 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -321,6 +321,12 @@ LSM_HOOK(int, 0, watch_key, struct key *key)
LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
struct sock *newsk)
LSM_HOOK(int, 0, unix_may_send, struct socket *sock, struct socket *other)
+
+#ifdef CONFIG_SECURITY_PATH
+LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
+ int flags)
+#endif /* CONFIG_SECURITY_PATH */
+
LSM_HOOK(int, 0, socket_create, int family, int type, int protocol, int kern)
LSM_HOOK(int, 0, socket_post_create, struct socket *sock, int family, int type,
int protocol, int kern)
diff --git a/include/linux/security.h b/include/linux/security.h
index 83a646d72f6f..3f8c23ad1199 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1641,6 +1641,14 @@ static inline int security_watch_key(struct key *key)
int security_netlink_send(struct sock *sk, struct sk_buff *skb);
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk);
int security_unix_may_send(struct socket *sock, struct socket *other);
+#ifdef CONFIG_SECURITY_PATH
+int security_unix_find(const struct path *path, struct sock *other, int flags);
+#else /* CONFIG_SECURITY_PATH */
+static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+ return 0;
+}
+#endif /* CONFIG_SECURITY_PATH */
int security_socket_create(int family, int type, int protocol, int kern);
int security_socket_post_create(struct socket *sock, int family,
int type, int protocol, int kern);
@@ -1712,6 +1720,11 @@ static inline int security_unix_may_send(struct socket *sock,
return 0;
}
+static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+ return 0;
+}
+
static inline int security_socket_create(int family, int type,
int protocol, int kern)
{
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 3756a93dc63a..5ef3c2e31757 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1231,11 +1231,15 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
goto path_put;
err = -EPROTOTYPE;
- if (sk->sk_type == type)
- touch_atime(&path);
- else
+ if (sk->sk_type != type)
goto sock_put;
+ err = security_unix_find(&path, sk, flags);
+ if (err)
+ goto sock_put;
+
+ touch_atime(&path);
+
path_put(&path);
return sk;
diff --git a/security/security.c b/security/security.c
index 67af9228c4e9..f8df5e1b55e6 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4073,6 +4073,25 @@ int security_unix_may_send(struct socket *sock, struct socket *other)
}
EXPORT_SYMBOL(security_unix_may_send);
+#ifdef CONFIG_SECURITY_PATH
+/**
+ * security_unix_find() - Check if a named AF_UNIX socket can connect
+ * @path: path of the socket being connected to
+ * @other: peer sock
+ * @flags: flags associated with the socket
+ *
+ * This hook is called to check permissions before connecting to a named
+ * AF_UNIX socket. The caller does not hold any locks on @other.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+ return call_int_hook(unix_find, path, other, flags);
+}
+EXPORT_SYMBOL(security_unix_find);
+#endif /* CONFIG_SECURITY_PATH */
+
/**
* security_socket_create() - Check if creating a new socket is allowed
* @family: protocol family
--
2.51.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v7 1/9] lsm: Add LSM hook security_unix_find
2026-03-17 23:20 ` [PATCH v7 " Justin Suess
@ 2026-03-18 1:28 ` Paul Moore
0 siblings, 0 replies; 43+ messages in thread
From: Paul Moore @ 2026-03-18 1:28 UTC (permalink / raw)
To: Justin Suess
Cc: bigeasy, brauner, demiobenour, fahimitahera, gnoack3000, hi,
horms, ivanov.mikhail1, jannh, jmorris, john.johansen,
konstantin.meskhidze, kuniyu, linux-security-module, m, matthieu,
mic, netdev, samasth.norway.ananda, serge, viro
On Tue, Mar 17, 2026 at 7:21 PM Justin Suess <utilityemal77@gmail.com> wrote:
>
> Paul,
>
> I updated the hook placement as per your suggestions. Moving the hook into
> the block does require duplicate stubs, but I don't see another way to move the
> stub into that block and properly handle the case where CONFIG_SECURITY_PATH is
> defined but CONFIG_SECURITY_NETWORK isn't. If the stub is moved into that #else
> block it will never be defined in that case.
Oof, yes, my apologies, I must have still been thinking about the
LSM_HOOK() change and didn't think through the problems with moving
the declaration. If you aren't too upset about changing it back, I
would prefer it back the way you had it in security.h originally.
Sorry for the noise :/
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..3f8c23ad1199 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1641,6 +1641,14 @@ static inline int security_watch_key(struct key *key)
> int security_netlink_send(struct sock *sk, struct sk_buff *skb);
> int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk);
> int security_unix_may_send(struct socket *sock, struct socket *other);
> +#ifdef CONFIG_SECURITY_PATH
> +int security_unix_find(const struct path *path, struct sock *other, int flags);
> +#else /* CONFIG_SECURITY_PATH */
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> + return 0;
> +}
> +#endif /* CONFIG_SECURITY_PATH */
> int security_socket_create(int family, int type, int protocol, int kern);
> int security_socket_post_create(struct socket *sock, int family,
> int type, int protocol, int kern);
> @@ -1712,6 +1720,11 @@ static inline int security_unix_may_send(struct socket *sock,
> return 0;
> }
>
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> + return 0;
> +}
> +
--
paul-moore.com
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-17 21:34 ` Paul Moore
2026-03-17 23:20 ` [PATCH v7 " Justin Suess
@ 2026-03-18 8:48 ` Mickaël Salaün
2026-03-18 14:44 ` Paul Moore
2026-03-23 14:37 ` Georgia Garcia
1 sibling, 2 replies; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 8:48 UTC (permalink / raw)
To: John Johansen, Georgia Garcia
Cc: Paul Moore, Günther Noack, James Morris, Serge E . Hallyn,
Tingmao Wang, Justin Suess, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima,
Simon Horman, netdev, Alexander Viro, Christian Brauner
On Tue, Mar 17, 2026 at 05:34:57PM -0400, Paul Moore wrote:
> On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> >
> > Add a LSM hook security_unix_find.
> >
> > This hook is called to check the path of a named unix socket before a
> > connection is initiated. The peer socket may be inspected as well.
> >
> > Why existing hooks are unsuitable:
> >
> > Existing socket hooks, security_unix_stream_connect(),
> > security_unix_may_send(), and security_socket_connect() don't provide
> > TOCTOU-free / namespace independent access to the paths of sockets.
> >
> > (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> > This requires another path lookup. A change in the path between the
> > two lookups will cause a TOCTOU bug.
> >
> > (2) We cannot use the struct path from the listening socket, because it
> > may be bound to a path in a different namespace than the caller,
> > resulting in a path that cannot be referenced at policy creation time.
> >
> > Cc: Günther Noack <gnoack3000@gmail.com>
> > Cc: Tingmao Wang <m@maowtm.org>
> > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > ---
> > include/linux/lsm_hook_defs.h | 5 +++++
> > include/linux/security.h | 11 +++++++++++
> > net/unix/af_unix.c | 13 ++++++++++---
> > security/security.c | 20 ++++++++++++++++++++
> > 4 files changed, 46 insertions(+), 3 deletions(-)
>
> Some really minor nitpicky things (below), but nothing critical.
> However, as we discussed, I would like to see the AppArmor folks comment
> on the new hook before we merge anything as I know they have an interest
> here.
John, Georgia, we've been discussing this new hook for a few months now
but didn't hear from you yet. We plan to merge this patch series with
the 7.1 merge window (in a few weeks), so before that I'd like to merge
it in -next in a few days to get a broader coverage. I'm pretty sure
this hook will work well with AppArmor too, but could you please take
look to confirm?
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-15 22:21 ` [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path Günther Noack
@ 2026-03-18 11:15 ` Sebastian Andrzej Siewior
2026-03-18 14:14 ` Justin Suess
2026-03-18 16:52 ` Mickaël Salaün
1 sibling, 1 reply; 43+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-03-18 11:15 UTC (permalink / raw)
To: Günther Noack
Cc: Mickaël Salaün, John Johansen, Tingmao Wang,
Justin Suess, Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On 2026-03-15 23:21:44 [+0100], Günther Noack wrote:
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
…
> @@ -1557,6 +1560,110 @@ static int hook_path_truncate(const struct path *const path)
…
> +static int hook_unix_find(const struct path *const path, struct sock *other,
> + int flags)
> +{
…
> + /* Checks the layers in which we are connecting within the same domain. */
> + unix_state_lock(other);
> + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket ||
> + !other->sk_socket->file)) {
> + unix_state_unlock(other);
> + return 0;
> + }
> + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> + unix_state_unlock(other);
> +
> + unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> + fs_resolve_unix.fs);
This might be obvious but in case it is not: You obtain the domain
pointer from f_cred->security. Within the unix_state_lock() block the fd
can not be closed. Once you drop the lock, the fd can be closed. What
guarantees that the domain/ dom_other point remains valid between
unix_state_unlock() and after unmask_scoped_access()?
Is this invoked within a RCU section which would delay put_cred_rcu() or
is there other magic involved?
Sebastian
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 11:15 ` Sebastian Andrzej Siewior
@ 2026-03-18 14:14 ` Justin Suess
2026-03-18 15:05 ` Sebastian Andrzej Siewior
0 siblings, 1 reply; 43+ messages in thread
From: Justin Suess @ 2026-03-18 14:14 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Günther Noack, Mickaël Salaün, John Johansen,
Tingmao Wang, Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On Wed, Mar 18, 2026 at 12:15:07PM +0100, Sebastian Andrzej Siewior wrote:
> On 2026-03-15 23:21:44 [+0100], Günther Noack wrote:
> > --- a/security/landlock/fs.c
> > +++ b/security/landlock/fs.c
> …
> > @@ -1557,6 +1560,110 @@ static int hook_path_truncate(const struct path *const path)
> …
> > +static int hook_unix_find(const struct path *const path, struct sock *other,
> > + int flags)
> > +{
> …
> > + /* Checks the layers in which we are connecting within the same domain. */
> > + unix_state_lock(other);
> > + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket ||
> > + !other->sk_socket->file)) {
> > + unix_state_unlock(other);
> > + return 0;
> > + }
> > + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> > + unix_state_unlock(other);
> > +
> > + unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> > + fs_resolve_unix.fs);
>
> This might be obvious but in case it is not: You obtain the domain
> pointer from f_cred->security. Within the unix_state_lock() block the fd
> can not be closed. Once you drop the lock, the fd can be closed. What
> guarantees that the domain/ dom_other point remains valid between
> unix_state_unlock() and after unmask_scoped_access()?
Sebastian,
In short: dom_other is a pointer to a landlock-owned refcounted struct.
There are two cases, one where there is a correspoinding landlock domain
with the other socket, and one where there is not.
We lookup the domain under lock:
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
The '->domain' part is important.
Thus dom_other isn't a pointer to the actual tasks credentials,
it's a pointer to a landlock controlled/refcounted domain.
This is the function which returns a pointer to the creds.
static inline struct landlock_cred_security *
landlock_cred(const struct cred *cred)
{
return cred->security + landlock_blob_sizes.lbs_cred;
}
If we were storing the landlock_cred_securit, that could potentially
be a problem.
But we copy the domain pointer, which points to a landlock allocated
and controlled object.
If it is a domain, dom_other points to a landlock controlled, refcounted
struct landlock_ruleset object. So even if the f_cred is freed
afterwards, that object is still valid.
Justin
> Is this invoked within a RCU section which would delay put_cred_rcu() or
> is there other magic involved?
>
> Sebastian
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-18 8:48 ` [PATCH v6 " Mickaël Salaün
@ 2026-03-18 14:44 ` Paul Moore
2026-03-18 16:22 ` Mickaël Salaün
2026-03-23 14:37 ` Georgia Garcia
1 sibling, 1 reply; 43+ messages in thread
From: Paul Moore @ 2026-03-18 14:44 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Georgia Garcia, Günther Noack, James Morris,
Serge E . Hallyn, Tingmao Wang, Justin Suess,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
On Wed, Mar 18, 2026 at 4:48 AM Mickaël Salaün <mic@digikod.net> wrote:
>
> On Tue, Mar 17, 2026 at 05:34:57PM -0400, Paul Moore wrote:
> > On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> > >
> > > Add a LSM hook security_unix_find.
> > >
> > > This hook is called to check the path of a named unix socket before a
> > > connection is initiated. The peer socket may be inspected as well.
> > >
> > > Why existing hooks are unsuitable:
> > >
> > > Existing socket hooks, security_unix_stream_connect(),
> > > security_unix_may_send(), and security_socket_connect() don't provide
> > > TOCTOU-free / namespace independent access to the paths of sockets.
> > >
> > > (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> > > This requires another path lookup. A change in the path between the
> > > two lookups will cause a TOCTOU bug.
> > >
> > > (2) We cannot use the struct path from the listening socket, because it
> > > may be bound to a path in a different namespace than the caller,
> > > resulting in a path that cannot be referenced at policy creation time.
> > >
> > > Cc: Günther Noack <gnoack3000@gmail.com>
> > > Cc: Tingmao Wang <m@maowtm.org>
> > > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > > ---
> > > include/linux/lsm_hook_defs.h | 5 +++++
> > > include/linux/security.h | 11 +++++++++++
> > > net/unix/af_unix.c | 13 ++++++++++---
> > > security/security.c | 20 ++++++++++++++++++++
> > > 4 files changed, 46 insertions(+), 3 deletions(-)
> >
> > Some really minor nitpicky things (below), but nothing critical.
> > However, as we discussed, I would like to see the AppArmor folks comment
> > on the new hook before we merge anything as I know they have an interest
> > here.
>
> John, Georgia, we've been discussing this new hook for a few months now
> but didn't hear from you yet. We plan to merge this patch series with
> the 7.1 merge window (in a few weeks), so before that I'd like to merge
> it in -next in a few days to get a broader coverage. I'm pretty sure
> this hook will work well with AppArmor too, but could you please take
> look to confirm?
I probably wasn't as clear as I should have been, my apologies. The
major reason I held back my ACK on this patch was that I wanted to
hear from the AA folks regarding the hook's suitability for their
needs. While I don't expect they will have an issue with this hook
as-is, they have expressed interest in the hook, and I would just
assume make sure it is okay for everyone before we send it to Linus.
Since this is a feature addition and not a critical bug fix, I will be
quite upset if this is sent to Linus without review by the AA
developers and my ACK.
--
paul-moore.com
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 14:14 ` Justin Suess
@ 2026-03-18 15:05 ` Sebastian Andrzej Siewior
2026-03-18 16:26 ` Mickaël Salaün
0 siblings, 1 reply; 43+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-03-18 15:05 UTC (permalink / raw)
To: Justin Suess
Cc: Günther Noack, Mickaël Salaün, John Johansen,
Tingmao Wang, Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On 2026-03-18 10:14:52 [-0400], Justin Suess wrote:
> Sebastian,
Justin,
> In short: dom_other is a pointer to a landlock-owned refcounted struct.
…
>
> But we copy the domain pointer, which points to a landlock allocated
> and controlled object.
and this is not going away while we are here and preempted after
dropping the lock? (if the landlock policy is updated/ changed/ …)
>
> Justin
Sebastian
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-18 14:44 ` Paul Moore
@ 2026-03-18 16:22 ` Mickaël Salaün
2026-03-18 16:43 ` Paul Moore
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:22 UTC (permalink / raw)
To: Paul Moore
Cc: John Johansen, Georgia Garcia, Günther Noack, James Morris,
Serge E . Hallyn, Tingmao Wang, Justin Suess,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
On Wed, Mar 18, 2026 at 10:44:26AM -0400, Paul Moore wrote:
> On Wed, Mar 18, 2026 at 4:48 AM Mickaël Salaün <mic@digikod.net> wrote:
> >
> > On Tue, Mar 17, 2026 at 05:34:57PM -0400, Paul Moore wrote:
> > > On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> > > >
> > > > Add a LSM hook security_unix_find.
> > > >
> > > > This hook is called to check the path of a named unix socket before a
> > > > connection is initiated. The peer socket may be inspected as well.
> > > >
> > > > Why existing hooks are unsuitable:
> > > >
> > > > Existing socket hooks, security_unix_stream_connect(),
> > > > security_unix_may_send(), and security_socket_connect() don't provide
> > > > TOCTOU-free / namespace independent access to the paths of sockets.
> > > >
> > > > (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> > > > This requires another path lookup. A change in the path between the
> > > > two lookups will cause a TOCTOU bug.
> > > >
> > > > (2) We cannot use the struct path from the listening socket, because it
> > > > may be bound to a path in a different namespace than the caller,
> > > > resulting in a path that cannot be referenced at policy creation time.
> > > >
> > > > Cc: Günther Noack <gnoack3000@gmail.com>
> > > > Cc: Tingmao Wang <m@maowtm.org>
> > > > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > > > ---
> > > > include/linux/lsm_hook_defs.h | 5 +++++
> > > > include/linux/security.h | 11 +++++++++++
> > > > net/unix/af_unix.c | 13 ++++++++++---
> > > > security/security.c | 20 ++++++++++++++++++++
> > > > 4 files changed, 46 insertions(+), 3 deletions(-)
> > >
> > > Some really minor nitpicky things (below), but nothing critical.
> > > However, as we discussed, I would like to see the AppArmor folks comment
> > > on the new hook before we merge anything as I know they have an interest
> > > here.
> >
> > John, Georgia, we've been discussing this new hook for a few months now
> > but didn't hear from you yet. We plan to merge this patch series with
> > the 7.1 merge window (in a few weeks), so before that I'd like to merge
> > it in -next in a few days to get a broader coverage. I'm pretty sure
> > this hook will work well with AppArmor too, but could you please take
> > look to confirm?
>
> I probably wasn't as clear as I should have been, my apologies. The
> major reason I held back my ACK on this patch was that I wanted to
> hear from the AA folks regarding the hook's suitability for their
> needs. While I don't expect they will have an issue with this hook
> as-is, they have expressed interest in the hook, and I would just
> assume make sure it is okay for everyone before we send it to Linus.
>
> Since this is a feature addition and not a critical bug fix, I will be
> quite upset if this is sent to Linus without review by the AA
> developers and my ACK.
I definitely understand and that makes sense, but even if it is not a
strict fix, please consider that this feature still fixes an important
gap in practice (e.g. run anything outside a sandbox with the help of
systemd; see cover letter and reported issues [1]). I don't want to
bypass anyone, but given the importance of this change, I don't want to
postpone it either (except if there is a major issue of course, but I
think the review was pretty good). That's why we'd like to get some
feedback sooner than later. While waiting for AA folks feedback, would
you still be OK for me to push it to -next to improve test and review
coverage?
[1] https://lore.kernel.org/all/cover.1767115163.git.m@maowtm.org/
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 15:05 ` Sebastian Andrzej Siewior
@ 2026-03-18 16:26 ` Mickaël Salaün
2026-03-18 16:43 ` Justin Suess
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:26 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Justin Suess, Günther Noack, John Johansen, Tingmao Wang,
Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On Wed, Mar 18, 2026 at 04:05:59PM +0100, Sebastian Andrzej Siewior wrote:
> On 2026-03-18 10:14:52 [-0400], Justin Suess wrote:
> > Sebastian,
> Justin,
>
> > In short: dom_other is a pointer to a landlock-owned refcounted struct.
> …
> >
> > But we copy the domain pointer, which points to a landlock allocated
> > and controlled object.
>
> and this is not going away while we are here and preempted after
> dropping the lock? (if the landlock policy is updated/ changed/ …)
I agree with Sebastian, this is a bug, see my original proposal:
https://lore.kernel.org/all/20260217.lievaS8eeng8@digikod.net/
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-18 16:22 ` Mickaël Salaün
@ 2026-03-18 16:43 ` Paul Moore
0 siblings, 0 replies; 43+ messages in thread
From: Paul Moore @ 2026-03-18 16:43 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Georgia Garcia, Günther Noack, James Morris,
Serge E . Hallyn, Tingmao Wang, Justin Suess,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
On Wed, Mar 18, 2026 at 12:22 PM Mickaël Salaün <mic@digikod.net> wrote:
> On Wed, Mar 18, 2026 at 10:44:26AM -0400, Paul Moore wrote:
> > On Wed, Mar 18, 2026 at 4:48 AM Mickaël Salaün <mic@digikod.net> wrote:
> > > On Tue, Mar 17, 2026 at 05:34:57PM -0400, Paul Moore wrote:
> > > > On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> > > > >
> > > > > Add a LSM hook security_unix_find.
> > > > >
> > > > > This hook is called to check the path of a named unix socket before a
> > > > > connection is initiated. The peer socket may be inspected as well.
> > > > >
> > > > > Why existing hooks are unsuitable:
> > > > >
> > > > > Existing socket hooks, security_unix_stream_connect(),
> > > > > security_unix_may_send(), and security_socket_connect() don't provide
> > > > > TOCTOU-free / namespace independent access to the paths of sockets.
> > > > >
> > > > > (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> > > > > This requires another path lookup. A change in the path between the
> > > > > two lookups will cause a TOCTOU bug.
> > > > >
> > > > > (2) We cannot use the struct path from the listening socket, because it
> > > > > may be bound to a path in a different namespace than the caller,
> > > > > resulting in a path that cannot be referenced at policy creation time.
> > > > >
> > > > > Cc: Günther Noack <gnoack3000@gmail.com>
> > > > > Cc: Tingmao Wang <m@maowtm.org>
> > > > > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > > > > ---
> > > > > include/linux/lsm_hook_defs.h | 5 +++++
> > > > > include/linux/security.h | 11 +++++++++++
> > > > > net/unix/af_unix.c | 13 ++++++++++---
> > > > > security/security.c | 20 ++++++++++++++++++++
> > > > > 4 files changed, 46 insertions(+), 3 deletions(-)
> > > >
> > > > Some really minor nitpicky things (below), but nothing critical.
> > > > However, as we discussed, I would like to see the AppArmor folks comment
> > > > on the new hook before we merge anything as I know they have an interest
> > > > here.
> > >
> > > John, Georgia, we've been discussing this new hook for a few months now
> > > but didn't hear from you yet. We plan to merge this patch series with
> > > the 7.1 merge window (in a few weeks), so before that I'd like to merge
> > > it in -next in a few days to get a broader coverage. I'm pretty sure
> > > this hook will work well with AppArmor too, but could you please take
> > > look to confirm?
> >
> > I probably wasn't as clear as I should have been, my apologies. The
> > major reason I held back my ACK on this patch was that I wanted to
> > hear from the AA folks regarding the hook's suitability for their
> > needs. While I don't expect they will have an issue with this hook
> > as-is, they have expressed interest in the hook, and I would just
> > assume make sure it is okay for everyone before we send it to Linus.
> >
> > Since this is a feature addition and not a critical bug fix, I will be
> > quite upset if this is sent to Linus without review by the AA
> > developers and my ACK.
>
> I definitely understand and that makes sense, but even if it is not a
> strict fix, please consider that this feature still fixes an important
> gap in practice (e.g. run anything outside a sandbox with the help of
> systemd; see cover letter and reported issues [1]). I don't want to
> bypass anyone, but given the importance of this change, I don't want to
> postpone it either (except if there is a major issue of course, but I
> think the review was pretty good).
I understand the desire to close the gap, but as this is a known
limitation of the existing Landlock implementation, I don't think this
is a MUST (in the RFC sense) for the next merge window. It's
definitely a want, and a *very* nice to have, but if they AA folks
need some more time to sort things out after what has happened
recently I think we can afford them that in this case.
> That's why we'd like to get some
> feedback sooner than later. While waiting for AA folks feedback, would
> you still be OK for me to push it to -next to improve test and review
> coverage?
That's fine, my comment about "sent to Linus" was deliberate. Feel
free to do what is appropriate to gather additional testing.
--
paul-moore.com
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 16:26 ` Mickaël Salaün
@ 2026-03-18 16:43 ` Justin Suess
2026-03-18 17:52 ` Mickaël Salaün
0 siblings, 1 reply; 43+ messages in thread
From: Justin Suess @ 2026-03-18 16:43 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Sebastian Andrzej Siewior, Günther Noack, John Johansen,
Tingmao Wang, Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On Wed, Mar 18, 2026 at 05:26:20PM +0100, Mickaël Salaün wrote:
> On Wed, Mar 18, 2026 at 04:05:59PM +0100, Sebastian Andrzej Siewior wrote:
> > On 2026-03-18 10:14:52 [-0400], Justin Suess wrote:
> > > Sebastian,
> > Justin,
> >
> > > In short: dom_other is a pointer to a landlock-owned refcounted struct.
> > …
> > >
> > > But we copy the domain pointer, which points to a landlock allocated
> > > and controlled object.
> >
> > and this is not going away while we are here and preempted after
> > dropping the lock? (if the landlock policy is updated/ changed/ …)
>
> I agree with Sebastian, this is a bug, see my original proposal:
> https://lore.kernel.org/all/20260217.lievaS8eeng8@digikod.net/
Mickaël,
Just to make sure we're speaking of the same thing (I spotted a bug
shortly after replying to Sebastian).
This is a potential UAF if the dom_other is freed before the access
check takes place correct?
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
unix_state_unlock(other);
unmask_scoped_access(subject->domain, dom_other, &layer_masks,
fs_resolve_unix.fs);
If the dom_other->usage reaches zero, then the domain could be
freed after the unix_state_unlock while we're checking access??
(I guess I assumed the sock_hold on the @other would prevent the task
@other belongs to from being freed.)
Would it be better to move the access check under the unix_state_lock or
to acquire another reference to the ruleset (or something else)?
(Good catch Sebastian sorry for the confusion.)
Justin
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
2026-03-17 21:14 ` Mickaël Salaün
2026-03-17 21:34 ` Paul Moore
@ 2026-03-18 16:51 ` Mickaël Salaün
2 siblings, 0 replies; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:51 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, Paul Moore, James Morris, Serge E . Hallyn,
Tingmao Wang, Justin Suess, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima,
Simon Horman, netdev, Alexander Viro, Christian Brauner
On Sun, Mar 15, 2026 at 11:21:42PM +0100, Günther Noack wrote:
> From: Justin Suess <utilityemal77@gmail.com>
>
> Add a LSM hook security_unix_find.
Add an LSM hook...
>
> This hook is called to check the path of a named unix socket before a
UNIX socket
> connection is initiated. The peer socket may be inspected as well.
>
> Why existing hooks are unsuitable:
>
> Existing socket hooks, security_unix_stream_connect(),
> security_unix_may_send(), and security_socket_connect() don't provide
> TOCTOU-free / namespace independent access to the paths of sockets.
>
> (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> This requires another path lookup. A change in the path between the
> two lookups will cause a TOCTOU bug.
>
> (2) We cannot use the struct path from the listening socket, because it
> may be bound to a path in a different namespace than the caller,
> resulting in a path that cannot be referenced at policy creation time.
>
> Cc: Günther Noack <gnoack3000@gmail.com>
> Cc: Tingmao Wang <m@maowtm.org>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
> include/linux/lsm_hook_defs.h | 5 +++++
> include/linux/security.h | 11 +++++++++++
> net/unix/af_unix.c | 13 ++++++++++---
> security/security.c | 20 ++++++++++++++++++++
> 4 files changed, 46 insertions(+), 3 deletions(-)
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed()
2026-03-15 22:21 ` [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed() Günther Noack
@ 2026-03-18 16:52 ` Mickaël Salaün
2026-03-20 10:50 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:52 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, linux-security-module, Tingmao Wang, Justin Suess,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
Subject should be "landlock: Use..."
On Sun, Mar 15, 2026 at 11:21:43PM +0100, Günther Noack wrote:
> This is equivalent, but expresses the intent a bit clearer.
>
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> security/landlock/fs.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index a03ec664c78e..97065d51685a 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -564,7 +564,7 @@ static void test_no_more_access(struct kunit *const test)
>
> static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
> {
> - return !memchr_inv(&masks->access, 0, sizeof(masks->access));
> + return mem_is_zero(&masks->access, sizeof(masks->access));
Thanks
> }
>
> /*
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-15 22:21 ` [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path Günther Noack
2026-03-18 11:15 ` Sebastian Andrzej Siewior
@ 2026-03-18 16:52 ` Mickaël Salaün
2026-03-20 16:15 ` Günther Noack
1 sibling, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:52 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi
On Sun, Mar 15, 2026 at 11:21:44PM +0100, Günther Noack wrote:
> * Add a new access right LANDLOCK_ACCESS_FS_RESOLVE_UNIX, which
> controls the look up operations for named UNIX domain sockets. The
lookup
> resolution happens during connect() and sendmsg() (depending on
> socket type).
> * Hook into the path lookup in unix_find_bsd() in af_unix.c, using a
> LSM hook. Make policy decisions based on the new access rights
> * Increment the Landlock ABI version.
> * Minor test adaptions to keep the tests working.
adaptations
> * Document the design rationale for scoped access rights,
> and cross-reference it from the header documentation.
>
> With this access right, access is granted if either of the following
> conditions is met:
>
> * The target socket's filesystem path was allow-listed using a
> LANDLOCK_RULE_PATH_BENEATH rule, *or*:
> * The target socket was created in the same Landlock domain in which
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX was restricted.
>
> In case of a denial, connect() and sendmsg() return EACCES, which is
> the same error as it is returned if the user does not have the write
> bit in the traditional Unix file system permissions of that file.
UNIX
>
> Document the (possible future) interaction between scoped flags and
> other access rights in struct landlock_ruleset_attr, and summarize the
> rationale, as discussed in code review leading up to [2].
>
> This feature was created with substantial discussion and input from
> Justin Suess, Tingmao Wang and Mickaël Salaün.
>
> Cc: Tingmao Wang <m@maowtm.org>
> Cc: Justin Suess <utilityemal77@gmail.com>
> Cc: Mickaël Salaün <mic@digikod.net>
> Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> Cc: Kuniyuki Iwashima <kuniyu@google.com>
> Suggested-by: Jann Horn <jannh@google.com>
> Link[1]: https://github.com/landlock-lsm/linux/issues/36
> Link[2]: https://lore.kernel.org/all/20260205.8531e4005118@gnoack.org/
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> Documentation/security/landlock.rst | 40 +++++++
> include/uapi/linux/landlock.h | 19 ++++
> security/landlock/access.h | 2 +-
> security/landlock/audit.c | 1 +
> security/landlock/fs.c | 110 ++++++++++++++++++-
> security/landlock/limits.h | 2 +-
> security/landlock/syscalls.c | 2 +-
> tools/testing/selftests/landlock/base_test.c | 2 +-
> tools/testing/selftests/landlock/fs_test.c | 5 +-
> 9 files changed, 176 insertions(+), 7 deletions(-)
>
> diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
> index 3e4d4d04cfae..4bbe250a6829 100644
> --- a/Documentation/security/landlock.rst
> +++ b/Documentation/security/landlock.rst
> @@ -89,6 +89,46 @@ this is required to keep access controls consistent over the whole system, and
> this avoids unattended bypasses through file descriptor passing (i.e. confused
> deputy attack).
>
> +.. _scoped-flags-interaction:
> +
> +Interaction between scoped flags and other access rights
> +--------------------------------------------------------
> +
> +The ``scoped`` flags in ``struct landlock_ruleset_attr`` restrict the
> +use of *outgoing* IPC from the created Landlock domain, while they
> +permit reaching out to IPC endpoints *within* the created Landlock
> +domain.
> +
> +In the future, scoped flags *may* interact with other access rights,
> +e.g. so that abstract UNIX sockets can be allow-listed by name, or so
> +that signals can be allow-listed by signal number or target process.
> +
> +When introducing ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``, we defined it to
> +implicitly have the same scoping semantics as a
> +``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` flag would have: connecting to
> +UNIX sockets within the same domain (where
> +``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` is used) is unconditionally
> +allowed.
> +
> +The reasoning is:
> +
> +* Like other IPC mechanisms, connecting to named UNIX sockets in the
> + same domain should be expected and harmless. (If needed, users can
> + further refine their Landlock policies with nested domains or by
> + restricting ``LANDLOCK_ACCESS_FS_MAKE_SOCK``.)
> +* We reserve the option to still introduce
> + ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the future. (This would
> + be useful if we wanted to have a Landlock rule to permit IPC access
> + to other Landlock domains.)
> +* But we can postpone the point in time when users have to deal with
> + two interacting flags visible in the userspace API. (In particular,
> + it is possible that it won't be needed in practice, in which case we
> + can avoid the second flag altogether.)
> +* If we *do* introduce ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the
> + future, setting this scoped flag in a ruleset does *not reduce* the
> + restrictions, because access within the same scope is already
> + allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.
> +
> Tests
> =====
>
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index f88fa1f68b77..751e3c143cba 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -248,6 +248,24 @@ struct landlock_net_port_attr {
> *
> * This access right is available since the fifth version of the Landlock
> * ABI.
> + * - %LANDLOCK_ACCESS_FS_RESOLVE_UNIX: Look up pathname UNIX domain sockets
> + * (:manpage:`unix(7)`). On UNIX domain sockets, this restricts both calls to
> + * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an
> + * explicit recipient address.
> + *
> + * This access right only applies to connections to UNIX server sockets which
> + * were created outside of the newly created Landlock domain (e.g. from within
> + * a parent domain or from an unrestricted process). Newly created UNIX
> + * servers within the same Landlock domain continue to be accessible. In this
> + * regard, %LANDLOCK_ACCESS_RESOLVE_UNIX has the same semantics as the
LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> + * ``LANDLOCK_SCOPE_*`` flags.
> + *
> + * If a resolve attempt is denied, the operation returns an ``EACCES`` error,
> + * in line with other filesystem access rights (but different to denials for
> + * abstract UNIX domain sockets).
This access right is available since the ninth version of the Landlock ABI.
> + *
> + * The rationale for this design is described in
> + * :ref:`Documentation/security/landlock.rst <scoped-flags-interaction>`.
> *
> * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
> * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
> @@ -333,6 +351,7 @@ struct landlock_net_port_attr {
> #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
> #define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
> #define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
> +#define LANDLOCK_ACCESS_FS_RESOLVE_UNIX (1ULL << 16)
> /* clang-format on */
>
> /**
> diff --git a/security/landlock/access.h b/security/landlock/access.h
> index 42c95747d7bd..89dc8e7b93da 100644
> --- a/security/landlock/access.h
> +++ b/security/landlock/access.h
> @@ -34,7 +34,7 @@
> LANDLOCK_ACCESS_FS_IOCTL_DEV)
> /* clang-format on */
>
> -typedef u16 access_mask_t;
> +typedef u32 access_mask_t;
This change and the underlying implications are not explained in the
commit message, especially regarding the stack delta.
>
> /* Makes sure all filesystem access rights can be stored. */
> static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> index 60ff217ab95b..8d0edf94037d 100644
> --- a/security/landlock/audit.c
> +++ b/security/landlock/audit.c
> @@ -37,6 +37,7 @@ static const char *const fs_access_strings[] = {
> [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
> [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
> [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
> + [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix",
> };
>
> static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 97065d51685a..0486f5ab06c9 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -27,6 +27,7 @@
> #include <linux/lsm_hooks.h>
> #include <linux/mount.h>
> #include <linux/namei.h>
> +#include <linux/net.h>
> #include <linux/path.h>
> #include <linux/pid.h>
> #include <linux/rcupdate.h>
> @@ -36,6 +37,7 @@
> #include <linux/types.h>
> #include <linux/wait_bit.h>
> #include <linux/workqueue.h>
> +#include <net/af_unix.h>
> #include <uapi/linux/fiemap.h>
> #include <uapi/linux/landlock.h>
>
> @@ -314,7 +316,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
> LANDLOCK_ACCESS_FS_WRITE_FILE | \
> LANDLOCK_ACCESS_FS_READ_FILE | \
> LANDLOCK_ACCESS_FS_TRUNCATE | \
> - LANDLOCK_ACCESS_FS_IOCTL_DEV)
> + LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> + LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> /* clang-format on */
>
> /*
> @@ -1557,6 +1560,110 @@ static int hook_path_truncate(const struct path *const path)
> return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
> }
>
> +/**
> + * unmask_scoped_access - Remove access right bits in @masks in all layers
> + * where @client and @server have the same domain
> + *
> + * This does the same as domain_is_scoped(), but unmasks bits in @masks.
> + * It can not return early as domain_is_scoped() does.
I'd like a summary of your previous excellent explanation of
unmask_scoped_access() in this comment.
> + *
> + * @client: Client domain
> + * @server: Server domain
> + * @masks: Layer access masks to unmask
> + * @access: Access bit that controls scoping
> + */
> +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> + const struct landlock_ruleset *const server,
> + struct layer_access_masks *const masks,
> + const access_mask_t access)
> +{
> + int client_layer, server_layer;
> + const struct landlock_hierarchy *client_walker, *server_walker;
> +
> + /* This should not happen. */
> + if (WARN_ON_ONCE(!client))
> + return;
> +
> + /* Server has no Landlock domain; nothing to clear. */
> + if (!server)
> + return;
> +
Please also copy the BUILD_BUG_ON() from domain_is_scoped().
> + client_layer = client->num_layers - 1;
> + client_walker = client->hierarchy;
> + server_layer = server->num_layers - 1;
> + server_walker = server->hierarchy;
> +
> + /*
> + * Clears the access bits at all layers where the client domain is the
> + * same as the server domain. We start the walk at min(client_layer,
> + * server_layer). The layer bits until there can not be cleared because
> + * either the client or the server domain is missing.
> + */
> + for (; client_layer > server_layer; client_layer--)
> + client_walker = client_walker->parent;
> +
> + for (; server_layer > client_layer; server_layer--)
> + server_walker = server_walker->parent;
> +
> + for (; client_layer >= 0; client_layer--) {
> + if (masks->access[client_layer] & access &&
> + client_walker == server_walker)
> + masks->access[client_layer] &= ~access;
> +
> + client_walker = client_walker->parent;
> + server_walker = server_walker->parent;
> + }
> +}
> +
> +static int hook_unix_find(const struct path *const path, struct sock *other,
> + int flags)
> +{
> + const struct landlock_ruleset *dom_other;
> + const struct landlock_cred_security *subject;
> + struct layer_access_masks layer_masks;
> + struct landlock_request request = {};
> + static const struct access_masks fs_resolve_unix = {
> + .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
> + };
> +
> + /* Lookup for the purpose of saving coredumps is OK. */
> + if (unlikely(flags & SOCK_COREDUMP))
> + return 0;
> +
> + /* Access to the same (or a lower) domain is always allowed. */
This comment is related to the unmask_scoped_access() call.
> + subject = landlock_get_applicable_subject(current_cred(),
> + fs_resolve_unix, NULL);
> +
> + if (!subject)
> + return 0;
> +
> + if (!landlock_init_layer_masks(subject->domain, fs_resolve_unix.fs,
> + &layer_masks, LANDLOCK_KEY_INODE))
This case is not possible because landlock_get_applicable_subject()
already check it. Other hooks just ignore the returned value in this
case.
> + return 0;
> +
> + /* Checks the layers in which we are connecting within the same domain. */
> + unix_state_lock(other);
> + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket ||
> + !other->sk_socket->file)) {
> + unix_state_unlock(other);
> + return 0;
Is it safe to not return -ECONNREFUSED?
> + }
> + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> + unix_state_unlock(other);
> +
> + unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> + fs_resolve_unix.fs);
dom_other is not safe to use without the lock.
> +
> + /* Checks the connections to allow-listed paths. */
> + if (is_access_to_paths_allowed(subject->domain, path,
> + fs_resolve_unix.fs, &layer_masks,
> + &request, NULL, 0, NULL, NULL, NULL))
> + return 0;
> +
> + landlock_log_denial(subject, &request);
> + return -EACCES;
> +}
> +
> /* File hooks */
>
> /**
> @@ -1834,6 +1941,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(path_unlink, hook_path_unlink),
> LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
> LSM_HOOK_INIT(path_truncate, hook_path_truncate),
> + LSM_HOOK_INIT(unix_find, hook_unix_find),
>
> LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
> LSM_HOOK_INIT(file_open, hook_file_open),
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index eb584f47288d..b454ad73b15e 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -19,7 +19,7 @@
> #define LANDLOCK_MAX_NUM_LAYERS 16
> #define LANDLOCK_MAX_NUM_RULES U32_MAX
>
> -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
> +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
> #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 3b33839b80c7..a6e23657f3ce 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
> * If the change involves a fix that requires userspace awareness, also update
> * the errata documentation in Documentation/userspace-api/landlock.rst .
> */
> -const int landlock_abi_version = 8;
> +const int landlock_abi_version = 9;
>
> /**
> * sys_landlock_create_ruleset - Create a new ruleset
> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index 0fea236ef4bd..30d37234086c 100644
> --- a/tools/testing/selftests/landlock/base_test.c
> +++ b/tools/testing/selftests/landlock/base_test.c
> @@ -76,7 +76,7 @@ TEST(abi_version)
> const struct landlock_ruleset_attr ruleset_attr = {
> .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
> };
> - ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
> + ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
> LANDLOCK_CREATE_RULESET_VERSION));
>
> ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 968a91c927a4..b318627e7561 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -575,9 +575,10 @@ TEST_F_FORK(layout1, inval)
> LANDLOCK_ACCESS_FS_WRITE_FILE | \
> LANDLOCK_ACCESS_FS_READ_FILE | \
> LANDLOCK_ACCESS_FS_TRUNCATE | \
> - LANDLOCK_ACCESS_FS_IOCTL_DEV)
> + LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> + LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
>
> -#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV
> +#define ACCESS_LAST LANDLOCK_ACCESS_FS_RESOLVE_UNIX
>
> #define ACCESS_ALL ( \
> ACCESS_FILE | \
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX
2026-03-15 22:21 ` [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
@ 2026-03-18 16:53 ` Mickaël Salaün
2026-03-20 10:51 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:53 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, Justin Suess, Tingmao Wang, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
The subject's prefix is swapped, it should be "selftests/landlock".
On Sun, Mar 15, 2026 at 11:21:46PM +0100, Günther Noack wrote:
> * Extract common helpers from an existing IOCTL test that
> also uses pathname unix(7) sockets.
> * These tests use the common scoped domains fixture which is also used
> in other Landlock scoping tests and which was used in Tingmao Wang's
> earlier patch set in [1].
>
> These tests exercise the cross product of the following scenarios:
>
> * Stream connect(), Datagram connect(), Datagram sendmsg() and
> Seqpacket connect().
> * Child-to-parent and parent-to-child communication
> * The Landlock policy configuration as listed in the scoped_domains
> fixture.
> * In the default variant, Landlock domains are only placed where
> prescribed in the fixture.
> * In the "ALL_DOMAINS" variant, Landlock domains are also placed in
> the places where the fixture says to omit them, but with a
> LANDLOCK_RULE_PATH_BENEATH that allows connection.
>
> Cc: Justin Suess <utilityemal77@gmail.com>
> Cc: Tingmao Wang <m@maowtm.org>
> Cc: Mickaël Salaün <mic@digikod.net>
> Link[1]: https://lore.kernel.org/all/53b9883648225d5a08e82d2636ab0b4fda003bc9.1767115163.git.m@maowtm.org/
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> tools/testing/selftests/landlock/fs_test.c | 392 ++++++++++++++++++++-
> 1 file changed, 376 insertions(+), 16 deletions(-)
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 6/9] landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX
2026-03-15 22:21 ` [PATCH v6 6/9] landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
@ 2026-03-18 16:53 ` Mickaël Salaün
0 siblings, 0 replies; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:53 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, linux-security-module, Tingmao Wang, Justin Suess,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
On Sun, Mar 15, 2026 at 11:21:47PM +0100, Günther Noack wrote:
> Add an audit test to check that Landlock denials from
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX result in audit logs in the expected
> format. (There is one audit test for each filesystem access right, so
> we should add one for LANDLOCK_ACCESS_FS_RESOLVE_UNIX as well.)
>
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> tools/testing/selftests/landlock/fs_test.c | 43 +++++++++++++++++++++-
> 1 file changed, 42 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index fdbb024da774..4198148e172f 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -7538,7 +7538,8 @@ static const __u64 access_fs_16 =
> LANDLOCK_ACCESS_FS_MAKE_SYM |
> LANDLOCK_ACCESS_FS_REFER |
> LANDLOCK_ACCESS_FS_TRUNCATE |
> - LANDLOCK_ACCESS_FS_IOCTL_DEV;
> + LANDLOCK_ACCESS_FS_IOCTL_DEV |
> + LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
The variable access_fs_16 contains 16 access rights. The idea was to
not need to change this variable with new FS access rights because the
tests should not change. I guess that was not a good choice because
new tests like audit_layout1.resolve_unix may not change the result of
existing tests (and then improve coverage). This variable can then be
replaced with ACCESS_ALL (in a dedicated patch please). If we need
exceptions we can mask the problematic access rights (see
audit_layout1.ioctl_dev).
> /* clang-format on */
>
> TEST_F(audit_layout1, execute_read)
> @@ -7983,6 +7984,46 @@ TEST_F(audit_layout1, ioctl_dev)
> EXPECT_EQ(1, records.domain);
> }
>
> +TEST_F(audit_layout1, resolve_unix)
> +{
> + struct audit_records records;
> + const char *const path = "sock";
> + int srv_fd, cli_fd, status;
> + pid_t child_pid;
> +
> + srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path);
> +
> + child_pid = fork();
> + ASSERT_LE(0, child_pid);
> + if (!child_pid) {
> + drop_access_rights(_metadata,
> + &(struct landlock_ruleset_attr){
> + .handled_access_fs = access_fs_16,
> + });
> +
> + cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
> + ASSERT_LE(0, cli_fd);
> + EXPECT_EQ(EACCES,
> + test_connect_named_unix(_metadata, cli_fd, path));
> +
> + EXPECT_EQ(0, close(cli_fd));
> + _exit(_metadata->exit_code);
> + }
> +
> + ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
> + EXPECT_EQ(1, WIFEXITED(status));
> + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
> +
> + EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd,
> + "fs\\.resolve_unix", path, NULL));
> +
> + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> + EXPECT_EQ(0, records.access);
> + EXPECT_EQ(1, records.domain);
> +
> + EXPECT_EQ(0, close(srv_fd));
> +}
> +
> TEST_F(audit_layout1, mount)
> {
> struct audit_records records;
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted
2026-03-15 22:21 ` [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted Günther Noack
@ 2026-03-18 16:53 ` Mickaël Salaün
2026-03-20 16:44 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:53 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, linux-security-module, Tingmao Wang, Justin Suess,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
On Sun, Mar 15, 2026 at 11:21:48PM +0100, Günther Noack wrote:
> Even when a process is restricted with the new
> LANDLOCK_ACCESS_FS_RESOLVE_SOCKET right, the kernel can continue
LANDLOCK_ACCESS_FS_RESOLVE_UNIX (twice)
> writing its coredump to the configured coredump socket.
>
> In the test, we create a local server and rewire the system to write
> coredumps into it. We then create a child process within a Landlock
> domain where LANDLOCK_ACCESS_FS_RESOLVE_SOCKET is restricted and make
> the process crash. The test uses SO_PEERCRED to check that the
> connecting client process is the expected one.
>
> Includes a fix by Mickaël Salaün for setting the EUID to 0 (see [1]).
>
> Link[1]: https://lore.kernel.org/all/20260218.ohth8theu8Yi@digikod.net/
> Suggested-by: Mickaël Salaün <mic@digikod.net>
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> tools/testing/selftests/landlock/fs_test.c | 141 +++++++++++++++++++++
> 1 file changed, 141 insertions(+)
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets
2026-03-15 22:21 ` [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets Günther Noack
@ 2026-03-18 16:54 ` Mickaël Salaün
2026-03-20 17:04 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 16:54 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, Justin Suess, linux-security-module, Tingmao Wang,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
Please always add some minimal description.
Also, as already requested, could you run the check-linux.sh all on each
patch? That would avoid me to fix things like the date (which would now
be OK because of the new patch in my next branch, but still).
On Sun, Mar 15, 2026 at 11:21:50PM +0100, Günther Noack wrote:
> Cc: Justin Suess <utilityemal77@gmail.com>
> Cc: Mickaël Salaün <mic@digikod.net>
> Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> ---
> Documentation/userspace-api/landlock.rst | 15 ++++++++++++++-
> 1 file changed, 14 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index 13134bccdd39..e60ebd07c5cc 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -77,7 +77,8 @@ to be explicit about the denied-by-default access rights.
> LANDLOCK_ACCESS_FS_MAKE_SYM |
> LANDLOCK_ACCESS_FS_REFER |
> LANDLOCK_ACCESS_FS_TRUNCATE |
> - LANDLOCK_ACCESS_FS_IOCTL_DEV,
> + LANDLOCK_ACCESS_FS_IOCTL_DEV |
> + LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
> .handled_access_net =
> LANDLOCK_ACCESS_NET_BIND_TCP |
> LANDLOCK_ACCESS_NET_CONNECT_TCP,
> @@ -127,6 +128,11 @@ version, and only use the available subset of access rights:
> /* Removes LANDLOCK_SCOPE_* for ABI < 6 */
> ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> LANDLOCK_SCOPE_SIGNAL);
> + __attribute__((fallthrough));
Case 6 should be handled too:
case 6 ... 8:
> + case 7:
> + case 8:
> + /* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
> + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
> }
>
> This enables the creation of an inclusive ruleset that will contain our rules.
> @@ -685,6 +691,13 @@ enforce Landlock rulesets across all threads of the calling process
> using the ``LANDLOCK_RESTRICT_SELF_TSYNC`` flag passed to
> sys_landlock_restrict_self().
>
> +Pathname UNIX sockets (ABI < 9)
> +-------------------------------
> +
> +Starting with the Landlock ABI version 9, it is possible to restrict
> +connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
> +the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
> +
> .. _kernel_support:
>
> Kernel support
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 16:43 ` Justin Suess
@ 2026-03-18 17:52 ` Mickaël Salaün
2026-03-20 12:28 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-18 17:52 UTC (permalink / raw)
To: Justin Suess
Cc: Sebastian Andrzej Siewior, Günther Noack, John Johansen,
Tingmao Wang, Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On Wed, Mar 18, 2026 at 12:43:55PM -0400, Justin Suess wrote:
> On Wed, Mar 18, 2026 at 05:26:20PM +0100, Mickaël Salaün wrote:
> > On Wed, Mar 18, 2026 at 04:05:59PM +0100, Sebastian Andrzej Siewior wrote:
> > > On 2026-03-18 10:14:52 [-0400], Justin Suess wrote:
> > > > Sebastian,
> > > Justin,
> > >
> > > > In short: dom_other is a pointer to a landlock-owned refcounted struct.
> > > …
> > > >
> > > > But we copy the domain pointer, which points to a landlock allocated
> > > > and controlled object.
> > >
> > > and this is not going away while we are here and preempted after
> > > dropping the lock? (if the landlock policy is updated/ changed/ …)
> >
> > I agree with Sebastian, this is a bug, see my original proposal:
> > https://lore.kernel.org/all/20260217.lievaS8eeng8@digikod.net/
> Mickaël,
>
> Just to make sure we're speaking of the same thing (I spotted a bug
> shortly after replying to Sebastian).
>
> This is a potential UAF if the dom_other is freed before the access
> check takes place correct?
Yes
>
> dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> unix_state_unlock(other);
>
> unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> fs_resolve_unix.fs);
>
> If the dom_other->usage reaches zero, then the domain could be
> freed after the unix_state_unlock while we're checking access??
>
> (I guess I assumed the sock_hold on the @other would prevent the task
> @other belongs to from being freed.)
>
> Would it be better to move the access check under the unix_state_lock or
> to acquire another reference to the ruleset (or something else)?
Because the unmask_scoped_access() only read a bounded array, it's
simpler to unlock just after.
The other alternatives would be to use an RCU lock or a new reference
but I don't think it's worth it.
>
> (Good catch Sebastian sorry for the confusion.)
>
> Justin
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed()
2026-03-18 16:52 ` Mickaël Salaün
@ 2026-03-20 10:50 ` Günther Noack
0 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-20 10:50 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, linux-security-module, Tingmao Wang, Justin Suess,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
On Wed, Mar 18, 2026 at 05:52:10PM +0100, Mickaël Salaün wrote:
> Subject should be "landlock: Use..."
Thanks, done.
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX
2026-03-18 16:53 ` Mickaël Salaün
@ 2026-03-20 10:51 ` Günther Noack
0 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-20 10:51 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Justin Suess, Tingmao Wang, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
On Wed, Mar 18, 2026 at 05:53:15PM +0100, Mickaël Salaün wrote:
> The subject's prefix is swapped, it should be "selftests/landlock".
Thanks, done!
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 17:52 ` Mickaël Salaün
@ 2026-03-20 12:28 ` Günther Noack
0 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-20 12:28 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Justin Suess, Sebastian Andrzej Siewior, John Johansen,
Tingmao Wang, Kuniyuki Iwashima, Jann Horn, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross,
Tahera Fahimi
On Wed, Mar 18, 2026 at 06:52:57PM +0100, Mickaël Salaün wrote:
> On Wed, Mar 18, 2026 at 12:43:55PM -0400, Justin Suess wrote:
> > On Wed, Mar 18, 2026 at 05:26:20PM +0100, Mickaël Salaün wrote:
> > > On Wed, Mar 18, 2026 at 04:05:59PM +0100, Sebastian Andrzej Siewior wrote:
> > > > On 2026-03-18 10:14:52 [-0400], Justin Suess wrote:
> > > > > Sebastian,
> > > > Justin,
> > > >
> > > > > In short: dom_other is a pointer to a landlock-owned refcounted struct.
> > > > …
> > > > >
> > > > > But we copy the domain pointer, which points to a landlock allocated
> > > > > and controlled object.
> > > >
> > > > and this is not going away while we are here and preempted after
> > > > dropping the lock? (if the landlock policy is updated/ changed/ …)
> > >
> > > I agree with Sebastian, this is a bug, see my original proposal:
> > > https://lore.kernel.org/all/20260217.lievaS8eeng8@digikod.net/
> > Mickaël,
> >
> > Just to make sure we're speaking of the same thing (I spotted a bug
> > shortly after replying to Sebastian).
> >
> > This is a potential UAF if the dom_other is freed before the access
> > check takes place correct?
>
> Yes
>
> >
> > dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> > unix_state_unlock(other);
> >
> > unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> > fs_resolve_unix.fs);
> >
> > If the dom_other->usage reaches zero, then the domain could be
> > freed after the unix_state_unlock while we're checking access??
> >
> > (I guess I assumed the sock_hold on the @other would prevent the task
> > @other belongs to from being freed.)
> >
> > Would it be better to move the access check under the unix_state_lock or
> > to acquire another reference to the ruleset (or something else)?
>
> Because the unmask_scoped_access() only read a bounded array, it's
> simpler to unlock just after.
>
> The other alternatives would be to use an RCU lock or a new reference
> but I don't think it's worth it.
Thank you, Sebastian, Mickaël and Justin for spotting this!
I agree, holding the existing lock across the unmask_scoped_access()
call seems like the simplest solution. This function only walks a
previously loaded bounded memory structure, so it does not seem worth
switching to a different lock for that.
I'm changing my WIP for V7 to hold the unix_state_lock across that
function call.
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-18 16:52 ` Mickaël Salaün
@ 2026-03-20 16:15 ` Günther Noack
2026-03-20 17:51 ` Mickaël Salaün
0 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-20 16:15 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi
Hello!
On Wed, Mar 18, 2026 at 05:52:48PM +0100, Mickaël Salaün wrote:
> On Sun, Mar 15, 2026 at 11:21:44PM +0100, Günther Noack wrote:
> > * Add a new access right LANDLOCK_ACCESS_FS_RESOLVE_UNIX, which
> > controls the look up operations for named UNIX domain sockets. The
>
> lookup
Done.
> > resolution happens during connect() and sendmsg() (depending on
> > socket type).
> > * Hook into the path lookup in unix_find_bsd() in af_unix.c, using a
> > LSM hook. Make policy decisions based on the new access rights
> > * Increment the Landlock ABI version.
> > * Minor test adaptions to keep the tests working.
>
> adaptations
Done.
> > * Document the design rationale for scoped access rights,
> > and cross-reference it from the header documentation.
> >
> > With this access right, access is granted if either of the following
> > conditions is met:
> >
> > * The target socket's filesystem path was allow-listed using a
> > LANDLOCK_RULE_PATH_BENEATH rule, *or*:
> > * The target socket was created in the same Landlock domain in which
> > LANDLOCK_ACCESS_FS_RESOLVE_UNIX was restricted.
> >
> > In case of a denial, connect() and sendmsg() return EACCES, which is
> > the same error as it is returned if the user does not have the write
> > bit in the traditional Unix file system permissions of that file.
>
> UNIX
DONE
> > Document the (possible future) interaction between scoped flags and
> > other access rights in struct landlock_ruleset_attr, and summarize the
> > rationale, as discussed in code review leading up to [2].
> >
> > This feature was created with substantial discussion and input from
> > Justin Suess, Tingmao Wang and Mickaël Salaün.
> >
> > Cc: Tingmao Wang <m@maowtm.org>
> > Cc: Justin Suess <utilityemal77@gmail.com>
> > Cc: Mickaël Salaün <mic@digikod.net>
> > Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > Cc: Kuniyuki Iwashima <kuniyu@google.com>
> > Suggested-by: Jann Horn <jannh@google.com>
> > Link[1]: https://github.com/landlock-lsm/linux/issues/36
> > Link[2]: https://lore.kernel.org/all/20260205.8531e4005118@gnoack.org/
> > Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> > ---
> > Documentation/security/landlock.rst | 40 +++++++
> > include/uapi/linux/landlock.h | 19 ++++
> > security/landlock/access.h | 2 +-
> > security/landlock/audit.c | 1 +
> > security/landlock/fs.c | 110 ++++++++++++++++++-
> > security/landlock/limits.h | 2 +-
> > security/landlock/syscalls.c | 2 +-
> > tools/testing/selftests/landlock/base_test.c | 2 +-
> > tools/testing/selftests/landlock/fs_test.c | 5 +-
> > 9 files changed, 176 insertions(+), 7 deletions(-)
> >
> > diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
> > index 3e4d4d04cfae..4bbe250a6829 100644
> > --- a/Documentation/security/landlock.rst
> > +++ b/Documentation/security/landlock.rst
> > @@ -89,6 +89,46 @@ this is required to keep access controls consistent over the whole system, and
> > this avoids unattended bypasses through file descriptor passing (i.e. confused
> > deputy attack).
> >
> > +.. _scoped-flags-interaction:
> > +
> > +Interaction between scoped flags and other access rights
> > +--------------------------------------------------------
> > +
> > +The ``scoped`` flags in ``struct landlock_ruleset_attr`` restrict the
> > +use of *outgoing* IPC from the created Landlock domain, while they
> > +permit reaching out to IPC endpoints *within* the created Landlock
> > +domain.
> > +
> > +In the future, scoped flags *may* interact with other access rights,
> > +e.g. so that abstract UNIX sockets can be allow-listed by name, or so
> > +that signals can be allow-listed by signal number or target process.
> > +
> > +When introducing ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``, we defined it to
> > +implicitly have the same scoping semantics as a
> > +``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` flag would have: connecting to
> > +UNIX sockets within the same domain (where
> > +``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` is used) is unconditionally
> > +allowed.
> > +
> > +The reasoning is:
> > +
> > +* Like other IPC mechanisms, connecting to named UNIX sockets in the
> > + same domain should be expected and harmless. (If needed, users can
> > + further refine their Landlock policies with nested domains or by
> > + restricting ``LANDLOCK_ACCESS_FS_MAKE_SOCK``.)
> > +* We reserve the option to still introduce
> > + ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the future. (This would
> > + be useful if we wanted to have a Landlock rule to permit IPC access
> > + to other Landlock domains.)
> > +* But we can postpone the point in time when users have to deal with
> > + two interacting flags visible in the userspace API. (In particular,
> > + it is possible that it won't be needed in practice, in which case we
> > + can avoid the second flag altogether.)
> > +* If we *do* introduce ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the
> > + future, setting this scoped flag in a ruleset does *not reduce* the
> > + restrictions, because access within the same scope is already
> > + allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.
> > +
> > Tests
> > =====
> >
> > diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> > index f88fa1f68b77..751e3c143cba 100644
> > --- a/include/uapi/linux/landlock.h
> > +++ b/include/uapi/linux/landlock.h
> > @@ -248,6 +248,24 @@ struct landlock_net_port_attr {
> > *
> > * This access right is available since the fifth version of the Landlock
> > * ABI.
> > + * - %LANDLOCK_ACCESS_FS_RESOLVE_UNIX: Look up pathname UNIX domain sockets
> > + * (:manpage:`unix(7)`). On UNIX domain sockets, this restricts both calls to
> > + * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an
> > + * explicit recipient address.
> > + *
> > + * This access right only applies to connections to UNIX server sockets which
> > + * were created outside of the newly created Landlock domain (e.g. from within
> > + * a parent domain or from an unrestricted process). Newly created UNIX
> > + * servers within the same Landlock domain continue to be accessible. In this
> > + * regard, %LANDLOCK_ACCESS_RESOLVE_UNIX has the same semantics as the
>
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX
Whoops, done.
> > + * ``LANDLOCK_SCOPE_*`` flags.
> > + *
> > + * If a resolve attempt is denied, the operation returns an ``EACCES`` error,
> > + * in line with other filesystem access rights (but different to denials for
> > + * abstract UNIX domain sockets).
>
> This access right is available since the ninth version of the Landlock ABI.
Thanks, added.
> > + *
> > + * The rationale for this design is described in
> > + * :ref:`Documentation/security/landlock.rst <scoped-flags-interaction>`.
> > *
> > * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
> > * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
> > @@ -333,6 +351,7 @@ struct landlock_net_port_attr {
> > #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
> > #define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
> > #define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
> > +#define LANDLOCK_ACCESS_FS_RESOLVE_UNIX (1ULL << 16)
> > /* clang-format on */
> >
> > /**
> > diff --git a/security/landlock/access.h b/security/landlock/access.h
> > index 42c95747d7bd..89dc8e7b93da 100644
> > --- a/security/landlock/access.h
> > +++ b/security/landlock/access.h
> > @@ -34,7 +34,7 @@
> > LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > /* clang-format on */
> >
> > -typedef u16 access_mask_t;
> > +typedef u32 access_mask_t;
>
> This change and the underlying implications are not explained in the
> commit message, especially regarding the stack delta.
Thanks, will add it.
> > /* Makes sure all filesystem access rights can be stored. */
> > static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> > diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> > index 60ff217ab95b..8d0edf94037d 100644
> > --- a/security/landlock/audit.c
> > +++ b/security/landlock/audit.c
> > @@ -37,6 +37,7 @@ static const char *const fs_access_strings[] = {
> > [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
> > [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
> > [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
> > + [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix",
> > };
> >
> > static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
> > diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> > index 97065d51685a..0486f5ab06c9 100644
> > --- a/security/landlock/fs.c
> > +++ b/security/landlock/fs.c
> > @@ -27,6 +27,7 @@
> > #include <linux/lsm_hooks.h>
> > #include <linux/mount.h>
> > #include <linux/namei.h>
> > +#include <linux/net.h>
> > #include <linux/path.h>
> > #include <linux/pid.h>
> > #include <linux/rcupdate.h>
> > @@ -36,6 +37,7 @@
> > #include <linux/types.h>
> > #include <linux/wait_bit.h>
> > #include <linux/workqueue.h>
> > +#include <net/af_unix.h>
> > #include <uapi/linux/fiemap.h>
> > #include <uapi/linux/landlock.h>
> >
> > @@ -314,7 +316,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
> > LANDLOCK_ACCESS_FS_WRITE_FILE | \
> > LANDLOCK_ACCESS_FS_READ_FILE | \
> > LANDLOCK_ACCESS_FS_TRUNCATE | \
> > - LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > + LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> > + LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> > /* clang-format on */
> >
> > /*
> > @@ -1557,6 +1560,110 @@ static int hook_path_truncate(const struct path *const path)
> > return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
> > }
> >
> > +/**
> > + * unmask_scoped_access - Remove access right bits in @masks in all layers
> > + * where @client and @server have the same domain
> > + *
> > + * This does the same as domain_is_scoped(), but unmasks bits in @masks.
> > + * It can not return early as domain_is_scoped() does.
>
> I'd like a summary of your previous excellent explanation of
> unmask_scoped_access() in this comment.
Adding:
A scoped access for a given access right bit is allowed iff, for all
layer depths where the access bit is set, the client and server
domain are the same. This function clears the access rights @access
in @masks at all layer depths where the client and server domain are
the same, so that, when they are all cleared, the access is allowed.
It's not as detailed as drawing a picture in the other mail, but I
hope it helps.
> > + * @client: Client domain
> > + * @server: Server domain
> > + * @masks: Layer access masks to unmask
> > + * @access: Access bit that controls scoping
> > + */
> > +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> > + const struct landlock_ruleset *const server,
> > + struct layer_access_masks *const masks,
> > + const access_mask_t access)
> > +{
> > + int client_layer, server_layer;
> > + const struct landlock_hierarchy *client_walker, *server_walker;
> > +
> > + /* This should not happen. */
> > + if (WARN_ON_ONCE(!client))
> > + return;
> > +
> > + /* Server has no Landlock domain; nothing to clear. */
> > + if (!server)
> > + return;
> > +
>
> Please also copy the BUILD_BUG_ON() from domain_is_scoped().
I don't understand what this check is good for. It says:
/*
* client_layer must be a signed integer with greater capacity
* than client->num_layers to ensure the following loop stops.
*/
BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
For the loop to terminate, in my understanding, client_layer must be
able to store client->num_layers - 1 down to - 1, but that is anyway a
given since num_layers can't exceed 16 and client_layer is signed. It
seems that the termination of this would anyway be caught in our tests
as well?
Could you please clarify what this BUILD_BUG_ON() is trying to assert?
> > + client_layer = client->num_layers - 1;
> > + client_walker = client->hierarchy;
> > + server_layer = server->num_layers - 1;
> > + server_walker = server->hierarchy;
> > +
> > + /*
> > + * Clears the access bits at all layers where the client domain is the
> > + * same as the server domain. We start the walk at min(client_layer,
> > + * server_layer). The layer bits until there can not be cleared because
> > + * either the client or the server domain is missing.
> > + */
> > + for (; client_layer > server_layer; client_layer--)
> > + client_walker = client_walker->parent;
> > +
> > + for (; server_layer > client_layer; server_layer--)
> > + server_walker = server_walker->parent;
> > +
> > + for (; client_layer >= 0; client_layer--) {
> > + if (masks->access[client_layer] & access &&
> > + client_walker == server_walker)
> > + masks->access[client_layer] &= ~access;
> > +
> > + client_walker = client_walker->parent;
> > + server_walker = server_walker->parent;
> > + }
> > +}
> > +
> > +static int hook_unix_find(const struct path *const path, struct sock *other,
> > + int flags)
> > +{
> > + const struct landlock_ruleset *dom_other;
> > + const struct landlock_cred_security *subject;
> > + struct layer_access_masks layer_masks;
> > + struct landlock_request request = {};
> > + static const struct access_masks fs_resolve_unix = {
> > + .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
> > + };
> > +
> > + /* Lookup for the purpose of saving coredumps is OK. */
> > + if (unlikely(flags & SOCK_COREDUMP))
> > + return 0;
> > +
> > + /* Access to the same (or a lower) domain is always allowed. */
>
> This comment is related to the unmask_scoped_access() call.
Thanks, I moved it down.
> > + subject = landlock_get_applicable_subject(current_cred(),
> > + fs_resolve_unix, NULL);
> > +
> > + if (!subject)
> > + return 0;
> > +
> > + if (!landlock_init_layer_masks(subject->domain, fs_resolve_unix.fs,
> > + &layer_masks, LANDLOCK_KEY_INODE))
>
> This case is not possible because landlock_get_applicable_subject()
> already check it. Other hooks just ignore the returned value in this
> case.
Hm, fair enough. I added a comment to explain why we are ignoring the
return value, as it wasn't as obvious to me. In the other places, we
are using the result of the landlock_init_layer_masks() function
(because in the generic case, it can be a subset of the original
access rights).
> > + return 0;
> > +
> > + /* Checks the layers in which we are connecting within the same domain. */
> > + unix_state_lock(other);
> > + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket ||
> > + !other->sk_socket->file)) {
> > + unix_state_unlock(other);
> > + return 0;
>
> Is it safe to not return -ECONNREFUSED?
Yes. My reasoning is:
In all three places where this gets called in af_unix.c (stream
connect, dgram connect, dgram send), these functions check for socket
death shortly after, and if they find the socket to be SOCK_DEAD, they
will *retry* the UNIX lookup. The code commentary about this says
that this is for a race condition where the VFS has "overslept" the
socket death, so I presume that the retry aims at getting a race-free
sitation on the next attempt.
Since sock_orphan() is a one-way teardown operation, when we observe
SOCK_DEAD in our hook, we can be sure that the caller will see it as
well when it does the same check a bit later after our hook.
If we *were* to return -ECONNREFUSED, the caller would immediately
return an error though, and it would not retry as it normally does
when it encounters this race condition. So we have to return 0 here.
> > + }
> > + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> > + unix_state_unlock(other);
> > +
> > + unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> > + fs_resolve_unix.fs);
>
> dom_other is not safe to use without the lock.
Thanks, fixed by extending the lock scope across that function call,
as discussed in other thread in more detail.
> > + /* Checks the connections to allow-listed paths. */
> > + if (is_access_to_paths_allowed(subject->domain, path,
> > + fs_resolve_unix.fs, &layer_masks,
> > + &request, NULL, 0, NULL, NULL, NULL))
> > + return 0;
> > +
> > + landlock_log_denial(subject, &request);
> > + return -EACCES;
> > +}
> > +
> > /* File hooks */
> >
> > /**
> > @@ -1834,6 +1941,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
> > LSM_HOOK_INIT(path_unlink, hook_path_unlink),
> > LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
> > LSM_HOOK_INIT(path_truncate, hook_path_truncate),
> > + LSM_HOOK_INIT(unix_find, hook_unix_find),
> >
> > LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
> > LSM_HOOK_INIT(file_open, hook_file_open),
> > diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> > index eb584f47288d..b454ad73b15e 100644
> > --- a/security/landlock/limits.h
> > +++ b/security/landlock/limits.h
> > @@ -19,7 +19,7 @@
> > #define LANDLOCK_MAX_NUM_LAYERS 16
> > #define LANDLOCK_MAX_NUM_RULES U32_MAX
> >
> > -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
> > +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> > #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
> > #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
> >
> > diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> > index 3b33839b80c7..a6e23657f3ce 100644
> > --- a/security/landlock/syscalls.c
> > +++ b/security/landlock/syscalls.c
> > @@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
> > * If the change involves a fix that requires userspace awareness, also update
> > * the errata documentation in Documentation/userspace-api/landlock.rst .
> > */
> > -const int landlock_abi_version = 8;
> > +const int landlock_abi_version = 9;
> >
> > /**
> > * sys_landlock_create_ruleset - Create a new ruleset
> > diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> > index 0fea236ef4bd..30d37234086c 100644
> > --- a/tools/testing/selftests/landlock/base_test.c
> > +++ b/tools/testing/selftests/landlock/base_test.c
> > @@ -76,7 +76,7 @@ TEST(abi_version)
> > const struct landlock_ruleset_attr ruleset_attr = {
> > .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
> > };
> > - ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
> > + ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
> > LANDLOCK_CREATE_RULESET_VERSION));
> >
> > ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
> > diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> > index 968a91c927a4..b318627e7561 100644
> > --- a/tools/testing/selftests/landlock/fs_test.c
> > +++ b/tools/testing/selftests/landlock/fs_test.c
> > @@ -575,9 +575,10 @@ TEST_F_FORK(layout1, inval)
> > LANDLOCK_ACCESS_FS_WRITE_FILE | \
> > LANDLOCK_ACCESS_FS_READ_FILE | \
> > LANDLOCK_ACCESS_FS_TRUNCATE | \
> > - LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > + LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> > + LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> >
> > -#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV
> > +#define ACCESS_LAST LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> >
> > #define ACCESS_ALL ( \
> > ACCESS_FILE | \
> > --
> > 2.53.0
> >
> >
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted
2026-03-18 16:53 ` Mickaël Salaün
@ 2026-03-20 16:44 ` Günther Noack
0 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-20 16:44 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, linux-security-module, Tingmao Wang, Justin Suess,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
On Wed, Mar 18, 2026 at 05:53:59PM +0100, Mickaël Salaün wrote:
> On Sun, Mar 15, 2026 at 11:21:48PM +0100, Günther Noack wrote:
> > Even when a process is restricted with the new
> > LANDLOCK_ACCESS_FS_RESOLVE_SOCKET right, the kernel can continue
>
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX (twice)
Thanks, well spotted! Fixed.
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets
2026-03-18 16:54 ` Mickaël Salaün
@ 2026-03-20 17:04 ` Günther Noack
0 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-20 17:04 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Justin Suess, linux-security-module, Tingmao Wang,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima
On Wed, Mar 18, 2026 at 05:54:19PM +0100, Mickaël Salaün wrote:
> Please always add some minimal description.
Done.
> Also, as already requested, could you run the check-linux.sh all on each
> patch? That would avoid me to fix things like the date (which would now
> be OK because of the new patch in my next branch, but still).
Will do.
> On Sun, Mar 15, 2026 at 11:21:50PM +0100, Günther Noack wrote:
> > Cc: Justin Suess <utilityemal77@gmail.com>
> > Cc: Mickaël Salaün <mic@digikod.net>
> > Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> > ---
> > Documentation/userspace-api/landlock.rst | 15 ++++++++++++++-
> > 1 file changed, 14 insertions(+), 1 deletion(-)
> >
> > diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> > index 13134bccdd39..e60ebd07c5cc 100644
> > --- a/Documentation/userspace-api/landlock.rst
> > +++ b/Documentation/userspace-api/landlock.rst
> > @@ -77,7 +77,8 @@ to be explicit about the denied-by-default access rights.
> > LANDLOCK_ACCESS_FS_MAKE_SYM |
> > LANDLOCK_ACCESS_FS_REFER |
> > LANDLOCK_ACCESS_FS_TRUNCATE |
> > - LANDLOCK_ACCESS_FS_IOCTL_DEV,
> > + LANDLOCK_ACCESS_FS_IOCTL_DEV |
> > + LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
> > .handled_access_net =
> > LANDLOCK_ACCESS_NET_BIND_TCP |
> > LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > @@ -127,6 +128,11 @@ version, and only use the available subset of access rights:
> > /* Removes LANDLOCK_SCOPE_* for ABI < 6 */
> > ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> > LANDLOCK_SCOPE_SIGNAL);
> > + __attribute__((fallthrough));
>
> Case 6 should be handled too:
>
> case 6 ... 8:
Thank you, good catch!
>
> > + case 7:
> > + case 8:
> > + /* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
> > + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
> > }
> >
-Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-20 16:15 ` Günther Noack
@ 2026-03-20 17:51 ` Mickaël Salaün
2026-03-20 22:25 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-20 17:51 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi
On Fri, Mar 20, 2026 at 05:15:40PM +0100, Günther Noack wrote:
> Hello!
>
> On Wed, Mar 18, 2026 at 05:52:48PM +0100, Mickaël Salaün wrote:
> > On Sun, Mar 15, 2026 at 11:21:44PM +0100, Günther Noack wrote:
> > > * Add a new access right LANDLOCK_ACCESS_FS_RESOLVE_UNIX, which
> > > controls the look up operations for named UNIX domain sockets. The
> >
> > lookup
>
> Done.
>
>
> > > resolution happens during connect() and sendmsg() (depending on
> > > socket type).
> > > * Hook into the path lookup in unix_find_bsd() in af_unix.c, using a
> > > LSM hook. Make policy decisions based on the new access rights
> > > * Increment the Landlock ABI version.
> > > * Minor test adaptions to keep the tests working.
> >
> > adaptations
>
> Done.
>
>
> > > * Document the design rationale for scoped access rights,
> > > and cross-reference it from the header documentation.
> > >
> > > With this access right, access is granted if either of the following
> > > conditions is met:
> > >
> > > * The target socket's filesystem path was allow-listed using a
> > > LANDLOCK_RULE_PATH_BENEATH rule, *or*:
> > > * The target socket was created in the same Landlock domain in which
> > > LANDLOCK_ACCESS_FS_RESOLVE_UNIX was restricted.
> > >
> > > In case of a denial, connect() and sendmsg() return EACCES, which is
> > > the same error as it is returned if the user does not have the write
> > > bit in the traditional Unix file system permissions of that file.
> >
> > UNIX
>
> DONE
>
>
> > > Document the (possible future) interaction between scoped flags and
> > > other access rights in struct landlock_ruleset_attr, and summarize the
> > > rationale, as discussed in code review leading up to [2].
> > >
> > > This feature was created with substantial discussion and input from
> > > Justin Suess, Tingmao Wang and Mickaël Salaün.
> > >
> > > Cc: Tingmao Wang <m@maowtm.org>
> > > Cc: Justin Suess <utilityemal77@gmail.com>
> > > Cc: Mickaël Salaün <mic@digikod.net>
> > > Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > > Cc: Kuniyuki Iwashima <kuniyu@google.com>
> > > Suggested-by: Jann Horn <jannh@google.com>
> > > Link[1]: https://github.com/landlock-lsm/linux/issues/36
> > > Link[2]: https://lore.kernel.org/all/20260205.8531e4005118@gnoack.org/
> > > Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> > > ---
> > > Documentation/security/landlock.rst | 40 +++++++
> > > include/uapi/linux/landlock.h | 19 ++++
> > > security/landlock/access.h | 2 +-
> > > security/landlock/audit.c | 1 +
> > > security/landlock/fs.c | 110 ++++++++++++++++++-
> > > security/landlock/limits.h | 2 +-
> > > security/landlock/syscalls.c | 2 +-
> > > tools/testing/selftests/landlock/base_test.c | 2 +-
> > > tools/testing/selftests/landlock/fs_test.c | 5 +-
> > > 9 files changed, 176 insertions(+), 7 deletions(-)
> > >
> > > diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
> > > index 3e4d4d04cfae..4bbe250a6829 100644
> > > --- a/Documentation/security/landlock.rst
> > > +++ b/Documentation/security/landlock.rst
> > > @@ -89,6 +89,46 @@ this is required to keep access controls consistent over the whole system, and
> > > this avoids unattended bypasses through file descriptor passing (i.e. confused
> > > deputy attack).
> > >
> > > +.. _scoped-flags-interaction:
> > > +
> > > +Interaction between scoped flags and other access rights
> > > +--------------------------------------------------------
> > > +
> > > +The ``scoped`` flags in ``struct landlock_ruleset_attr`` restrict the
> > > +use of *outgoing* IPC from the created Landlock domain, while they
> > > +permit reaching out to IPC endpoints *within* the created Landlock
> > > +domain.
> > > +
> > > +In the future, scoped flags *may* interact with other access rights,
> > > +e.g. so that abstract UNIX sockets can be allow-listed by name, or so
> > > +that signals can be allow-listed by signal number or target process.
> > > +
> > > +When introducing ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``, we defined it to
> > > +implicitly have the same scoping semantics as a
> > > +``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` flag would have: connecting to
> > > +UNIX sockets within the same domain (where
> > > +``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` is used) is unconditionally
> > > +allowed.
> > > +
> > > +The reasoning is:
> > > +
> > > +* Like other IPC mechanisms, connecting to named UNIX sockets in the
> > > + same domain should be expected and harmless. (If needed, users can
> > > + further refine their Landlock policies with nested domains or by
> > > + restricting ``LANDLOCK_ACCESS_FS_MAKE_SOCK``.)
> > > +* We reserve the option to still introduce
> > > + ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the future. (This would
> > > + be useful if we wanted to have a Landlock rule to permit IPC access
> > > + to other Landlock domains.)
> > > +* But we can postpone the point in time when users have to deal with
> > > + two interacting flags visible in the userspace API. (In particular,
> > > + it is possible that it won't be needed in practice, in which case we
> > > + can avoid the second flag altogether.)
> > > +* If we *do* introduce ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the
> > > + future, setting this scoped flag in a ruleset does *not reduce* the
> > > + restrictions, because access within the same scope is already
> > > + allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``.
> > > +
> > > Tests
> > > =====
> > >
> > > diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> > > index f88fa1f68b77..751e3c143cba 100644
> > > --- a/include/uapi/linux/landlock.h
> > > +++ b/include/uapi/linux/landlock.h
> > > @@ -248,6 +248,24 @@ struct landlock_net_port_attr {
> > > *
> > > * This access right is available since the fifth version of the Landlock
> > > * ABI.
> > > + * - %LANDLOCK_ACCESS_FS_RESOLVE_UNIX: Look up pathname UNIX domain sockets
> > > + * (:manpage:`unix(7)`). On UNIX domain sockets, this restricts both calls to
> > > + * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an
> > > + * explicit recipient address.
> > > + *
> > > + * This access right only applies to connections to UNIX server sockets which
> > > + * were created outside of the newly created Landlock domain (e.g. from within
> > > + * a parent domain or from an unrestricted process). Newly created UNIX
> > > + * servers within the same Landlock domain continue to be accessible. In this
> > > + * regard, %LANDLOCK_ACCESS_RESOLVE_UNIX has the same semantics as the
> >
> > LANDLOCK_ACCESS_FS_RESOLVE_UNIX
>
> Whoops, done.
>
>
> > > + * ``LANDLOCK_SCOPE_*`` flags.
> > > + *
> > > + * If a resolve attempt is denied, the operation returns an ``EACCES`` error,
> > > + * in line with other filesystem access rights (but different to denials for
> > > + * abstract UNIX domain sockets).
> >
> > This access right is available since the ninth version of the Landlock ABI.
>
> Thanks, added.
>
>
> > > + *
> > > + * The rationale for this design is described in
> > > + * :ref:`Documentation/security/landlock.rst <scoped-flags-interaction>`.
> > > *
> > > * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
> > > * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
> > > @@ -333,6 +351,7 @@ struct landlock_net_port_attr {
> > > #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
> > > #define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
> > > #define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
> > > +#define LANDLOCK_ACCESS_FS_RESOLVE_UNIX (1ULL << 16)
> > > /* clang-format on */
> > >
> > > /**
> > > diff --git a/security/landlock/access.h b/security/landlock/access.h
> > > index 42c95747d7bd..89dc8e7b93da 100644
> > > --- a/security/landlock/access.h
> > > +++ b/security/landlock/access.h
> > > @@ -34,7 +34,7 @@
> > > LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > > /* clang-format on */
> > >
> > > -typedef u16 access_mask_t;
> > > +typedef u32 access_mask_t;
> >
> > This change and the underlying implications are not explained in the
> > commit message, especially regarding the stack delta.
>
> Thanks, will add it.
>
>
> > > /* Makes sure all filesystem access rights can be stored. */
> > > static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> > > diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> > > index 60ff217ab95b..8d0edf94037d 100644
> > > --- a/security/landlock/audit.c
> > > +++ b/security/landlock/audit.c
> > > @@ -37,6 +37,7 @@ static const char *const fs_access_strings[] = {
> > > [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
> > > [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
> > > [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
> > > + [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix",
> > > };
> > >
> > > static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
> > > diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> > > index 97065d51685a..0486f5ab06c9 100644
> > > --- a/security/landlock/fs.c
> > > +++ b/security/landlock/fs.c
> > > @@ -27,6 +27,7 @@
> > > #include <linux/lsm_hooks.h>
> > > #include <linux/mount.h>
> > > #include <linux/namei.h>
> > > +#include <linux/net.h>
> > > #include <linux/path.h>
> > > #include <linux/pid.h>
> > > #include <linux/rcupdate.h>
> > > @@ -36,6 +37,7 @@
> > > #include <linux/types.h>
> > > #include <linux/wait_bit.h>
> > > #include <linux/workqueue.h>
> > > +#include <net/af_unix.h>
> > > #include <uapi/linux/fiemap.h>
> > > #include <uapi/linux/landlock.h>
> > >
> > > @@ -314,7 +316,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
> > > LANDLOCK_ACCESS_FS_WRITE_FILE | \
> > > LANDLOCK_ACCESS_FS_READ_FILE | \
> > > LANDLOCK_ACCESS_FS_TRUNCATE | \
> > > - LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > > + LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> > > + LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> > > /* clang-format on */
> > >
> > > /*
> > > @@ -1557,6 +1560,110 @@ static int hook_path_truncate(const struct path *const path)
> > > return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
> > > }
> > >
> > > +/**
> > > + * unmask_scoped_access - Remove access right bits in @masks in all layers
> > > + * where @client and @server have the same domain
> > > + *
> > > + * This does the same as domain_is_scoped(), but unmasks bits in @masks.
> > > + * It can not return early as domain_is_scoped() does.
> >
> > I'd like a summary of your previous excellent explanation of
> > unmask_scoped_access() in this comment.
>
> Adding:
>
> A scoped access for a given access right bit is allowed iff, for all
> layer depths where the access bit is set, the client and server
> domain are the same. This function clears the access rights @access
> in @masks at all layer depths where the client and server domain are
> the same, so that, when they are all cleared, the access is allowed.
>
> It's not as detailed as drawing a picture in the other mail, but I
> hope it helps.
Good
>
>
> > > + * @client: Client domain
> > > + * @server: Server domain
> > > + * @masks: Layer access masks to unmask
> > > + * @access: Access bit that controls scoping
> > > + */
> > > +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> > > + const struct landlock_ruleset *const server,
> > > + struct layer_access_masks *const masks,
> > > + const access_mask_t access)
> > > +{
> > > + int client_layer, server_layer;
> > > + const struct landlock_hierarchy *client_walker, *server_walker;
> > > +
> > > + /* This should not happen. */
> > > + if (WARN_ON_ONCE(!client))
> > > + return;
> > > +
> > > + /* Server has no Landlock domain; nothing to clear. */
> > > + if (!server)
> > > + return;
> > > +
> >
> > Please also copy the BUILD_BUG_ON() from domain_is_scoped().
>
> I don't understand what this check is good for. It says:
>
> /*
> * client_layer must be a signed integer with greater capacity
> * than client->num_layers to ensure the following loop stops.
> */
> BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
>
> For the loop to terminate, in my understanding, client_layer must be
> able to store client->num_layers - 1 down to - 1, but that is anyway a
> given since num_layers can't exceed 16 and client_layer is signed. It
> seems that the termination of this would anyway be caught in our tests
> as well?
>
> Could you please clarify what this BUILD_BUG_ON() is trying to assert?
The intent was to make sure client_layer is indeed an int and not an
unsigned int for instance. Hopefully tests catch that but using a
build-time assert catch the potential issue and document it. Also, it
would be weird to not have the same checks in both copies of code.
>
>
> > > + client_layer = client->num_layers - 1;
> > > + client_walker = client->hierarchy;
> > > + server_layer = server->num_layers - 1;
> > > + server_walker = server->hierarchy;
> > > +
> > > + /*
> > > + * Clears the access bits at all layers where the client domain is the
> > > + * same as the server domain. We start the walk at min(client_layer,
> > > + * server_layer). The layer bits until there can not be cleared because
> > > + * either the client or the server domain is missing.
> > > + */
> > > + for (; client_layer > server_layer; client_layer--)
> > > + client_walker = client_walker->parent;
> > > +
> > > + for (; server_layer > client_layer; server_layer--)
> > > + server_walker = server_walker->parent;
> > > +
> > > + for (; client_layer >= 0; client_layer--) {
> > > + if (masks->access[client_layer] & access &&
> > > + client_walker == server_walker)
> > > + masks->access[client_layer] &= ~access;
> > > +
> > > + client_walker = client_walker->parent;
> > > + server_walker = server_walker->parent;
> > > + }
> > > +}
> > > +
> > > +static int hook_unix_find(const struct path *const path, struct sock *other,
> > > + int flags)
> > > +{
> > > + const struct landlock_ruleset *dom_other;
> > > + const struct landlock_cred_security *subject;
> > > + struct layer_access_masks layer_masks;
> > > + struct landlock_request request = {};
> > > + static const struct access_masks fs_resolve_unix = {
> > > + .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
> > > + };
> > > +
> > > + /* Lookup for the purpose of saving coredumps is OK. */
> > > + if (unlikely(flags & SOCK_COREDUMP))
> > > + return 0;
> > > +
> > > + /* Access to the same (or a lower) domain is always allowed. */
> >
> > This comment is related to the unmask_scoped_access() call.
>
> Thanks, I moved it down.
>
>
> > > + subject = landlock_get_applicable_subject(current_cred(),
> > > + fs_resolve_unix, NULL);
> > > +
> > > + if (!subject)
> > > + return 0;
> > > +
> > > + if (!landlock_init_layer_masks(subject->domain, fs_resolve_unix.fs,
> > > + &layer_masks, LANDLOCK_KEY_INODE))
> >
> > This case is not possible because landlock_get_applicable_subject()
> > already check it. Other hooks just ignore the returned value in this
> > case.
>
> Hm, fair enough. I added a comment to explain why we are ignoring the
> return value, as it wasn't as obvious to me. In the other places, we
> are using the result of the landlock_init_layer_masks() function
> (because in the generic case, it can be a subset of the original
> access rights).
Another way to document it would be to use a WARN_ON_ONCE(), but that
would not be very useful in this case and add code which cannot be
tested/covered.
>
>
> > > + return 0;
> > > +
> > > + /* Checks the layers in which we are connecting within the same domain. */
> > > + unix_state_lock(other);
> > > + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket ||
> > > + !other->sk_socket->file)) {
> > > + unix_state_unlock(other);
> > > + return 0;
> >
> > Is it safe to not return -ECONNREFUSED?
>
> Yes. My reasoning is:
>
> In all three places where this gets called in af_unix.c (stream
> connect, dgram connect, dgram send), these functions check for socket
> death shortly after, and if they find the socket to be SOCK_DEAD, they
> will *retry* the UNIX lookup. The code commentary about this says
> that this is for a race condition where the VFS has "overslept" the
> socket death, so I presume that the retry aims at getting a race-free
> sitation on the next attempt.
>
> Since sock_orphan() is a one-way teardown operation, when we observe
> SOCK_DEAD in our hook, we can be sure that the caller will see it as
> well when it does the same check a bit later after our hook.
>
> If we *were* to return -ECONNREFUSED, the caller would immediately
> return an error though, and it would not retry as it normally does
> when it encounters this race condition. So we have to return 0 here.
OK, sound good.
>
>
> > > + }
> > > + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> > > + unix_state_unlock(other);
> > > +
> > > + unmask_scoped_access(subject->domain, dom_other, &layer_masks,
> > > + fs_resolve_unix.fs);
> >
> > dom_other is not safe to use without the lock.
>
> Thanks, fixed by extending the lock scope across that function call,
> as discussed in other thread in more detail.
>
>
> > > + /* Checks the connections to allow-listed paths. */
> > > + if (is_access_to_paths_allowed(subject->domain, path,
> > > + fs_resolve_unix.fs, &layer_masks,
> > > + &request, NULL, 0, NULL, NULL, NULL))
> > > + return 0;
> > > +
> > > + landlock_log_denial(subject, &request);
> > > + return -EACCES;
> > > +}
> > > +
> > > /* File hooks */
> > >
> > > /**
> > > @@ -1834,6 +1941,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
> > > LSM_HOOK_INIT(path_unlink, hook_path_unlink),
> > > LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
> > > LSM_HOOK_INIT(path_truncate, hook_path_truncate),
> > > + LSM_HOOK_INIT(unix_find, hook_unix_find),
> > >
> > > LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
> > > LSM_HOOK_INIT(file_open, hook_file_open),
> > > diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> > > index eb584f47288d..b454ad73b15e 100644
> > > --- a/security/landlock/limits.h
> > > +++ b/security/landlock/limits.h
> > > @@ -19,7 +19,7 @@
> > > #define LANDLOCK_MAX_NUM_LAYERS 16
> > > #define LANDLOCK_MAX_NUM_RULES U32_MAX
> > >
> > > -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
> > > +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> > > #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
> > > #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
> > >
> > > diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> > > index 3b33839b80c7..a6e23657f3ce 100644
> > > --- a/security/landlock/syscalls.c
> > > +++ b/security/landlock/syscalls.c
> > > @@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
> > > * If the change involves a fix that requires userspace awareness, also update
> > > * the errata documentation in Documentation/userspace-api/landlock.rst .
> > > */
> > > -const int landlock_abi_version = 8;
> > > +const int landlock_abi_version = 9;
> > >
> > > /**
> > > * sys_landlock_create_ruleset - Create a new ruleset
> > > diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> > > index 0fea236ef4bd..30d37234086c 100644
> > > --- a/tools/testing/selftests/landlock/base_test.c
> > > +++ b/tools/testing/selftests/landlock/base_test.c
> > > @@ -76,7 +76,7 @@ TEST(abi_version)
> > > const struct landlock_ruleset_attr ruleset_attr = {
> > > .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
> > > };
> > > - ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
> > > + ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
> > > LANDLOCK_CREATE_RULESET_VERSION));
> > >
> > > ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
> > > diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> > > index 968a91c927a4..b318627e7561 100644
> > > --- a/tools/testing/selftests/landlock/fs_test.c
> > > +++ b/tools/testing/selftests/landlock/fs_test.c
> > > @@ -575,9 +575,10 @@ TEST_F_FORK(layout1, inval)
> > > LANDLOCK_ACCESS_FS_WRITE_FILE | \
> > > LANDLOCK_ACCESS_FS_READ_FILE | \
> > > LANDLOCK_ACCESS_FS_TRUNCATE | \
> > > - LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > > + LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> > > + LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> > >
> > > -#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV
> > > +#define ACCESS_LAST LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> > >
> > > #define ACCESS_ALL ( \
> > > ACCESS_FILE | \
> > > --
> > > 2.53.0
> > >
> > >
>
> –Günther
>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-20 17:51 ` Mickaël Salaün
@ 2026-03-20 22:25 ` Günther Noack
2026-03-21 9:09 ` Mickaël Salaün
0 siblings, 1 reply; 43+ messages in thread
From: Günther Noack @ 2026-03-20 22:25 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi
On Fri, Mar 20, 2026 at 06:51:13PM +0100, Mickaël Salaün wrote:
> On Fri, Mar 20, 2026 at 05:15:40PM +0100, Günther Noack wrote:
> > On Wed, Mar 18, 2026 at 05:52:48PM +0100, Mickaël Salaün wrote:
> > > On Sun, Mar 15, 2026 at 11:21:44PM +0100, Günther Noack wrote:
> > > > + * @client: Client domain
> > > > + * @server: Server domain
> > > > + * @masks: Layer access masks to unmask
> > > > + * @access: Access bit that controls scoping
> > > > + */
> > > > +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> > > > + const struct landlock_ruleset *const server,
> > > > + struct layer_access_masks *const masks,
> > > > + const access_mask_t access)
> > > > +{
> > > > + int client_layer, server_layer;
> > > > + const struct landlock_hierarchy *client_walker, *server_walker;
> > > > +
> > > > + /* This should not happen. */
> > > > + if (WARN_ON_ONCE(!client))
> > > > + return;
> > > > +
> > > > + /* Server has no Landlock domain; nothing to clear. */
> > > > + if (!server)
> > > > + return;
> > > > +
> > >
> > > Please also copy the BUILD_BUG_ON() from domain_is_scoped().
> >
> > I don't understand what this check is good for. It says:
> >
> > /*
> > * client_layer must be a signed integer with greater capacity
> > * than client->num_layers to ensure the following loop stops.
> > */
> > BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
> >
> > For the loop to terminate, in my understanding, client_layer must be
> > able to store client->num_layers - 1 down to - 1, but that is anyway a
> > given since num_layers can't exceed 16 and client_layer is signed. It
> > seems that the termination of this would anyway be caught in our tests
> > as well?
> >
> > Could you please clarify what this BUILD_BUG_ON() is trying to assert?
>
> The intent was to make sure client_layer is indeed an int and not an
> unsigned int for instance. Hopefully tests catch that but using a
> build-time assert catch the potential issue and document it. Also, it
> would be weird to not have the same checks in both copies of code.
It isn't clear to me why the BUILD_BUG_ON is checking based on the
sizeof() of these variables then. The number of bytes in an integer
type is independent of its signedness, after all. Should we rather do
this maybe?
/*
* client_layer must be a signed integer to ensure the following
* loop stops.
*/
BUILD_BUG_ON(!is_signed_type(typeof(client_layer)));
(Although that is also a bit of a triviality given that the same
variable is being defined as a signed integer just a few lines above,
but at least it is very explicit about it.)
I'd drop the remark about the capacity, as even a signed 8-bit integer
is large enough to hold the layer indices and the -1.
Does that sound reasonable? I can do it in the other function as well
if you want to keep things consistent.
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-20 22:25 ` Günther Noack
@ 2026-03-21 9:09 ` Mickaël Salaün
2026-03-23 15:31 ` Günther Noack
0 siblings, 1 reply; 43+ messages in thread
From: Mickaël Salaün @ 2026-03-21 9:09 UTC (permalink / raw)
To: Günther Noack
Cc: John Johansen, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi, Kees Cook
On Fri, Mar 20, 2026 at 11:25:04PM +0100, Günther Noack wrote:
> On Fri, Mar 20, 2026 at 06:51:13PM +0100, Mickaël Salaün wrote:
> > On Fri, Mar 20, 2026 at 05:15:40PM +0100, Günther Noack wrote:
> > > On Wed, Mar 18, 2026 at 05:52:48PM +0100, Mickaël Salaün wrote:
> > > > On Sun, Mar 15, 2026 at 11:21:44PM +0100, Günther Noack wrote:
> > > > > + * @client: Client domain
> > > > > + * @server: Server domain
> > > > > + * @masks: Layer access masks to unmask
> > > > > + * @access: Access bit that controls scoping
> > > > > + */
> > > > > +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> > > > > + const struct landlock_ruleset *const server,
> > > > > + struct layer_access_masks *const masks,
> > > > > + const access_mask_t access)
> > > > > +{
> > > > > + int client_layer, server_layer;
> > > > > + const struct landlock_hierarchy *client_walker, *server_walker;
> > > > > +
> > > > > + /* This should not happen. */
> > > > > + if (WARN_ON_ONCE(!client))
> > > > > + return;
> > > > > +
> > > > > + /* Server has no Landlock domain; nothing to clear. */
> > > > > + if (!server)
> > > > > + return;
> > > > > +
> > > >
> > > > Please also copy the BUILD_BUG_ON() from domain_is_scoped().
> > >
> > > I don't understand what this check is good for. It says:
> > >
> > > /*
> > > * client_layer must be a signed integer with greater capacity
> > > * than client->num_layers to ensure the following loop stops.
> > > */
> > > BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
> > >
> > > For the loop to terminate, in my understanding, client_layer must be
> > > able to store client->num_layers - 1 down to - 1, but that is anyway a
> > > given since num_layers can't exceed 16 and client_layer is signed. It
> > > seems that the termination of this would anyway be caught in our tests
> > > as well?
> > >
> > > Could you please clarify what this BUILD_BUG_ON() is trying to assert?
> >
> > The intent was to make sure client_layer is indeed an int and not an
> > unsigned int for instance. Hopefully tests catch that but using a
> > build-time assert catch the potential issue and document it. Also, it
> > would be weird to not have the same checks in both copies of code.
>
> It isn't clear to me why the BUILD_BUG_ON is checking based on the
> sizeof() of these variables then. The number of bytes in an integer
> type is independent of its signedness, after all. Should we rather do
> this maybe?
>
> /*
> * client_layer must be a signed integer to ensure the following
> * loop stops.
> */
> BUILD_BUG_ON(!is_signed_type(typeof(client_layer)));
I didn't know about this macro, looks good.
>
> (Although that is also a bit of a triviality given that the same
> variable is being defined as a signed integer just a few lines above,
> but at least it is very explicit about it.)
Yeah, I know, but I added this canary check because I was bitten by a
bug. Even if it might be trivial now, it might help when working on
other parts, and it's just a build-time check that also serves as doc.
If in doubt, let's keep build-time checks that were most likely added
for a good reason. I prefer to have build-time errors than run-time
ones.
>
> I'd drop the remark about the capacity, as even a signed 8-bit integer
> is large enough to hold the layer indices and the -1.
Large enough for now... All these checks are useful to easily spot
issues if/when the current invariant change (e.g. if one day we extend
the number of max layers). Integer C types can silently cast to other
integer types (with different capacity or sign/unsigned), which might be
the source of bugs. So yeah, please keep the current capacity check and
add the sign one, it won't do any harm.
>
> Does that sound reasonable? I can do it in the other function as well
> if you want to keep things consistent.
Yes, please update the other function as well.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-18 8:48 ` [PATCH v6 " Mickaël Salaün
2026-03-18 14:44 ` Paul Moore
@ 2026-03-23 14:37 ` Georgia Garcia
2026-03-23 20:26 ` Paul Moore
1 sibling, 1 reply; 43+ messages in thread
From: Georgia Garcia @ 2026-03-23 14:37 UTC (permalink / raw)
To: Mickaël Salaün, John Johansen
Cc: Paul Moore, Günther Noack, James Morris, Serge E . Hallyn,
Tingmao Wang, Justin Suess, linux-security-module,
Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Sebastian Andrzej Siewior, Kuniyuki Iwashima,
Simon Horman, netdev, Alexander Viro, Christian Brauner
Hello,
On Wed, 2026-03-18 at 09:48 +0100, Mickaël Salaün wrote:
> On Tue, Mar 17, 2026 at 05:34:57PM -0400, Paul Moore wrote:
> > On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> > >
> > > Add a LSM hook security_unix_find.
> > >
> > > This hook is called to check the path of a named unix socket before a
> > > connection is initiated. The peer socket may be inspected as well.
> > >
> > > Why existing hooks are unsuitable:
> > >
> > > Existing socket hooks, security_unix_stream_connect(),
> > > security_unix_may_send(), and security_socket_connect() don't provide
> > > TOCTOU-free / namespace independent access to the paths of sockets.
> > >
> > > (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> > > This requires another path lookup. A change in the path between the
> > > two lookups will cause a TOCTOU bug.
> > >
> > > (2) We cannot use the struct path from the listening socket, because it
> > > may be bound to a path in a different namespace than the caller,
> > > resulting in a path that cannot be referenced at policy creation time.
> > >
> > > Cc: Günther Noack <gnoack3000@gmail.com>
> > > Cc: Tingmao Wang <m@maowtm.org>
> > > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > > ---
> > > include/linux/lsm_hook_defs.h | 5 +++++
> > > include/linux/security.h | 11 +++++++++++
> > > net/unix/af_unix.c | 13 ++++++++++---
> > > security/security.c | 20 ++++++++++++++++++++
> > > 4 files changed, 46 insertions(+), 3 deletions(-)
> >
> > Some really minor nitpicky things (below), but nothing critical.
> > However, as we discussed, I would like to see the AppArmor folks comment
> > on the new hook before we merge anything as I know they have an interest
> > here.
>
> John, Georgia, we've been discussing this new hook for a few months now
> but didn't hear from you yet. We plan to merge this patch series with
> the 7.1 merge window (in a few weeks), so before that I'd like to merge
> it in -next in a few days to get a broader coverage. I'm pretty sure
> this hook will work well with AppArmor too, but could you please take
> look to confirm?
Apologies for the long delay replying. I have looked it over and I have
no objections on the hook, it looks good to me. I would prefer if we
got a reply from John as well since I'm not 100% confident but he
should be out this week. In any case,
Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com>
Thanks and sorry again for the long time to reply.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path
2026-03-21 9:09 ` Mickaël Salaün
@ 2026-03-23 15:31 ` Günther Noack
0 siblings, 0 replies; 43+ messages in thread
From: Günther Noack @ 2026-03-23 15:31 UTC (permalink / raw)
To: Mickaël Salaün
Cc: John Johansen, Tingmao Wang, Justin Suess,
Sebastian Andrzej Siewior, Kuniyuki Iwashima, Jann Horn,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Tahera Fahimi, Kees Cook
Hello!
On Sat, Mar 21, 2026 at 10:09:35AM +0100, Mickaël Salaün wrote:
> On Fri, Mar 20, 2026 at 11:25:04PM +0100, Günther Noack wrote:
> > On Fri, Mar 20, 2026 at 06:51:13PM +0100, Mickaël Salaün wrote:
> > > On Fri, Mar 20, 2026 at 05:15:40PM +0100, Günther Noack wrote:
> > > > On Wed, Mar 18, 2026 at 05:52:48PM +0100, Mickaël Salaün wrote:
> > > > > On Sun, Mar 15, 2026 at 11:21:44PM +0100, Günther Noack wrote:
> > > > > > + * @client: Client domain
> > > > > > + * @server: Server domain
> > > > > > + * @masks: Layer access masks to unmask
> > > > > > + * @access: Access bit that controls scoping
> > > > > > + */
> > > > > > +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> > > > > > + const struct landlock_ruleset *const server,
> > > > > > + struct layer_access_masks *const masks,
> > > > > > + const access_mask_t access)
> > > > > > +{
> > > > > > + int client_layer, server_layer;
> > > > > > + const struct landlock_hierarchy *client_walker, *server_walker;
> > > > > > +
> > > > > > + /* This should not happen. */
> > > > > > + if (WARN_ON_ONCE(!client))
> > > > > > + return;
> > > > > > +
> > > > > > + /* Server has no Landlock domain; nothing to clear. */
> > > > > > + if (!server)
> > > > > > + return;
> > > > > > +
> > > > >
> > > > > Please also copy the BUILD_BUG_ON() from domain_is_scoped().
> > > >
> > > > I don't understand what this check is good for. It says:
> > > >
> > > > /*
> > > > * client_layer must be a signed integer with greater capacity
> > > > * than client->num_layers to ensure the following loop stops.
> > > > */
> > > > BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
> > > >
> > > > For the loop to terminate, in my understanding, client_layer must be
> > > > able to store client->num_layers - 1 down to - 1, but that is anyway a
> > > > given since num_layers can't exceed 16 and client_layer is signed. It
> > > > seems that the termination of this would anyway be caught in our tests
> > > > as well?
> > > >
> > > > Could you please clarify what this BUILD_BUG_ON() is trying to assert?
> > >
> > > The intent was to make sure client_layer is indeed an int and not an
> > > unsigned int for instance. Hopefully tests catch that but using a
> > > build-time assert catch the potential issue and document it. Also, it
> > > would be weird to not have the same checks in both copies of code.
> >
> > It isn't clear to me why the BUILD_BUG_ON is checking based on the
> > sizeof() of these variables then. The number of bytes in an integer
> > type is independent of its signedness, after all. Should we rather do
> > this maybe?
> >
> > /*
> > * client_layer must be a signed integer to ensure the following
> > * loop stops.
> > */
> > BUILD_BUG_ON(!is_signed_type(typeof(client_layer)));
>
> I didn't know about this macro, looks good.
>
> >
> > (Although that is also a bit of a triviality given that the same
> > variable is being defined as a signed integer just a few lines above,
> > but at least it is very explicit about it.)
>
> Yeah, I know, but I added this canary check because I was bitten by a
> bug. Even if it might be trivial now, it might help when working on
> other parts, and it's just a build-time check that also serves as doc.
> If in doubt, let's keep build-time checks that were most likely added
> for a good reason. I prefer to have build-time errors than run-time
> ones.
>
> >
> > I'd drop the remark about the capacity, as even a signed 8-bit integer
> > is large enough to hold the layer indices and the -1.
>
> Large enough for now... All these checks are useful to easily spot
> issues if/when the current invariant change (e.g. if one day we extend
> the number of max layers). Integer C types can silently cast to other
> integer types (with different capacity or sign/unsigned), which might be
> the source of bugs. So yeah, please keep the current capacity check and
> add the sign one, it won't do any harm.
I feel that this is still not fully addressed. I know it is a
triviality to go in circles over this BUILD_BUG, but the current form:
BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
checks that the size of client_layer is smaller(!) or equal than the
size of client->num_layers, so it seems that the check is accidentally
done the wrong way around. It does not fire because the two integers
happen to have the same size, but it feels a bit misleading to leave
it like that?
To keep things moving, and because it feels like a minor (any maybe
optional) issue, I will send V7 now and optimistically do the
following:
* Copy the BUILD_BUG_ON and matching comment from domain_is_checked()
as you initially requested.
* Make a constructive proposal that rewrites the check in both
functions, to check that all numbers from -1 to
LANDLOCK_MAX_NUM_LAYERS are representable (and the loop therefore
terminates). In my understanding that was the intention here.
–Günther
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
2026-03-23 14:37 ` Georgia Garcia
@ 2026-03-23 20:26 ` Paul Moore
0 siblings, 0 replies; 43+ messages in thread
From: Paul Moore @ 2026-03-23 20:26 UTC (permalink / raw)
To: Georgia Garcia
Cc: Mickaël Salaün, John Johansen, Günther Noack,
James Morris, Serge E . Hallyn, Tingmao Wang, Justin Suess,
linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
Christian Brauner
On Mon, Mar 23, 2026 at 10:37 AM Georgia Garcia
<georgia.garcia@canonical.com> wrote:
> On Wed, 2026-03-18 at 09:48 +0100, Mickaël Salaün wrote:
> > On Tue, Mar 17, 2026 at 05:34:57PM -0400, Paul Moore wrote:
> > > On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> > > >
> > > > Add a LSM hook security_unix_find.
> > > >
> > > > This hook is called to check the path of a named unix socket before a
> > > > connection is initiated. The peer socket may be inspected as well.
> > > >
> > > > Why existing hooks are unsuitable:
> > > >
> > > > Existing socket hooks, security_unix_stream_connect(),
> > > > security_unix_may_send(), and security_socket_connect() don't provide
> > > > TOCTOU-free / namespace independent access to the paths of sockets.
> > > >
> > > > (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> > > > This requires another path lookup. A change in the path between the
> > > > two lookups will cause a TOCTOU bug.
> > > >
> > > > (2) We cannot use the struct path from the listening socket, because it
> > > > may be bound to a path in a different namespace than the caller,
> > > > resulting in a path that cannot be referenced at policy creation time.
> > > >
> > > > Cc: Günther Noack <gnoack3000@gmail.com>
> > > > Cc: Tingmao Wang <m@maowtm.org>
> > > > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > > > ---
> > > > include/linux/lsm_hook_defs.h | 5 +++++
> > > > include/linux/security.h | 11 +++++++++++
> > > > net/unix/af_unix.c | 13 ++++++++++---
> > > > security/security.c | 20 ++++++++++++++++++++
> > > > 4 files changed, 46 insertions(+), 3 deletions(-)
> > >
> > > Some really minor nitpicky things (below), but nothing critical.
> > > However, as we discussed, I would like to see the AppArmor folks comment
> > > on the new hook before we merge anything as I know they have an interest
> > > here.
> >
> > John, Georgia, we've been discussing this new hook for a few months now
> > but didn't hear from you yet. We plan to merge this patch series with
> > the 7.1 merge window (in a few weeks), so before that I'd like to merge
> > it in -next in a few days to get a broader coverage. I'm pretty sure
> > this hook will work well with AppArmor too, but could you please take
> > look to confirm?
>
> Apologies for the long delay replying. I have looked it over and I have
> no objections on the hook, it looks good to me. I would prefer if we
> got a reply from John as well since I'm not 100% confident but he
> should be out this week. In any case,
>
> Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com>
Thanks! I can understand wanting to wait for John, and if I'm
understanding the other aspects of the discussion (unrelated to this)
I think there still might be some additional discussions or revisions
before this goes anywhere.
--
paul-moore.com
^ permalink raw reply [flat|nested] 43+ messages in thread
end of thread, other threads:[~2026-03-23 20:26 UTC | newest]
Thread overview: 43+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-15 22:21 [PATCH v6 0/9] landlock: UNIX connect() control by pathname and scope Günther Noack
2026-03-15 22:21 ` [PATCH v6 1/9] lsm: Add LSM hook security_unix_find Günther Noack
2026-03-17 21:14 ` Mickaël Salaün
2026-03-17 21:34 ` Paul Moore
2026-03-17 23:20 ` [PATCH v7 " Justin Suess
2026-03-18 1:28 ` Paul Moore
2026-03-18 8:48 ` [PATCH v6 " Mickaël Salaün
2026-03-18 14:44 ` Paul Moore
2026-03-18 16:22 ` Mickaël Salaün
2026-03-18 16:43 ` Paul Moore
2026-03-23 14:37 ` Georgia Garcia
2026-03-23 20:26 ` Paul Moore
2026-03-18 16:51 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 2/9] landlock: use mem_is_zero() in is_layer_masks_allowed() Günther Noack
2026-03-18 16:52 ` Mickaël Salaün
2026-03-20 10:50 ` Günther Noack
2026-03-15 22:21 ` [PATCH v6 3/9] landlock: Control pathname UNIX domain socket resolution by path Günther Noack
2026-03-18 11:15 ` Sebastian Andrzej Siewior
2026-03-18 14:14 ` Justin Suess
2026-03-18 15:05 ` Sebastian Andrzej Siewior
2026-03-18 16:26 ` Mickaël Salaün
2026-03-18 16:43 ` Justin Suess
2026-03-18 17:52 ` Mickaël Salaün
2026-03-20 12:28 ` Günther Noack
2026-03-18 16:52 ` Mickaël Salaün
2026-03-20 16:15 ` Günther Noack
2026-03-20 17:51 ` Mickaël Salaün
2026-03-20 22:25 ` Günther Noack
2026-03-21 9:09 ` Mickaël Salaün
2026-03-23 15:31 ` Günther Noack
2026-03-15 22:21 ` [PATCH v6 4/9] samples/landlock: Add support for named UNIX domain socket restrictions Günther Noack
2026-03-15 22:21 ` [PATCH v6 5/9] landlock/selftests: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
2026-03-18 16:53 ` Mickaël Salaün
2026-03-20 10:51 ` Günther Noack
2026-03-15 22:21 ` [PATCH v6 6/9] landlock/selftests: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX Günther Noack
2026-03-18 16:53 ` Mickaël Salaün
2026-03-15 22:21 ` [PATCH v6 7/9] landlock/selftests: Check that coredump sockets stay unrestricted Günther Noack
2026-03-18 16:53 ` Mickaël Salaün
2026-03-20 16:44 ` Günther Noack
2026-03-15 22:21 ` [PATCH v6 8/9] landlock/selftests: fs_test: Simplify ruleset creation and enforcement Günther Noack
2026-03-15 22:21 ` [PATCH v6 9/9] landlock: Document FS access right for pathname UNIX sockets Günther Noack
2026-03-18 16:54 ` Mickaël Salaün
2026-03-20 17:04 ` Günther Noack
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox