* [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
@ 2025-12-30 17:20 Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI Tingmao Wang
` (6 more replies)
0 siblings, 7 replies; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
Changes in v2:
Fix grammar in doc, rebased on mic/next, and extracted common code from
hook_unix_stream_connect and hook_unix_may_send into a separate
function.
The rest is the same as the v1 cover letter:
This patch series extend the existing abstract Unix socket scoping to
pathname (i.e. normal file-based) sockets as well, by adding a new scope
bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET that works the same as
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, except that restricts pathname Unix
sockets. This means that a sandboxed process with this scope enabled will
not be able to connect to Unix sockets created outside the sandbox via the
filesystem.
There is a future plan [1] for allowing specific sockets based on FS
hierarchy, but this series is only determining access based on domain
parent-child relationship. There is currently no way to allow specific
(outside the Landlock domain) Unix sockets, and none of the existing
Landlock filesystem controls apply to socket connect().
With this series, we can now properly protect against things like the the
following while only relying on Landlock:
(running under tmux)
root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= ./sandboxer bash
Executing the sandboxed command...
root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
cat: /tmp/hi: No such file or directory
root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
hi
The above but with Unix socket scoping enabled (both pathname and abstract
sockets) - the sandboxed shell can now no longer talk to tmux due to the
socket being created from outside the Landlock sandbox:
(running under tmux)
root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= LL_SCOPED=u:a ./sandboxer bash
Executing the sandboxed command...
root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
cat: /tmp/hi: No such file or directory
root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
error connecting to /tmp/tmux-0/default (Operation not permitted)
root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
cat: /tmp/hi: No such file or directory
Tmux is just one example. In a standard systemd session, `systemd-run
--user` can also be used (--user will run the command in the user's
session, without requiring any root privileges), and likely a lot more if
running in a desktop environment with many popular applications. This
change therefore makes it possible to create sandboxes without relying on
additional mechanisms like seccomp to protect against such issues.
These kind of issues was originally discussed on here (I took the idea for
systemd-run from Demi):
https://spectrum-os.org/lists/archives/spectrum-devel/00256266-26db-40cf-8f5b-f7c7064084c2@gmail.com/
Demo with socat + sandboxer:
Outside:
socat unix-listen:/foo.sock,fork -
Sandbox with pathname socket scope bit:
root@6-19-0-rc1-dev-00023-g0994a10d6512 ~# LL_FS_RW=/ LL_FS_RO= LL_SCOPED=u /sandboxer socat -d2 unix:/foo.sock -
Executing the sandboxed command...
2025/12/27 20:28:54 socat[1227] E UNIX-CLIENT: /foo.sock: Operation not permitted
2025/12/27 20:28:54 socat[1227] N exit(1)
Sandbox without pathname socket scope bit:
root@6-19-0-rc1-dev-00023-g0994a10d6512 ~# LL_FS_RW=/ LL_FS_RO= LL_SCOPED= /sandboxer socat -d2 unix:/foo.sock -
Executing the sandboxed command...
2025/12/27 20:29:22 socat[1250] N successfully connected from local address AF=1 "(7\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xB0\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xAE\xC3\xAE\xAE\xAE\xAE"
...
Sandbox with only abstract socket scope bit:
root@6-19-0-rc1-dev-00023-g0994a10d6512 ~# LL_FS_RW=/ LL_FS_RO= LL_SCOPED=a /sandboxer socat -d2 unix:/foo.sock -
Executing the sandboxed command...
2025/12/27 20:29:26 socat[1259] N successfully connected from local address AF=1 "\0\0\0\0\0\0\0\0\0"
...
Sendmsg/recvmsg - outside:
socat unix-recvfrom:/datagram.sock -
Sandbox with pathname socket scope bit:
root@6-19-0-rc1-dev-00023-g0994a10d6512 ~# LL_FS_RW=/ LL_FS_RO= LL_SCOPED=u /sandboxer socat -d2 unix-sendto:/datagram.sock -
Executing the sandboxed command...
...
2025/12/27 20:33:04 socat[1446] N starting data transfer loop with FDs [5,5] and [0,1]
123
2025/12/27 20:33:05 socat[1446] E sendto(5, 0x55d260d8f000, 4, 0, AF=1 "/datagram.sock", 16): Operation not permitted
2025/12/27 20:33:05 socat[1446] N exit(1)
[1]: https://github.com/landlock-lsm/linux/issues/36
Closes: https://github.com/landlock-lsm/linux/issues/51
Tingmao Wang (6):
landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI
landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
selftests/landlock: Support pathname socket path in set_unix_address
selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname
sockets too.
selftests/landlock: Add pathname socket variants for more tests
Documentation/userspace-api/landlock.rst | 37 +-
include/uapi/linux/landlock.h | 8 +-
samples/landlock/sandboxer.c | 23 +-
security/landlock/audit.c | 4 +
security/landlock/audit.h | 1 +
security/landlock/limits.h | 2 +-
security/landlock/syscalls.c | 2 +-
security/landlock/task.c | 109 ++-
tools/testing/selftests/landlock/base_test.c | 2 +-
tools/testing/selftests/landlock/common.h | 33 +-
tools/testing/selftests/landlock/net_test.c | 2 +-
.../selftests/landlock/scoped_signal_test.c | 2 +-
.../testing/selftests/landlock/scoped_test.c | 2 +-
...bstract_unix_test.c => scoped_unix_test.c} | 855 ++++++++++++------
14 files changed, 757 insertions(+), 325 deletions(-)
rename tools/testing/selftests/landlock/{scoped_abstract_unix_test.c => scoped_unix_test.c} (51%)
base-commit: ef4536f15224418b327a7b5d5cae07dab042760f
--
2.52.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2026-01-29 21:27 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
` (5 subsequent siblings)
6 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
Add the new scope bit to the uAPI header, add documentation, and bump ABI
version to 8.
This documentation edit specifically calls out the security implications of
not restricting sockets.
Fix some minor cosmetic issue in landlock.h around the changed lines as
well.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v2:
- Fix grammar
Note that in the code block in "Defining and enforcing a security policy"
the switch case currently jumps from 5 to 7. This should be fixed by
https://lore.kernel.org/all/20251216210248.4150777-1-samasth.norway.ananda@oracle.com/
Documentation/userspace-api/landlock.rst | 37 ++++++++++++++++---
include/uapi/linux/landlock.h | 8 +++-
security/landlock/limits.h | 2 +-
security/landlock/syscalls.c | 2 +-
tools/testing/selftests/landlock/base_test.c | 2 +-
.../testing/selftests/landlock/scoped_test.c | 2 +-
6 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 1d0c2c15c22e..5620a2be1091 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -83,7 +83,8 @@ to be explicit about the denied-by-default access rights.
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped =
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
- LANDLOCK_SCOPE_SIGNAL,
+ LANDLOCK_SCOPE_SIGNAL |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
};
Because we may not know which kernel version an application will be executed
@@ -127,6 +128,10 @@ 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:
+ /* Removes LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET for ABI < 8 */
+ ruleset_attr.scoped &= ~LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
}
This enables the creation of an inclusive ruleset that will contain our rules.
@@ -328,10 +333,15 @@ The operations which can be scoped are:
This limits the sending of signals to target processes which run within the
same or a nested Landlock domain.
-``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET``
- This limits the set of abstract :manpage:`unix(7)` sockets to which we can
- :manpage:`connect(2)` to socket addresses which were created by a process in
- the same or a nested Landlock domain.
+``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` and ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET``
+ This limits the set of :manpage:`unix(7)` sockets to which we can
+ :manpage:`connect(2)` to socket addresses which were created by a
+ process in the same or a nested Landlock domain.
+ ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` applies to abstract sockets,
+ and ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` applies to pathname
+ sockets. Even though pathname sockets are represented in the
+ filesystem, Landlock filesystem rules do not currently control access
+ to them.
A :manpage:`sendto(2)` on a non-connected datagram socket is treated as if
it were doing an implicit :manpage:`connect(2)` and will be blocked if the
@@ -604,6 +614,23 @@ Landlock audit events with the ``LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF``,
sys_landlock_restrict_self(). See Documentation/admin-guide/LSM/landlock.rst
for more details on audit.
+Pathname UNIX socket (ABI < 8)
+------------------------------
+
+Starting with the Landlock ABI version 8, it is possible to restrict
+connections to a pathname (non-abstract) :manpage:`unix(7)` socket by
+setting ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` to the ``scoped`` ruleset
+attribute. This works the same way as the abstract socket scoping.
+
+This allows sandboxing applications using only Landlock to protect against
+bypasses relying on connecting to Unix sockets of other services running
+under the same user. These services typically assume that any process
+capable of connecting to a local Unix socket, or connecting with the
+expected user credentials, is trusted. Without this protection, sandbox
+escapes may be possible, especially when running in a standard desktop
+environment, such as by using systemd-run, or sockets exposed by other
+common applications.
+
.. _kernel_support:
Kernel support
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f030adc462ee..590c6d4171a0 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -364,10 +364,14 @@ struct landlock_net_port_attr {
* related Landlock domain (e.g., a parent domain or a non-sandboxed process).
* - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal
* to another process outside the domain.
+ * - %LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET: Restrict a sandboxed process from
+ * connecting to a pathname UNIX socket created by a process outside the
+ * related Landlock domain.
*/
/* clang-format off */
#define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
-#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
-/* clang-format on*/
+#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
+#define LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (1ULL << 2)
+/* clang-format on */
#endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 65b5ff051674..d653e14dba10 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -27,7 +27,7 @@
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
-#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL
+#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 0116e9f93ffe..66fd196be85a 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -161,7 +161,7 @@ static const struct file_operations ruleset_fops = {
* Documentation/userspace-api/landlock.rst should be updated to reflect the
* UAPI change.
*/
-const int landlock_abi_version = 7;
+const int landlock_abi_version = 8;
/**
* 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 7b69002239d7..f4b1a275d8d9 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(7, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(8, 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/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
index b90f76ed0d9c..7f83512a328d 100644
--- a/tools/testing/selftests/landlock/scoped_test.c
+++ b/tools/testing/selftests/landlock/scoped_test.c
@@ -12,7 +12,7 @@
#include "common.h"
-#define ACCESS_LAST LANDLOCK_SCOPE_SIGNAL
+#define ACCESS_LAST LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
TEST(ruleset_with_unknown_scope)
{
--
2.52.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v2 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2026-01-29 21:27 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
` (4 subsequent siblings)
6 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
Extend the existing abstract UNIX socket scoping to pathname sockets as
well. Basically all of the logic is reused between the two types, just
that pathname sockets scoping are controlled by another bit, and has its
own audit request type (since the current one is named
"abstract_unix_socket").
Closes: https://github.com/landlock-lsm/linux/issues/51
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v2:
- Factor out common code in hook_unix_stream_connect and
hook_unix_may_send into check_socket_access(), and inline
is_abstract_socket().
security/landlock/audit.c | 4 ++
security/landlock/audit.h | 1 +
security/landlock/task.c | 109 ++++++++++++++++++++++----------------
3 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index e899995f1fd5..0626cc553ab0 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -75,6 +75,10 @@ get_blocker(const enum landlock_request_type type,
WARN_ON_ONCE(access_bit != -1);
return "scope.abstract_unix_socket";
+ case LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET:
+ WARN_ON_ONCE(access_bit != -1);
+ return "scope.pathname_unix_socket";
+
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
WARN_ON_ONCE(access_bit != -1);
return "scope.signal";
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 92428b7fc4d8..1c9ce8588102 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -21,6 +21,7 @@ enum landlock_request_type {
LANDLOCK_REQUEST_NET_ACCESS,
LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
LANDLOCK_REQUEST_SCOPE_SIGNAL,
+ LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET,
};
/*
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 833bc0cfe5c9..10dc356baf6f 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -232,35 +232,81 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client,
return false;
}
+/**
+ * sock_is_scoped - Check if socket connect or send should be restricted
+ * based on scope controls.
+ *
+ * @other: The server socket.
+ * @domain: The client domain.
+ * @scope: The relevant scope bit to check (i.e. pathname or abstract).
+ *
+ * Returns: True if connect should be restricted, false otherwise.
+ */
static bool sock_is_scoped(struct sock *const other,
- const struct landlock_ruleset *const domain)
+ const struct landlock_ruleset *const domain,
+ access_mask_t scope)
{
const struct landlock_ruleset *dom_other;
/* The credentials will not change. */
lockdep_assert_held(&unix_sk(other)->lock);
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
- return domain_is_scoped(domain, dom_other,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ return domain_is_scoped(domain, dom_other, scope);
}
-static bool is_abstract_socket(struct sock *const sock)
+/* Allow us to quickly test if the current domain scopes any form of socket */
+static const struct access_masks unix_scope = {
+ .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
+};
+
+/*
+ * UNIX sockets can have three types of addresses: pathname (a filesystem path),
+ * unnamed (not bound to an address), and abstract (sun_path[0] is '\0').
+ * Unnamed sockets include those created with socketpair() and unbound sockets.
+ * We do not restrict unnamed sockets since they have no address to identify.
+ */
+static int
+check_socket_access(struct sock *const other,
+ const struct landlock_cred_security *const subject,
+ const size_t handle_layer)
{
- struct unix_address *addr = unix_sk(sock)->addr;
+ const struct unix_address *addr = unix_sk(other)->addr;
+ access_mask_t scope;
+ enum landlock_request_type request_type;
+ /* Unnamed sockets are not restricted. */
if (!addr)
- return false;
+ return 0;
+ /*
+ * Abstract and pathname Unix sockets have separate scope and audit
+ * request type.
+ */
if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
- addr->name->sun_path[0] == '\0')
- return true;
+ addr->name->sun_path[0] == '\0') {
+ scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
+ request_type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET;
+ } else {
+ scope = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ request_type = LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET;
+ }
- return false;
-}
+ if (!sock_is_scoped(other, subject->domain, scope))
+ return 0;
-static const struct access_masks unix_scope = {
- .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
-};
+ landlock_log_denial(subject, &(struct landlock_request) {
+ .type = request_type,
+ .audit = {
+ .type = LSM_AUDIT_DATA_NET,
+ .u.net = &(struct lsm_network_audit) {
+ .sk = other,
+ },
+ },
+ .layer_plus_one = handle_layer + 1,
+ });
+ return -EPERM;
+}
static int hook_unix_stream_connect(struct sock *const sock,
struct sock *const other,
@@ -275,23 +321,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
if (!subject)
return 0;
- if (!is_abstract_socket(other))
- return 0;
-
- if (!sock_is_scoped(other, subject->domain))
- return 0;
-
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
- .audit = {
- .type = LSM_AUDIT_DATA_NET,
- .u.net = &(struct lsm_network_audit) {
- .sk = other,
- },
- },
- .layer_plus_one = handle_layer + 1,
- });
- return -EPERM;
+ return check_socket_access(other, subject, handle_layer);
}
static int hook_unix_may_send(struct socket *const sock,
@@ -302,6 +332,7 @@ static int hook_unix_may_send(struct socket *const sock,
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
+ /* Quick return for non-landlocked tasks. */
if (!subject)
return 0;
@@ -312,23 +343,7 @@ static int hook_unix_may_send(struct socket *const sock,
if (unix_peer(sock->sk) == other->sk)
return 0;
- if (!is_abstract_socket(other->sk))
- return 0;
-
- if (!sock_is_scoped(other->sk, subject->domain))
- return 0;
-
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
- .audit = {
- .type = LSM_AUDIT_DATA_NET,
- .u.net = &(struct lsm_network_audit) {
- .sk = other->sk,
- },
- },
- .layer_plus_one = handle_layer + 1,
- });
- return -EPERM;
+ return check_socket_access(other->sk, subject, handle_layer);
}
static const struct access_masks signal_scope = {
--
2.52.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2026-01-29 21:27 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 4/6] selftests/landlock: Support pathname socket path in set_unix_address Tingmao Wang
` (3 subsequent siblings)
6 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
I've decided to use "u" as the character to control this scope bit since
it stands for (normal) Unix sockets. Imo using "p" or "n" would make it less
clear / memorable. Open to suggestions.
Also, open to suggestion whether socket scoping (pathname and abstract)
should be enabled by default, if LL_SCOPED is not set. This would break
backward compatibility, but maybe we shouldn't guarentee backward
compatibility of this sandboxer in the first place, and almost all cases
of Landlock usage would want socket scoping.
samples/landlock/sandboxer.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e7af02f98208..2de14e1c787d 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -234,14 +234,16 @@ static bool check_ruleset_scope(const char *const env_var,
bool error = false;
bool abstract_scoping = false;
bool signal_scoping = false;
+ bool named_scoping = false;
/* Scoping is not supported by Landlock ABI */
if (!(ruleset_attr->scoped &
- (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL)))
+ (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET)))
goto out_unset;
env_type_scope = getenv(env_var);
- /* Scoping is not supported by the user */
+ /* Scoping is not requested by the user */
if (!env_type_scope || strcmp("", env_type_scope) == 0)
goto out_unset;
@@ -254,6 +256,9 @@ static bool check_ruleset_scope(const char *const env_var,
} else if (strcmp("s", ipc_scoping_name) == 0 &&
!signal_scoping) {
signal_scoping = true;
+ } else if (strcmp("u", ipc_scoping_name) == 0 &&
+ !named_scoping) {
+ named_scoping = true;
} else {
fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",
ipc_scoping_name);
@@ -270,6 +275,8 @@ static bool check_ruleset_scope(const char *const env_var,
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
if (!signal_scoping)
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL;
+ if (!named_scoping)
+ ruleset_attr->scoped &= ~LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
unsetenv(env_var);
return error;
@@ -299,7 +306,7 @@ static bool check_ruleset_scope(const char *const env_var,
/* clang-format on */
-#define LANDLOCK_ABI_LAST 7
+#define LANDLOCK_ABI_LAST 8
#define XSTR(s) #s
#define STR(s) XSTR(s)
@@ -325,6 +332,7 @@ static const char help[] =
"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
" - \"a\" to restrict opening abstract unix sockets\n"
" - \"s\" to restrict sending signals\n"
+ " - \"u\" to restrict opening pathname (non-abstract) unix sockets\n"
"\n"
"A sandboxer should not log denied access requests to avoid spamming logs, "
"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
@@ -334,7 +342,7 @@ static const char help[] =
ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
ENV_TCP_BIND_NAME "=\"9418\" "
ENV_TCP_CONNECT_NAME "=\"80:443\" "
- ENV_SCOPED_NAME "=\"a:s\" "
+ ENV_SCOPED_NAME "=\"a:s:u\" "
"%1$s bash -i\n"
"\n"
"This sandboxer can use Landlock features up to ABI version "
@@ -356,7 +364,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
- LANDLOCK_SCOPE_SIGNAL,
+ LANDLOCK_SCOPE_SIGNAL |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
};
int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
int set_restrict_flags = 0;
@@ -436,6 +445,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
/* Removes LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON for ABI < 7 */
supported_restrict_flags &=
~LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
+ __attribute__((fallthrough));
+ case 7:
+ /* Removes LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET for ABI < 8 */
+ ruleset_attr.scoped &= ~LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
--
2.52.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v2 4/6] selftests/landlock: Support pathname socket path in set_unix_address
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
` (2 preceding siblings ...)
2025-12-30 17:20 ` [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too Tingmao Wang
` (2 subsequent siblings)
6 siblings, 0 replies; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
To prepare for extending the socket tests to do non-abstract sockets too,
extend set_unix_address() to also be able to populate a non-abstract
socket path under TMP_DIR. Also use snprintf for good measure.
This also changes existing callers to pass true for the abstract argument.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
tools/testing/selftests/landlock/common.h | 33 +++++++++++++++----
tools/testing/selftests/landlock/net_test.c | 2 +-
.../landlock/scoped_abstract_unix_test.c | 30 ++++++++---------
.../selftests/landlock/scoped_signal_test.c | 2 +-
4 files changed, 44 insertions(+), 23 deletions(-)
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 90551650299c..c55c11434e27 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -241,13 +241,34 @@ struct service_fixture {
};
};
+#define PATHNAME_UNIX_SOCK_DIR TMP_DIR
+
+/**
+ * set_unix_address - Set up srv->unix_addr and srv->unix_addr_len.
+ * @srv: Service fixture containing the socket address to initialize
+ * @index: Index to include in socket names
+ * @abstract: If true, creates an abstract socket address (sun_path[0] ==
+ * '\0') with the given name. If false, creates a pathname socket
+ * address with the given path.
+ */
static void __maybe_unused set_unix_address(struct service_fixture *const srv,
- const unsigned short index)
+ const unsigned short index,
+ const bool abstract)
{
srv->unix_addr.sun_family = AF_UNIX;
- sprintf(srv->unix_addr.sun_path,
- "_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(),
- index);
- srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
- srv->unix_addr.sun_path[0] = '\0';
+ if (abstract) {
+ snprintf(srv->unix_addr.sun_path,
+ sizeof(srv->unix_addr.sun_path),
+ "_selftests-landlock-abstract-unix-tid%d-index%d",
+ sys_gettid(), index);
+ srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
+ srv->unix_addr.sun_path[0] = '\0';
+ } else {
+ snprintf(srv->unix_addr.sun_path,
+ sizeof(srv->unix_addr.sun_path),
+ PATHNAME_UNIX_SOCK_DIR
+ "/pathname-unix-tid%d-index%d.sock",
+ sys_gettid(), index);
+ srv->unix_addr_len = sizeof(srv->unix_addr);
+ }
}
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index b34b139b3f89..fd3fe51ce92f 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -69,7 +69,7 @@ static int set_service(struct service_fixture *const srv,
return 0;
case AF_UNIX:
- set_unix_address(srv, index);
+ set_unix_address(srv, index, true);
return 0;
}
return 1;
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 72f97648d4a7..4a790e2d387d 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -58,8 +58,8 @@ FIXTURE_SETUP(scoped_domains)
memset(&self->stream_address, 0, sizeof(self->stream_address));
memset(&self->dgram_address, 0, sizeof(self->dgram_address));
- set_unix_address(&self->stream_address, 0);
- set_unix_address(&self->dgram_address, 1);
+ set_unix_address(&self->stream_address, 0, true);
+ set_unix_address(&self->dgram_address, 1, true);
}
FIXTURE_TEARDOWN(scoped_domains)
@@ -280,7 +280,7 @@ FIXTURE_SETUP(scoped_audit)
disable_caps(_metadata);
memset(&self->dgram_address, 0, sizeof(self->dgram_address));
- set_unix_address(&self->dgram_address, 1);
+ set_unix_address(&self->dgram_address, 1, true);
set_cap(_metadata, CAP_AUDIT_CONTROL);
self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
@@ -392,16 +392,16 @@ FIXTURE_SETUP(scoped_vs_unscoped)
memset(&self->parent_stream_address, 0,
sizeof(self->parent_stream_address));
- set_unix_address(&self->parent_stream_address, 0);
+ set_unix_address(&self->parent_stream_address, 0, true);
memset(&self->parent_dgram_address, 0,
sizeof(self->parent_dgram_address));
- set_unix_address(&self->parent_dgram_address, 1);
+ set_unix_address(&self->parent_dgram_address, 1, true);
memset(&self->child_stream_address, 0,
sizeof(self->child_stream_address));
- set_unix_address(&self->child_stream_address, 2);
+ set_unix_address(&self->child_stream_address, 2, true);
memset(&self->child_dgram_address, 0,
sizeof(self->child_dgram_address));
- set_unix_address(&self->child_dgram_address, 3);
+ set_unix_address(&self->child_dgram_address, 3, true);
}
FIXTURE_TEARDOWN(scoped_vs_unscoped)
@@ -622,9 +622,9 @@ FIXTURE_SETUP(outside_socket)
drop_caps(_metadata);
memset(&self->transit_address, 0, sizeof(self->transit_address));
- set_unix_address(&self->transit_address, 0);
+ set_unix_address(&self->transit_address, 0, true);
memset(&self->address, 0, sizeof(self->address));
- set_unix_address(&self->address, 1);
+ set_unix_address(&self->address, 1, true);
}
FIXTURE_TEARDOWN(outside_socket)
@@ -802,9 +802,9 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
/* Abstract address. */
memset(&stream_abstract_addr, 0, sizeof(stream_abstract_addr));
- set_unix_address(&stream_abstract_addr, 0);
+ set_unix_address(&stream_abstract_addr, 0, true);
memset(&dgram_abstract_addr, 0, sizeof(dgram_abstract_addr));
- set_unix_address(&dgram_abstract_addr, 1);
+ set_unix_address(&dgram_abstract_addr, 1, true);
/* Unnamed address for datagram socket. */
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM, 0, unnamed_sockets));
@@ -990,9 +990,9 @@ TEST(datagram_sockets)
drop_caps(_metadata);
memset(&connected_addr, 0, sizeof(connected_addr));
- set_unix_address(&connected_addr, 0);
+ set_unix_address(&connected_addr, 0, true);
memset(&non_connected_addr, 0, sizeof(non_connected_addr));
- set_unix_address(&non_connected_addr, 1);
+ set_unix_address(&non_connected_addr, 1, true);
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
@@ -1090,9 +1090,9 @@ TEST(self_connect)
drop_caps(_metadata);
memset(&connected_addr, 0, sizeof(connected_addr));
- set_unix_address(&connected_addr, 0);
+ set_unix_address(&connected_addr, 0, true);
memset(&non_connected_addr, 0, sizeof(non_connected_addr));
- set_unix_address(&non_connected_addr, 1);
+ set_unix_address(&non_connected_addr, 1, true);
connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
non_connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index d8bf33417619..8d1e1dc89c43 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -463,7 +463,7 @@ TEST_F(fown, sigurg_socket)
pid_t child;
memset(&server_address, 0, sizeof(server_address));
- set_unix_address(&server_address, 0);
+ set_unix_address(&server_address, 0, true);
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
--
2.52.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too.
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
` (3 preceding siblings ...)
2025-12-30 17:20 ` [PATCH v2 4/6] selftests/landlock: Support pathname socket path in set_unix_address Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2026-01-29 21:28 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests Tingmao Wang
2025-12-30 23:16 ` [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Günther Noack
6 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
Since there is very little difference between abstract and pathname
sockets in terms of testing of the scoped access checks (the only
difference is in which scope bit control which form of socket), it makes
sense to reuse the existing test for both type of sockets. Therefore, we
rename scoped_abstract_unix_test.c to scoped_unix_test.c and extend the
scoped_domains test to test pathname (i.e. non-abstract) sockets too.
Since we can't change the variant data of scoped_domains (as it is defined
in the shared .h file), we do this by extracting the actual test code into
a function, and call it from different test cases.
Also extend scoped_audit (this time we can use variants) to test both
abstract and pathname sockets. For pathname sockets, audit_log_lsm_data
will produce path="..." (or hex if path contains control characters) with
absolute paths from the dentry, so we need to construct the escaped regex
for the real path like in fs_test.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
...bstract_unix_test.c => scoped_unix_test.c} | 256 ++++++++++++++----
1 file changed, 206 insertions(+), 50 deletions(-)
rename tools/testing/selftests/landlock/{scoped_abstract_unix_test.c => scoped_unix_test.c} (81%)
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_unix_test.c
similarity index 81%
rename from tools/testing/selftests/landlock/scoped_abstract_unix_test.c
rename to tools/testing/selftests/landlock/scoped_unix_test.c
index 4a790e2d387d..669418c97509 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_unix_test.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Landlock tests - Abstract UNIX socket
+ * Landlock tests - Scoped access checks for UNIX socket (abstract and
+ * pathname)
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
@@ -19,6 +20,7 @@
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <stdlib.h>
#include "audit.h"
#include "common.h"
@@ -47,7 +49,8 @@ static void create_fs_domain(struct __test_metadata *const _metadata)
FIXTURE(scoped_domains)
{
- struct service_fixture stream_address, dgram_address;
+ struct service_fixture stream_address_abstract, dgram_address_abstract,
+ stream_address_pathname, dgram_address_pathname;
};
#include "scoped_base_variants.h"
@@ -56,27 +59,62 @@ FIXTURE_SETUP(scoped_domains)
{
drop_caps(_metadata);
- memset(&self->stream_address, 0, sizeof(self->stream_address));
- memset(&self->dgram_address, 0, sizeof(self->dgram_address));
- set_unix_address(&self->stream_address, 0, true);
- set_unix_address(&self->dgram_address, 1, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ memset(&self->stream_address_abstract, 0,
+ sizeof(self->stream_address_abstract));
+ memset(&self->dgram_address_abstract, 0,
+ sizeof(self->dgram_address_abstract));
+ memset(&self->stream_address_pathname, 0,
+ sizeof(self->stream_address_pathname));
+ memset(&self->dgram_address_pathname, 0,
+ sizeof(self->dgram_address_pathname));
+ set_unix_address(&self->stream_address_abstract, 0, true);
+ set_unix_address(&self->dgram_address_abstract, 1, true);
+ set_unix_address(&self->stream_address_pathname, 0, false);
+ set_unix_address(&self->dgram_address_pathname, 1, false);
+}
+
+/* Remove @path if it exists */
+int remove_path(const char *path)
+{
+ if (unlink(path) == -1) {
+ if (errno != ENOENT)
+ return -errno;
+ }
+ return 0;
}
FIXTURE_TEARDOWN(scoped_domains)
{
+ EXPECT_EQ(0, remove_path(self->stream_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/*
* Test unix_stream_connect() and unix_may_send() for a child connecting to its
* parent, when they have scoped domain or no domain.
*/
-TEST_F(scoped_domains, connect_to_parent)
+static void test_connect_to_parent(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(scoped_domains) * self,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ const bool abstract)
{
pid_t child;
bool can_connect_to_parent;
int status;
int pipe_parent[2];
int stream_server, dgram_server;
+ const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *stream_address =
+ abstract ? &self->stream_address_abstract :
+ &self->stream_address_pathname;
+ const struct service_fixture *dgram_address =
+ abstract ? &self->dgram_address_abstract :
+ &self->dgram_address_pathname;
/*
* can_connect_to_parent is true if a child process can connect to its
@@ -87,8 +125,7 @@ TEST_F(scoped_domains, connect_to_parent)
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
if (variant->domain_both) {
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
if (!__test_passed(_metadata))
return;
}
@@ -102,8 +139,7 @@ TEST_F(scoped_domains, connect_to_parent)
EXPECT_EQ(0, close(pipe_parent[1]));
if (variant->domain_child)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_client);
@@ -113,8 +149,8 @@ TEST_F(scoped_domains, connect_to_parent)
/* Waits for the server. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
- err = connect(stream_client, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len);
+ err = connect(stream_client, &stream_address->unix_addr,
+ stream_address->unix_addr_len);
if (can_connect_to_parent) {
EXPECT_EQ(0, err);
} else {
@@ -123,8 +159,8 @@ TEST_F(scoped_domains, connect_to_parent)
}
EXPECT_EQ(0, close(stream_client));
- err = connect(dgram_client, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len);
+ err = connect(dgram_client, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len);
if (can_connect_to_parent) {
EXPECT_EQ(0, err);
} else {
@@ -137,17 +173,16 @@ TEST_F(scoped_domains, connect_to_parent)
}
EXPECT_EQ(0, close(pipe_parent[0]));
if (variant->domain_parent)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server);
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server);
- ASSERT_EQ(0, bind(stream_server, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_server, &stream_address->unix_addr,
+ stream_address->unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server, backlog));
/* Signals to child that the parent is listening. */
@@ -166,7 +201,11 @@ TEST_F(scoped_domains, connect_to_parent)
* Test unix_stream_connect() and unix_may_send() for a parent connecting to
* its child, when they have scoped domain or no domain.
*/
-TEST_F(scoped_domains, connect_to_child)
+static void test_connect_to_child(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(scoped_domains) * self,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ const bool abstract)
{
pid_t child;
bool can_connect_to_child;
@@ -174,6 +213,14 @@ TEST_F(scoped_domains, connect_to_child)
int pipe_child[2], pipe_parent[2];
char buf;
int stream_client, dgram_client;
+ const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *stream_address =
+ abstract ? &self->stream_address_abstract :
+ &self->stream_address_pathname;
+ const struct service_fixture *dgram_address =
+ abstract ? &self->dgram_address_abstract :
+ &self->dgram_address_pathname;
/*
* can_connect_to_child is true if a parent process can connect to its
@@ -185,8 +232,7 @@ TEST_F(scoped_domains, connect_to_child)
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
if (variant->domain_both) {
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
if (!__test_passed(_metadata))
return;
}
@@ -199,8 +245,7 @@ TEST_F(scoped_domains, connect_to_child)
EXPECT_EQ(0, close(pipe_parent[1]));
EXPECT_EQ(0, close(pipe_child[0]));
if (variant->domain_child)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/* Waits for the parent to be in a domain, if any. */
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
@@ -209,11 +254,10 @@ TEST_F(scoped_domains, connect_to_child)
ASSERT_LE(0, stream_server);
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server);
- ASSERT_EQ(0,
- bind(stream_server, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_server, &stream_address->unix_addr,
+ stream_address->unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server, backlog));
/* Signals to the parent that child is listening. */
@@ -230,8 +274,7 @@ TEST_F(scoped_domains, connect_to_child)
EXPECT_EQ(0, close(pipe_parent[0]));
if (variant->domain_parent)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/* Signals that the parent is in a domain, if any. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -243,11 +286,11 @@ TEST_F(scoped_domains, connect_to_child)
/* Waits for the child to listen */
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
- err_stream = connect(stream_client, &self->stream_address.unix_addr,
- self->stream_address.unix_addr_len);
+ err_stream = connect(stream_client, &stream_address->unix_addr,
+ stream_address->unix_addr_len);
errno_stream = errno;
- err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len);
+ err_dgram = connect(dgram_client, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len);
errno_dgram = errno;
if (can_connect_to_child) {
EXPECT_EQ(0, err_stream);
@@ -268,19 +311,79 @@ TEST_F(scoped_domains, connect_to_child)
_metadata->exit_code = KSFT_FAIL;
}
+/*
+ * Test unix_stream_connect() and unix_may_send() for a child connecting to its
+ * parent, when they have scoped domain or no domain.
+ */
+TEST_F(scoped_domains, abstract_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, self, variant, true);
+}
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for a parent connecting to
+ * its child, when they have scoped domain or no domain.
+ */
+TEST_F(scoped_domains, abstract_connect_to_child)
+{
+ test_connect_to_child(_metadata, self, variant, true);
+}
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for a child connecting to its
+ * parent with pathname sockets.
+ */
+TEST_F(scoped_domains, pathname_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, self, variant, false);
+}
+
+/*
+ * Test unix_stream_connect() and unix_may_send() for a parent connecting to
+ * its child with pathname sockets.
+ */
+TEST_F(scoped_domains, pathname_connect_to_child)
+{
+ test_connect_to_child(_metadata, self, variant, false);
+}
+
FIXTURE(scoped_audit)
{
- struct service_fixture dgram_address;
+ struct service_fixture dgram_address_abstract, dgram_address_pathname;
struct audit_filter audit_filter;
int audit_fd;
};
+FIXTURE_VARIANT(scoped_audit)
+{
+ const bool abstract_socket;
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, abstract_socket)
+{
+ // clang-format on
+ .abstract_socket = true,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, pathname_socket)
+{
+ // clang-format on
+ .abstract_socket = false,
+};
+
FIXTURE_SETUP(scoped_audit)
{
disable_caps(_metadata);
- memset(&self->dgram_address, 0, sizeof(self->dgram_address));
- set_unix_address(&self->dgram_address, 1, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+ memset(&self->dgram_address_abstract, 0,
+ sizeof(self->dgram_address_abstract));
+ memset(&self->dgram_address_pathname, 0,
+ sizeof(self->dgram_address_pathname));
+ set_unix_address(&self->dgram_address_abstract, 1, true);
+ set_unix_address(&self->dgram_address_pathname, 1, false);
set_cap(_metadata, CAP_AUDIT_CONTROL);
self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
@@ -291,6 +394,8 @@ FIXTURE_SETUP(scoped_audit)
FIXTURE_TEARDOWN_PARENT(scoped_audit)
{
EXPECT_EQ(0, audit_cleanup(-1, NULL));
+ EXPECT_EQ(0, remove_path(self->dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/* python -c 'print(b"\0selftests-landlock-abstract-unix-".hex().upper())' */
@@ -308,6 +413,12 @@ TEST_F(scoped_audit, connect_to_child)
char buf;
int dgram_client;
struct audit_records records;
+ struct service_fixture *const dgram_address =
+ variant->abstract_socket ? &self->dgram_address_abstract :
+ &self->dgram_address_pathname;
+ size_t log_match_remaining = 500;
+ char log_match[log_match_remaining];
+ char *log_match_cursor = log_match;
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
@@ -330,8 +441,8 @@ TEST_F(scoped_audit, connect_to_child)
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server);
- ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len));
/* Signals to the parent that child is listening. */
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
@@ -345,7 +456,9 @@ TEST_F(scoped_audit, connect_to_child)
EXPECT_EQ(0, close(pipe_child[1]));
EXPECT_EQ(0, close(pipe_parent[0]));
- create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata,
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
/* Signals that the parent is in a domain, if any. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -355,19 +468,62 @@ TEST_F(scoped_audit, connect_to_child)
/* Waits for the child to listen */
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
- err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
- self->dgram_address.unix_addr_len);
+ err_dgram = connect(dgram_client, &dgram_address->unix_addr,
+ dgram_address->unix_addr_len);
EXPECT_EQ(-1, err_dgram);
EXPECT_EQ(EPERM, errno);
- EXPECT_EQ(
- 0,
- audit_match_record(
- self->audit_fd, AUDIT_LANDLOCK_ACCESS,
+ if (variant->abstract_socket) {
+ log_match_cursor = stpncpy(
+ log_match,
REGEX_LANDLOCK_PREFIX
" blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
"[0-9A-F]\\+$",
- NULL));
+ log_match_remaining);
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ } else {
+ /*
+ * It is assumed that absolute_path does not contain control
+ * characters nor spaces, see audit_string_contains_control().
+ */
+ char *absolute_path =
+ realpath(dgram_address->unix_addr.sun_path, NULL);
+
+ EXPECT_NE(NULL, absolute_path)
+ {
+ TH_LOG("realpath() failed: %s", strerror(errno));
+ return;
+ }
+
+ log_match_cursor =
+ stpncpy(log_match,
+ REGEX_LANDLOCK_PREFIX
+ " blockers=scope\\.pathname_unix_socket path=\"",
+ log_match_remaining);
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ log_match_cursor = regex_escape(absolute_path, log_match_cursor,
+ log_match_remaining);
+ free(absolute_path);
+ if (log_match_cursor < 0) {
+ TH_LOG("regex_escape() failed (buffer too small)");
+ return;
+ }
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ log_match_cursor =
+ stpncpy(log_match_cursor, "\"$", log_match_remaining);
+ log_match_remaining =
+ sizeof(log_match) - (log_match_cursor - log_match);
+ ASSERT_NE(0, log_match_remaining);
+ }
+
+ EXPECT_EQ(0, audit_match_record(self->audit_fd, AUDIT_LANDLOCK_ACCESS,
+ log_match, NULL));
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
EXPECT_EQ(0, close(dgram_client));
--
2.52.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
` (4 preceding siblings ...)
2025-12-30 17:20 ` [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too Tingmao Wang
@ 2025-12-30 17:20 ` Tingmao Wang
2026-01-29 21:28 ` Mickaël Salaün
2025-12-30 23:16 ` [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Günther Noack
6 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2025-12-30 17:20 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
While this produces a lot of change, it does allow us to "simultaneously"
test both abstract and pathname UNIX sockets with reletively little code
duplication, since they are really similar.
Tests touched: scoped_vs_unscoped, outside_socket,
various_address_sockets, datagram_sockets, self_connect.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
.../selftests/landlock/scoped_unix_test.c | 599 ++++++++++++------
1 file changed, 395 insertions(+), 204 deletions(-)
diff --git a/tools/testing/selftests/landlock/scoped_unix_test.c b/tools/testing/selftests/landlock/scoped_unix_test.c
index 669418c97509..6d1541f77dbe 100644
--- a/tools/testing/selftests/landlock/scoped_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_unix_test.c
@@ -536,8 +536,12 @@ TEST_F(scoped_audit, connect_to_child)
FIXTURE(scoped_vs_unscoped)
{
- struct service_fixture parent_stream_address, parent_dgram_address,
- child_stream_address, child_dgram_address;
+ struct service_fixture parent_stream_address_abstract,
+ parent_dgram_address_abstract, child_stream_address_abstract,
+ child_dgram_address_abstract;
+ struct service_fixture parent_stream_address_pathname,
+ parent_dgram_address_pathname, child_stream_address_pathname,
+ child_dgram_address_pathname;
};
#include "scoped_multiple_domain_variants.h"
@@ -546,35 +550,75 @@ FIXTURE_SETUP(scoped_vs_unscoped)
{
drop_caps(_metadata);
- memset(&self->parent_stream_address, 0,
- sizeof(self->parent_stream_address));
- set_unix_address(&self->parent_stream_address, 0, true);
- memset(&self->parent_dgram_address, 0,
- sizeof(self->parent_dgram_address));
- set_unix_address(&self->parent_dgram_address, 1, true);
- memset(&self->child_stream_address, 0,
- sizeof(self->child_stream_address));
- set_unix_address(&self->child_stream_address, 2, true);
- memset(&self->child_dgram_address, 0,
- sizeof(self->child_dgram_address));
- set_unix_address(&self->child_dgram_address, 3, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ /* Abstract addresses. */
+ memset(&self->parent_stream_address_abstract, 0,
+ sizeof(self->parent_stream_address_abstract));
+ set_unix_address(&self->parent_stream_address_abstract, 0, true);
+ memset(&self->parent_dgram_address_abstract, 0,
+ sizeof(self->parent_dgram_address_abstract));
+ set_unix_address(&self->parent_dgram_address_abstract, 1, true);
+ memset(&self->child_stream_address_abstract, 0,
+ sizeof(self->child_stream_address_abstract));
+ set_unix_address(&self->child_stream_address_abstract, 2, true);
+ memset(&self->child_dgram_address_abstract, 0,
+ sizeof(self->child_dgram_address_abstract));
+ set_unix_address(&self->child_dgram_address_abstract, 3, true);
+
+ /* Pathname addresses. */
+ memset(&self->parent_stream_address_pathname, 0,
+ sizeof(self->parent_stream_address_pathname));
+ set_unix_address(&self->parent_stream_address_pathname, 4, false);
+ memset(&self->parent_dgram_address_pathname, 0,
+ sizeof(self->parent_dgram_address_pathname));
+ set_unix_address(&self->parent_dgram_address_pathname, 5, false);
+ memset(&self->child_stream_address_pathname, 0,
+ sizeof(self->child_stream_address_pathname));
+ set_unix_address(&self->child_stream_address_pathname, 6, false);
+ memset(&self->child_dgram_address_pathname, 0,
+ sizeof(self->child_dgram_address_pathname));
+ set_unix_address(&self->child_dgram_address_pathname, 7, false);
}
FIXTURE_TEARDOWN(scoped_vs_unscoped)
{
+ EXPECT_EQ(0, remove_path(self->parent_stream_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->parent_dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->child_stream_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->child_dgram_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/*
* Test unix_stream_connect and unix_may_send for parent, child and
* grand child processes when they can have scoped or non-scoped domains.
*/
-TEST_F(scoped_vs_unscoped, unix_scoping)
+static void test_scoped_vs_unscoped(
+ struct __test_metadata *const _metadata,
+ FIXTURE_DATA(scoped_vs_unscoped) * self,
+ const FIXTURE_VARIANT(scoped_vs_unscoped) * variant,
+ const bool abstract)
{
pid_t child;
int status;
bool can_connect_to_parent, can_connect_to_child;
int pipe_parent[2];
int stream_server_parent, dgram_server_parent;
+ const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *parent_stream_address =
+ abstract ? &self->parent_stream_address_abstract :
+ &self->parent_stream_address_pathname;
+ const struct service_fixture *parent_dgram_address =
+ abstract ? &self->parent_dgram_address_abstract :
+ &self->parent_dgram_address_pathname;
+ const struct service_fixture *child_stream_address =
+ abstract ? &self->child_stream_address_abstract :
+ &self->child_stream_address_pathname;
+ const struct service_fixture *child_dgram_address =
+ abstract ? &self->child_dgram_address_abstract :
+ &self->child_dgram_address_pathname;
can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX);
can_connect_to_parent = (can_connect_to_child &&
@@ -585,8 +629,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_all == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_all == SCOPE_SANDBOX)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
child = fork();
ASSERT_LE(0, child);
@@ -600,8 +643,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_children == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_children == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
grand_child = fork();
ASSERT_LE(0, grand_child);
@@ -616,9 +658,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_grand_child == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_grand_child == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_client);
@@ -626,15 +666,13 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
ASSERT_LE(0, dgram_client);
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
- stream_err = connect(
- stream_client,
- &self->child_stream_address.unix_addr,
- self->child_stream_address.unix_addr_len);
+ stream_err = connect(stream_client,
+ &child_stream_address->unix_addr,
+ child_stream_address->unix_addr_len);
stream_errno = errno;
- dgram_err = connect(
- dgram_client,
- &self->child_dgram_address.unix_addr,
- self->child_dgram_address.unix_addr_len);
+ dgram_err = connect(dgram_client,
+ &child_dgram_address->unix_addr,
+ child_dgram_address->unix_addr_len);
dgram_errno = errno;
if (can_connect_to_child) {
EXPECT_EQ(0, stream_err);
@@ -653,14 +691,12 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
stream_err = connect(
- stream_client,
- &self->parent_stream_address.unix_addr,
- self->parent_stream_address.unix_addr_len);
+ stream_client, &parent_stream_address->unix_addr,
+ parent_stream_address->unix_addr_len);
stream_errno = errno;
dgram_err = connect(
- dgram_client,
- &self->parent_dgram_address.unix_addr,
- self->parent_dgram_address.unix_addr_len);
+ dgram_client, &parent_dgram_address->unix_addr,
+ parent_dgram_address->unix_addr_len);
dgram_errno = errno;
if (can_connect_to_parent) {
EXPECT_EQ(0, stream_err);
@@ -681,8 +717,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_child == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_child == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_server_child = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server_child);
@@ -690,11 +725,11 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
ASSERT_LE(0, dgram_server_child);
ASSERT_EQ(0, bind(stream_server_child,
- &self->child_stream_address.unix_addr,
- self->child_stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server_child,
- &self->child_dgram_address.unix_addr,
- self->child_dgram_address.unix_addr_len));
+ &child_stream_address->unix_addr,
+ child_stream_address->unix_addr_len));
+ ASSERT_EQ(0,
+ bind(dgram_server_child, &child_dgram_address->unix_addr,
+ child_dgram_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server_child, backlog));
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
@@ -708,19 +743,16 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
if (variant->domain_parent == OTHER_SANDBOX)
create_fs_domain(_metadata);
else if (variant->domain_parent == SCOPE_SANDBOX)
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
stream_server_parent = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server_parent);
dgram_server_parent = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_server_parent);
- ASSERT_EQ(0, bind(stream_server_parent,
- &self->parent_stream_address.unix_addr,
- self->parent_stream_address.unix_addr_len));
- ASSERT_EQ(0, bind(dgram_server_parent,
- &self->parent_dgram_address.unix_addr,
- self->parent_dgram_address.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_server_parent, &parent_stream_address->unix_addr,
+ parent_stream_address->unix_addr_len));
+ ASSERT_EQ(0, bind(dgram_server_parent, &parent_dgram_address->unix_addr,
+ parent_dgram_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server_parent, backlog));
@@ -734,57 +766,119 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
_metadata->exit_code = KSFT_FAIL;
}
+TEST_F(scoped_vs_unscoped, unix_scoping_abstract)
+{
+ test_scoped_vs_unscoped(_metadata, self, variant, true);
+}
+
+TEST_F(scoped_vs_unscoped, unix_scoping_pathname)
+{
+ test_scoped_vs_unscoped(_metadata, self, variant, false);
+}
+
FIXTURE(outside_socket)
{
- struct service_fixture address, transit_address;
+ struct service_fixture address_abstract, transit_address_abstract;
+ struct service_fixture address_pathname, transit_address_pathname;
};
FIXTURE_VARIANT(outside_socket)
{
const bool child_socket;
const int type;
+ const bool abstract;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, abstract_allow_dgram_child) {
+ /* clang-format on */
+ .child_socket = true,
+ .type = SOCK_DGRAM,
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, abstract_deny_dgram_server) {
+ /* clang-format on */
+ .child_socket = false,
+ .type = SOCK_DGRAM,
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, abstract_allow_stream_child) {
+ /* clang-format on */
+ .child_socket = true,
+ .type = SOCK_STREAM,
+ .abstract = true,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_child) {
+FIXTURE_VARIANT_ADD(outside_socket, abstract_deny_stream_server) {
+ /* clang-format on */
+ .child_socket = false,
+ .type = SOCK_STREAM,
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(outside_socket, pathname_allow_dgram_child) {
/* clang-format on */
.child_socket = true,
.type = SOCK_DGRAM,
+ .abstract = false,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server) {
+FIXTURE_VARIANT_ADD(outside_socket, pathname_deny_dgram_server) {
/* clang-format on */
.child_socket = false,
.type = SOCK_DGRAM,
+ .abstract = false,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, allow_stream_child) {
+FIXTURE_VARIANT_ADD(outside_socket, pathname_allow_stream_child) {
/* clang-format on */
.child_socket = true,
.type = SOCK_STREAM,
+ .abstract = false,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server) {
+FIXTURE_VARIANT_ADD(outside_socket, pathname_deny_stream_server) {
/* clang-format on */
.child_socket = false,
.type = SOCK_STREAM,
+ .abstract = false,
};
FIXTURE_SETUP(outside_socket)
{
drop_caps(_metadata);
- memset(&self->transit_address, 0, sizeof(self->transit_address));
- set_unix_address(&self->transit_address, 0, true);
- memset(&self->address, 0, sizeof(self->address));
- set_unix_address(&self->address, 1, true);
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ /* Abstract addresses. */
+ memset(&self->transit_address_abstract, 0,
+ sizeof(self->transit_address_abstract));
+ set_unix_address(&self->transit_address_abstract, 0, true);
+ memset(&self->address_abstract, 0, sizeof(self->address_abstract));
+ set_unix_address(&self->address_abstract, 1, true);
+
+ /* Pathname addresses. */
+ memset(&self->transit_address_pathname, 0,
+ sizeof(self->transit_address_pathname));
+ set_unix_address(&self->transit_address_pathname, 2, false);
+ memset(&self->address_pathname, 0, sizeof(self->address_pathname));
+ set_unix_address(&self->address_pathname, 3, false);
}
FIXTURE_TEARDOWN(outside_socket)
{
+ EXPECT_EQ(0, remove_path(self->transit_address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->address_pathname.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
/*
@@ -798,6 +892,15 @@ TEST_F(outside_socket, socket_with_different_domain)
int pipe_child[2], pipe_parent[2];
char buf_parent;
int server_socket;
+ const __u16 scope = variant->abstract ?
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
+ const struct service_fixture *transit_address =
+ variant->abstract ? &self->transit_address_abstract :
+ &self->transit_address_pathname;
+ const struct service_fixture *address =
+ variant->abstract ? &self->address_abstract :
+ &self->address_pathname;
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
@@ -812,8 +915,7 @@ TEST_F(outside_socket, socket_with_different_domain)
EXPECT_EQ(0, close(pipe_child[0]));
/* Client always has a domain. */
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
if (variant->child_socket) {
int data_socket, passed_socket, stream_server;
@@ -823,8 +925,8 @@ TEST_F(outside_socket, socket_with_different_domain)
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_server);
ASSERT_EQ(0, bind(stream_server,
- &self->transit_address.unix_addr,
- self->transit_address.unix_addr_len));
+ &transit_address->unix_addr,
+ transit_address->unix_addr_len));
ASSERT_EQ(0, listen(stream_server, backlog));
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
data_socket = accept(stream_server, NULL, NULL);
@@ -839,8 +941,8 @@ TEST_F(outside_socket, socket_with_different_domain)
/* Waits for parent signal for connection. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
- err = connect(client_socket, &self->address.unix_addr,
- self->address.unix_addr_len);
+ err = connect(client_socket, &address->unix_addr,
+ address->unix_addr_len);
if (variant->child_socket) {
EXPECT_EQ(0, err);
} else {
@@ -859,9 +961,8 @@ TEST_F(outside_socket, socket_with_different_domain)
ASSERT_LE(0, client_child);
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
- ASSERT_EQ(0, connect(client_child,
- &self->transit_address.unix_addr,
- self->transit_address.unix_addr_len));
+ ASSERT_EQ(0, connect(client_child, &transit_address->unix_addr,
+ transit_address->unix_addr_len));
server_socket = recv_fd(client_child);
EXPECT_EQ(0, close(client_child));
} else {
@@ -870,10 +971,10 @@ TEST_F(outside_socket, socket_with_different_domain)
ASSERT_LE(0, server_socket);
/* Server always has a domain. */
- create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
- ASSERT_EQ(0, bind(server_socket, &self->address.unix_addr,
- self->address.unix_addr_len));
+ ASSERT_EQ(0,
+ bind(server_socket, &address->unix_addr, address->unix_addr_len));
if (variant->type == SOCK_STREAM)
ASSERT_EQ(0, listen(server_socket, backlog));
@@ -888,52 +989,85 @@ TEST_F(outside_socket, socket_with_different_domain)
_metadata->exit_code = KSFT_FAIL;
}
-static const char stream_path[] = TMP_DIR "/stream.sock";
-static const char dgram_path[] = TMP_DIR "/dgram.sock";
-
/* clang-format off */
-FIXTURE(various_address_sockets) {};
+FIXTURE(various_address_sockets) {
+ struct service_fixture stream_pathname_addr, dgram_pathname_addr;
+ struct service_fixture stream_abstract_addr, dgram_abstract_addr;
+};
/* clang-format on */
-FIXTURE_VARIANT(various_address_sockets)
-{
- const int domain;
+/*
+ * Test all 4 combinations of abstract and pathname socket scope bits,
+ * plus a case with no Landlock domain at all.
+ */
+/* clang-format off */
+FIXTURE_VARIANT(various_address_sockets) {
+ /* clang-format on */
+ const __u16 scope_bits;
+ const bool no_sandbox;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_abstract) {
+ /* clang-format on */
+ .scope_bits = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_pathname) {
+ /* clang-format on */
+ .scope_bits = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_scoped_domain) {
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_both) {
/* clang-format on */
- .domain = SCOPE_SANDBOX,
+ .scope_bits = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_other_domain) {
+FIXTURE_VARIANT_ADD(various_address_sockets, scope_none) {
/* clang-format on */
- .domain = OTHER_SANDBOX,
+ .scope_bits = 0,
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_no_domain) {
+FIXTURE_VARIANT_ADD(various_address_sockets, no_domain) {
/* clang-format on */
- .domain = NO_SANDBOX,
+ .no_sandbox = true,
};
FIXTURE_SETUP(various_address_sockets)
{
drop_caps(_metadata);
- umask(0077);
- ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ memset(&self->stream_pathname_addr, 0, sizeof(self->stream_pathname_addr));
+ set_unix_address(&self->stream_pathname_addr, 0, false);
+ memset(&self->dgram_pathname_addr, 0, sizeof(self->dgram_pathname_addr));
+ set_unix_address(&self->dgram_pathname_addr, 1, false);
+
+ memset(&self->stream_abstract_addr, 0, sizeof(self->stream_abstract_addr));
+ set_unix_address(&self->stream_abstract_addr, 2, true);
+ memset(&self->dgram_abstract_addr, 0, sizeof(self->dgram_abstract_addr));
+ set_unix_address(&self->dgram_abstract_addr, 3, true);
}
FIXTURE_TEARDOWN(various_address_sockets)
{
- EXPECT_EQ(0, unlink(stream_path));
- EXPECT_EQ(0, unlink(dgram_path));
- EXPECT_EQ(0, rmdir(TMP_DIR));
+ EXPECT_EQ(0, remove_path(self->stream_pathname_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->dgram_pathname_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
}
-TEST_F(various_address_sockets, scoped_pathname_sockets)
+/*
+ * Test interaction of various scope flags (controlled by variant->domain)
+ * with pathname and abstract sockets when connecting from a sandboxed
+ * child.
+ */
+TEST_F(various_address_sockets, scoped_sockets)
{
pid_t child;
int status;
@@ -942,25 +1076,10 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
int unnamed_sockets[2];
int stream_pathname_socket, dgram_pathname_socket,
stream_abstract_socket, dgram_abstract_socket, data_socket;
- struct service_fixture stream_abstract_addr, dgram_abstract_addr;
- struct sockaddr_un stream_pathname_addr = {
- .sun_family = AF_UNIX,
- };
- struct sockaddr_un dgram_pathname_addr = {
- .sun_family = AF_UNIX,
- };
-
- /* Pathname address. */
- snprintf(stream_pathname_addr.sun_path,
- sizeof(stream_pathname_addr.sun_path), "%s", stream_path);
- snprintf(dgram_pathname_addr.sun_path,
- sizeof(dgram_pathname_addr.sun_path), "%s", dgram_path);
-
- /* Abstract address. */
- memset(&stream_abstract_addr, 0, sizeof(stream_abstract_addr));
- set_unix_address(&stream_abstract_addr, 0, true);
- memset(&dgram_abstract_addr, 0, sizeof(dgram_abstract_addr));
- set_unix_address(&dgram_abstract_addr, 1, true);
+ bool pathname_restricted =
+ (variant->scope_bits & LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
+ bool abstract_restricted =
+ (variant->scope_bits & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
/* Unnamed address for datagram socket. */
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM, 0, unnamed_sockets));
@@ -975,82 +1094,103 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
EXPECT_EQ(0, close(pipe_parent[1]));
EXPECT_EQ(0, close(unnamed_sockets[1]));
- if (variant->domain == SCOPE_SANDBOX)
- create_scoped_domain(
- _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
- else if (variant->domain == OTHER_SANDBOX)
+ /* Create domain based on variant. */
+ if (variant->scope_bits)
+ create_scoped_domain(_metadata, variant->scope_bits);
+ else if (!variant->no_sandbox)
create_fs_domain(_metadata);
/* Waits for parent to listen. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
EXPECT_EQ(0, close(pipe_parent[0]));
- /* Checks that we can send data through a datagram socket. */
+ /* Checks that we can send data through a unnamed socket. */
ASSERT_EQ(1, write(unnamed_sockets[0], "a", 1));
EXPECT_EQ(0, close(unnamed_sockets[0]));
/* Connects with pathname sockets. */
stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_pathname_socket);
- ASSERT_EQ(0,
- connect(stream_pathname_socket, &stream_pathname_addr,
- sizeof(stream_pathname_addr)));
- ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
+ err = connect(stream_pathname_socket,
+ &self->stream_pathname_addr.unix_addr,
+ self->stream_pathname_addr.unix_addr_len);
+ if (pathname_restricted) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(0, err);
+ ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
+ }
EXPECT_EQ(0, close(stream_pathname_socket));
- /* Sends without connection. */
+ /* Sends without connection (pathname). */
dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_pathname_socket);
err = sendto(dgram_pathname_socket, "c", 1, 0,
- &dgram_pathname_addr, sizeof(dgram_pathname_addr));
- EXPECT_EQ(1, err);
+ &self->dgram_pathname_addr.unix_addr,
+ self->dgram_pathname_addr.unix_addr_len);
+ if (pathname_restricted) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(1, err);
+ }
+
+ /* Sends with connection (pathname). */
+ err = connect(dgram_pathname_socket,
+ &self->dgram_pathname_addr.unix_addr,
+ self->dgram_pathname_addr.unix_addr_len);
+ if (pathname_restricted) {
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EPERM, errno);
+ } else {
+ EXPECT_EQ(0, err);
+ ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
+ }
- /* Sends with connection. */
- ASSERT_EQ(0,
- connect(dgram_pathname_socket, &dgram_pathname_addr,
- sizeof(dgram_pathname_addr)));
- ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
EXPECT_EQ(0, close(dgram_pathname_socket));
/* Connects with abstract sockets. */
stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_abstract_socket);
err = connect(stream_abstract_socket,
- &stream_abstract_addr.unix_addr,
- stream_abstract_addr.unix_addr_len);
- if (variant->domain == SCOPE_SANDBOX) {
+ &self->stream_abstract_addr.unix_addr,
+ self->stream_abstract_addr.unix_addr_len);
+ if (abstract_restricted) {
EXPECT_EQ(-1, err);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(0, err);
ASSERT_EQ(1, write(stream_abstract_socket, "e", 1));
}
+
EXPECT_EQ(0, close(stream_abstract_socket));
- /* Sends without connection. */
+ /* Sends without connection (abstract). */
dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_abstract_socket);
err = sendto(dgram_abstract_socket, "f", 1, 0,
- &dgram_abstract_addr.unix_addr,
- dgram_abstract_addr.unix_addr_len);
- if (variant->domain == SCOPE_SANDBOX) {
+ &self->dgram_abstract_addr.unix_addr,
+ self->dgram_abstract_addr.unix_addr_len);
+ if (abstract_restricted) {
EXPECT_EQ(-1, err);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(1, err);
}
- /* Sends with connection. */
+ /* Sends with connection (abstract). */
err = connect(dgram_abstract_socket,
- &dgram_abstract_addr.unix_addr,
- dgram_abstract_addr.unix_addr_len);
- if (variant->domain == SCOPE_SANDBOX) {
+ &self->dgram_abstract_addr.unix_addr,
+ self->dgram_abstract_addr.unix_addr_len);
+ if (abstract_restricted) {
EXPECT_EQ(-1, err);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(0, err);
ASSERT_EQ(1, write(dgram_abstract_socket, "g", 1));
}
+
EXPECT_EQ(0, close(dgram_abstract_socket));
_exit(_metadata->exit_code);
@@ -1062,27 +1202,30 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
/* Sets up pathname servers. */
stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_pathname_socket);
- ASSERT_EQ(0, bind(stream_pathname_socket, &stream_pathname_addr,
- sizeof(stream_pathname_addr)));
+ ASSERT_EQ(0, bind(stream_pathname_socket,
+ &self->stream_pathname_addr.unix_addr,
+ self->stream_pathname_addr.unix_addr_len));
ASSERT_EQ(0, listen(stream_pathname_socket, backlog));
dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_pathname_socket);
- ASSERT_EQ(0, bind(dgram_pathname_socket, &dgram_pathname_addr,
- sizeof(dgram_pathname_addr)));
+ ASSERT_EQ(0, bind(dgram_pathname_socket,
+ &self->dgram_pathname_addr.unix_addr,
+ self->dgram_pathname_addr.unix_addr_len));
/* Sets up abstract servers. */
stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, stream_abstract_socket);
- ASSERT_EQ(0,
- bind(stream_abstract_socket, &stream_abstract_addr.unix_addr,
- stream_abstract_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(stream_abstract_socket,
+ &self->stream_abstract_addr.unix_addr,
+ self->stream_abstract_addr.unix_addr_len));
+ ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, dgram_abstract_socket);
- ASSERT_EQ(0, bind(dgram_abstract_socket, &dgram_abstract_addr.unix_addr,
- dgram_abstract_addr.unix_addr_len));
- ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
+ ASSERT_EQ(0, bind(dgram_abstract_socket,
+ &self->dgram_abstract_addr.unix_addr,
+ self->dgram_abstract_addr.unix_addr_len));
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
EXPECT_EQ(0, close(pipe_parent[1]));
@@ -1092,24 +1235,31 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
ASSERT_EQ('a', buf_parent);
EXPECT_LE(0, close(unnamed_sockets[1]));
- /* Reads from pathname sockets. */
- data_socket = accept(stream_pathname_socket, NULL, NULL);
- ASSERT_LE(0, data_socket);
- ASSERT_EQ(1, read(data_socket, &buf_parent, sizeof(buf_parent)));
- ASSERT_EQ('b', buf_parent);
- EXPECT_EQ(0, close(data_socket));
- EXPECT_EQ(0, close(stream_pathname_socket));
+ if (!pathname_restricted) {
+ /*
+ * Reads from pathname sockets if we expect child to be able to
+ * send.
+ */
+ data_socket = accept(stream_pathname_socket, NULL, NULL);
+ ASSERT_LE(0, data_socket);
+ ASSERT_EQ(1,
+ read(data_socket, &buf_parent, sizeof(buf_parent)));
+ ASSERT_EQ('b', buf_parent);
+ EXPECT_EQ(0, close(data_socket));
- ASSERT_EQ(1,
- read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
- ASSERT_EQ('c', buf_parent);
- ASSERT_EQ(1,
- read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
- ASSERT_EQ('d', buf_parent);
- EXPECT_EQ(0, close(dgram_pathname_socket));
+ ASSERT_EQ(1, read(dgram_pathname_socket, &buf_parent,
+ sizeof(buf_parent)));
+ ASSERT_EQ('c', buf_parent);
+ ASSERT_EQ(1, read(dgram_pathname_socket, &buf_parent,
+ sizeof(buf_parent)));
+ ASSERT_EQ('d', buf_parent);
+ }
- if (variant->domain != SCOPE_SANDBOX) {
- /* Reads from abstract sockets if allowed to send. */
+ if (!abstract_restricted) {
+ /*
+ * Reads from abstract sockets if we expect child to be able to
+ * send.
+ */
data_socket = accept(stream_abstract_socket, NULL, NULL);
ASSERT_LE(0, data_socket);
ASSERT_EQ(1,
@@ -1125,30 +1275,73 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
ASSERT_EQ('g', buf_parent);
}
- /* Waits for all abstract socket tests. */
+ /* Waits for child to complete, and only close the socket afterwards. */
ASSERT_EQ(child, waitpid(child, &status, 0));
EXPECT_EQ(0, close(stream_abstract_socket));
EXPECT_EQ(0, close(dgram_abstract_socket));
+ EXPECT_EQ(0, close(stream_pathname_socket));
+ EXPECT_EQ(0, close(dgram_pathname_socket));
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
WEXITSTATUS(status) != EXIT_SUCCESS)
_metadata->exit_code = KSFT_FAIL;
}
-TEST(datagram_sockets)
+/* Fixture for datagram_sockets and self_connect tests */
+FIXTURE(socket_type_test)
{
struct service_fixture connected_addr, non_connected_addr;
+};
+
+FIXTURE_VARIANT(socket_type_test)
+{
+ const bool abstract;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket_type_test, abstract) {
+ /* clang-format on */
+ .abstract = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket_type_test, pathname) {
+ /* clang-format on */
+ .abstract = false,
+};
+
+FIXTURE_SETUP(socket_type_test)
+{
+ drop_caps(_metadata);
+
+ if (!variant->abstract)
+ ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
+
+ memset(&self->connected_addr, 0, sizeof(self->connected_addr));
+ set_unix_address(&self->connected_addr, 0, variant->abstract);
+ memset(&self->non_connected_addr, 0, sizeof(self->non_connected_addr));
+ set_unix_address(&self->non_connected_addr, 1, variant->abstract);
+}
+
+FIXTURE_TEARDOWN(socket_type_test)
+{
+ if (!variant->abstract) {
+ EXPECT_EQ(0, remove_path(self->connected_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, remove_path(self->non_connected_addr.unix_addr.sun_path));
+ EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
+ }
+}
+
+TEST_F(socket_type_test, datagram_sockets)
+{
int server_conn_socket, server_unconn_socket;
int pipe_parent[2], pipe_child[2];
int status;
char buf;
pid_t child;
-
- drop_caps(_metadata);
- memset(&connected_addr, 0, sizeof(connected_addr));
- set_unix_address(&connected_addr, 0, true);
- memset(&non_connected_addr, 0, sizeof(non_connected_addr));
- set_unix_address(&non_connected_addr, 1, true);
+ const __u16 scope = variant->abstract ?
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
@@ -1169,8 +1362,9 @@ TEST(datagram_sockets)
/* Waits for parent to listen. */
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
ASSERT_EQ(0,
- connect(client_conn_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
+ connect(client_conn_socket,
+ &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
/*
* Both connected and non-connected sockets can send data when
@@ -1178,13 +1372,12 @@ TEST(datagram_sockets)
*/
ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
ASSERT_EQ(1, sendto(client_unconn_socket, ".", 1, 0,
- &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
/* Scopes the domain. */
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/*
* Connected socket sends data to the receiver, but the
@@ -1192,8 +1385,8 @@ TEST(datagram_sockets)
*/
ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
ASSERT_EQ(-1, sendto(client_unconn_socket, ".", 1, 0,
- &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
@@ -1210,10 +1403,11 @@ TEST(datagram_sockets)
ASSERT_LE(0, server_conn_socket);
ASSERT_LE(0, server_unconn_socket);
- ASSERT_EQ(0, bind(server_conn_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
- ASSERT_EQ(0, bind(server_unconn_socket, &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(server_conn_socket, &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(server_unconn_socket,
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
/* Waits for child to test. */
@@ -1238,52 +1432,49 @@ TEST(datagram_sockets)
_metadata->exit_code = KSFT_FAIL;
}
-TEST(self_connect)
+TEST_F(socket_type_test, self_connect)
{
- struct service_fixture connected_addr, non_connected_addr;
int connected_socket, non_connected_socket, status;
pid_t child;
-
- drop_caps(_metadata);
- memset(&connected_addr, 0, sizeof(connected_addr));
- set_unix_address(&connected_addr, 0, true);
- memset(&non_connected_addr, 0, sizeof(non_connected_addr));
- set_unix_address(&non_connected_addr, 1, true);
+ const __u16 scope = variant->abstract ?
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
+ LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
non_connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
ASSERT_LE(0, connected_socket);
ASSERT_LE(0, non_connected_socket);
- ASSERT_EQ(0, bind(connected_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
- ASSERT_EQ(0, bind(non_connected_socket, &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(connected_socket, &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
+ ASSERT_EQ(0, bind(non_connected_socket,
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
/* Child's domain is scoped. */
- create_scoped_domain(_metadata,
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ create_scoped_domain(_metadata, scope);
/*
* The child inherits the sockets, and cannot connect or
* send data to them.
*/
ASSERT_EQ(-1,
- connect(connected_socket, &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
+ connect(connected_socket,
+ &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
ASSERT_EQ(-1, sendto(connected_socket, ".", 1, 0,
- &connected_addr.unix_addr,
- connected_addr.unix_addr_len));
+ &self->connected_addr.unix_addr,
+ self->connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
ASSERT_EQ(-1, sendto(non_connected_socket, ".", 1, 0,
- &non_connected_addr.unix_addr,
- non_connected_addr.unix_addr_len));
+ &self->non_connected_addr.unix_addr,
+ self->non_connected_addr.unix_addr_len));
ASSERT_EQ(EPERM, errno);
EXPECT_EQ(0, close(connected_socket));
--
2.52.0
^ permalink raw reply related [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
` (5 preceding siblings ...)
2025-12-30 17:20 ` [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests Tingmao Wang
@ 2025-12-30 23:16 ` Günther Noack
2025-12-31 16:54 ` Demi Marie Obenour
6 siblings, 1 reply; 41+ messages in thread
From: Günther Noack @ 2025-12-30 23:16 UTC (permalink / raw)
To: Tingmao Wang
Cc: Mickaël Salaün, Günther Noack, Demi Marie Obenour,
Alyssa Ross, Jann Horn, Tahera Fahimi, Justin Suess,
linux-security-module
Hello!
Thanks for sending this patch!
On Tue, Dec 30, 2025 at 05:20:18PM +0000, Tingmao Wang wrote:
> Changes in v2:
> Fix grammar in doc, rebased on mic/next, and extracted common code from
> hook_unix_stream_connect and hook_unix_may_send into a separate
> function.
>
> The rest is the same as the v1 cover letter:
>
> This patch series extend the existing abstract Unix socket scoping to
> pathname (i.e. normal file-based) sockets as well, by adding a new scope
> bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET that works the same as
> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, except that restricts pathname Unix
> sockets. This means that a sandboxed process with this scope enabled will
> not be able to connect to Unix sockets created outside the sandbox via the
> filesystem.
>
> There is a future plan [1] for allowing specific sockets based on FS
> hierarchy, but this series is only determining access based on domain
> parent-child relationship. There is currently no way to allow specific
> (outside the Landlock domain) Unix sockets, and none of the existing
> Landlock filesystem controls apply to socket connect().
>
> With this series, we can now properly protect against things like the the
> following while only relying on Landlock:
>
> (running under tmux)
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= ./sandboxer bash
> Executing the sandboxed command...
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> cat: /tmp/hi: No such file or directory
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> hi
>
> The above but with Unix socket scoping enabled (both pathname and abstract
> sockets) - the sandboxed shell can now no longer talk to tmux due to the
> socket being created from outside the Landlock sandbox:
>
> (running under tmux)
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= LL_SCOPED=u:a ./sandboxer bash
> Executing the sandboxed command...
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> cat: /tmp/hi: No such file or directory
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
> error connecting to /tmp/tmux-0/default (Operation not permitted)
> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> cat: /tmp/hi: No such file or directory
>
> Tmux is just one example. In a standard systemd session, `systemd-run
> --user` can also be used (--user will run the command in the user's
> session, without requiring any root privileges), and likely a lot more if
> running in a desktop environment with many popular applications. This
> change therefore makes it possible to create sandboxes without relying on
> additional mechanisms like seccomp to protect against such issues.
>
> These kind of issues was originally discussed on here (I took the idea for
> systemd-run from Demi):
> https://spectrum-os.org/lists/archives/spectrum-devel/00256266-26db-40cf-8f5b-f7c7064084c2@gmail.com/
What is unclear to me from the examples and the description is: Why is
the boundary between allowed and denied connection targets drawn at
the border of the Landlock domain (the "scope") and why don't we solve
this with the file-system-based approach described in [1]?
**Do we have existing use cases where a service is both offered and
connected to all from within the same Landlock domain, and where the
process enforcing the policy does not control the child process enough
so that it would be possible to allow-list it with a
LANDLOCK_ACCESS_FS_CONNECT_UNIX rule?**
If we do not have such a use case, it seems that the planned FS-based
control mechanism from [1] would do the same job? Long term, we might
be better off if we only have only one control -- as we have discussed
in [2], having two of these might mean that they interact in
unconventional and possibly confusing ways.
Apart from that, there are some other weaker hints that make me
slightly critical of this patch set:
* We discussed the idea that a FS-based path_beneath rule would act
implicitly also as an exception for
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET, in [2] - one possible way to
interpret this is that the gravity of the system's logic pulls us
back towards a FS-based control, and we would have to swim less
against the stream if we integrated the Unix connect() control in
that way?
* I am struggling to convince myself that we can tell users to
restrict LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET as a default, as we are
currently doing it with the other "scoped" and file system controls.
(The scoped signals are OK because killing out-of-domain processes
is clearly bad. The scoped abstract sockets are usually OK because
most processes do not need that feature.)
But there are legitimate Unix services that are still needed by
unprivileged processes and which are designed to be safe to use.
For instance, systemd exposes a user database lookup service over
varlink [3], which can be accessed from arbitrary glibc programs
through a NSS module. Using this is incompatible with
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET as long as we do not have the
FS-based control and the surprising implicit permission through a
path_beneath rule as discussed in [2].
(Another example is the X11 socket, to which the same reasoning
should apply, I think, and which is also used by a large number of
programs.)
I agree that the bug [1] has been asleep for a bit too long, and we
should probably pick this up soon. As we have not heard back from
Ryan after our last inquiry, IMHO I think it would be fair to take it
over.
Apologies for the difficult feedback - I do not mean to get in the way
of your enthusiasm here, but I would like to make sure that we don't
implement this as a stop-gap measure just because the other bug [1]
seemed more difficult and/or stuck in a github issue interaction.
Let's rather unblock that bug, if that is the case. :)
As usual, I fully acknowledge that I might well be wrong and might
have missed some of the underlying reasons, in which case I will
happily be corrected and change my mind. :)
[1] https://github.com/landlock-lsm/linux/issues/36
[2] https://github.com/landlock-lsm/linux/issues/36#issuecomment-3699749541
[3] https://systemd.io/USER_GROUP_API/
Have a good start into the new year!
–Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2025-12-30 23:16 ` [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Günther Noack
@ 2025-12-31 16:54 ` Demi Marie Obenour
2026-01-09 12:01 ` Mickaël Salaün
0 siblings, 1 reply; 41+ messages in thread
From: Demi Marie Obenour @ 2025-12-31 16:54 UTC (permalink / raw)
To: Günther Noack, Tingmao Wang
Cc: Mickaël Salaün, Günther Noack, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
[-- Attachment #1.1.1: Type: text/plain, Size: 7681 bytes --]
On 12/30/25 18:16, Günther Noack wrote:
> Hello!
>
> Thanks for sending this patch!
>
> On Tue, Dec 30, 2025 at 05:20:18PM +0000, Tingmao Wang wrote:
>> Changes in v2:
>> Fix grammar in doc, rebased on mic/next, and extracted common code from
>> hook_unix_stream_connect and hook_unix_may_send into a separate
>> function.
>>
>> The rest is the same as the v1 cover letter:
>>
>> This patch series extend the existing abstract Unix socket scoping to
>> pathname (i.e. normal file-based) sockets as well, by adding a new scope
>> bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET that works the same as
>> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, except that restricts pathname Unix
>> sockets. This means that a sandboxed process with this scope enabled will
>> not be able to connect to Unix sockets created outside the sandbox via the
>> filesystem.
>>
>> There is a future plan [1] for allowing specific sockets based on FS
>> hierarchy, but this series is only determining access based on domain
>> parent-child relationship. There is currently no way to allow specific
>> (outside the Landlock domain) Unix sockets, and none of the existing
>> Landlock filesystem controls apply to socket connect().
>>
>> With this series, we can now properly protect against things like the the
>> following while only relying on Landlock:
>>
>> (running under tmux)
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= ./sandboxer bash
>> Executing the sandboxed command...
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
>> cat: /tmp/hi: No such file or directory
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
>> hi
>>
>> The above but with Unix socket scoping enabled (both pathname and abstract
>> sockets) - the sandboxed shell can now no longer talk to tmux due to the
>> socket being created from outside the Landlock sandbox:
>>
>> (running under tmux)
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= LL_SCOPED=u:a ./sandboxer bash
>> Executing the sandboxed command...
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
>> cat: /tmp/hi: No such file or directory
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
>> error connecting to /tmp/tmux-0/default (Operation not permitted)
>> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
>> cat: /tmp/hi: No such file or directory
>>
>> Tmux is just one example. In a standard systemd session, `systemd-run
>> --user` can also be used (--user will run the command in the user's
>> session, without requiring any root privileges), and likely a lot more if
>> running in a desktop environment with many popular applications. This
>> change therefore makes it possible to create sandboxes without relying on
>> additional mechanisms like seccomp to protect against such issues.
>>
>> These kind of issues was originally discussed on here (I took the idea for
>> systemd-run from Demi):
>> https://spectrum-os.org/lists/archives/spectrum-devel/00256266-26db-40cf-8f5b-f7c7064084c2@gmail.com/
>
> What is unclear to me from the examples and the description is: Why is
> the boundary between allowed and denied connection targets drawn at
> the border of the Landlock domain (the "scope") and why don't we solve
> this with the file-system-based approach described in [1]?
>
> **Do we have existing use cases where a service is both offered and
> connected to all from within the same Landlock domain, and where the
> process enforcing the policy does not control the child process enough
> so that it would be possible to allow-list it with a
> LANDLOCK_ACCESS_FS_CONNECT_UNIX rule?**
>
> If we do not have such a use case, it seems that the planned FS-based
> control mechanism from [1] would do the same job? Long term, we might
> be better off if we only have only one control -- as we have discussed
> in [2], having two of these might mean that they interact in
> unconventional and possibly confusing ways.
I agree with this.
> Apart from that, there are some other weaker hints that make me
> slightly critical of this patch set:
>
> * We discussed the idea that a FS-based path_beneath rule would act
> implicitly also as an exception for
> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET, in [2] - one possible way to
> interpret this is that the gravity of the system's logic pulls us
> back towards a FS-based control, and we would have to swim less
> against the stream if we integrated the Unix connect() control in
> that way?
I agree with this as well. Having FS-based controls apply consistently
to all types of inodes is what I would expect.
> * I am struggling to convince myself that we can tell users to
> restrict LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET as a default, as we are
> currently doing it with the other "scoped" and file system controls.
> (The scoped signals are OK because killing out-of-domain processes
> is clearly bad. The scoped abstract sockets are usually OK because
> most processes do not need that feature.)
>
> But there are legitimate Unix services that are still needed by
> unprivileged processes and which are designed to be safe to use.
> For instance, systemd exposes a user database lookup service over
> varlink [3], which can be accessed from arbitrary glibc programs
> through a NSS module. Using this is incompatible with
> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET as long as we do not have the
> FS-based control and the surprising implicit permission through a
> path_beneath rule as discussed in [2].
>
> (Another example is the X11 socket, to which the same reasoning
> should apply, I think, and which is also used by a large number of
> programs.)
I think making FS-based controls on by default is the least surprising
option for users. It's what I suspect most programs intend, and it
would stop a lot of unexpected sandbox escapes.
Without FS-based controls, scoping is also necessary to prevent
sandbox escapes. In my opinion, it's better to block access to
unprivileged services than to allow connecting to any service.
X11 is a trivial sandbox escape by taking over the user's GUI.
A sandboxed program accessing it indicates a misdesign. The NSS module
is not needed by programs that do not do user database resolution,
which I suspect includes almost all sandboxed programs.
> I agree that the bug [1] has been asleep for a bit too long, and we
> should probably pick this up soon. As we have not heard back from
> Ryan after our last inquiry, IMHO I think it would be fair to take it
> over.
I definitely support this!
> Apologies for the difficult feedback - I do not mean to get in the way
> of your enthusiasm here, but I would like to make sure that we don't
> implement this as a stop-gap measure just because the other bug [1]
> seemed more difficult and/or stuck in a github issue interaction.
> Let's rather unblock that bug, if that is the case. :)
>
> As usual, I fully acknowledge that I might well be wrong and might
> have missed some of the underlying reasons, in which case I will
> happily be corrected and change my mind. :)
>
> [1] https://github.com/landlock-lsm/linux/issues/36
> [2] https://github.com/landlock-lsm/linux/issues/36#issuecomment-3699749541
> [3] https://systemd.io/USER_GROUP_API/
>
> Have a good start into the new year!
> –Günther
--
Sincerely,
Demi Marie Obenour (she/her/hers)
[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2025-12-31 16:54 ` Demi Marie Obenour
@ 2026-01-09 12:01 ` Mickaël Salaün
2026-01-31 17:41 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-01-09 12:01 UTC (permalink / raw)
To: Demi Marie Obenour
Cc: Günther Noack, Tingmao Wang, Günther Noack, Alyssa Ross,
Jann Horn, Tahera Fahimi, Justin Suess, linux-security-module
On Wed, Dec 31, 2025 at 11:54:27AM -0500, Demi Marie Obenour wrote:
> On 12/30/25 18:16, Günther Noack wrote:
> > Hello!
> >
> > Thanks for sending this patch!
> >
> > On Tue, Dec 30, 2025 at 05:20:18PM +0000, Tingmao Wang wrote:
> >> Changes in v2:
> >> Fix grammar in doc, rebased on mic/next, and extracted common code from
> >> hook_unix_stream_connect and hook_unix_may_send into a separate
> >> function.
> >>
> >> The rest is the same as the v1 cover letter:
> >>
> >> This patch series extend the existing abstract Unix socket scoping to
> >> pathname (i.e. normal file-based) sockets as well, by adding a new scope
> >> bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET that works the same as
> >> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, except that restricts pathname Unix
> >> sockets. This means that a sandboxed process with this scope enabled will
> >> not be able to connect to Unix sockets created outside the sandbox via the
> >> filesystem.
> >>
> >> There is a future plan [1] for allowing specific sockets based on FS
> >> hierarchy, but this series is only determining access based on domain
> >> parent-child relationship. There is currently no way to allow specific
> >> (outside the Landlock domain) Unix sockets, and none of the existing
> >> Landlock filesystem controls apply to socket connect().
> >>
> >> With this series, we can now properly protect against things like the the
> >> following while only relying on Landlock:
> >>
> >> (running under tmux)
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= ./sandboxer bash
> >> Executing the sandboxed command...
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> >> cat: /tmp/hi: No such file or directory
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> >> hi
> >>
> >> The above but with Unix socket scoping enabled (both pathname and abstract
> >> sockets) - the sandboxed shell can now no longer talk to tmux due to the
> >> socket being created from outside the Landlock sandbox:
> >>
> >> (running under tmux)
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb ~# LL_FS_RO=/ LL_FS_RW= LL_SCOPED=u:a ./sandboxer bash
> >> Executing the sandboxed command...
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> >> cat: /tmp/hi: No such file or directory
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# tmux new-window 'echo hi > /tmp/hi'
> >> error connecting to /tmp/tmux-0/default (Operation not permitted)
> >> root@6-19-0-rc1-dev-00023-g68f0b276cbeb:/# cat /tmp/hi
> >> cat: /tmp/hi: No such file or directory
> >>
> >> Tmux is just one example. In a standard systemd session, `systemd-run
> >> --user` can also be used (--user will run the command in the user's
> >> session, without requiring any root privileges), and likely a lot more if
> >> running in a desktop environment with many popular applications. This
> >> change therefore makes it possible to create sandboxes without relying on
> >> additional mechanisms like seccomp to protect against such issues.
> >>
> >> These kind of issues was originally discussed on here (I took the idea for
> >> systemd-run from Demi):
> >> https://spectrum-os.org/lists/archives/spectrum-devel/00256266-26db-40cf-8f5b-f7c7064084c2@gmail.com/
> >
> > What is unclear to me from the examples and the description is: Why is
> > the boundary between allowed and denied connection targets drawn at
> > the border of the Landlock domain (the "scope") and why don't we solve
> > this with the file-system-based approach described in [1]?
> >
> > **Do we have existing use cases where a service is both offered and
> > connected to all from within the same Landlock domain, and where the
> > process enforcing the policy does not control the child process enough
> > so that it would be possible to allow-list it with a
> > LANDLOCK_ACCESS_FS_CONNECT_UNIX rule?**
Yes, with the sandboxer use case. It's similar to a container that
doesn't know all programs that could be run in it. We should be able to
create sandboxes that don't assume (nor restrict) internal IPCs (or any
kind of direct process I/O).
Landlock should make it possible to scope any kind of IPC. It's a
mental model which is easy to understand and that should be enforced by
default. Of course, we need some ways to add exceptions to this
deny-by-default policy, and that's why we also need the FS-based control
mechanism.
> >
> > If we do not have such a use case, it seems that the planned FS-based
> > control mechanism from [1] would do the same job? Long term, we might
> > be better off if we only have only one control -- as we have discussed
> > in [2], having two of these might mean that they interact in
> > unconventional and possibly confusing ways.
>
> I agree with this.
Both approaches are complementary/orthogonal and make sense long term
too. This might be the first time we can restrict the same operations,
but I'm pretty sure it will not be the last, and it's OK. Handled FS
access and scoped restrictions are two ways to describe a part of the
security policy. Having different ways makes the interface much simpler
than a generic one that would have to take into account all potential
future access controls.
One thing to keep in mind is that UAPI doesn't have to map 1:1 to the
kernel implementation, but the UAPI is stable and future proof, whereas
the kernel implementation can change a lot.
The ruleset's handled fields serve two purposes: define what should be
denied by default, and define which type of rules are valid
(compatibility). The ruleset's scoped field serve similar purposes but
it also implies implicit rules (i.e. communications with processes
inside the sandbox are allowed). Without the soped field, we would have
to create dedicated handled field per type (i.e. scope's bit) and
dedicated rule type for the related handled field, which would make the
interface more generic but also more complex, for something which is not
needed.
In a nutshell, in the case of the FS-based and scope-based unix socket
control, we should see one kind of restrictions (e.g. connect to unix
socket), which can accept two types of rules: (explicit) file path, or
(implicit) peer's scope. Access should be granted as long as a rule
matches, whatever its type.
This rationale should be explained in a commit message.
>
> > Apart from that, there are some other weaker hints that make me
> > slightly critical of this patch set:
> >
> > * We discussed the idea that a FS-based path_beneath rule would act
> > implicitly also as an exception for
> > LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET, in [2] - one possible way to
> > interpret this is that the gravity of the system's logic pulls us
> > back towards a FS-based control, and we would have to swim less
> > against the stream if we integrated the Unix connect() control in
> > that way?
>
> I agree with this as well. Having FS-based controls apply consistently
> to all types of inodes is what I would expect.
I'm not sure to understand, but I hope my explanation above will answer
this question.
>
> > * I am struggling to convince myself that we can tell users to
> > restrict LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET as a default, as we are
> > currently doing it with the other "scoped" and file system controls.
> > (The scoped signals are OK because killing out-of-domain processes
> > is clearly bad. The scoped abstract sockets are usually OK because
> > most processes do not need that feature.)
> >
> > But there are legitimate Unix services that are still needed by
> > unprivileged processes and which are designed to be safe to use.
> > For instance, systemd exposes a user database lookup service over
> > varlink [3], which can be accessed from arbitrary glibc programs
> > through a NSS module. Using this is incompatible with
> > LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET as long as we do not have the
> > FS-based control and the surprising implicit permission through a
> > path_beneath rule as discussed in [2].
> >
> > (Another example is the X11 socket, to which the same reasoning
> > should apply, I think, and which is also used by a large number of
> > programs.)
Yes, restricting a sandbox too much may not work for any possible use
cases. Some use cases would need the FS-based control before using the
scope-based one, and that's OK. Some use cases would not need the
FS-based control, and the scope-based one would be enough (e.g.
sandboxed archive manager).
> I think making FS-based controls on by default is the least surprising
> option for users. It's what I suspect most programs intend, and it
> would stop a lot of unexpected sandbox escapes.
Nothing in Landlock is on by default for compatibility reasons.
>
> Without FS-based controls, scoping is also necessary to prevent
> sandbox escapes. In my opinion, it's better to block access to
> unprivileged services than to allow connecting to any service.
Scoped-based control should be enforced in most cases, but FS-based
control might be a requirement/dependency. In any case, a sandbox
should not break legitimate use cases.
>
> X11 is a trivial sandbox escape by taking over the user's GUI.
> A sandboxed program accessing it indicates a misdesign.
I would say a *fully* sandboxed program for a desktop use case using X11
(instead of Wayland), which is not possible with Landlock *alone* yet.
Desktop environment is the worse case scenario for sandboxing, for a lot
of different reasons... but there are a lot of other use cases for
sandboxing.
> The NSS module
> is not needed by programs that do not do user database resolution,
> which I suspect includes almost all sandboxed programs.
>
> > I agree that the bug [1] has been asleep for a bit too long, and we
> > should probably pick this up soon. As we have not heard back from
> > Ryan after our last inquiry, IMHO I think it would be fair to take it
> > over.
>
> I definitely support this!
Yes, thanks for working on this.
>
> > Apologies for the difficult feedback - I do not mean to get in the way
> > of your enthusiasm here, but I would like to make sure that we don't
> > implement this as a stop-gap measure just because the other bug [1]
> > seemed more difficult and/or stuck in a github issue interaction.
> > Let's rather unblock that bug, if that is the case. :)
Receiving criticisms is part of the review process, which is a sign of
healthy development. I understand your concerns and it's good discuss
about them to make sure we are going in the right direction.
> >
> > As usual, I fully acknowledge that I might well be wrong and might
> > have missed some of the underlying reasons, in which case I will
> > happily be corrected and change my mind. :)
In any case, if someone doesn't understand something, it probably means
that this should be explained better somewhere. Such design artifacts
will be useful for future developments too.
> >
> > [1] https://github.com/landlock-lsm/linux/issues/36
> > [2] https://github.com/landlock-lsm/linux/issues/36#issuecomment-3699749541
> > [3] https://systemd.io/USER_GROUP_API/
> >
> > Have a good start into the new year!
> > –Günther
> --
> Sincerely,
> Demi Marie Obenour (she/her/hers)
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI
2025-12-30 17:20 ` [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI Tingmao Wang
@ 2026-01-29 21:27 ` Mickaël Salaün
0 siblings, 0 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-01-29 21:27 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
On Tue, Dec 30, 2025 at 05:20:19PM +0000, Tingmao Wang wrote:
> Add the new scope bit to the uAPI header, add documentation, and bump ABI
> version to 8.
This patch and the next one should be fold together. If a new UAPI is
added, it should come with the kernel implementation.
>
> This documentation edit specifically calls out the security implications of
> not restricting sockets.
>
> Fix some minor cosmetic issue in landlock.h around the changed lines as
> well.
>
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
>
> Changes in v2:
> - Fix grammar
>
> Note that in the code block in "Defining and enforcing a security policy"
> the switch case currently jumps from 5 to 7. This should be fixed by
> https://lore.kernel.org/all/20251216210248.4150777-1-samasth.norway.ananda@oracle.com/
>
> Documentation/userspace-api/landlock.rst | 37 ++++++++++++++++---
> include/uapi/linux/landlock.h | 8 +++-
> security/landlock/limits.h | 2 +-
> security/landlock/syscalls.c | 2 +-
> tools/testing/selftests/landlock/base_test.c | 2 +-
> .../testing/selftests/landlock/scoped_test.c | 2 +-
> 6 files changed, 42 insertions(+), 11 deletions(-)
>
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index 1d0c2c15c22e..5620a2be1091 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -83,7 +83,8 @@ to be explicit about the denied-by-default access rights.
> LANDLOCK_ACCESS_NET_CONNECT_TCP,
> .scoped =
> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> - LANDLOCK_SCOPE_SIGNAL,
> + LANDLOCK_SCOPE_SIGNAL |
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
> };
>
> Because we may not know which kernel version an application will be executed
> @@ -127,6 +128,10 @@ 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:
> + /* Removes LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET for ABI < 8 */
> + ruleset_attr.scoped &= ~LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> }
>
> This enables the creation of an inclusive ruleset that will contain our rules.
> @@ -328,10 +333,15 @@ The operations which can be scoped are:
> This limits the sending of signals to target processes which run within the
> same or a nested Landlock domain.
>
> -``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET``
> - This limits the set of abstract :manpage:`unix(7)` sockets to which we can
> - :manpage:`connect(2)` to socket addresses which were created by a process in
> - the same or a nested Landlock domain.
> +``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` and ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET``
> + This limits the set of :manpage:`unix(7)` sockets to which we can
> + :manpage:`connect(2)` to socket addresses which were created by a
> + process in the same or a nested Landlock domain.
> + ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` applies to abstract sockets,
> + and ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` applies to pathname
> + sockets.
The following part is not needed:
> Even though pathname sockets are represented in the
> + filesystem, Landlock filesystem rules do not currently control access
> + to them.
>
> A :manpage:`sendto(2)` on a non-connected datagram socket is treated as if
> it were doing an implicit :manpage:`connect(2)` and will be blocked if the
> @@ -604,6 +614,23 @@ Landlock audit events with the ``LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF``,
> sys_landlock_restrict_self(). See Documentation/admin-guide/LSM/landlock.rst
> for more details on audit.
>
> +Pathname UNIX socket (ABI < 8)
> +------------------------------
> +
> +Starting with the Landlock ABI version 8, it is possible to restrict
> +connections to a pathname (non-abstract) :manpage:`unix(7)` socket by
> +setting ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` to the ``scoped`` ruleset
> +attribute. This works the same way as the abstract socket scoping.
> +
> +This allows sandboxing applications using only Landlock to protect against
> +bypasses relying on connecting to Unix sockets of other services running
> +under the same user. These services typically assume that any process
> +capable of connecting to a local Unix socket, or connecting with the
> +expected user credentials, is trusted. Without this protection, sandbox
> +escapes may be possible, especially when running in a standard desktop
> +environment, such as by using systemd-run, or sockets exposed by other
> +common applications.
> +
> .. _kernel_support:
>
> Kernel support
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index f030adc462ee..590c6d4171a0 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -364,10 +364,14 @@ struct landlock_net_port_attr {
> * related Landlock domain (e.g., a parent domain or a non-sandboxed process).
> * - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal
> * to another process outside the domain.
> + * - %LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET: Restrict a sandboxed process from
> + * connecting to a pathname UNIX socket created by a process outside the
> + * related Landlock domain.
> */
> /* clang-format off */
> #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
> -#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
> -/* clang-format on*/
> +#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
> +#define LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (1ULL << 2)
> +/* clang-format on */
>
> #endif /* _UAPI_LINUX_LANDLOCK_H */
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index 65b5ff051674..d653e14dba10 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -27,7 +27,7 @@
> #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
> #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>
> -#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL
> +#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
> #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
>
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 0116e9f93ffe..66fd196be85a 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -161,7 +161,7 @@ static const struct file_operations ruleset_fops = {
> * Documentation/userspace-api/landlock.rst should be updated to reflect the
> * UAPI change.
> */
> -const int landlock_abi_version = 7;
> +const int landlock_abi_version = 8;
>
> /**
> * 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 7b69002239d7..f4b1a275d8d9 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(7, landlock_create_ruleset(NULL, 0,
> + ASSERT_EQ(8, 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/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
> index b90f76ed0d9c..7f83512a328d 100644
> --- a/tools/testing/selftests/landlock/scoped_test.c
> +++ b/tools/testing/selftests/landlock/scoped_test.c
> @@ -12,7 +12,7 @@
>
> #include "common.h"
>
> -#define ACCESS_LAST LANDLOCK_SCOPE_SIGNAL
> +#define ACCESS_LAST LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
>
> TEST(ruleset_with_unknown_scope)
> {
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
2025-12-30 17:20 ` [PATCH v2 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
@ 2026-01-29 21:27 ` Mickaël Salaün
0 siblings, 0 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-01-29 21:27 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
On Tue, Dec 30, 2025 at 05:20:20PM +0000, Tingmao Wang wrote:
> Extend the existing abstract UNIX socket scoping to pathname sockets as
> well. Basically all of the logic is reused between the two types, just
> that pathname sockets scoping are controlled by another bit, and has its
> own audit request type (since the current one is named
> "abstract_unix_socket").
>
> Closes: https://github.com/landlock-lsm/linux/issues/51
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
>
> Changes in v2:
> - Factor out common code in hook_unix_stream_connect and
> hook_unix_may_send into check_socket_access(), and inline
> is_abstract_socket().
>
> security/landlock/audit.c | 4 ++
> security/landlock/audit.h | 1 +
> security/landlock/task.c | 109 ++++++++++++++++++++++----------------
> 3 files changed, 67 insertions(+), 47 deletions(-)
>
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> index e899995f1fd5..0626cc553ab0 100644
> --- a/security/landlock/audit.c
> +++ b/security/landlock/audit.c
> @@ -75,6 +75,10 @@ get_blocker(const enum landlock_request_type type,
> WARN_ON_ONCE(access_bit != -1);
> return "scope.abstract_unix_socket";
>
> + case LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET:
> + WARN_ON_ONCE(access_bit != -1);
> + return "scope.pathname_unix_socket";
> +
> case LANDLOCK_REQUEST_SCOPE_SIGNAL:
> WARN_ON_ONCE(access_bit != -1);
> return "scope.signal";
> diff --git a/security/landlock/audit.h b/security/landlock/audit.h
> index 92428b7fc4d8..1c9ce8588102 100644
> --- a/security/landlock/audit.h
> +++ b/security/landlock/audit.h
> @@ -21,6 +21,7 @@ enum landlock_request_type {
> LANDLOCK_REQUEST_NET_ACCESS,
> LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
> LANDLOCK_REQUEST_SCOPE_SIGNAL,
> + LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET,
> };
>
> /*
> diff --git a/security/landlock/task.c b/security/landlock/task.c
> index 833bc0cfe5c9..10dc356baf6f 100644
> --- a/security/landlock/task.c
> +++ b/security/landlock/task.c
> @@ -232,35 +232,81 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client,
> return false;
> }
>
> +/**
> + * sock_is_scoped - Check if socket connect or send should be restricted
> + * based on scope controls.
> + *
> + * @other: The server socket.
> + * @domain: The client domain.
> + * @scope: The relevant scope bit to check (i.e. pathname or abstract).
> + *
> + * Returns: True if connect should be restricted, false otherwise.
> + */
> static bool sock_is_scoped(struct sock *const other,
> - const struct landlock_ruleset *const domain)
> + const struct landlock_ruleset *const domain,
> + access_mask_t scope)
> {
> const struct landlock_ruleset *dom_other;
>
> /* The credentials will not change. */
> lockdep_assert_held(&unix_sk(other)->lock);
> dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
> - return domain_is_scoped(domain, dom_other,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + return domain_is_scoped(domain, dom_other, scope);
> }
>
> -static bool is_abstract_socket(struct sock *const sock)
> +/* Allow us to quickly test if the current domain scopes any form of socket */
Missing final dot.
> +static const struct access_masks unix_scope = {
> + .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
> +};
> +
> +/*
> + * UNIX sockets can have three types of addresses: pathname (a filesystem path),
> + * unnamed (not bound to an address), and abstract (sun_path[0] is '\0').
> + * Unnamed sockets include those created with socketpair() and unbound sockets.
> + * We do not restrict unnamed sockets since they have no address to identify.
Not because they have no address but because they cannot be used to
reach a new peer, right?
> + */
> +static int
> +check_socket_access(struct sock *const other,
> + const struct landlock_cred_security *const subject,
> + const size_t handle_layer)
> {
> - struct unix_address *addr = unix_sk(sock)->addr;
> + const struct unix_address *addr = unix_sk(other)->addr;
> + access_mask_t scope;
> + enum landlock_request_type request_type;
>
> + /* Unnamed sockets are not restricted. */
> if (!addr)
> - return false;
> + return 0;
>
> + /*
> + * Abstract and pathname Unix sockets have separate scope and audit
UNIX
> + * request type.
> + */
> if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
> - addr->name->sun_path[0] == '\0')
> - return true;
> + addr->name->sun_path[0] == '\0') {
> + scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
> + request_type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET;
> + } else {
> + scope = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> + request_type = LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET;
> + }
>
> - return false;
> -}
> + if (!sock_is_scoped(other, subject->domain, scope))
> + return 0;
>
> -static const struct access_masks unix_scope = {
> - .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
> -};
> + landlock_log_denial(subject, &(struct landlock_request) {
> + .type = request_type,
> + .audit = {
> + .type = LSM_AUDIT_DATA_NET,
> + .u.net = &(struct lsm_network_audit) {
> + .sk = other,
> + },
> + },
> + .layer_plus_one = handle_layer + 1,
> + });
> + return -EPERM;
> +}
>
> static int hook_unix_stream_connect(struct sock *const sock,
> struct sock *const other,
> @@ -275,23 +321,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
> if (!subject)
> return 0;
>
> - if (!is_abstract_socket(other))
> - return 0;
> -
> - if (!sock_is_scoped(other, subject->domain))
> - return 0;
> -
> - landlock_log_denial(subject, &(struct landlock_request) {
> - .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
> - .audit = {
> - .type = LSM_AUDIT_DATA_NET,
> - .u.net = &(struct lsm_network_audit) {
> - .sk = other,
> - },
> - },
> - .layer_plus_one = handle_layer + 1,
> - });
> - return -EPERM;
> + return check_socket_access(other, subject, handle_layer);
> }
>
> static int hook_unix_may_send(struct socket *const sock,
> @@ -302,6 +332,7 @@ static int hook_unix_may_send(struct socket *const sock,
> landlock_get_applicable_subject(current_cred(), unix_scope,
> &handle_layer);
>
> + /* Quick return for non-landlocked tasks. */
> if (!subject)
> return 0;
>
> @@ -312,23 +343,7 @@ static int hook_unix_may_send(struct socket *const sock,
> if (unix_peer(sock->sk) == other->sk)
> return 0;
>
> - if (!is_abstract_socket(other->sk))
> - return 0;
> -
> - if (!sock_is_scoped(other->sk, subject->domain))
> - return 0;
> -
> - landlock_log_denial(subject, &(struct landlock_request) {
> - .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
> - .audit = {
> - .type = LSM_AUDIT_DATA_NET,
> - .u.net = &(struct lsm_network_audit) {
> - .sk = other->sk,
> - },
> - },
> - .layer_plus_one = handle_layer + 1,
> - });
> - return -EPERM;
> + return check_socket_access(other->sk, subject, handle_layer);
> }
>
> static const struct access_masks signal_scope = {
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
2025-12-30 17:20 ` [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
@ 2026-01-29 21:27 ` Mickaël Salaün
2026-01-31 17:48 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-01-29 21:27 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
We should have a (potentially small) description of what this patch
does, even if it's a bit redundant with the subject.
On Tue, Dec 30, 2025 at 05:20:21PM +0000, Tingmao Wang wrote:
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
>
> I've decided to use "u" as the character to control this scope bit since
> it stands for (normal) Unix sockets. Imo using "p" or "n" would make it less
> clear / memorable. Open to suggestions.
Looks good to me.
>
> Also, open to suggestion whether socket scoping (pathname and abstract)
> should be enabled by default, if LL_SCOPED is not set. This would break
> backward compatibility, but maybe we shouldn't guarentee backward
> compatibility of this sandboxer in the first place, and almost all cases
> of Landlock usage would want socket scoping.
I agree that this example could have better defaults, but this should be
done with a standalone patch series. An important point to keep in mind
is that this example is used by developers (e.g. potential copy/paste),
so we need to be careful to not encourage them to create code which is
backward incompatible. I think the best way to do it is to request a
default behavior for a specific Landlock ABI version (e.g. with a new
parameter).
I'd also like this example to still be simple to understand, update, and
maintain.
>
> samples/landlock/sandboxer.c | 23 ++++++++++++++++++-----
> 1 file changed, 18 insertions(+), 5 deletions(-)
>
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index e7af02f98208..2de14e1c787d 100644
> --- a/samples/landlock/sandboxer.c
> +++ b/samples/landlock/sandboxer.c
> @@ -234,14 +234,16 @@ static bool check_ruleset_scope(const char *const env_var,
> bool error = false;
> bool abstract_scoping = false;
> bool signal_scoping = false;
> + bool named_scoping = false;
>
> /* Scoping is not supported by Landlock ABI */
> if (!(ruleset_attr->scoped &
> - (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL)))
> + (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL |
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET)))
> goto out_unset;
>
> env_type_scope = getenv(env_var);
> - /* Scoping is not supported by the user */
> + /* Scoping is not requested by the user */
> if (!env_type_scope || strcmp("", env_type_scope) == 0)
> goto out_unset;
>
> @@ -254,6 +256,9 @@ static bool check_ruleset_scope(const char *const env_var,
> } else if (strcmp("s", ipc_scoping_name) == 0 &&
> !signal_scoping) {
> signal_scoping = true;
> + } else if (strcmp("u", ipc_scoping_name) == 0 &&
> + !named_scoping) {
> + named_scoping = true;
> } else {
> fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",
> ipc_scoping_name);
> @@ -270,6 +275,8 @@ static bool check_ruleset_scope(const char *const env_var,
> ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
> if (!signal_scoping)
> ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL;
> + if (!named_scoping)
> + ruleset_attr->scoped &= ~LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
>
> unsetenv(env_var);
> return error;
> @@ -299,7 +306,7 @@ static bool check_ruleset_scope(const char *const env_var,
>
> /* clang-format on */
>
> -#define LANDLOCK_ABI_LAST 7
> +#define LANDLOCK_ABI_LAST 8
>
> #define XSTR(s) #s
> #define STR(s) XSTR(s)
> @@ -325,6 +332,7 @@ static const char help[] =
> "* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
> " - \"a\" to restrict opening abstract unix sockets\n"
> " - \"s\" to restrict sending signals\n"
> + " - \"u\" to restrict opening pathname (non-abstract) unix sockets\n"
> "\n"
> "A sandboxer should not log denied access requests to avoid spamming logs, "
> "but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
> @@ -334,7 +342,7 @@ static const char help[] =
> ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> ENV_TCP_BIND_NAME "=\"9418\" "
> ENV_TCP_CONNECT_NAME "=\"80:443\" "
> - ENV_SCOPED_NAME "=\"a:s\" "
> + ENV_SCOPED_NAME "=\"a:s:u\" "
> "%1$s bash -i\n"
> "\n"
> "This sandboxer can use Landlock features up to ABI version "
> @@ -356,7 +364,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
> .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> LANDLOCK_ACCESS_NET_CONNECT_TCP,
> .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> - LANDLOCK_SCOPE_SIGNAL,
> + LANDLOCK_SCOPE_SIGNAL |
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
> };
> int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
> int set_restrict_flags = 0;
> @@ -436,6 +445,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
> /* Removes LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON for ABI < 7 */
> supported_restrict_flags &=
> ~LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
> + __attribute__((fallthrough));
> + case 7:
> + /* Removes LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET for ABI < 8 */
> + ruleset_attr.scoped &= ~LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
>
> /* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
> fprintf(stderr,
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too.
2025-12-30 17:20 ` [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too Tingmao Wang
@ 2026-01-29 21:28 ` Mickaël Salaün
2026-02-02 0:06 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-01-29 21:28 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
Commit messages should fit in 72 columns. The subject can be a bit more
but we should avoid that, and it should not end with a dot.
On Tue, Dec 30, 2025 at 05:20:23PM +0000, Tingmao Wang wrote:
> Since there is very little difference between abstract and pathname
> sockets in terms of testing of the scoped access checks (the only
> difference is in which scope bit control which form of socket), it makes
> sense to reuse the existing test for both type of sockets. Therefore, we
> rename scoped_abstract_unix_test.c to scoped_unix_test.c and extend the
> scoped_domains test to test pathname (i.e. non-abstract) sockets too.
>
> Since we can't change the variant data of scoped_domains (as it is defined
> in the shared .h file), we do this by extracting the actual test code into
> a function, and call it from different test cases.
>
> Also extend scoped_audit (this time we can use variants) to test both
> abstract and pathname sockets. For pathname sockets, audit_log_lsm_data
> will produce path="..." (or hex if path contains control characters) with
> absolute paths from the dentry, so we need to construct the escaped regex
> for the real path like in fs_test.
>
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
> ...bstract_unix_test.c => scoped_unix_test.c} | 256 ++++++++++++++----
> 1 file changed, 206 insertions(+), 50 deletions(-)
> rename tools/testing/selftests/landlock/{scoped_abstract_unix_test.c => scoped_unix_test.c} (81%)
>
> diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_unix_test.c
> similarity index 81%
> rename from tools/testing/selftests/landlock/scoped_abstract_unix_test.c
> rename to tools/testing/selftests/landlock/scoped_unix_test.c
> index 4a790e2d387d..669418c97509 100644
> --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
> +++ b/tools/testing/selftests/landlock/scoped_unix_test.c
> @@ -1,6 +1,7 @@
> // SPDX-License-Identifier: GPL-2.0
> /*
> - * Landlock tests - Abstract UNIX socket
> + * Landlock tests - Scoped access checks for UNIX socket (abstract and
> + * pathname)
> *
> * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
> */
> @@ -19,6 +20,7 @@
> #include <sys/un.h>
> #include <sys/wait.h>
> #include <unistd.h>
> +#include <stdlib.h>
>
> #include "audit.h"
> #include "common.h"
> @@ -47,7 +49,8 @@ static void create_fs_domain(struct __test_metadata *const _metadata)
>
> FIXTURE(scoped_domains)
> {
> - struct service_fixture stream_address, dgram_address;
> + struct service_fixture stream_address_abstract, dgram_address_abstract,
> + stream_address_pathname, dgram_address_pathname;
> };
>
> #include "scoped_base_variants.h"
> @@ -56,27 +59,62 @@ FIXTURE_SETUP(scoped_domains)
> {
> drop_caps(_metadata);
>
> - memset(&self->stream_address, 0, sizeof(self->stream_address));
> - memset(&self->dgram_address, 0, sizeof(self->dgram_address));
> - set_unix_address(&self->stream_address, 0, true);
> - set_unix_address(&self->dgram_address, 1, true);
> + ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
> +
> + memset(&self->stream_address_abstract, 0,
> + sizeof(self->stream_address_abstract));
> + memset(&self->dgram_address_abstract, 0,
> + sizeof(self->dgram_address_abstract));
> + memset(&self->stream_address_pathname, 0,
> + sizeof(self->stream_address_pathname));
> + memset(&self->dgram_address_pathname, 0,
> + sizeof(self->dgram_address_pathname));
> + set_unix_address(&self->stream_address_abstract, 0, true);
> + set_unix_address(&self->dgram_address_abstract, 1, true);
> + set_unix_address(&self->stream_address_pathname, 0, false);
> + set_unix_address(&self->dgram_address_pathname, 1, false);
> +}
> +
> +/* Remove @path if it exists */
> +int remove_path(const char *path)
> +{
> + if (unlink(path) == -1) {
> + if (errno != ENOENT)
> + return -errno;
> + }
> + return 0;
> }
>
> FIXTURE_TEARDOWN(scoped_domains)
> {
> + EXPECT_EQ(0, remove_path(self->stream_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->dgram_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
> }
>
> /*
> * Test unix_stream_connect() and unix_may_send() for a child connecting to its
> * parent, when they have scoped domain or no domain.
> */
> -TEST_F(scoped_domains, connect_to_parent)
> +static void test_connect_to_parent(struct __test_metadata *const _metadata,
> + FIXTURE_DATA(scoped_domains) * self,
> + const FIXTURE_VARIANT(scoped_domains) *
> + variant,
> + const bool abstract)
> {
> pid_t child;
> bool can_connect_to_parent;
> int status;
> int pipe_parent[2];
> int stream_server, dgram_server;
> + const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> + const struct service_fixture *stream_address =
> + abstract ? &self->stream_address_abstract :
> + &self->stream_address_pathname;
> + const struct service_fixture *dgram_address =
> + abstract ? &self->dgram_address_abstract :
> + &self->dgram_address_pathname;
>
> /*
> * can_connect_to_parent is true if a child process can connect to its
> @@ -87,8 +125,7 @@ TEST_F(scoped_domains, connect_to_parent)
>
> ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> if (variant->domain_both) {
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
> if (!__test_passed(_metadata))
> return;
> }
> @@ -102,8 +139,7 @@ TEST_F(scoped_domains, connect_to_parent)
>
> EXPECT_EQ(0, close(pipe_parent[1]));
> if (variant->domain_child)
> - create_scoped_domain(
> - _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_client);
> @@ -113,8 +149,8 @@ TEST_F(scoped_domains, connect_to_parent)
> /* Waits for the server. */
> ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
>
> - err = connect(stream_client, &self->stream_address.unix_addr,
> - self->stream_address.unix_addr_len);
> + err = connect(stream_client, &stream_address->unix_addr,
> + stream_address->unix_addr_len);
> if (can_connect_to_parent) {
> EXPECT_EQ(0, err);
> } else {
> @@ -123,8 +159,8 @@ TEST_F(scoped_domains, connect_to_parent)
> }
> EXPECT_EQ(0, close(stream_client));
>
> - err = connect(dgram_client, &self->dgram_address.unix_addr,
> - self->dgram_address.unix_addr_len);
> + err = connect(dgram_client, &dgram_address->unix_addr,
> + dgram_address->unix_addr_len);
> if (can_connect_to_parent) {
> EXPECT_EQ(0, err);
> } else {
> @@ -137,17 +173,16 @@ TEST_F(scoped_domains, connect_to_parent)
> }
> EXPECT_EQ(0, close(pipe_parent[0]));
> if (variant->domain_parent)
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_server);
> dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_server);
> - ASSERT_EQ(0, bind(stream_server, &self->stream_address.unix_addr,
> - self->stream_address.unix_addr_len));
> - ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
> - self->dgram_address.unix_addr_len));
> + ASSERT_EQ(0, bind(stream_server, &stream_address->unix_addr,
> + stream_address->unix_addr_len));
> + ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
> + dgram_address->unix_addr_len));
> ASSERT_EQ(0, listen(stream_server, backlog));
>
> /* Signals to child that the parent is listening. */
> @@ -166,7 +201,11 @@ TEST_F(scoped_domains, connect_to_parent)
> * Test unix_stream_connect() and unix_may_send() for a parent connecting to
> * its child, when they have scoped domain or no domain.
> */
> -TEST_F(scoped_domains, connect_to_child)
> +static void test_connect_to_child(struct __test_metadata *const _metadata,
> + FIXTURE_DATA(scoped_domains) * self,
> + const FIXTURE_VARIANT(scoped_domains) *
> + variant,
> + const bool abstract)
> {
> pid_t child;
> bool can_connect_to_child;
> @@ -174,6 +213,14 @@ TEST_F(scoped_domains, connect_to_child)
> int pipe_child[2], pipe_parent[2];
> char buf;
> int stream_client, dgram_client;
> + const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> + const struct service_fixture *stream_address =
> + abstract ? &self->stream_address_abstract :
> + &self->stream_address_pathname;
> + const struct service_fixture *dgram_address =
> + abstract ? &self->dgram_address_abstract :
> + &self->dgram_address_pathname;
>
> /*
> * can_connect_to_child is true if a parent process can connect to its
> @@ -185,8 +232,7 @@ TEST_F(scoped_domains, connect_to_child)
> ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> if (variant->domain_both) {
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
> if (!__test_passed(_metadata))
> return;
> }
> @@ -199,8 +245,7 @@ TEST_F(scoped_domains, connect_to_child)
> EXPECT_EQ(0, close(pipe_parent[1]));
> EXPECT_EQ(0, close(pipe_child[0]));
> if (variant->domain_child)
> - create_scoped_domain(
> - _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> /* Waits for the parent to be in a domain, if any. */
> ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
> @@ -209,11 +254,10 @@ TEST_F(scoped_domains, connect_to_child)
> ASSERT_LE(0, stream_server);
> dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_server);
> - ASSERT_EQ(0,
> - bind(stream_server, &self->stream_address.unix_addr,
> - self->stream_address.unix_addr_len));
> - ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
> - self->dgram_address.unix_addr_len));
> + ASSERT_EQ(0, bind(stream_server, &stream_address->unix_addr,
> + stream_address->unix_addr_len));
> + ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
> + dgram_address->unix_addr_len));
> ASSERT_EQ(0, listen(stream_server, backlog));
>
> /* Signals to the parent that child is listening. */
> @@ -230,8 +274,7 @@ TEST_F(scoped_domains, connect_to_child)
> EXPECT_EQ(0, close(pipe_parent[0]));
>
> if (variant->domain_parent)
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> /* Signals that the parent is in a domain, if any. */
> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> @@ -243,11 +286,11 @@ TEST_F(scoped_domains, connect_to_child)
>
> /* Waits for the child to listen */
> ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
> - err_stream = connect(stream_client, &self->stream_address.unix_addr,
> - self->stream_address.unix_addr_len);
> + err_stream = connect(stream_client, &stream_address->unix_addr,
> + stream_address->unix_addr_len);
> errno_stream = errno;
> - err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
> - self->dgram_address.unix_addr_len);
> + err_dgram = connect(dgram_client, &dgram_address->unix_addr,
> + dgram_address->unix_addr_len);
> errno_dgram = errno;
> if (can_connect_to_child) {
> EXPECT_EQ(0, err_stream);
> @@ -268,19 +311,79 @@ TEST_F(scoped_domains, connect_to_child)
> _metadata->exit_code = KSFT_FAIL;
> }
>
> +/*
> + * Test unix_stream_connect() and unix_may_send() for a child connecting to its
> + * parent, when they have scoped domain or no domain.
> + */
> +TEST_F(scoped_domains, abstract_connect_to_parent)
> +{
> + test_connect_to_parent(_metadata, self, variant, true);
> +}
> +
> +/*
> + * Test unix_stream_connect() and unix_may_send() for a parent connecting to
> + * its child, when they have scoped domain or no domain.
> + */
> +TEST_F(scoped_domains, abstract_connect_to_child)
> +{
> + test_connect_to_child(_metadata, self, variant, true);
> +}
> +
> +/*
> + * Test unix_stream_connect() and unix_may_send() for a child connecting to its
> + * parent with pathname sockets.
> + */
> +TEST_F(scoped_domains, pathname_connect_to_parent)
> +{
> + test_connect_to_parent(_metadata, self, variant, false);
> +}
> +
> +/*
> + * Test unix_stream_connect() and unix_may_send() for a parent connecting to
> + * its child with pathname sockets.
> + */
> +TEST_F(scoped_domains, pathname_connect_to_child)
> +{
> + test_connect_to_child(_metadata, self, variant, false);
> +}
> +
> FIXTURE(scoped_audit)
> {
> - struct service_fixture dgram_address;
> + struct service_fixture dgram_address_abstract, dgram_address_pathname;
> struct audit_filter audit_filter;
> int audit_fd;
> };
>
> +FIXTURE_VARIANT(scoped_audit)
> +{
> + const bool abstract_socket;
> +};
> +
> +// clang-format off
We always use /* */ comments. Ditto for all clang-format markups.
> +FIXTURE_VARIANT_ADD(scoped_audit, abstract_socket)
> +{
> + // clang-format on
> + .abstract_socket = true,
> +};
> +
> +// clang-format off
> +FIXTURE_VARIANT_ADD(scoped_audit, pathname_socket)
> +{
> + // clang-format on
> + .abstract_socket = false,
> +};
> +
> FIXTURE_SETUP(scoped_audit)
> {
> disable_caps(_metadata);
>
> - memset(&self->dgram_address, 0, sizeof(self->dgram_address));
> - set_unix_address(&self->dgram_address, 1, true);
> + ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
> + memset(&self->dgram_address_abstract, 0,
> + sizeof(self->dgram_address_abstract));
> + memset(&self->dgram_address_pathname, 0,
> + sizeof(self->dgram_address_pathname));
> + set_unix_address(&self->dgram_address_abstract, 1, true);
> + set_unix_address(&self->dgram_address_pathname, 1, false);
>
> set_cap(_metadata, CAP_AUDIT_CONTROL);
> self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
> @@ -291,6 +394,8 @@ FIXTURE_SETUP(scoped_audit)
> FIXTURE_TEARDOWN_PARENT(scoped_audit)
> {
> EXPECT_EQ(0, audit_cleanup(-1, NULL));
> + EXPECT_EQ(0, remove_path(self->dgram_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
> }
>
> /* python -c 'print(b"\0selftests-landlock-abstract-unix-".hex().upper())' */
> @@ -308,6 +413,12 @@ TEST_F(scoped_audit, connect_to_child)
> char buf;
> int dgram_client;
> struct audit_records records;
> + struct service_fixture *const dgram_address =
> + variant->abstract_socket ? &self->dgram_address_abstract :
> + &self->dgram_address_pathname;
> + size_t log_match_remaining = 500;
const
Why this number? Could you please follow the same logic as in
matches_log_fs_extra()?
> + char log_match[log_match_remaining];
> + char *log_match_cursor = log_match;
>
> /* Makes sure there is no superfluous logged records. */
> EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> @@ -330,8 +441,8 @@ TEST_F(scoped_audit, connect_to_child)
>
> dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_server);
> - ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
> - self->dgram_address.unix_addr_len));
> + ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
> + dgram_address->unix_addr_len));
>
> /* Signals to the parent that child is listening. */
> ASSERT_EQ(1, write(pipe_child[1], ".", 1));
> @@ -345,7 +456,9 @@ TEST_F(scoped_audit, connect_to_child)
> EXPECT_EQ(0, close(pipe_child[1]));
> EXPECT_EQ(0, close(pipe_parent[0]));
>
> - create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata,
> + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
>
> /* Signals that the parent is in a domain, if any. */
> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> @@ -355,19 +468,62 @@ TEST_F(scoped_audit, connect_to_child)
>
> /* Waits for the child to listen */
> ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
> - err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
> - self->dgram_address.unix_addr_len);
> + err_dgram = connect(dgram_client, &dgram_address->unix_addr,
> + dgram_address->unix_addr_len);
> EXPECT_EQ(-1, err_dgram);
> EXPECT_EQ(EPERM, errno);
>
> - EXPECT_EQ(
> - 0,
> - audit_match_record(
> - self->audit_fd, AUDIT_LANDLOCK_ACCESS,
> + if (variant->abstract_socket) {
> + log_match_cursor = stpncpy(
> + log_match,
> REGEX_LANDLOCK_PREFIX
> " blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
> "[0-9A-F]\\+$",
> - NULL));
> + log_match_remaining);
> + log_match_remaining =
> + sizeof(log_match) - (log_match_cursor - log_match);
> + ASSERT_NE(0, log_match_remaining);
> + } else {
> + /*
> + * It is assumed that absolute_path does not contain control
> + * characters nor spaces, see audit_string_contains_control().
> + */
> + char *absolute_path =
const char *absolute_path
> + realpath(dgram_address->unix_addr.sun_path, NULL);
> +
> + EXPECT_NE(NULL, absolute_path)
> + {
> + TH_LOG("realpath() failed: %s", strerror(errno));
> + return;
> + }
> +
> + log_match_cursor =
> + stpncpy(log_match,
> + REGEX_LANDLOCK_PREFIX
> + " blockers=scope\\.pathname_unix_socket path=\"",
> + log_match_remaining);
> + log_match_remaining =
> + sizeof(log_match) - (log_match_cursor - log_match);
> + ASSERT_NE(0, log_match_remaining);
> + log_match_cursor = regex_escape(absolute_path, log_match_cursor,
> + log_match_remaining);
> + free(absolute_path);
> + if (log_match_cursor < 0) {
> + TH_LOG("regex_escape() failed (buffer too small)");
> + return;
> + }
> + log_match_remaining =
> + sizeof(log_match) - (log_match_cursor - log_match);
> + ASSERT_NE(0, log_match_remaining);
> + log_match_cursor =
> + stpncpy(log_match_cursor, "\"$", log_match_remaining);
> + log_match_remaining =
> + sizeof(log_match) - (log_match_cursor - log_match);
> + ASSERT_NE(0, log_match_remaining);
> + }
> +
> + EXPECT_EQ(0, audit_match_record(self->audit_fd, AUDIT_LANDLOCK_ACCESS,
> + log_match, NULL));
>
> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> EXPECT_EQ(0, close(dgram_client));
> --
> 2.52.0
>
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests
2025-12-30 17:20 ` [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests Tingmao Wang
@ 2026-01-29 21:28 ` Mickaël Salaün
0 siblings, 0 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-01-29 21:28 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
The whole series looks good, thanks!
On Tue, Dec 30, 2025 at 05:20:24PM +0000, Tingmao Wang wrote:
> While this produces a lot of change, it does allow us to "simultaneously"
> test both abstract and pathname UNIX sockets with reletively little code
> duplication, since they are really similar.
>
> Tests touched: scoped_vs_unscoped, outside_socket,
> various_address_sockets, datagram_sockets, self_connect.
>
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
> .../selftests/landlock/scoped_unix_test.c | 599 ++++++++++++------
> 1 file changed, 395 insertions(+), 204 deletions(-)
>
> diff --git a/tools/testing/selftests/landlock/scoped_unix_test.c b/tools/testing/selftests/landlock/scoped_unix_test.c
> index 669418c97509..6d1541f77dbe 100644
> --- a/tools/testing/selftests/landlock/scoped_unix_test.c
> +++ b/tools/testing/selftests/landlock/scoped_unix_test.c
> @@ -536,8 +536,12 @@ TEST_F(scoped_audit, connect_to_child)
>
> FIXTURE(scoped_vs_unscoped)
> {
> - struct service_fixture parent_stream_address, parent_dgram_address,
> - child_stream_address, child_dgram_address;
> + struct service_fixture parent_stream_address_abstract,
> + parent_dgram_address_abstract, child_stream_address_abstract,
> + child_dgram_address_abstract;
> + struct service_fixture parent_stream_address_pathname,
> + parent_dgram_address_pathname, child_stream_address_pathname,
> + child_dgram_address_pathname;
> };
>
> #include "scoped_multiple_domain_variants.h"
> @@ -546,35 +550,75 @@ FIXTURE_SETUP(scoped_vs_unscoped)
> {
> drop_caps(_metadata);
>
> - memset(&self->parent_stream_address, 0,
> - sizeof(self->parent_stream_address));
> - set_unix_address(&self->parent_stream_address, 0, true);
> - memset(&self->parent_dgram_address, 0,
> - sizeof(self->parent_dgram_address));
> - set_unix_address(&self->parent_dgram_address, 1, true);
> - memset(&self->child_stream_address, 0,
> - sizeof(self->child_stream_address));
> - set_unix_address(&self->child_stream_address, 2, true);
> - memset(&self->child_dgram_address, 0,
> - sizeof(self->child_dgram_address));
> - set_unix_address(&self->child_dgram_address, 3, true);
> + ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
> +
> + /* Abstract addresses. */
> + memset(&self->parent_stream_address_abstract, 0,
> + sizeof(self->parent_stream_address_abstract));
> + set_unix_address(&self->parent_stream_address_abstract, 0, true);
> + memset(&self->parent_dgram_address_abstract, 0,
> + sizeof(self->parent_dgram_address_abstract));
> + set_unix_address(&self->parent_dgram_address_abstract, 1, true);
> + memset(&self->child_stream_address_abstract, 0,
> + sizeof(self->child_stream_address_abstract));
> + set_unix_address(&self->child_stream_address_abstract, 2, true);
> + memset(&self->child_dgram_address_abstract, 0,
> + sizeof(self->child_dgram_address_abstract));
> + set_unix_address(&self->child_dgram_address_abstract, 3, true);
> +
> + /* Pathname addresses. */
> + memset(&self->parent_stream_address_pathname, 0,
> + sizeof(self->parent_stream_address_pathname));
> + set_unix_address(&self->parent_stream_address_pathname, 4, false);
> + memset(&self->parent_dgram_address_pathname, 0,
> + sizeof(self->parent_dgram_address_pathname));
> + set_unix_address(&self->parent_dgram_address_pathname, 5, false);
> + memset(&self->child_stream_address_pathname, 0,
> + sizeof(self->child_stream_address_pathname));
> + set_unix_address(&self->child_stream_address_pathname, 6, false);
> + memset(&self->child_dgram_address_pathname, 0,
> + sizeof(self->child_dgram_address_pathname));
> + set_unix_address(&self->child_dgram_address_pathname, 7, false);
> }
>
> FIXTURE_TEARDOWN(scoped_vs_unscoped)
> {
> + EXPECT_EQ(0, remove_path(self->parent_stream_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->parent_dgram_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->child_stream_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->child_dgram_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
> }
>
> /*
> * Test unix_stream_connect and unix_may_send for parent, child and
> * grand child processes when they can have scoped or non-scoped domains.
> */
> -TEST_F(scoped_vs_unscoped, unix_scoping)
> +static void test_scoped_vs_unscoped(
> + struct __test_metadata *const _metadata,
> + FIXTURE_DATA(scoped_vs_unscoped) * self,
> + const FIXTURE_VARIANT(scoped_vs_unscoped) * variant,
> + const bool abstract)
> {
> pid_t child;
> int status;
> bool can_connect_to_parent, can_connect_to_child;
> int pipe_parent[2];
> int stream_server_parent, dgram_server_parent;
> + const __u16 scope = abstract ? LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> + const struct service_fixture *parent_stream_address =
> + abstract ? &self->parent_stream_address_abstract :
> + &self->parent_stream_address_pathname;
> + const struct service_fixture *parent_dgram_address =
> + abstract ? &self->parent_dgram_address_abstract :
> + &self->parent_dgram_address_pathname;
> + const struct service_fixture *child_stream_address =
> + abstract ? &self->child_stream_address_abstract :
> + &self->child_stream_address_pathname;
> + const struct service_fixture *child_dgram_address =
> + abstract ? &self->child_dgram_address_abstract :
> + &self->child_dgram_address_pathname;
>
> can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX);
> can_connect_to_parent = (can_connect_to_child &&
> @@ -585,8 +629,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> if (variant->domain_all == OTHER_SANDBOX)
> create_fs_domain(_metadata);
> else if (variant->domain_all == SCOPE_SANDBOX)
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> child = fork();
> ASSERT_LE(0, child);
> @@ -600,8 +643,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> if (variant->domain_children == OTHER_SANDBOX)
> create_fs_domain(_metadata);
> else if (variant->domain_children == SCOPE_SANDBOX)
> - create_scoped_domain(
> - _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> grand_child = fork();
> ASSERT_LE(0, grand_child);
> @@ -616,9 +658,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> if (variant->domain_grand_child == OTHER_SANDBOX)
> create_fs_domain(_metadata);
> else if (variant->domain_grand_child == SCOPE_SANDBOX)
> - create_scoped_domain(
> - _metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_client);
> @@ -626,15 +666,13 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> ASSERT_LE(0, dgram_client);
>
> ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
> - stream_err = connect(
> - stream_client,
> - &self->child_stream_address.unix_addr,
> - self->child_stream_address.unix_addr_len);
> + stream_err = connect(stream_client,
> + &child_stream_address->unix_addr,
> + child_stream_address->unix_addr_len);
> stream_errno = errno;
> - dgram_err = connect(
> - dgram_client,
> - &self->child_dgram_address.unix_addr,
> - self->child_dgram_address.unix_addr_len);
> + dgram_err = connect(dgram_client,
> + &child_dgram_address->unix_addr,
> + child_dgram_address->unix_addr_len);
> dgram_errno = errno;
> if (can_connect_to_child) {
> EXPECT_EQ(0, stream_err);
> @@ -653,14 +691,12 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
>
> ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
> stream_err = connect(
> - stream_client,
> - &self->parent_stream_address.unix_addr,
> - self->parent_stream_address.unix_addr_len);
> + stream_client, &parent_stream_address->unix_addr,
> + parent_stream_address->unix_addr_len);
> stream_errno = errno;
> dgram_err = connect(
> - dgram_client,
> - &self->parent_dgram_address.unix_addr,
> - self->parent_dgram_address.unix_addr_len);
> + dgram_client, &parent_dgram_address->unix_addr,
> + parent_dgram_address->unix_addr_len);
> dgram_errno = errno;
> if (can_connect_to_parent) {
> EXPECT_EQ(0, stream_err);
> @@ -681,8 +717,7 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> if (variant->domain_child == OTHER_SANDBOX)
> create_fs_domain(_metadata);
> else if (variant->domain_child == SCOPE_SANDBOX)
> - create_scoped_domain(
> - _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> stream_server_child = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_server_child);
> @@ -690,11 +725,11 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> ASSERT_LE(0, dgram_server_child);
>
> ASSERT_EQ(0, bind(stream_server_child,
> - &self->child_stream_address.unix_addr,
> - self->child_stream_address.unix_addr_len));
> - ASSERT_EQ(0, bind(dgram_server_child,
> - &self->child_dgram_address.unix_addr,
> - self->child_dgram_address.unix_addr_len));
> + &child_stream_address->unix_addr,
> + child_stream_address->unix_addr_len));
> + ASSERT_EQ(0,
> + bind(dgram_server_child, &child_dgram_address->unix_addr,
> + child_dgram_address->unix_addr_len));
> ASSERT_EQ(0, listen(stream_server_child, backlog));
>
> ASSERT_EQ(1, write(pipe_child[1], ".", 1));
> @@ -708,19 +743,16 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> if (variant->domain_parent == OTHER_SANDBOX)
> create_fs_domain(_metadata);
> else if (variant->domain_parent == SCOPE_SANDBOX)
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> stream_server_parent = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_server_parent);
> dgram_server_parent = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_server_parent);
> - ASSERT_EQ(0, bind(stream_server_parent,
> - &self->parent_stream_address.unix_addr,
> - self->parent_stream_address.unix_addr_len));
> - ASSERT_EQ(0, bind(dgram_server_parent,
> - &self->parent_dgram_address.unix_addr,
> - self->parent_dgram_address.unix_addr_len));
> + ASSERT_EQ(0, bind(stream_server_parent, &parent_stream_address->unix_addr,
> + parent_stream_address->unix_addr_len));
> + ASSERT_EQ(0, bind(dgram_server_parent, &parent_dgram_address->unix_addr,
> + parent_dgram_address->unix_addr_len));
>
> ASSERT_EQ(0, listen(stream_server_parent, backlog));
>
> @@ -734,57 +766,119 @@ TEST_F(scoped_vs_unscoped, unix_scoping)
> _metadata->exit_code = KSFT_FAIL;
> }
>
> +TEST_F(scoped_vs_unscoped, unix_scoping_abstract)
> +{
> + test_scoped_vs_unscoped(_metadata, self, variant, true);
> +}
> +
> +TEST_F(scoped_vs_unscoped, unix_scoping_pathname)
> +{
> + test_scoped_vs_unscoped(_metadata, self, variant, false);
> +}
> +
> FIXTURE(outside_socket)
> {
> - struct service_fixture address, transit_address;
> + struct service_fixture address_abstract, transit_address_abstract;
> + struct service_fixture address_pathname, transit_address_pathname;
> };
>
> FIXTURE_VARIANT(outside_socket)
> {
> const bool child_socket;
> const int type;
> + const bool abstract;
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(outside_socket, abstract_allow_dgram_child) {
> + /* clang-format on */
> + .child_socket = true,
> + .type = SOCK_DGRAM,
> + .abstract = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(outside_socket, abstract_deny_dgram_server) {
> + /* clang-format on */
> + .child_socket = false,
> + .type = SOCK_DGRAM,
> + .abstract = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(outside_socket, abstract_allow_stream_child) {
> + /* clang-format on */
> + .child_socket = true,
> + .type = SOCK_STREAM,
> + .abstract = true,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_child) {
> +FIXTURE_VARIANT_ADD(outside_socket, abstract_deny_stream_server) {
> + /* clang-format on */
> + .child_socket = false,
> + .type = SOCK_STREAM,
> + .abstract = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(outside_socket, pathname_allow_dgram_child) {
> /* clang-format on */
> .child_socket = true,
> .type = SOCK_DGRAM,
> + .abstract = false,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server) {
> +FIXTURE_VARIANT_ADD(outside_socket, pathname_deny_dgram_server) {
> /* clang-format on */
> .child_socket = false,
> .type = SOCK_DGRAM,
> + .abstract = false,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(outside_socket, allow_stream_child) {
> +FIXTURE_VARIANT_ADD(outside_socket, pathname_allow_stream_child) {
> /* clang-format on */
> .child_socket = true,
> .type = SOCK_STREAM,
> + .abstract = false,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server) {
> +FIXTURE_VARIANT_ADD(outside_socket, pathname_deny_stream_server) {
> /* clang-format on */
> .child_socket = false,
> .type = SOCK_STREAM,
> + .abstract = false,
> };
>
> FIXTURE_SETUP(outside_socket)
> {
> drop_caps(_metadata);
>
> - memset(&self->transit_address, 0, sizeof(self->transit_address));
> - set_unix_address(&self->transit_address, 0, true);
> - memset(&self->address, 0, sizeof(self->address));
> - set_unix_address(&self->address, 1, true);
> + ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
> +
> + /* Abstract addresses. */
> + memset(&self->transit_address_abstract, 0,
> + sizeof(self->transit_address_abstract));
> + set_unix_address(&self->transit_address_abstract, 0, true);
> + memset(&self->address_abstract, 0, sizeof(self->address_abstract));
> + set_unix_address(&self->address_abstract, 1, true);
> +
> + /* Pathname addresses. */
> + memset(&self->transit_address_pathname, 0,
> + sizeof(self->transit_address_pathname));
> + set_unix_address(&self->transit_address_pathname, 2, false);
> + memset(&self->address_pathname, 0, sizeof(self->address_pathname));
> + set_unix_address(&self->address_pathname, 3, false);
> }
>
> FIXTURE_TEARDOWN(outside_socket)
> {
> + EXPECT_EQ(0, remove_path(self->transit_address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->address_pathname.unix_addr.sun_path));
> + EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
> }
>
> /*
> @@ -798,6 +892,15 @@ TEST_F(outside_socket, socket_with_different_domain)
> int pipe_child[2], pipe_parent[2];
> char buf_parent;
> int server_socket;
> + const __u16 scope = variant->abstract ?
> + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> + const struct service_fixture *transit_address =
> + variant->abstract ? &self->transit_address_abstract :
> + &self->transit_address_pathname;
> + const struct service_fixture *address =
> + variant->abstract ? &self->address_abstract :
> + &self->address_pathname;
>
> ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> @@ -812,8 +915,7 @@ TEST_F(outside_socket, socket_with_different_domain)
> EXPECT_EQ(0, close(pipe_child[0]));
>
> /* Client always has a domain. */
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> if (variant->child_socket) {
> int data_socket, passed_socket, stream_server;
> @@ -823,8 +925,8 @@ TEST_F(outside_socket, socket_with_different_domain)
> stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_server);
> ASSERT_EQ(0, bind(stream_server,
> - &self->transit_address.unix_addr,
> - self->transit_address.unix_addr_len));
> + &transit_address->unix_addr,
> + transit_address->unix_addr_len));
> ASSERT_EQ(0, listen(stream_server, backlog));
> ASSERT_EQ(1, write(pipe_child[1], ".", 1));
> data_socket = accept(stream_server, NULL, NULL);
> @@ -839,8 +941,8 @@ TEST_F(outside_socket, socket_with_different_domain)
>
> /* Waits for parent signal for connection. */
> ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> - err = connect(client_socket, &self->address.unix_addr,
> - self->address.unix_addr_len);
> + err = connect(client_socket, &address->unix_addr,
> + address->unix_addr_len);
> if (variant->child_socket) {
> EXPECT_EQ(0, err);
> } else {
> @@ -859,9 +961,8 @@ TEST_F(outside_socket, socket_with_different_domain)
>
> ASSERT_LE(0, client_child);
> ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
> - ASSERT_EQ(0, connect(client_child,
> - &self->transit_address.unix_addr,
> - self->transit_address.unix_addr_len));
> + ASSERT_EQ(0, connect(client_child, &transit_address->unix_addr,
> + transit_address->unix_addr_len));
> server_socket = recv_fd(client_child);
> EXPECT_EQ(0, close(client_child));
> } else {
> @@ -870,10 +971,10 @@ TEST_F(outside_socket, socket_with_different_domain)
> ASSERT_LE(0, server_socket);
>
> /* Server always has a domain. */
> - create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> - ASSERT_EQ(0, bind(server_socket, &self->address.unix_addr,
> - self->address.unix_addr_len));
> + ASSERT_EQ(0,
> + bind(server_socket, &address->unix_addr, address->unix_addr_len));
> if (variant->type == SOCK_STREAM)
> ASSERT_EQ(0, listen(server_socket, backlog));
>
> @@ -888,52 +989,85 @@ TEST_F(outside_socket, socket_with_different_domain)
> _metadata->exit_code = KSFT_FAIL;
> }
>
> -static const char stream_path[] = TMP_DIR "/stream.sock";
> -static const char dgram_path[] = TMP_DIR "/dgram.sock";
> -
> /* clang-format off */
> -FIXTURE(various_address_sockets) {};
> +FIXTURE(various_address_sockets) {
> + struct service_fixture stream_pathname_addr, dgram_pathname_addr;
> + struct service_fixture stream_abstract_addr, dgram_abstract_addr;
> +};
> /* clang-format on */
>
> -FIXTURE_VARIANT(various_address_sockets)
> -{
> - const int domain;
> +/*
> + * Test all 4 combinations of abstract and pathname socket scope bits,
> + * plus a case with no Landlock domain at all.
> + */
> +/* clang-format off */
> +FIXTURE_VARIANT(various_address_sockets) {
> + /* clang-format on */
> + const __u16 scope_bits;
> + const bool no_sandbox;
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(various_address_sockets, scope_abstract) {
> + /* clang-format on */
> + .scope_bits = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(various_address_sockets, scope_pathname) {
> + /* clang-format on */
> + .scope_bits = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_scoped_domain) {
> +FIXTURE_VARIANT_ADD(various_address_sockets, scope_both) {
> /* clang-format on */
> - .domain = SCOPE_SANDBOX,
> + .scope_bits = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_other_domain) {
> +FIXTURE_VARIANT_ADD(various_address_sockets, scope_none) {
> /* clang-format on */
> - .domain = OTHER_SANDBOX,
> + .scope_bits = 0,
> };
>
> /* clang-format off */
> -FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_no_domain) {
> +FIXTURE_VARIANT_ADD(various_address_sockets, no_domain) {
> /* clang-format on */
> - .domain = NO_SANDBOX,
> + .no_sandbox = true,
> };
>
> FIXTURE_SETUP(various_address_sockets)
> {
> drop_caps(_metadata);
>
> - umask(0077);
> - ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
> + ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
> +
> + memset(&self->stream_pathname_addr, 0, sizeof(self->stream_pathname_addr));
> + set_unix_address(&self->stream_pathname_addr, 0, false);
> + memset(&self->dgram_pathname_addr, 0, sizeof(self->dgram_pathname_addr));
> + set_unix_address(&self->dgram_pathname_addr, 1, false);
> +
> + memset(&self->stream_abstract_addr, 0, sizeof(self->stream_abstract_addr));
> + set_unix_address(&self->stream_abstract_addr, 2, true);
> + memset(&self->dgram_abstract_addr, 0, sizeof(self->dgram_abstract_addr));
> + set_unix_address(&self->dgram_abstract_addr, 3, true);
> }
>
> FIXTURE_TEARDOWN(various_address_sockets)
> {
> - EXPECT_EQ(0, unlink(stream_path));
> - EXPECT_EQ(0, unlink(dgram_path));
> - EXPECT_EQ(0, rmdir(TMP_DIR));
> + EXPECT_EQ(0, remove_path(self->stream_pathname_addr.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->dgram_pathname_addr.unix_addr.sun_path));
> + EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
> }
>
> -TEST_F(various_address_sockets, scoped_pathname_sockets)
> +/*
> + * Test interaction of various scope flags (controlled by variant->domain)
> + * with pathname and abstract sockets when connecting from a sandboxed
> + * child.
> + */
> +TEST_F(various_address_sockets, scoped_sockets)
> {
> pid_t child;
> int status;
> @@ -942,25 +1076,10 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
> int unnamed_sockets[2];
> int stream_pathname_socket, dgram_pathname_socket,
> stream_abstract_socket, dgram_abstract_socket, data_socket;
> - struct service_fixture stream_abstract_addr, dgram_abstract_addr;
> - struct sockaddr_un stream_pathname_addr = {
> - .sun_family = AF_UNIX,
> - };
> - struct sockaddr_un dgram_pathname_addr = {
> - .sun_family = AF_UNIX,
> - };
> -
> - /* Pathname address. */
> - snprintf(stream_pathname_addr.sun_path,
> - sizeof(stream_pathname_addr.sun_path), "%s", stream_path);
> - snprintf(dgram_pathname_addr.sun_path,
> - sizeof(dgram_pathname_addr.sun_path), "%s", dgram_path);
> -
> - /* Abstract address. */
> - memset(&stream_abstract_addr, 0, sizeof(stream_abstract_addr));
> - set_unix_address(&stream_abstract_addr, 0, true);
> - memset(&dgram_abstract_addr, 0, sizeof(dgram_abstract_addr));
> - set_unix_address(&dgram_abstract_addr, 1, true);
> + bool pathname_restricted =
> + (variant->scope_bits & LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
> + bool abstract_restricted =
> + (variant->scope_bits & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
>
> /* Unnamed address for datagram socket. */
> ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM, 0, unnamed_sockets));
> @@ -975,82 +1094,103 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
> EXPECT_EQ(0, close(pipe_parent[1]));
> EXPECT_EQ(0, close(unnamed_sockets[1]));
>
> - if (variant->domain == SCOPE_SANDBOX)
> - create_scoped_domain(
> - _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> - else if (variant->domain == OTHER_SANDBOX)
> + /* Create domain based on variant. */
> + if (variant->scope_bits)
> + create_scoped_domain(_metadata, variant->scope_bits);
> + else if (!variant->no_sandbox)
> create_fs_domain(_metadata);
>
> /* Waits for parent to listen. */
> ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> EXPECT_EQ(0, close(pipe_parent[0]));
>
> - /* Checks that we can send data through a datagram socket. */
> + /* Checks that we can send data through a unnamed socket. */
an unnamed
> ASSERT_EQ(1, write(unnamed_sockets[0], "a", 1));
> EXPECT_EQ(0, close(unnamed_sockets[0]));
>
> /* Connects with pathname sockets. */
> stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_pathname_socket);
> - ASSERT_EQ(0,
> - connect(stream_pathname_socket, &stream_pathname_addr,
> - sizeof(stream_pathname_addr)));
> - ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
> + err = connect(stream_pathname_socket,
> + &self->stream_pathname_addr.unix_addr,
> + self->stream_pathname_addr.unix_addr_len);
> + if (pathname_restricted) {
> + EXPECT_EQ(-1, err);
> + EXPECT_EQ(EPERM, errno);
> + } else {
> + EXPECT_EQ(0, err);
> + ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
> + }
> EXPECT_EQ(0, close(stream_pathname_socket));
>
> - /* Sends without connection. */
> + /* Sends without connection (pathname). */
> dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_pathname_socket);
> err = sendto(dgram_pathname_socket, "c", 1, 0,
> - &dgram_pathname_addr, sizeof(dgram_pathname_addr));
> - EXPECT_EQ(1, err);
> + &self->dgram_pathname_addr.unix_addr,
> + self->dgram_pathname_addr.unix_addr_len);
> + if (pathname_restricted) {
> + EXPECT_EQ(-1, err);
> + EXPECT_EQ(EPERM, errno);
> + } else {
> + EXPECT_EQ(1, err);
> + }
> +
> + /* Sends with connection (pathname). */
> + err = connect(dgram_pathname_socket,
> + &self->dgram_pathname_addr.unix_addr,
> + self->dgram_pathname_addr.unix_addr_len);
> + if (pathname_restricted) {
> + EXPECT_EQ(-1, err);
> + EXPECT_EQ(EPERM, errno);
> + } else {
> + EXPECT_EQ(0, err);
> + ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
> + }
>
> - /* Sends with connection. */
> - ASSERT_EQ(0,
> - connect(dgram_pathname_socket, &dgram_pathname_addr,
> - sizeof(dgram_pathname_addr)));
> - ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
> EXPECT_EQ(0, close(dgram_pathname_socket));
>
> /* Connects with abstract sockets. */
> stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_abstract_socket);
> err = connect(stream_abstract_socket,
> - &stream_abstract_addr.unix_addr,
> - stream_abstract_addr.unix_addr_len);
> - if (variant->domain == SCOPE_SANDBOX) {
> + &self->stream_abstract_addr.unix_addr,
> + self->stream_abstract_addr.unix_addr_len);
> + if (abstract_restricted) {
> EXPECT_EQ(-1, err);
> EXPECT_EQ(EPERM, errno);
> } else {
> EXPECT_EQ(0, err);
> ASSERT_EQ(1, write(stream_abstract_socket, "e", 1));
> }
> +
> EXPECT_EQ(0, close(stream_abstract_socket));
>
> - /* Sends without connection. */
> + /* Sends without connection (abstract). */
> dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_abstract_socket);
> err = sendto(dgram_abstract_socket, "f", 1, 0,
> - &dgram_abstract_addr.unix_addr,
> - dgram_abstract_addr.unix_addr_len);
> - if (variant->domain == SCOPE_SANDBOX) {
> + &self->dgram_abstract_addr.unix_addr,
> + self->dgram_abstract_addr.unix_addr_len);
> + if (abstract_restricted) {
> EXPECT_EQ(-1, err);
> EXPECT_EQ(EPERM, errno);
> } else {
> EXPECT_EQ(1, err);
> }
>
> - /* Sends with connection. */
> + /* Sends with connection (abstract). */
> err = connect(dgram_abstract_socket,
> - &dgram_abstract_addr.unix_addr,
> - dgram_abstract_addr.unix_addr_len);
> - if (variant->domain == SCOPE_SANDBOX) {
> + &self->dgram_abstract_addr.unix_addr,
> + self->dgram_abstract_addr.unix_addr_len);
> + if (abstract_restricted) {
> EXPECT_EQ(-1, err);
> EXPECT_EQ(EPERM, errno);
> } else {
> EXPECT_EQ(0, err);
> ASSERT_EQ(1, write(dgram_abstract_socket, "g", 1));
> }
> +
> EXPECT_EQ(0, close(dgram_abstract_socket));
>
> _exit(_metadata->exit_code);
> @@ -1062,27 +1202,30 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
> /* Sets up pathname servers. */
> stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_pathname_socket);
> - ASSERT_EQ(0, bind(stream_pathname_socket, &stream_pathname_addr,
> - sizeof(stream_pathname_addr)));
> + ASSERT_EQ(0, bind(stream_pathname_socket,
> + &self->stream_pathname_addr.unix_addr,
> + self->stream_pathname_addr.unix_addr_len));
> ASSERT_EQ(0, listen(stream_pathname_socket, backlog));
>
> dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_pathname_socket);
> - ASSERT_EQ(0, bind(dgram_pathname_socket, &dgram_pathname_addr,
> - sizeof(dgram_pathname_addr)));
> + ASSERT_EQ(0, bind(dgram_pathname_socket,
> + &self->dgram_pathname_addr.unix_addr,
> + self->dgram_pathname_addr.unix_addr_len));
>
> /* Sets up abstract servers. */
> stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
> ASSERT_LE(0, stream_abstract_socket);
> - ASSERT_EQ(0,
> - bind(stream_abstract_socket, &stream_abstract_addr.unix_addr,
> - stream_abstract_addr.unix_addr_len));
> + ASSERT_EQ(0, bind(stream_abstract_socket,
> + &self->stream_abstract_addr.unix_addr,
> + self->stream_abstract_addr.unix_addr_len));
> + ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
>
> dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, dgram_abstract_socket);
> - ASSERT_EQ(0, bind(dgram_abstract_socket, &dgram_abstract_addr.unix_addr,
> - dgram_abstract_addr.unix_addr_len));
> - ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
> + ASSERT_EQ(0, bind(dgram_abstract_socket,
> + &self->dgram_abstract_addr.unix_addr,
> + self->dgram_abstract_addr.unix_addr_len));
>
> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> EXPECT_EQ(0, close(pipe_parent[1]));
> @@ -1092,24 +1235,31 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
> ASSERT_EQ('a', buf_parent);
> EXPECT_LE(0, close(unnamed_sockets[1]));
>
> - /* Reads from pathname sockets. */
> - data_socket = accept(stream_pathname_socket, NULL, NULL);
> - ASSERT_LE(0, data_socket);
> - ASSERT_EQ(1, read(data_socket, &buf_parent, sizeof(buf_parent)));
> - ASSERT_EQ('b', buf_parent);
> - EXPECT_EQ(0, close(data_socket));
> - EXPECT_EQ(0, close(stream_pathname_socket));
> + if (!pathname_restricted) {
> + /*
> + * Reads from pathname sockets if we expect child to be able to
> + * send.
> + */
> + data_socket = accept(stream_pathname_socket, NULL, NULL);
> + ASSERT_LE(0, data_socket);
> + ASSERT_EQ(1,
> + read(data_socket, &buf_parent, sizeof(buf_parent)));
> + ASSERT_EQ('b', buf_parent);
> + EXPECT_EQ(0, close(data_socket));
>
> - ASSERT_EQ(1,
> - read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
> - ASSERT_EQ('c', buf_parent);
> - ASSERT_EQ(1,
> - read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
> - ASSERT_EQ('d', buf_parent);
> - EXPECT_EQ(0, close(dgram_pathname_socket));
> + ASSERT_EQ(1, read(dgram_pathname_socket, &buf_parent,
> + sizeof(buf_parent)));
> + ASSERT_EQ('c', buf_parent);
> + ASSERT_EQ(1, read(dgram_pathname_socket, &buf_parent,
> + sizeof(buf_parent)));
> + ASSERT_EQ('d', buf_parent);
> + }
>
> - if (variant->domain != SCOPE_SANDBOX) {
> - /* Reads from abstract sockets if allowed to send. */
> + if (!abstract_restricted) {
> + /*
> + * Reads from abstract sockets if we expect child to be able to
> + * send.
> + */
> data_socket = accept(stream_abstract_socket, NULL, NULL);
> ASSERT_LE(0, data_socket);
> ASSERT_EQ(1,
> @@ -1125,30 +1275,73 @@ TEST_F(various_address_sockets, scoped_pathname_sockets)
> ASSERT_EQ('g', buf_parent);
> }
>
> - /* Waits for all abstract socket tests. */
> + /* Waits for child to complete, and only close the socket afterwards. */
> ASSERT_EQ(child, waitpid(child, &status, 0));
> EXPECT_EQ(0, close(stream_abstract_socket));
> EXPECT_EQ(0, close(dgram_abstract_socket));
> + EXPECT_EQ(0, close(stream_pathname_socket));
> + EXPECT_EQ(0, close(dgram_pathname_socket));
>
> if (WIFSIGNALED(status) || !WIFEXITED(status) ||
> WEXITSTATUS(status) != EXIT_SUCCESS)
> _metadata->exit_code = KSFT_FAIL;
> }
>
> -TEST(datagram_sockets)
> +/* Fixture for datagram_sockets and self_connect tests */
> +FIXTURE(socket_type_test)
> {
> struct service_fixture connected_addr, non_connected_addr;
> +};
> +
> +FIXTURE_VARIANT(socket_type_test)
> +{
> + const bool abstract;
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket_type_test, abstract) {
> + /* clang-format on */
> + .abstract = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket_type_test, pathname) {
> + /* clang-format on */
> + .abstract = false,
> +};
> +
> +FIXTURE_SETUP(socket_type_test)
> +{
> + drop_caps(_metadata);
> +
> + if (!variant->abstract)
> + ASSERT_EQ(0, mkdir(PATHNAME_UNIX_SOCK_DIR, 0700));
> +
> + memset(&self->connected_addr, 0, sizeof(self->connected_addr));
> + set_unix_address(&self->connected_addr, 0, variant->abstract);
> + memset(&self->non_connected_addr, 0, sizeof(self->non_connected_addr));
> + set_unix_address(&self->non_connected_addr, 1, variant->abstract);
> +}
> +
> +FIXTURE_TEARDOWN(socket_type_test)
> +{
> + if (!variant->abstract) {
> + EXPECT_EQ(0, remove_path(self->connected_addr.unix_addr.sun_path));
> + EXPECT_EQ(0, remove_path(self->non_connected_addr.unix_addr.sun_path));
> + EXPECT_EQ(0, rmdir(PATHNAME_UNIX_SOCK_DIR));
> + }
> +}
> +
> +TEST_F(socket_type_test, datagram_sockets)
> +{
> int server_conn_socket, server_unconn_socket;
> int pipe_parent[2], pipe_child[2];
> int status;
> char buf;
> pid_t child;
> -
> - drop_caps(_metadata);
> - memset(&connected_addr, 0, sizeof(connected_addr));
> - set_unix_address(&connected_addr, 0, true);
> - memset(&non_connected_addr, 0, sizeof(non_connected_addr));
> - set_unix_address(&non_connected_addr, 1, true);
> + const __u16 scope = variant->abstract ?
> + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
>
> ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> @@ -1169,8 +1362,9 @@ TEST(datagram_sockets)
> /* Waits for parent to listen. */
> ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
> ASSERT_EQ(0,
> - connect(client_conn_socket, &connected_addr.unix_addr,
> - connected_addr.unix_addr_len));
> + connect(client_conn_socket,
> + &self->connected_addr.unix_addr,
> + self->connected_addr.unix_addr_len));
>
> /*
> * Both connected and non-connected sockets can send data when
> @@ -1178,13 +1372,12 @@ TEST(datagram_sockets)
> */
> ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
> ASSERT_EQ(1, sendto(client_unconn_socket, ".", 1, 0,
> - &non_connected_addr.unix_addr,
> - non_connected_addr.unix_addr_len));
> + &self->non_connected_addr.unix_addr,
> + self->non_connected_addr.unix_addr_len));
> ASSERT_EQ(1, write(pipe_child[1], ".", 1));
>
> /* Scopes the domain. */
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> /*
> * Connected socket sends data to the receiver, but the
> @@ -1192,8 +1385,8 @@ TEST(datagram_sockets)
> */
> ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
> ASSERT_EQ(-1, sendto(client_unconn_socket, ".", 1, 0,
> - &non_connected_addr.unix_addr,
> - non_connected_addr.unix_addr_len));
> + &self->non_connected_addr.unix_addr,
> + self->non_connected_addr.unix_addr_len));
> ASSERT_EQ(EPERM, errno);
> ASSERT_EQ(1, write(pipe_child[1], ".", 1));
>
> @@ -1210,10 +1403,11 @@ TEST(datagram_sockets)
> ASSERT_LE(0, server_conn_socket);
> ASSERT_LE(0, server_unconn_socket);
>
> - ASSERT_EQ(0, bind(server_conn_socket, &connected_addr.unix_addr,
> - connected_addr.unix_addr_len));
> - ASSERT_EQ(0, bind(server_unconn_socket, &non_connected_addr.unix_addr,
> - non_connected_addr.unix_addr_len));
> + ASSERT_EQ(0, bind(server_conn_socket, &self->connected_addr.unix_addr,
> + self->connected_addr.unix_addr_len));
> + ASSERT_EQ(0, bind(server_unconn_socket,
> + &self->non_connected_addr.unix_addr,
> + self->non_connected_addr.unix_addr_len));
> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
>
> /* Waits for child to test. */
> @@ -1238,52 +1432,49 @@ TEST(datagram_sockets)
> _metadata->exit_code = KSFT_FAIL;
> }
>
> -TEST(self_connect)
> +TEST_F(socket_type_test, self_connect)
> {
> - struct service_fixture connected_addr, non_connected_addr;
> int connected_socket, non_connected_socket, status;
> pid_t child;
> -
> - drop_caps(_metadata);
> - memset(&connected_addr, 0, sizeof(connected_addr));
> - set_unix_address(&connected_addr, 0, true);
> - memset(&non_connected_addr, 0, sizeof(non_connected_addr));
> - set_unix_address(&non_connected_addr, 1, true);
> + const __u16 scope = variant->abstract ?
> + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET :
> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
>
> connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
> non_connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
> ASSERT_LE(0, connected_socket);
> ASSERT_LE(0, non_connected_socket);
>
> - ASSERT_EQ(0, bind(connected_socket, &connected_addr.unix_addr,
> - connected_addr.unix_addr_len));
> - ASSERT_EQ(0, bind(non_connected_socket, &non_connected_addr.unix_addr,
> - non_connected_addr.unix_addr_len));
> + ASSERT_EQ(0, bind(connected_socket, &self->connected_addr.unix_addr,
> + self->connected_addr.unix_addr_len));
> + ASSERT_EQ(0, bind(non_connected_socket,
> + &self->non_connected_addr.unix_addr,
> + self->non_connected_addr.unix_addr_len));
>
> child = fork();
> ASSERT_LE(0, child);
> if (child == 0) {
> /* Child's domain is scoped. */
> - create_scoped_domain(_metadata,
> - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
> + create_scoped_domain(_metadata, scope);
>
> /*
> * The child inherits the sockets, and cannot connect or
> * send data to them.
> */
> ASSERT_EQ(-1,
> - connect(connected_socket, &connected_addr.unix_addr,
> - connected_addr.unix_addr_len));
> + connect(connected_socket,
> + &self->connected_addr.unix_addr,
> + self->connected_addr.unix_addr_len));
> ASSERT_EQ(EPERM, errno);
>
> ASSERT_EQ(-1, sendto(connected_socket, ".", 1, 0,
> - &connected_addr.unix_addr,
> - connected_addr.unix_addr_len));
> + &self->connected_addr.unix_addr,
> + self->connected_addr.unix_addr_len));
> ASSERT_EQ(EPERM, errno);
>
> ASSERT_EQ(-1, sendto(non_connected_socket, ".", 1, 0,
> - &non_connected_addr.unix_addr,
> - non_connected_addr.unix_addr_len));
> + &self->non_connected_addr.unix_addr,
> + self->non_connected_addr.unix_addr_len));
> ASSERT_EQ(EPERM, errno);
>
> EXPECT_EQ(0, close(connected_socket));
> --
> 2.52.0
>
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-01-09 12:01 ` Mickaël Salaün
@ 2026-01-31 17:41 ` Tingmao Wang
2026-02-02 20:32 ` Mickaël Salaün
0 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2026-01-31 17:41 UTC (permalink / raw)
To: Mickaël Salaün, Günther Noack, Demi Marie Obenour
Cc: Günther Noack, Alyssa Ross, Jann Horn, Tahera Fahimi,
Justin Suess, linux-security-module
On 1/9/26 12:01, Mickaël Salaün wrote:
> On Wed, Dec 31, 2025 at 11:54:27AM -0500, Demi Marie Obenour wrote:
>> On 12/30/25 18:16, Günther Noack wrote:
>>> [...]
>>> On Tue, Dec 30, 2025 at 05:20:18PM +0000, Tingmao Wang wrote:
>>>> [...]
>>>
>>> What is unclear to me from the examples and the description is: Why is
>>> the boundary between allowed and denied connection targets drawn at
>>> the border of the Landlock domain (the "scope") and why don't we solve
>>> this with the file-system-based approach described in [1]?
>>>
>>> **Do we have existing use cases where a service is both offered and
>>> connected to all from within the same Landlock domain, and where the
>>> process enforcing the policy does not control the child process enough
>>> so that it would be possible to allow-list it with a
>>> LANDLOCK_ACCESS_FS_CONNECT_UNIX rule?**
>
> Yes, with the sandboxer use case. It's similar to a container that
> doesn't know all programs that could be run in it. We should be able to
> create sandboxes that don't assume (nor restrict) internal IPCs (or any
> kind of direct process I/O).
>
> Landlock should make it possible to scope any kind of IPC. It's a
> mental model which is easy to understand and that should be enforced by
> default. Of course, we need some ways to add exceptions to this
> deny-by-default policy, and that's why we also need the FS-based control
> mechanism.
>
>>>
>>> If we do not have such a use case, it seems that the planned FS-based
>>> control mechanism from [1] would do the same job? Long term, we might
>>> be better off if we only have only one control -- as we have discussed
>>> in [2], having two of these might mean that they interact in
>>> unconventional and possibly confusing ways.
>>
>> I agree with this.
>
> Both approaches are complementary/orthogonal and make sense long term
> too. This might be the first time we can restrict the same operations,
> but I'm pretty sure it will not be the last, and it's OK. Handled FS
> access and scoped restrictions are two ways to describe a part of the
> security policy. Having different ways makes the interface much simpler
> than a generic one that would have to take into account all potential
> future access controls.
>
> One thing to keep in mind is that UAPI doesn't have to map 1:1 to the
> kernel implementation, but the UAPI is stable and future proof, whereas
> the kernel implementation can change a lot.
>
> The ruleset's handled fields serve two purposes: define what should be
> denied by default, and define which type of rules are valid
> (compatibility). The ruleset's scoped field serve similar purposes but
> it also implies implicit rules (i.e. communications with processes
> inside the sandbox are allowed). Without the soped field, we would have
> to create dedicated handled field per type (i.e. scope's bit) and
> dedicated rule type for the related handled field, which would make the
> interface more generic but also more complex, for something which is not
> needed.
>
> In a nutshell, in the case of the FS-based and scope-based unix socket
> control, we should see one kind of restrictions (e.g. connect to unix
> socket), which can accept two types of rules: (explicit) file path, or
> (implicit) peer's scope. Access should be granted as long as a rule
> matches, whatever its type.
>
> This rationale should be explained in a commit message.
>
>>
>>> Apart from that, there are some other weaker hints that make me
>>> slightly critical of this patch set:
>>>
>>> * We discussed the idea that a FS-based path_beneath rule would act
>>> implicitly also as an exception for
>>> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET, in [2] - one possible way to
>>> interpret this is that the gravity of the system's logic pulls us
>>> back towards a FS-based control, and we would have to swim less
>>> against the stream if we integrated the Unix connect() control in
>>> that way?
While I do think being able to control UNIX socket access via domain
scoping is useful (after all, it is an additional bit of information /
relationship that "normal" filesystem objects doesn't have, and
applications using Landlock might find it useful to control access based
on this extra information, either to simplify the policy, or because a
generic sandboxer can't come up with a pre-determined list of UNIX socket
fs rules, e.g. for applications which places socket in hard-coded "/tmp"),
I do share the worry about how this might get confusing from an API
perspective, as discussed in my GitHub comment [1].
Another way to put it is that, if FS-based and scope-based controls
interacts in the above proposed way, both mechanisms feel like "poking
holes" in the other. But as Mickaël said, one can think of the two
mechanisms not as independent controls, but rather as two interfaces for
the same control. The socket access control is "enabled" if either the
LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
proposed in this patch is enabled.
With that said, I can think of some alternative ways that might make this
API look "better" (from a subjective point of view, feedback welcome),
however it does mean more delays, and specifically, these will depend on
LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
One possibility is to simply always allow a Landlock domain to connect to
its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
handled, otherwise all sockets are allowed). This might be reasonable, as
one can only connect to a socket it creates if it has the permission to
create it in the first place, which is already controlled by
LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
flexibility here - if for some reason the sandboxer don't want to allow
access to any (pathname) sockets, even the sandboxed app's own ones, it
can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
Another possibility is to have this scope control as part of the
LANDLOCK_ACCESS_FS_RESOLVE_UNIX rule itself, rather than as a separate
scope bit, via introducing a "rule flag" (which is a new mechanism
proposed in [2]) which I will tentatively call
(LANDLOCK_ADD_RULE_)SAME_SCOPE_ONLY. So for example:
- If the sandboxer wants to allow all socket access, don't handle
LANDLOCK_ACCESS_FS_RESOLVE_UNIX at all, or handle it but have an allow
rule at / with no flags.
- If it wants to allow access to only the sandboxed app's own sockets,
handle LANDLOCK_ACCESS_FS_RESOLVE_UNIX, then place one allow rule on /
with the rule flag SAME_SCOPE_ONLY. This means that the allow rule
"UNIX socket under /" only allows connecting to sockets in the same
domain scope. Additional rules without this rule flag can be placed on
more specific places to add "exceptions" to allow access to more
privileged sockets.
- To allow access to specific sockets only, not even the sandboxed app's
own sockets, just use LANDLOCK_ACCESS_FS_RESOLVE_UNIX without this new
rule flag.
This is slightly more flexible than just the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
scope bit plus LANDLOCK_ACCESS_FS_RESOLVE_UNIX, as it allows the sandboxer
to specify where an application can connect, even with sockets in its own
domain, and it also nicely avoids the "what feels like two independent
controls poking holes in each other" problem. But it does mean more
complexity (although hopefully not too much), as we now need to introduce
the rule flags concept, but that is required for some other proposed stuff
anyway - quiet flags [3] and no_inherit [4] rules, and the rule flags
design is in a good state I think.
What do folks think?
(btw, when I say "sandboxed app" this can of course also mean multiple
applications in the same sandbox, e.g. like a container runtime as Mickaël
pointed out, which raises the probability that just having
LANDLOCK_ACCESS_FS_RESOLVE_UNIX would be insufficient, e.g. when bind
mounts are involved, or other cases which I haven't thought of right now)
[1]: https://github.com/landlock-lsm/linux/issues/36#issuecomment-3693123942
[2]: https://lore.kernel.org/all/f238931bc813fc50fc8e11a007a8ad2136024df3.1766330134.git.m@maowtm.org/
[3]: https://lore.kernel.org/all/cover.1766330134.git.m@maowtm.org/
[4]: https://lore.kernel.org/all/20251221194301.247484-1-utilityemal77@gmail.com/
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
2026-01-29 21:27 ` Mickaël Salaün
@ 2026-01-31 17:48 ` Tingmao Wang
2026-02-02 20:14 ` Mickaël Salaün
0 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2026-01-31 17:48 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
On 1/29/26 21:27, Mickaël Salaün wrote:
> We should have a (potentially small) description of what this patch
> does, even if it's a bit redundant with the subject.
>
>
> On Tue, Dec 30, 2025 at 05:20:21PM +0000, Tingmao Wang wrote:
>> Signed-off-by: Tingmao Wang <m@maowtm.org>
>> ---
>>
>> I've decided to use "u" as the character to control this scope bit since
>> it stands for (normal) Unix sockets. Imo using "p" or "n" would make it less
>> clear / memorable. Open to suggestions.
>
> Looks good to me.
>
>>
>> Also, open to suggestion whether socket scoping (pathname and abstract)
>> should be enabled by default, if LL_SCOPED is not set. This would break
>> backward compatibility, but maybe we shouldn't guarentee backward
>> compatibility of this sandboxer in the first place, and almost all cases
>> of Landlock usage would want socket scoping.
>
> I agree that this example could have better defaults, but this should be
> done with a standalone patch series. An important point to keep in mind
> is that this example is used by developers (e.g. potential copy/paste),
> so we need to be careful to not encourage them to create code which is
> backward incompatible. I think the best way to do it is to request a
> default behavior for a specific Landlock ABI version (e.g. with a new
> parameter).
Just to make sure we're on the same page, I was only talking about whether
we keep the behavior of the sandboxer "backward compatible" (i.e. if
someone ran a program that relied on accessing UNIX sockets of more
privileged programs, if we make the sandboxer start enforcing socket
scoping by default, their program would stop working under this
sandboxer), I was not suggesting that we do something which will cause the
sandboxer itself to no longer work on older kernels.
But on second thought, the sandboxer is already not designed to be relied
upon to always behave the same way after an update, since the user don't
get to choose which handled access rights are added to the ruleset. With
new bits added to either ACCESS_FS_ROUGHLY_READ or
ACCESS_FS_ROUGHLY_WRITE, the policy effectively gets more restrictive
automatically. For example, once Günther's patch [1] that adds
LANDLOCK_ACCESS_FS_RESOLVE_UNIX is merged, the sandboxer will effectively
starts restricting pathname UNIX sockets "by default" anyway (under any
dirs not listed in LL_FS_RW). So maybe we don't need to think too hard
about this.
[1]: https://lore.kernel.org/all/20260119203457.97676-6-gnoack3000@gmail.com/
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too.
2026-01-29 21:28 ` Mickaël Salaün
@ 2026-02-02 0:06 ` Tingmao Wang
0 siblings, 0 replies; 41+ messages in thread
From: Tingmao Wang @ 2026-02-02 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
On 1/29/26 21:28, Mickaël Salaün wrote:
> [...]
> On Tue, Dec 30, 2025 at 05:20:23PM +0000, Tingmao Wang wrote:
>> [...]
>> @@ -308,6 +413,12 @@ TEST_F(scoped_audit, connect_to_child)
>> char buf;
>> int dgram_client;
>> struct audit_records records;
>> + struct service_fixture *const dgram_address =
>> + variant->abstract_socket ? &self->dgram_address_abstract :
>> + &self->dgram_address_pathname;
>> + size_t log_match_remaining = 500;
>
> const
>
> Why this number? Could you please follow the same logic as in
> matches_log_fs_extra()?
It's not a const since we decrement it below as we fill log_match with
stpncpy().
matches_log_fs_extra() uses 2*PATH_MAX + length of various fixed strings.
I guess since the path is a resolved absolute path which can vary based on
where this test is ran, it is safest to use a value based on PATH_MAX. I
will update.
>
>> + char log_match[log_match_remaining];
>> + char *log_match_cursor = log_match;
>>
>> /* Makes sure there is no superfluous logged records. */
>> EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
>> @@ -330,8 +441,8 @@ TEST_F(scoped_audit, connect_to_child)
>>
>> dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
>> ASSERT_LE(0, dgram_server);
>> - ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
>> - self->dgram_address.unix_addr_len));
>> + ASSERT_EQ(0, bind(dgram_server, &dgram_address->unix_addr,
>> + dgram_address->unix_addr_len));
>>
>> /* Signals to the parent that child is listening. */
>> ASSERT_EQ(1, write(pipe_child[1], ".", 1));
>> @@ -345,7 +456,9 @@ TEST_F(scoped_audit, connect_to_child)
>> EXPECT_EQ(0, close(pipe_child[1]));
>> EXPECT_EQ(0, close(pipe_parent[0]));
>>
>> - create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
>> + create_scoped_domain(_metadata,
>> + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
>> + LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET);
>>
>> /* Signals that the parent is in a domain, if any. */
>> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
>> @@ -355,19 +468,62 @@ TEST_F(scoped_audit, connect_to_child)
>>
>> /* Waits for the child to listen */
>> ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
>> - err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
>> - self->dgram_address.unix_addr_len);
>> + err_dgram = connect(dgram_client, &dgram_address->unix_addr,
>> + dgram_address->unix_addr_len);
>> EXPECT_EQ(-1, err_dgram);
>> EXPECT_EQ(EPERM, errno);
>>
>> - EXPECT_EQ(
>> - 0,
>> - audit_match_record(
>> - self->audit_fd, AUDIT_LANDLOCK_ACCESS,
>> + if (variant->abstract_socket) {
>> + log_match_cursor = stpncpy(
>> + log_match,
>> REGEX_LANDLOCK_PREFIX
>> " blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
>> "[0-9A-F]\\+$",
>> - NULL));
>> + log_match_remaining);
>> + log_match_remaining =
>> + sizeof(log_match) - (log_match_cursor - log_match);
>> + ASSERT_NE(0, log_match_remaining);
>> + } else {
>> + /*
>> + * It is assumed that absolute_path does not contain control
>> + * characters nor spaces, see audit_string_contains_control().
>> + */
>> + char *absolute_path =
>
> const char *absolute_path
Can't use const char * here since we free() it later:
scoped_unix_test.c:513:22: warning: passing argument 1 of ‘free’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
513 | free(absolute_path);
| ^~~~~~~~~~~~~
But I guess we can char *const. Will update to:
char *const absolute_path =
realpath(dgram_address->unix_addr.sun_path, NULL);
>
>> + realpath(dgram_address->unix_addr.sun_path, NULL);
>> +
>> + EXPECT_NE(NULL, absolute_path)
>> + {
>> + TH_LOG("realpath() failed: %s", strerror(errno));
>> + return;
>> + }
>> +
>> + log_match_cursor =
>> + stpncpy(log_match,
>> + REGEX_LANDLOCK_PREFIX
>> + " blockers=scope\\.pathname_unix_socket path=\"",
>> + log_match_remaining);
>> + log_match_remaining =
>> + sizeof(log_match) - (log_match_cursor - log_match);
>> + ASSERT_NE(0, log_match_remaining);
>> + log_match_cursor = regex_escape(absolute_path, log_match_cursor,
>> + log_match_remaining);
>> + free(absolute_path);
>> + if (log_match_cursor < 0) {
>> + TH_LOG("regex_escape() failed (buffer too small)");
>> + return;
>> + }
>> + log_match_remaining =
>> + sizeof(log_match) - (log_match_cursor - log_match);
>> + ASSERT_NE(0, log_match_remaining);
>> + log_match_cursor =
>> + stpncpy(log_match_cursor, "\"$", log_match_remaining);
>> + log_match_remaining =
>> + sizeof(log_match) - (log_match_cursor - log_match);
>> + ASSERT_NE(0, log_match_remaining);
>> + }
>> +
>> + EXPECT_EQ(0, audit_match_record(self->audit_fd, AUDIT_LANDLOCK_ACCESS,
>> + log_match, NULL));
>>
>> ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
>> EXPECT_EQ(0, close(dgram_client));
>> --
>> 2.52.0
>>
>>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
2026-01-31 17:48 ` Tingmao Wang
@ 2026-02-02 20:14 ` Mickaël Salaün
0 siblings, 0 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-02 20:14 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Justin Suess, linux-security-module
On Sat, Jan 31, 2026 at 05:48:24PM +0000, Tingmao Wang wrote:
> On 1/29/26 21:27, Mickaël Salaün wrote:
> > We should have a (potentially small) description of what this patch
> > does, even if it's a bit redundant with the subject.
> >
> >
> > On Tue, Dec 30, 2025 at 05:20:21PM +0000, Tingmao Wang wrote:
> >> Signed-off-by: Tingmao Wang <m@maowtm.org>
> >> ---
> >>
> >> I've decided to use "u" as the character to control this scope bit since
> >> it stands for (normal) Unix sockets. Imo using "p" or "n" would make it less
> >> clear / memorable. Open to suggestions.
> >
> > Looks good to me.
> >
> >>
> >> Also, open to suggestion whether socket scoping (pathname and abstract)
> >> should be enabled by default, if LL_SCOPED is not set. This would break
> >> backward compatibility, but maybe we shouldn't guarentee backward
> >> compatibility of this sandboxer in the first place, and almost all cases
> >> of Landlock usage would want socket scoping.
> >
> > I agree that this example could have better defaults, but this should be
> > done with a standalone patch series. An important point to keep in mind
> > is that this example is used by developers (e.g. potential copy/paste),
> > so we need to be careful to not encourage them to create code which is
> > backward incompatible. I think the best way to do it is to request a
> > default behavior for a specific Landlock ABI version (e.g. with a new
> > parameter).
>
> Just to make sure we're on the same page, I was only talking about whether
> we keep the behavior of the sandboxer "backward compatible" (i.e. if
> someone ran a program that relied on accessing UNIX sockets of more
> privileged programs, if we make the sandboxer start enforcing socket
> scoping by default, their program would stop working under this
> sandboxer), I was not suggesting that we do something which will cause the
> sandboxer itself to no longer work on older kernels.
Yep, I extrapolated a bit.
>
> But on second thought, the sandboxer is already not designed to be relied
> upon to always behave the same way after an update, since the user don't
> get to choose which handled access rights are added to the ruleset. With
> new bits added to either ACCESS_FS_ROUGHLY_READ or
> ACCESS_FS_ROUGHLY_WRITE, the policy effectively gets more restrictive
> automatically. For example, once Günther's patch [1] that adds
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX is merged, the sandboxer will effectively
> starts restricting pathname UNIX sockets "by default" anyway (under any
> dirs not listed in LL_FS_RW). So maybe we don't need to think too hard
> about this.
Indeed :)
>
> [1]: https://lore.kernel.org/all/20260119203457.97676-6-gnoack3000@gmail.com/
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-01-31 17:41 ` Tingmao Wang
@ 2026-02-02 20:32 ` Mickaël Salaün
2026-02-02 22:03 ` Justin Suess
0 siblings, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-02 20:32 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Günther Noack,
Alyssa Ross, Jann Horn, Tahera Fahimi, Justin Suess,
linux-security-module
On Sat, Jan 31, 2026 at 05:41:14PM +0000, Tingmao Wang wrote:
> On 1/9/26 12:01, Mickaël Salaün wrote:
> > On Wed, Dec 31, 2025 at 11:54:27AM -0500, Demi Marie Obenour wrote:
> >> On 12/30/25 18:16, Günther Noack wrote:
> >>> [...]
> >>> On Tue, Dec 30, 2025 at 05:20:18PM +0000, Tingmao Wang wrote:
> >>>> [...]
> >>>
> >>> What is unclear to me from the examples and the description is: Why is
> >>> the boundary between allowed and denied connection targets drawn at
> >>> the border of the Landlock domain (the "scope") and why don't we solve
> >>> this with the file-system-based approach described in [1]?
> >>>
> >>> **Do we have existing use cases where a service is both offered and
> >>> connected to all from within the same Landlock domain, and where the
> >>> process enforcing the policy does not control the child process enough
> >>> so that it would be possible to allow-list it with a
> >>> LANDLOCK_ACCESS_FS_CONNECT_UNIX rule?**
> >
> > Yes, with the sandboxer use case. It's similar to a container that
> > doesn't know all programs that could be run in it. We should be able to
> > create sandboxes that don't assume (nor restrict) internal IPCs (or any
> > kind of direct process I/O).
> >
> > Landlock should make it possible to scope any kind of IPC. It's a
> > mental model which is easy to understand and that should be enforced by
> > default. Of course, we need some ways to add exceptions to this
> > deny-by-default policy, and that's why we also need the FS-based control
> > mechanism.
> >
> >>>
> >>> If we do not have such a use case, it seems that the planned FS-based
> >>> control mechanism from [1] would do the same job? Long term, we might
> >>> be better off if we only have only one control -- as we have discussed
> >>> in [2], having two of these might mean that they interact in
> >>> unconventional and possibly confusing ways.
> >>
> >> I agree with this.
> >
> > Both approaches are complementary/orthogonal and make sense long term
> > too. This might be the first time we can restrict the same operations,
> > but I'm pretty sure it will not be the last, and it's OK. Handled FS
> > access and scoped restrictions are two ways to describe a part of the
> > security policy. Having different ways makes the interface much simpler
> > than a generic one that would have to take into account all potential
> > future access controls.
> >
> > One thing to keep in mind is that UAPI doesn't have to map 1:1 to the
> > kernel implementation, but the UAPI is stable and future proof, whereas
> > the kernel implementation can change a lot.
> >
> > The ruleset's handled fields serve two purposes: define what should be
> > denied by default, and define which type of rules are valid
> > (compatibility). The ruleset's scoped field serve similar purposes but
> > it also implies implicit rules (i.e. communications with processes
> > inside the sandbox are allowed). Without the soped field, we would have
> > to create dedicated handled field per type (i.e. scope's bit) and
> > dedicated rule type for the related handled field, which would make the
> > interface more generic but also more complex, for something which is not
> > needed.
> >
> > In a nutshell, in the case of the FS-based and scope-based unix socket
> > control, we should see one kind of restrictions (e.g. connect to unix
> > socket), which can accept two types of rules: (explicit) file path, or
> > (implicit) peer's scope. Access should be granted as long as a rule
> > matches, whatever its type.
> >
> > This rationale should be explained in a commit message.
> >
> >>
> >>> Apart from that, there are some other weaker hints that make me
> >>> slightly critical of this patch set:
> >>>
> >>> * We discussed the idea that a FS-based path_beneath rule would act
> >>> implicitly also as an exception for
> >>> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET, in [2] - one possible way to
> >>> interpret this is that the gravity of the system's logic pulls us
> >>> back towards a FS-based control, and we would have to swim less
> >>> against the stream if we integrated the Unix connect() control in
> >>> that way?
>
> While I do think being able to control UNIX socket access via domain
> scoping is useful (after all, it is an additional bit of information /
> relationship that "normal" filesystem objects doesn't have, and
> applications using Landlock might find it useful to control access based
> on this extra information, either to simplify the policy, or because a
> generic sandboxer can't come up with a pre-determined list of UNIX socket
> fs rules, e.g. for applications which places socket in hard-coded "/tmp"),
> I do share the worry about how this might get confusing from an API
> perspective, as discussed in my GitHub comment [1].
>
> Another way to put it is that, if FS-based and scope-based controls
> interacts in the above proposed way, both mechanisms feel like "poking
> holes" in the other. But as Mickaël said, one can think of the two
> mechanisms not as independent controls, but rather as two interfaces for
> the same control. The socket access control is "enabled" if either the
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> proposed in this patch is enabled.
>
> With that said, I can think of some alternative ways that might make this
> API look "better" (from a subjective point of view, feedback welcome),
> however it does mean more delays, and specifically, these will depend on
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
>
> One possibility is to simply always allow a Landlock domain to connect to
> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> handled, otherwise all sockets are allowed). This might be reasonable, as
> one can only connect to a socket it creates if it has the permission to
> create it in the first place, which is already controlled by
> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> flexibility here - if for some reason the sandboxer don't want to allow
> access to any (pathname) sockets, even the sandboxed app's own ones, it
> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
socket, not to connect. I guess you was thinking about
LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>
> Another possibility is to have this scope control as part of the
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX rule itself, rather than as a separate
> scope bit, via introducing a "rule flag" (which is a new mechanism
> proposed in [2]) which I will tentatively call
> (LANDLOCK_ADD_RULE_)SAME_SCOPE_ONLY. So for example:
>
> - If the sandboxer wants to allow all socket access, don't handle
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX at all, or handle it but have an allow
> rule at / with no flags.
>
> - If it wants to allow access to only the sandboxed app's own sockets,
> handle LANDLOCK_ACCESS_FS_RESOLVE_UNIX, then place one allow rule on /
> with the rule flag SAME_SCOPE_ONLY. This means that the allow rule
> "UNIX socket under /" only allows connecting to sockets in the same
> domain scope. Additional rules without this rule flag can be placed on
> more specific places to add "exceptions" to allow access to more
> privileged sockets.
>
> - To allow access to specific sockets only, not even the sandboxed app's
> own sockets, just use LANDLOCK_ACCESS_FS_RESOLVE_UNIX without this new
> rule flag.
>
> This is slightly more flexible than just the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> scope bit plus LANDLOCK_ACCESS_FS_RESOLVE_UNIX, as it allows the sandboxer
> to specify where an application can connect, even with sockets in its own
> domain, and it also nicely avoids the "what feels like two independent
> controls poking holes in each other" problem. But it does mean more
> complexity (although hopefully not too much), as we now need to introduce
> the rule flags concept, but that is required for some other proposed stuff
> anyway - quiet flags [3] and no_inherit [4] rules, and the rule flags
> design is in a good state I think.
This looks too complex and would intertwine both concept, which is more
confusing IMO.
>
> What do folks think?
I'd like to keep a clean API, with a "scoped" field handling IPC
scoping, and an "handled_access_fs" field handling filesystem-related
accesses.
One thing to keep in mind is that we could add a new kind of "handled"
field that would enable to add rules identifying e.g. processes,
cgroups, or Landlock domains, and that could be used to add exceptions
to the current scopes. This means that we need to have a generic way to
handle this case.
What is the issue with two complementary interfaces (scope and access)
used to express a policy about connecting to UNIX sockets? We just need
to make sure that scopes and handled_access_fs dealing with UNIX sockets
are like binary OR: if the scope is set, then the domain can communicate
with peers which are in the same domain, and if the handled_access_fs
right is set, then the domain can only communicate with matching sockets
(OR scoped ones if the scope is set).
My main concern is about user space libraries and users that may want to
have conditional enforcement for compatibility reasons e.g., only
enforce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (let's say ABI v8) if it can
also set LANDLOCK_ACCESS_FS_RESOLVE_UNIX (let's say ABI v9). I see two
ways to deal with this case (if needed):
- add synthetic access right to easily let users "combine" two access
rigths or none;
- have a more generic way to AND and OR access rights. I'm thinking
about updating the Rust library in this direction.
Anyway, we need to decide if this should be merged in Linux 7.0 (next
week) or not. I'd prefer to merge it now because I think it works well
and it's not a new concept wrt the abstract UNIX socket scoping.
However, if there are any concern, I'd like to hear them now and I can
delay this merge if needed. This patch series still need a new version
but that should only be about cosmetic fixes. WDYT?
>
> (btw, when I say "sandboxed app" this can of course also mean multiple
> applications in the same sandbox, e.g. like a container runtime as Mickaël
> pointed out, which raises the probability that just having
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX would be insufficient, e.g. when bind
> mounts are involved, or other cases which I haven't thought of right now)
>
> [1]: https://github.com/landlock-lsm/linux/issues/36#issuecomment-3693123942
> [2]: https://lore.kernel.org/all/f238931bc813fc50fc8e11a007a8ad2136024df3.1766330134.git.m@maowtm.org/
> [3]: https://lore.kernel.org/all/cover.1766330134.git.m@maowtm.org/
> [4]: https://lore.kernel.org/all/20251221194301.247484-1-utilityemal77@gmail.com/
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-02 20:32 ` Mickaël Salaün
@ 2026-02-02 22:03 ` Justin Suess
2026-02-03 1:26 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Justin Suess @ 2026-02-02 22:03 UTC (permalink / raw)
To: Mickaël Salaün, Tingmao Wang
Cc: Günther Noack, Demi Marie Obenour, Günther Noack,
Alyssa Ross, Jann Horn, Tahera Fahimi, linux-security-module
On 2/2/26 15:32, Mickaël Salaün wrote:
> On Sat, Jan 31, 2026 at 05:41:14PM +0000, Tingmao Wang wrote:
>> On 1/9/26 12:01, Mickaël Salaün wrote:
>>> On Wed, Dec 31, 2025 at 11:54:27AM -0500, Demi Marie Obenour wrote:
>>>> On 12/30/25 18:16, Günther Noack wrote:
>>>>> [...]
>>>>> On Tue, Dec 30, 2025 at 05:20:18PM +0000, Tingmao Wang wrote:
>>>>>> [...]
>>>>> What is unclear to me from the examples and the description is: Why is
>>>>> the boundary between allowed and denied connection targets drawn at
>>>>> the border of the Landlock domain (the "scope") and why don't we solve
>>>>> this with the file-system-based approach described in [1]?
>>>>>
>>>>> **Do we have existing use cases where a service is both offered and
>>>>> connected to all from within the same Landlock domain, and where the
>>>>> process enforcing the policy does not control the child process enough
>>>>> so that it would be possible to allow-list it with a
>>>>> LANDLOCK_ACCESS_FS_CONNECT_UNIX rule?**
>>> Yes, with the sandboxer use case. It's similar to a container that
>>> doesn't know all programs that could be run in it. We should be able to
>>> create sandboxes that don't assume (nor restrict) internal IPCs (or any
>>> kind of direct process I/O).
>>>
>>> Landlock should make it possible to scope any kind of IPC. It's a
>>> mental model which is easy to understand and that should be enforced by
>>> default. Of course, we need some ways to add exceptions to this
>>> deny-by-default policy, and that's why we also need the FS-based control
>>> mechanism.
>>>
>>>>> If we do not have such a use case, it seems that the planned FS-based
>>>>> control mechanism from [1] would do the same job? Long term, we might
>>>>> be better off if we only have only one control -- as we have discussed
>>>>> in [2], having two of these might mean that they interact in
>>>>> unconventional and possibly confusing ways.
>>>> I agree with this.
>>> Both approaches are complementary/orthogonal and make sense long term
>>> too. This might be the first time we can restrict the same operations,
>>> but I'm pretty sure it will not be the last, and it's OK. Handled FS
>>> access and scoped restrictions are two ways to describe a part of the
>>> security policy. Having different ways makes the interface much simpler
>>> than a generic one that would have to take into account all potential
>>> future access controls.
>>>
>>> One thing to keep in mind is that UAPI doesn't have to map 1:1 to the
>>> kernel implementation, but the UAPI is stable and future proof, whereas
>>> the kernel implementation can change a lot.
>>>
>>> The ruleset's handled fields serve two purposes: define what should be
>>> denied by default, and define which type of rules are valid
>>> (compatibility). The ruleset's scoped field serve similar purposes but
>>> it also implies implicit rules (i.e. communications with processes
>>> inside the sandbox are allowed). Without the soped field, we would have
>>> to create dedicated handled field per type (i.e. scope's bit) and
>>> dedicated rule type for the related handled field, which would make the
>>> interface more generic but also more complex, for something which is not
>>> needed.
>>>
>>> In a nutshell, in the case of the FS-based and scope-based unix socket
>>> control, we should see one kind of restrictions (e.g. connect to unix
>>> socket), which can accept two types of rules: (explicit) file path, or
>>> (implicit) peer's scope. Access should be granted as long as a rule
>>> matches, whatever its type.
>>>
>>> This rationale should be explained in a commit message.
>>>
>>>>> Apart from that, there are some other weaker hints that make me
>>>>> slightly critical of this patch set:
>>>>>
>>>>> * We discussed the idea that a FS-based path_beneath rule would act
>>>>> implicitly also as an exception for
>>>>> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET, in [2] - one possible way to
>>>>> interpret this is that the gravity of the system's logic pulls us
>>>>> back towards a FS-based control, and we would have to swim less
>>>>> against the stream if we integrated the Unix connect() control in
>>>>> that way?
>> While I do think being able to control UNIX socket access via domain
>> scoping is useful (after all, it is an additional bit of information /
>> relationship that "normal" filesystem objects doesn't have, and
>> applications using Landlock might find it useful to control access based
>> on this extra information, either to simplify the policy, or because a
>> generic sandboxer can't come up with a pre-determined list of UNIX socket
>> fs rules, e.g. for applications which places socket in hard-coded "/tmp"),
>> I do share the worry about how this might get confusing from an API
>> perspective, as discussed in my GitHub comment [1].
>>
>> Another way to put it is that, if FS-based and scope-based controls
>> interacts in the above proposed way, both mechanisms feel like "poking
>> holes" in the other. But as Mickaël said, one can think of the two
>> mechanisms not as independent controls, but rather as two interfaces for
>> the same control. The socket access control is "enabled" if either the
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
>> proposed in this patch is enabled.
>>
>> With that said, I can think of some alternative ways that might make this
>> API look "better" (from a subjective point of view, feedback welcome),
>> however it does mean more delays, and specifically, these will depend on
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
>>
>> One possibility is to simply always allow a Landlock domain to connect to
>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
>> handled, otherwise all sockets are allowed). This might be reasonable, as
>> one can only connect to a socket it creates if it has the permission to
>> create it in the first place, which is already controlled by
>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
>> flexibility here - if for some reason the sandboxer don't want to allow
>> access to any (pathname) sockets, even the sandboxed app's own ones, it
>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> socket, not to connect. I guess you was thinking about
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>
>> Another possibility is to have this scope control as part of the
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX rule itself, rather than as a separate
>> scope bit, via introducing a "rule flag" (which is a new mechanism
>> proposed in [2]) which I will tentatively call
>> (LANDLOCK_ADD_RULE_)SAME_SCOPE_ONLY. So for example:
>>
>> - If the sandboxer wants to allow all socket access, don't handle
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX at all, or handle it but have an allow
>> rule at / with no flags.
>>
>> - If it wants to allow access to only the sandboxed app's own sockets,
>> handle LANDLOCK_ACCESS_FS_RESOLVE_UNIX, then place one allow rule on /
>> with the rule flag SAME_SCOPE_ONLY. This means that the allow rule
>> "UNIX socket under /" only allows connecting to sockets in the same
>> domain scope. Additional rules without this rule flag can be placed on
>> more specific places to add "exceptions" to allow access to more
>> privileged sockets.
>>
>> - To allow access to specific sockets only, not even the sandboxed app's
>> own sockets, just use LANDLOCK_ACCESS_FS_RESOLVE_UNIX without this new
>> rule flag.
>>
>> This is slightly more flexible than just the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
>> scope bit plus LANDLOCK_ACCESS_FS_RESOLVE_UNIX, as it allows the sandboxer
>> to specify where an application can connect, even with sockets in its own
>> domain, and it also nicely avoids the "what feels like two independent
>> controls poking holes in each other" problem. But it does mean more
>> complexity (although hopefully not too much), as we now need to introduce
>> the rule flags concept, but that is required for some other proposed stuff
>> anyway - quiet flags [3] and no_inherit [4] rules, and the rule flags
>> design is in a good state I think.
> This looks too complex and would intertwine both concept, which is more
> confusing IMO.
>
>> What do folks think?
> I'd like to keep a clean API, with a "scoped" field handling IPC
> scoping, and an "handled_access_fs" field handling filesystem-related
> accesses.
>
> One thing to keep in mind is that we could add a new kind of "handled"
> field that would enable to add rules identifying e.g. processes,
> cgroups, or Landlock domains, and that could be used to add exceptions
> to the current scopes. This means that we need to have a generic way to
> handle this case.
>
> What is the issue with two complementary interfaces (scope and access)
> used to express a policy about connecting to UNIX sockets? We just need
> to make sure that scopes and handled_access_fs dealing with UNIX sockets
> are like binary OR: if the scope is set, then the domain can communicate
> with peers which are in the same domain, and if the handled_access_fs
> right is set, then the domain can only communicate with matching sockets
> (OR scoped ones if the scope is set).
>
> My main concern is about user space libraries and users that may want to
> have conditional enforcement for compatibility reasons e.g., only
> enforce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (let's say ABI v8) if it can
> also set LANDLOCK_ACCESS_FS_RESOLVE_UNIX (let's say ABI v9). I see two
> ways to deal with this case (if needed):
> - add synthetic access right to easily let users "combine" two access
> rigths or none;
> - have a more generic way to AND and OR access rights. I'm thinking
> about updating the Rust library in this direction.
>
> Anyway, we need to decide if this should be merged in Linux 7.0 (next
> week) or not. I'd prefer to merge it now because I think it works well
> and it's not a new concept wrt the abstract UNIX socket scoping.
> However, if there are any concern, I'd like to hear them now and I can
> delay this merge if needed. This patch series still need a new version
> but that should only be about cosmetic fixes. WDYT?
Regardless if you merge the patch series now in 7.0 or a later version, I think there is something to be said
about having the filesystem and scoped unix access right merged in the same ABI version / merge window.
As you pointed out earlier, the combination of the two flags is much flexible and useful to userspace
consumers than one or the other, and if the features were merged separately, there would be an
awkward middle ABI where user space consumers may have to make compromises or changes to
sandbox between different versions or change application behavior.
I think the discussion has died down on the security_unix_find hook for
LANDLOCK_ACCESS_FS_RESOLVE_UNIX [1] [2], and it seemed the general consensus was favorable.
The series for LANDLOCK_ACCESS_FS_RESOLVE_UNIX is pretty small, and feedback on the last version
was pretty quiet as well. So maybe it's worth a final look into. [3]
(PS: I do think if they are merged together, there should be tests specifically for the combination of the
two access rights. I'd be happy to draft some)
Justin
[1] : https://lore.kernel.org/linux-security-module/4bc22faa-2927-4ef9-b5dc-67a7575177e9@gmail.com/
[2] : https://lore.kernel.org/linux-security-module/20260110143300.71048-4-gnoack3000@gmail.com/
[3] : https://lore.kernel.org/linux-security-module/20260119203457.97676-2-gnoack3000@gmail.com/
>
>> (btw, when I say "sandboxed app" this can of course also mean multiple
>> applications in the same sandbox, e.g. like a container runtime as Mickaël
>> pointed out, which raises the probability that just having
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX would be insufficient, e.g. when bind
>> mounts are involved, or other cases which I haven't thought of right now)
>>
>> [1]: https://github.com/landlock-lsm/linux/issues/36#issuecomment-3693123942
>> [2]: https://lore.kernel.org/all/f238931bc813fc50fc8e11a007a8ad2136024df3.1766330134.git.m@maowtm.org/
>> [3]: https://lore.kernel.org/all/cover.1766330134.git.m@maowtm.org/
>> [4]: https://lore.kernel.org/all/20251221194301.247484-1-utilityemal77@gmail.com/
>>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-02 22:03 ` Justin Suess
@ 2026-02-03 1:26 ` Tingmao Wang
2026-02-03 17:54 ` Günther Noack
2026-02-04 17:39 ` Mickaël Salaün
0 siblings, 2 replies; 41+ messages in thread
From: Tingmao Wang @ 2026-02-03 1:26 UTC (permalink / raw)
To: Justin Suess, Mickaël Salaün
Cc: Günther Noack, Demi Marie Obenour, Günther Noack,
Alyssa Ross, Jann Horn, Tahera Fahimi, linux-security-module
Hi Mickaël,
Thanks for the feedback and explanations :)
On 2/2/26 20:32, Mickaël Salaün wrote:
> On Sat, Jan 31, 2026 at 05:41:14PM +0000, Tingmao Wang wrote:
>> [...]
>> What do folks think?
>
> I'd like to keep a clean API, with a "scoped" field handling IPC
> scoping, and an "handled_access_fs" field handling filesystem-related
> accesses.
>
> One thing to keep in mind is that we could add a new kind of "handled"
> field that would enable to add rules identifying e.g. processes,
> cgroups, or Landlock domains, and that could be used to add exceptions
> to the current scopes. This means that we need to have a generic way to
> handle this case.
>
> What is the issue with two complementary interfaces (scope and access)
> used to express a policy about connecting to UNIX sockets? We just need
> to make sure that scopes and handled_access_fs dealing with UNIX sockets
> are like binary OR: if the scope is set, then the domain can communicate
> with peers which are in the same domain, and if the handled_access_fs
> right is set, then the domain can only communicate with matching sockets
> (OR scoped ones if the scope is set).
Right, I see what you're saying, especially with the "additional access
rules for other scopes" example, and I think I'm happy with this. I guess
my attempt at trying to make the API more "elegant" would introduce
complexity and also create future inconsistency if other existing scope
bits are combined with handled_access rules.
> [...]
> Anyway, we need to decide if this should be merged in Linux 7.0 (next
> week) or not. I'd prefer to merge it now because I think it works well
> and it's not a new concept wrt the abstract UNIX socket scoping.
> However, if there are any concern, I'd like to hear them now and I can
> delay this merge if needed. This patch series still need a new version
> but that should only be about cosmetic fixes. WDYT?
I ended up being pretty busy today but I can definitely send the next
version tomorrow with your formatting changes and comments. I'm happy
with it going into the next merge window if you are. Justin raises a
point about having these two mechanisms in the same ABI version - see
below for consideration.
>> [...]
>
> My main concern is about user space libraries and users that may want to
> have conditional enforcement for compatibility reasons e.g., only
> enforce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (let's say ABI v8) if it can
> also set LANDLOCK_ACCESS_FS_RESOLVE_UNIX (let's say ABI v9). I see two
> ways to deal with this case (if needed):
> - add synthetic access right to easily let users "combine" two access
> rigths or none;
> - have a more generic way to AND and OR access rights. I'm thinking
> about updating the Rust library in this direction.
I'm not sure I fully understand the complexity here, but I think, assuming
these land in separate kernel versions, it will have to be that if both
the scope bit and LANDLOCK_ACCESS_FS_RESOLVE_UNIX is requested (maybe if
the user actually adds rules containing RESOLVE_UNIX access), but only the
scope bit is supported, then it will have to skip enforcing pathname UNIX
socket restrictions altogether by skipping both the scope bit and the
RESOLVE_UNIX access (if in best effort mode), or fail (if in hard
requirement mode).
I don't immediately see how further customization ability (e.g. synthetic
access rights or other AND/OR combination) could be used - if an app needs
access to a privileged socket and can't pre-open it before
landlock_restrict_self(), then the only realistic choice is to not use the
scope bits if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is not supported.
On 2/2/26 22:03, Justin Suess wrote:
> Regardless if you merge the patch series now in 7.0 or a later version, I think there is something to be said
> about having the filesystem and scoped unix access right merged in the same ABI version / merge window.
>
> As you pointed out earlier, the combination of the two flags is much flexible and useful to userspace
> consumers than one or the other, and if the features were merged separately, there would be an
> awkward middle ABI where user space consumers may have to make compromises or changes to
> sandbox between different versions or change application behavior.
> [...]
Given that the scope bit and RESOLVE_UNIX access right are in some sense
part of the same system (they interact in an OR manner, after all), there
is some positive for having them introduced in the same version, but on
the other hand, with my above reasoning, I don't think these two
mechanisms (scope bit and RESOLVE_UNIX access) being in different ABI
versions would be too much of a problem. In either case, for applications
which require access to more "privileged" sockets, when running on a
kernel without the RESOLVE_UNIX access right support, no pathname socket
restrictions can be applied (i.e. it won't use the scope bit either, there
isn't much "compromise" it can make here). On the other hand, if
RESOLVE_UNIX is supported, then it knows that the scope bit is also
supported, and can just use it.
Furthermore, an application / Landlock config etc can always opt to not
use the scope bit at all, if it "knows" all the locations where the
application's sockets would be placed, and just use RESOLVE_UNIX access
right (or nothing if it is not supported).
(The following is a bit of a side note, not terribly relevant if we're
deciding to go with the patch as is.)
>> [...]
>> Another way to put it is that, if FS-based and scope-based controls
>> interacts in the above proposed way, both mechanisms feel like "poking
>> holes" in the other. But as Mickaël said, one can think of the two
>> mechanisms not as independent controls, but rather as two interfaces for
>> the same control. The socket access control is "enabled" if either the
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
>> proposed in this patch is enabled.
>>
>> With that said, I can think of some alternative ways that might make this
>> API look "better" (from a subjective point of view, feedback welcome),
>> however it does mean more delays, and specifically, these will depend on
>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
>>
>> One possibility is to simply always allow a Landlock domain to connect to
>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
>> handled, otherwise all sockets are allowed). This might be reasonable, as
>> one can only connect to a socket it creates if it has the permission to
>> create it in the first place, which is already controlled by
>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
>> flexibility here - if for some reason the sandboxer don't want to allow
>> access to any (pathname) sockets, even the sandboxed app's own ones, it
>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
>
> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> socket, not to connect. I guess you was thinking about
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
In this "allow same-scope connect unconditionally" proposal, the
application would still be able to (bind to and) connect to its own
sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
which for whatever reason doesn't want this "allow same scope" default can
still prevent the use of (pathname) sockets by restricting
LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
sockets it doesn't own, and can't create any sockets itself either, then
it effectively can't connect to any sockets at all.
(Although on second thought, I guess there could be a case where an app
first creates some socket files before doing landlock_restrict_self(),
then it might still be able to bind to these even without
LANDLOCK_ACCESS_FS_MAKE_SOCK?)
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-03 1:26 ` Tingmao Wang
@ 2026-02-03 17:54 ` Günther Noack
2026-02-03 21:53 ` Tingmao Wang
2026-02-04 17:39 ` Mickaël Salaün
1 sibling, 1 reply; 41+ messages in thread
From: Günther Noack @ 2026-02-03 17:54 UTC (permalink / raw)
To: Tingmao Wang
Cc: Justin Suess, Mickaël Salaün, Günther Noack,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module
On Tue, Feb 03, 2026 at 01:26:31AM +0000, Tingmao Wang wrote:
> On 2/2/26 22:03, Justin Suess wrote:
> > Regardless if you merge the patch series now in 7.0 or a later version, I think there is something to be said
> > about having the filesystem and scoped unix access right merged in the same ABI version / merge window.
> >
> > As you pointed out earlier, the combination of the two flags is much flexible and useful to userspace
> > consumers than one or the other, and if the features were merged separately, there would be an
> > awkward middle ABI where user space consumers may have to make compromises or changes to
> > sandbox between different versions or change application behavior.
> > [...]
>
> Given that the scope bit and RESOLVE_UNIX access right are in some sense
> part of the same system (they interact in an OR manner, after all), there
> is some positive for having them introduced in the same version, but on
> the other hand, with my above reasoning, I don't think these two
> mechanisms (scope bit and RESOLVE_UNIX access) being in different ABI
> versions would be too much of a problem. In either case, for applications
> which require access to more "privileged" sockets, when running on a
> kernel without the RESOLVE_UNIX access right support, no pathname socket
> restrictions can be applied (i.e. it won't use the scope bit either, there
> isn't much "compromise" it can make here). On the other hand, if
> RESOLVE_UNIX is supported, then it knows that the scope bit is also
> supported, and can just use it.
Yes, but that does require additional subtle backwards compatibility
logic in userspace libraries, to implement the "best effort" fallbacks.
Assuming the scoped bit is added in v8 and the FS_RESOLVE_UNIX right in v9,
if a user does this (in Go-landlock syntax):
// restrict both scoped bit and FS RESOLVE_UNIX right, if possible
landlock.V9.BestEffort().RestrictPaths(
landlock.ResolveUnix("/tmp/socket"), // allow to connect to /tmp/socket
)
then if the system only supports ABI v8, it will have to clear both
bits so that connections to /tmp/socket work,
even though the scoped bit is technically supported on v8.
**This requires additional logic in client libraries**,
similar to our "refer" semantics (which users often get wrong):
if (there is a rule that allows connections by path name)
clear_the_scoped_bit_as_well();
// even though the path name rule normally only affects a different bit
In contrast, if both the scoped bit and FS_RESOLVE_UNIX were added in
the same ABI version, then if a user does the above call, we are
either equal-or-above that ABI version, in which case it works, or we
are below that ABI version, in which case the two bits already get
cleared from the landlock_ruleset_attr through the existing backwards
compatibility mechanism.
**In my mind, Justin is right that we should ideally introduce these
together.** We have seen users implementing the "Refer" special case
wrongly very often, it will likely happen here too, if we require
extra logic in userspace libraries.
BTW, regarding the implementation: To have *OR* semantics for "within
scope" and "allow-listed path", the implementation will be
non-trivial, and I suspect we won't hit the merge window if we try to
get them both in for 7.0. But in my mind, a simple UAPI is more
important than trying to make it in time for the next merge window.
(The implementation is difficult because the path-based and
scope-based check currently happen in different LSM hooks, and none of
the two hooks has enough information to make the decision alone. The
second hook only gets called if the first returns 0. It'll require
some further discussion to make it work together.)
> Furthermore, an application / Landlock config etc can always opt to not
> use the scope bit at all, if it "knows" all the locations where the
> application's sockets would be placed, and just use RESOLVE_UNIX access
> right (or nothing if it is not supported).
>
> (The following is a bit of a side note, not terribly relevant if we're
> deciding to go with the patch as is.)
>
> >> [...]
> >> Another way to put it is that, if FS-based and scope-based controls
> >> interacts in the above proposed way, both mechanisms feel like "poking
> >> holes" in the other. But as Mickaël said, one can think of the two
> >> mechanisms not as independent controls, but rather as two interfaces for
> >> the same control. The socket access control is "enabled" if either the
> >> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> >> proposed in this patch is enabled.
> >>
> >> With that said, I can think of some alternative ways that might make this
> >> API look "better" (from a subjective point of view, feedback welcome),
> >> however it does mean more delays, and specifically, these will depend on
> >> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> >>
> >> One possibility is to simply always allow a Landlock domain to connect to
> >> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> >> handled, otherwise all sockets are allowed). This might be reasonable, as
> >> one can only connect to a socket it creates if it has the permission to
> >> create it in the first place, which is already controlled by
> >> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> >> flexibility here - if for some reason the sandboxer don't want to allow
> >> access to any (pathname) sockets, even the sandboxed app's own ones, it
> >> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> >
> > LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> > socket, not to connect. I guess you was thinking about
> > LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>
> In this "allow same-scope connect unconditionally" proposal, the
> application would still be able to (bind to and) connect to its own
> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> which for whatever reason doesn't want this "allow same scope" default can
> still prevent the use of (pathname) sockets by restricting
> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> sockets it doesn't own, and can't create any sockets itself either, then
> it effectively can't connect to any sockets at all.
>
> (Although on second thought, I guess there could be a case where an app
> first creates some socket files before doing landlock_restrict_self(),
> then it might still be able to bind to these even without
> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
FWIW, I also really liked Tingmao's first of the two listed
possibilities in [1], where she proposed to introduce both rights
together. In my understanding, the arguments we have discussed so far
for that are:
IN FAVOR:
(pro1) Connecting to a UNIX socket in the same scope is always safe,
and it makes it possible to use named UNIX sockets between the
processes within a Landlock domains. (Mickaël convinced me in
discussion at FOSDEM that this is true.)
If someone absolutely does not want that, they can restrict
LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
Tingmao said above).
(pro2) The implementation of this is simpler.
(I attempted to understand how the "or" semantics would be
implemented, and I found it non-trivial when you try to do it
for all layers at once. (Kernighan's Law applies, IMHO))
AGAINST:
(con1) It would work differently than the other scoped access rights
that we already have.
A speculative feature that could potentially be built with the
scoped access rights is that we could add a rule to permit IPC
to other Landlock scopes, e.g. introducing a new rule type
struct landlock_scope_attr {
__u64 allowed_access; /* for "scoped" bits */
/* some way to identify domains */
}
so that we could make IPC access to other Landlock domains
configurable.
If the scoped bit and the FS RESOLVE_UNIX bit were both
conflated in RESOLVE_UNIX, it would not be possible to make
UNIX connections configurable in such a way.
(con2) Consistent behaviour between scoped flags and their
interactions with other access rights:
The existing scoped access rights (signal, abstract sockets)
could hypothetically be extended with a related access right of
another type. For instance, there could be an access right type
__u64 handled_signal_number;
and then you could add a rule to permit the use of certain
signal numbers. The interaction between the scoped flags and
other access rights should work the same.
Constructive Proposal for consideration: Why not both?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Why not do both what Tingmao proposed in [1] **and** reserve the
option to add the matching "scoped flag" later?
* Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
If it is handled, UNIX connections are allowed either:
(1) if the connection is to a service in the same scope, or
(2) if the path was allow-listed with a "path beneath" rule.
* Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
Let's go through the arguments again:
We have observed that it is harmless to allow connections to services
in the same scope (1), and that if users absolutely don't want that,
they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
(pro1).
(con1): Can we still implement the feature idea where we poke a hole
to get UNIX-connect() access to other Landlock domains?
I think the answer is yes. The implementation strategy is:
* Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
* The scoped bit can now be used to allow-list connections to
other Landlock domains.
For users, just setting the scoped bit on its own does the same as
handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
implementation can also stay simple. The only reason why the scoped
bit is needed is because it makes it possible to allow-list
connections to other Landlock domains, but at the same time, it is
safe if libraries set the scoped bit once it exists, as it does not
have any bad runtime impact either.
(con2): Consistency: Do all the scoped flags interact with their
corresponding access rights in the same way?
The other scope flags do not have corresponding access rights, so
far.
If we were to add corresponding access rights for the other scope
flags, I would argue that we could apply a consistent logic there,
because IPC access within the same scope is always safe:
- A hypothetical access right type for "signal numbers" would only
restrict signals that go beyond the current scope.
- A hypothetical access right type for "abstract UNIX domain socket
names" would only restrict connections to abstract UNIX domain
servers that go beyond the current scope.
I can not come up with a scenario where this doesn't work.
In conclusion, I think the approach has significant upsides:
* Simpler UAPI: Users only have one access bit to deal with, in the
near future. Once we do add a scope flag for UNIX connections, it
does not interact in a surprising way with the corresponding FS
access right, because with either of these, scoped access is
allowed.
If users absolutely need to restrict scoped access, they can
restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
API, but in line with the "make easy things easy, make hard things
possible" API philosophy. And needing this should be the
exception rather than the norm, after all.
* Consistent behaviour between scoped flags and regular access
rights, also for speculative access rights affecting the existing
scoped flags for signals and abstract UNIX domain sockets.
I know this was a slightly long mail, but I thought long and tried to
be structured. Please let me know what you think.
—Günther
[1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-03 17:54 ` Günther Noack
@ 2026-02-03 21:53 ` Tingmao Wang
2026-02-04 11:44 ` Günther Noack
0 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2026-02-03 21:53 UTC (permalink / raw)
To: Günther Noack, Justin Suess, Mickaël Salaün
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, linux-security-module
On 2/3/26 17:54, Günther Noack wrote:
> On Tue, Feb 03, 2026 at 01:26:31AM +0000, Tingmao Wang wrote:
>> On 2/2/26 22:03, Justin Suess wrote:
>>> Regardless if you merge the patch series now in 7.0 or a later version, I think there is something to be said
>>> about having the filesystem and scoped unix access right merged in the same ABI version / merge window.
>>>
>>> As you pointed out earlier, the combination of the two flags is much flexible and useful to userspace
>>> consumers than one or the other, and if the features were merged separately, there would be an
>>> awkward middle ABI where user space consumers may have to make compromises or changes to
>>> sandbox between different versions or change application behavior.
>>> [...]
>>
>> Given that the scope bit and RESOLVE_UNIX access right are in some sense
>> part of the same system (they interact in an OR manner, after all), there
>> is some positive for having them introduced in the same version, but on
>> the other hand, with my above reasoning, I don't think these two
>> mechanisms (scope bit and RESOLVE_UNIX access) being in different ABI
>> versions would be too much of a problem. In either case, for applications
>> which require access to more "privileged" sockets, when running on a
>> kernel without the RESOLVE_UNIX access right support, no pathname socket
>> restrictions can be applied (i.e. it won't use the scope bit either, there
>> isn't much "compromise" it can make here). On the other hand, if
>> RESOLVE_UNIX is supported, then it knows that the scope bit is also
>> supported, and can just use it.
>
> Yes, but that does require additional subtle backwards compatibility
> logic in userspace libraries, to implement the "best effort" fallbacks.
>
> Assuming the scoped bit is added in v8 and the FS_RESOLVE_UNIX right in v9,
> if a user does this (in Go-landlock syntax):
>
> // restrict both scoped bit and FS RESOLVE_UNIX right, if possible
> landlock.V9.BestEffort().RestrictPaths(
> landlock.ResolveUnix("/tmp/socket"), // allow to connect to /tmp/socket
> )
>
> then if the system only supports ABI v8, it will have to clear both
> bits so that connections to /tmp/socket work,
> even though the scoped bit is technically supported on v8.
>
> **This requires additional logic in client libraries**,
> similar to our "refer" semantics (which users often get wrong):
>
> if (there is a rule that allows connections by path name)
> clear_the_scoped_bit_as_well();
> // even though the path name rule normally only affects a different bit
>
>
> In contrast, if both the scoped bit and FS_RESOLVE_UNIX were added in
> the same ABI version, then if a user does the above call, we are
> either equal-or-above that ABI version, in which case it works, or we
> are below that ABI version, in which case the two bits already get
> cleared from the landlock_ruleset_attr through the existing backwards
> compatibility mechanism.
>
> **In my mind, Justin is right that we should ideally introduce these
> together.** We have seen users implementing the "Refer" special case
> wrongly very often, it will likely happen here too, if we require
> extra logic in userspace libraries.
Ok, this makes sense to me. I will send the patch's next version as-is
anyway for completeness since it's basically done but I recognize that we
might change the plan based on this discussion.
>
>
> BTW, regarding the implementation: To have *OR* semantics for "within
> scope" and "allow-listed path", the implementation will be
> non-trivial, and I suspect we won't hit the merge window if we try to
> get them both in for 7.0. But in my mind, a simple UAPI is more
> important than trying to make it in time for the next merge window.
>
> (The implementation is difficult because the path-based and
> scope-based check currently happen in different LSM hooks, and none of
> the two hooks has enough information to make the decision alone. The
> second hook only gets called if the first returns 0. It'll require
> some further discussion to make it work together.)
Right. In that case, would it make sense to pass sk into the new
security_unix_find() hook, perhaps with the new argument named `struct
sock *other`? Then we can use this hook for the scope check as well by
using landlock_cred(other->sk_socket->file->f_cred)->domain etc.
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 227467236930..db9d279b3883 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1223,24 +1223,24 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
err = -ECONNREFUSED;
inode = d_backing_inode(path.dentry);
if (!S_ISSOCK(inode->i_mode))
goto path_put;
+ err = -ECONNREFUSED;
+ sk = unix_find_socket_byinode(inode);
+ if (!sk)
+ goto path_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, type, flags);
+ err = security_unix_find(&path, sk, flags);
if (err)
- goto path_put;
-
- err = -ECONNREFUSED;
- sk = unix_find_socket_byinode(inode);
- if (!sk)
- goto path_put;
+ goto sock_put;
err = -EPROTOTYPE;
if (sk->sk_type == type)
touch_atime(&path);
else
goto sock_put;
By doing this we won't even need to pass `type` separately anymore. The
only change would be that now one can determine if a socket is bound or
not even without being allowed RESOLVE_UNIX access. I'm not sure how much
of an issue this is, but we could also call the hook anyway with a NULL in
place of the new argument, if unix_find_socket_byinode() fails. Other
LSMs can then decide what to do in that case (either return -ECONNREFUSED
or -EPERM).
>
>
>> Furthermore, an application / Landlock config etc can always opt to not
>> use the scope bit at all, if it "knows" all the locations where the
>> application's sockets would be placed, and just use RESOLVE_UNIX access
>> right (or nothing if it is not supported).
>>
>> (The following is a bit of a side note, not terribly relevant if we're
>> deciding to go with the patch as is.)
>>
>>>> [...]
>>>> Another way to put it is that, if FS-based and scope-based controls
>>>> interacts in the above proposed way, both mechanisms feel like "poking
>>>> holes" in the other. But as Mickaël said, one can think of the two
>>>> mechanisms not as independent controls, but rather as two interfaces for
>>>> the same control. The socket access control is "enabled" if either the
>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
>>>> proposed in this patch is enabled.
>>>>
>>>> With that said, I can think of some alternative ways that might make this
>>>> API look "better" (from a subjective point of view, feedback welcome),
>>>> however it does mean more delays, and specifically, these will depend on
>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
>>>>
>>>> One possibility is to simply always allow a Landlock domain to connect to
>>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
>>>> handled, otherwise all sockets are allowed). This might be reasonable, as
>>>> one can only connect to a socket it creates if it has the permission to
>>>> create it in the first place, which is already controlled by
>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
>>>> flexibility here - if for some reason the sandboxer don't want to allow
>>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
>>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
>>>
>>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
>>> socket, not to connect. I guess you was thinking about
>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>>
>> In this "allow same-scope connect unconditionally" proposal, the
>> application would still be able to (bind to and) connect to its own
>> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
>> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
>> which for whatever reason doesn't want this "allow same scope" default can
>> still prevent the use of (pathname) sockets by restricting
>> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
>> sockets it doesn't own, and can't create any sockets itself either, then
>> it effectively can't connect to any sockets at all.
>>
>> (Although on second thought, I guess there could be a case where an app
>> first creates some socket files before doing landlock_restrict_self(),
>> then it might still be able to bind to these even without
>> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
>
> FWIW, I also really liked Tingmao's first of the two listed
> possibilities in [1], where she proposed to introduce both rights
> together. In my understanding, the arguments we have discussed so far
> for that are:
>
> IN FAVOR:
>
> (pro1) Connecting to a UNIX socket in the same scope is always safe,
> and it makes it possible to use named UNIX sockets between the
> processes within a Landlock domains. (Mickaël convinced me in
> discussion at FOSDEM that this is true.)
>
> If someone absolutely does not want that, they can restrict
> LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
> Tingmao said above).
>
> (pro2) The implementation of this is simpler.
>
> (I attempted to understand how the "or" semantics would be
> implemented, and I found it non-trivial when you try to do it
> for all layers at once. (Kernighan's Law applies, IMHO))
I think the logic would basically be:
1. if any layers deny the access due to handled RESOLVE_UNIX but does not
have the scope bit set, then we will deny rightaway, without calling
domain_is_scoped().
2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
there are RESOLVE_UNIX rules covering the socket being accessed, and
essentially ignore those layers in the scope violation check.
I definitely agree that it is tricky, but making same-scope access be
allowed (i.e. the suggested idea above) would only get rid of step 1,
which I think is the "simpler" bit. The extra logic in step 2 is still
needed.
I definitely agree with pro1 tho.
>
> AGAINST:
>
> (con1) It would work differently than the other scoped access rights
> that we already have.
>
> A speculative feature that could potentially be built with the
> scoped access rights is that we could add a rule to permit IPC
> to other Landlock scopes, e.g. introducing a new rule type
>
> struct landlock_scope_attr {
> __u64 allowed_access; /* for "scoped" bits */
> /* some way to identify domains */
> }
>
> so that we could make IPC access to other Landlock domains
> configurable.
>
> If the scoped bit and the FS RESOLVE_UNIX bit were both
> conflated in RESOLVE_UNIX, it would not be possible to make
> UNIX connections configurable in such a way.
This exact API would no longer work, but if we give up the equivalence
between scope bits and the landlock_scope_attr struct, then we can do
something like:
struct landlock_scope_attr {
__u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
__u64 abstract_unix_socket:1;
__u64 pathname_unix_socket:1;
/* ... */
__u64 allowed_signals;
/*
* some way to identify domains, maybe we could use the audit domain
* ID, with 0 denoting "allow access to non-Landlocked processes?
*/
}
>
> (con2) Consistent behaviour between scoped flags and their
> interactions with other access rights:
>
> The existing scoped access rights (signal, abstract sockets)
> could hypothetically be extended with a related access right of
> another type. For instance, there could be an access right type
>
> __u64 handled_signal_number;
>
> and then you could add a rule to permit the use of certain
> signal numbers. The interaction between the scoped flags and
> other access rights should work the same.
>
>
> Constructive Proposal for consideration: Why not both?
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I will think about the following a bit more but I'm afraid that I feel
like it might get slightly confusing. With this, the only reason for
having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
later enable allowing access to other domains (if I understood correctly),
in which case I personally feel like the suggestion on landlock_scope_attr
above, where we essentially accept that it is decoupled with the scope
bits in the ruleset, might be simpler...?
>
> Why not do both what Tingmao proposed in [1] **and** reserve the
> option to add the matching "scoped flag" later?
>
> * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
>
> If it is handled, UNIX connections are allowed either:
>
> (1) if the connection is to a service in the same scope, or
> (2) if the path was allow-listed with a "path beneath" rule.
>
> * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
>
>
> Let's go through the arguments again:
>
> We have observed that it is harmless to allow connections to services
> in the same scope (1), and that if users absolutely don't want that,
> they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> (pro1).
>
> (con1): Can we still implement the feature idea where we poke a hole
> to get UNIX-connect() access to other Landlock domains?
>
> I think the answer is yes. The implementation strategy is:
>
> * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> * The scoped bit can now be used to allow-list connections to
> other Landlock domains.
>
> For users, just setting the scoped bit on its own does the same as
> handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> implementation can also stay simple. The only reason why the scoped
> bit is needed is because it makes it possible to allow-list
> connections to other Landlock domains, but at the same time, it is
> safe if libraries set the scoped bit once it exists, as it does not
> have any bad runtime impact either.
>
> (con2): Consistency: Do all the scoped flags interact with their
> corresponding access rights in the same way?
>
> The other scope flags do not have corresponding access rights, so
> far.
>
> If we were to add corresponding access rights for the other scope
> flags, I would argue that we could apply a consistent logic there,
> because IPC access within the same scope is always safe:
>
> - A hypothetical access right type for "signal numbers" would only
> restrict signals that go beyond the current scope.
>
> - A hypothetical access right type for "abstract UNIX domain socket
> names" would only restrict connections to abstract UNIX domain
> servers that go beyond the current scope.
>
> I can not come up with a scenario where this doesn't work.
>
>
> In conclusion, I think the approach has significant upsides:
>
> * Simpler UAPI: Users only have one access bit to deal with, in the
> near future. Once we do add a scope flag for UNIX connections, it
> does not interact in a surprising way with the corresponding FS
> access right, because with either of these, scoped access is
> allowed.
>
> If users absolutely need to restrict scoped access, they can
> restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> API, but in line with the "make easy things easy, make hard things
> possible" API philosophy. And needing this should be the
> exception rather than the norm, after all.
>
> * Consistent behaviour between scoped flags and regular access
> rights, also for speculative access rights affecting the existing
> scoped flags for signals and abstract UNIX domain sockets.
>
> I know this was a slightly long mail, but I thought long and tried to
> be structured. Please let me know what you think.
>
> —Günther
>
>
> [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
^ permalink raw reply related [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-03 21:53 ` Tingmao Wang
@ 2026-02-04 11:44 ` Günther Noack
2026-02-04 16:36 ` Justin Suess
2026-02-04 17:43 ` Mickaël Salaün
0 siblings, 2 replies; 41+ messages in thread
From: Günther Noack @ 2026-02-04 11:44 UTC (permalink / raw)
To: Tingmao Wang, Mickaël Salaün, Justin Suess
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, linux-security-module
On Tue, Feb 03, 2026 at 09:53:11PM +0000, Tingmao Wang wrote:
> On 2/3/26 17:54, Günther Noack wrote:
> > BTW, regarding the implementation: To have *OR* semantics for "within
> > scope" and "allow-listed path", the implementation will be
> > non-trivial, and I suspect we won't hit the merge window if we try to
> > get them both in for 7.0. But in my mind, a simple UAPI is more
> > important than trying to make it in time for the next merge window.
> >
> > (The implementation is difficult because the path-based and
> > scope-based check currently happen in different LSM hooks, and none of
> > the two hooks has enough information to make the decision alone. The
> > second hook only gets called if the first returns 0. It'll require
> > some further discussion to make it work together.)
>
> Right. In that case, would it make sense to pass sk into the new
> security_unix_find() hook, perhaps with the new argument named `struct
> sock *other`? Then we can use this hook for the scope check as well by
> using landlock_cred(other->sk_socket->file->f_cred)->domain etc.
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 227467236930..db9d279b3883 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1223,24 +1223,24 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
>
> err = -ECONNREFUSED;
> inode = d_backing_inode(path.dentry);
> if (!S_ISSOCK(inode->i_mode))
> goto path_put;
>
> + err = -ECONNREFUSED;
> + sk = unix_find_socket_byinode(inode);
> + if (!sk)
> + goto path_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, type, flags);
> + err = security_unix_find(&path, sk, flags);
> if (err)
> - goto path_put;
> -
> - err = -ECONNREFUSED;
> - sk = unix_find_socket_byinode(inode);
> - if (!sk)
> - goto path_put;
> + goto sock_put;
>
> err = -EPROTOTYPE;
> if (sk->sk_type == type)
> touch_atime(&path);
> else
> goto sock_put;
>
> By doing this we won't even need to pass `type` separately anymore. The
> only change would be that now one can determine if a socket is bound or
> not even without being allowed RESOLVE_UNIX access. I'm not sure how much
> of an issue this is, but we could also call the hook anyway with a NULL in
> place of the new argument, if unix_find_socket_byinode() fails. Other
> LSMs can then decide what to do in that case (either return -ECONNREFUSED
> or -EPERM).
Thank you for the suggestion.
Small caveat is that the LSM interface is very central and we should
be careful. We have previously gotten the advice from Paul to design
the hooks in an LSM-independent way that ideally reflects the
arguments to the unix_find_bsd() function, and this would now deviate
(slightly) from that, but simplifying the implementation for us. In
my personal opinion, this might be worth doing the trade-off, if
AppArmor people also agree, but we should double check.
To keep the discussion of implementation and interface separate, I
have raised this question in the pathname-restricted-UNIX patch set
thread in [1].
[1] https://lore.kernel.org/all/aYMenaSmBkAsFowd@google.com/
> >> Furthermore, an application / Landlock config etc can always opt to not
> >> use the scope bit at all, if it "knows" all the locations where the
> >> application's sockets would be placed, and just use RESOLVE_UNIX access
> >> right (or nothing if it is not supported).
> >>
> >> (The following is a bit of a side note, not terribly relevant if we're
> >> deciding to go with the patch as is.)
> >>
> >>>> [...]
> >>>> Another way to put it is that, if FS-based and scope-based controls
> >>>> interacts in the above proposed way, both mechanisms feel like "poking
> >>>> holes" in the other. But as Mickaël said, one can think of the two
> >>>> mechanisms not as independent controls, but rather as two interfaces for
> >>>> the same control. The socket access control is "enabled" if either the
> >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> >>>> proposed in this patch is enabled.
> >>>>
> >>>> With that said, I can think of some alternative ways that might make this
> >>>> API look "better" (from a subjective point of view, feedback welcome),
> >>>> however it does mean more delays, and specifically, these will depend on
> >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> >>>>
> >>>> One possibility is to simply always allow a Landlock domain to connect to
> >>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> >>>> handled, otherwise all sockets are allowed). This might be reasonable, as
> >>>> one can only connect to a socket it creates if it has the permission to
> >>>> create it in the first place, which is already controlled by
> >>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> >>>> flexibility here - if for some reason the sandboxer don't want to allow
> >>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
> >>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> >>>
> >>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> >>> socket, not to connect. I guess you was thinking about
> >>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
> >>
> >> In this "allow same-scope connect unconditionally" proposal, the
> >> application would still be able to (bind to and) connect to its own
> >> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> >> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> >> which for whatever reason doesn't want this "allow same scope" default can
> >> still prevent the use of (pathname) sockets by restricting
> >> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> >> sockets it doesn't own, and can't create any sockets itself either, then
> >> it effectively can't connect to any sockets at all.
> >>
> >> (Although on second thought, I guess there could be a case where an app
> >> first creates some socket files before doing landlock_restrict_self(),
> >> then it might still be able to bind to these even without
> >> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
> >
> > FWIW, I also really liked Tingmao's first of the two listed
> > possibilities in [1], where she proposed to introduce both rights
> > together. In my understanding, the arguments we have discussed so far
> > for that are:
> >
> > IN FAVOR:
> >
> > (pro1) Connecting to a UNIX socket in the same scope is always safe,
> > and it makes it possible to use named UNIX sockets between the
> > processes within a Landlock domains. (Mickaël convinced me in
> > discussion at FOSDEM that this is true.)
> >
> > If someone absolutely does not want that, they can restrict
> > LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
> > Tingmao said above).
> >
> > (pro2) The implementation of this is simpler.
> >
> > (I attempted to understand how the "or" semantics would be
> > implemented, and I found it non-trivial when you try to do it
> > for all layers at once. (Kernighan's Law applies, IMHO))
>
> I think the logic would basically be:
>
> 1. if any layers deny the access due to handled RESOLVE_UNIX but does not
> have the scope bit set, then we will deny rightaway, without calling
> domain_is_scoped().
>
> 2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
> there are RESOLVE_UNIX rules covering the socket being accessed, and
> essentially ignore those layers in the scope violation check.
>
> I definitely agree that it is tricky, but making same-scope access be
> allowed (i.e. the suggested idea above) would only get rid of step 1,
> which I think is the "simpler" bit. The extra logic in step 2 is still
> needed.
>
> I definitely agree with pro1 tho.
Yes, you are describing the logic for the variant where
LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
within the same scope. In that variant, there can be situations where
the first hook can deny the action immediately.
In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
allow access from within the same scope, that shortcutting is not
possible. On the upside however, there is no need to distinguish
whether the scope flag is set when we are in the security_unix_find()
hook, because access from within the same scope is always permitted.
(That is the simplification I meant.)
> > AGAINST:
> >
> > (con1) It would work differently than the other scoped access rights
> > that we already have.
> >
> > A speculative feature that could potentially be built with the
> > scoped access rights is that we could add a rule to permit IPC
> > to other Landlock scopes, e.g. introducing a new rule type
> >
> > struct landlock_scope_attr {
> > __u64 allowed_access; /* for "scoped" bits */
> > /* some way to identify domains */
> > }
> >
> > so that we could make IPC access to other Landlock domains
> > configurable.
> >
> > If the scoped bit and the FS RESOLVE_UNIX bit were both
> > conflated in RESOLVE_UNIX, it would not be possible to make
> > UNIX connections configurable in such a way.
>
> This exact API would no longer work, but if we give up the equivalence
> between scope bits and the landlock_scope_attr struct, then we can do
> something like:
>
> struct landlock_scope_attr {
> __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
> __u64 abstract_unix_socket:1;
> __u64 pathname_unix_socket:1;
> /* ... */
>
> __u64 allowed_signals;
>
> /*
> * some way to identify domains, maybe we could use the audit domain
> * ID, with 0 denoting "allow access to non-Landlocked processes?
> */
> }
Yes, it would be possible to use such a struct for that scenario where
IPC access gets allowed for other Landlock scopes. It would mean that
we would not need to introduce a scoped flag for the pathname UNIX
socket connections. But the relationship between that struct
landlock_scope_attr and the flags and access rights in struct
landlock_ruleset_attr would become less clear, which is a slight
downside, and maybe error prone for users to work with.
If we introduced an additional scoped flag, it would also be
consistent though.
(con1) was written under the assumption that we do not have an
additional scoped flag. If that is lacking, it is not possible to
express UNIX connect() access to other Landlock domains with that
struct. But as outlined in the proposal below, if we *do* (later)
introduce the additional scoped flag *in addition* to the FS access
right, this *both* stays consistent in semantics with the signal and
abstract UNIX support, *and* it starts working in a world where ICP
access can be allowed to talk to other Landlock domains.
> > (con2) Consistent behaviour between scoped flags and their
> > interactions with other access rights:
> >
> > The existing scoped access rights (signal, abstract sockets)
> > could hypothetically be extended with a related access right of
> > another type. For instance, there could be an access right type
> >
> > __u64 handled_signal_number;
> >
> > and then you could add a rule to permit the use of certain
> > signal numbers. The interaction between the scoped flags and
> > other access rights should work the same.
> >
> >
> > Constructive Proposal for consideration: Why not both?
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> I will think about the following a bit more but I'm afraid that I feel
> like it might get slightly confusing. With this, the only reason for
> having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
> later enable allowing access to other domains (if I understood correctly),
> in which case I personally feel like the suggestion on landlock_scope_attr
> above, where we essentially accept that it is decoupled with the scope
> bits in the ruleset, might be simpler...?
Mickaël expressed the opinion to me that he would like to APIs to stay
consistent between signals, abstract UNIX sockets, named UNIX sockets
and other future "scoped" operations, in scenarios where:
* the "scoped" (IPC) operations can be configured to give access to
other Landlock domains (and that should work for UNIX connections too)
* the existing "scoped" operations also start having matching access rights
I think with the way I proposed, that would be consistent.
> > Why not do both what Tingmao proposed in [1] **and** reserve the
> > option to add the matching "scoped flag" later?
> >
> > * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
> >
> > If it is handled, UNIX connections are allowed either:
> >
> > (1) if the connection is to a service in the same scope, or
> > (2) if the path was allow-listed with a "path beneath" rule.
> >
> > * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
> >
> >
> > Let's go through the arguments again:
> >
> > We have observed that it is harmless to allow connections to services
> > in the same scope (1), and that if users absolutely don't want that,
> > they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> > (pro1).
> >
> > (con1): Can we still implement the feature idea where we poke a hole
> > to get UNIX-connect() access to other Landlock domains?
> >
> > I think the answer is yes. The implementation strategy is:
> >
> > * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> > * The scoped bit can now be used to allow-list connections to
> > other Landlock domains.
> >
> > For users, just setting the scoped bit on its own does the same as
> > handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> > implementation can also stay simple. The only reason why the scoped
> > bit is needed is because it makes it possible to allow-list
> > connections to other Landlock domains, but at the same time, it is
> > safe if libraries set the scoped bit once it exists, as it does not
> > have any bad runtime impact either.
> >
> > (con2): Consistency: Do all the scoped flags interact with their
> > corresponding access rights in the same way?
> >
> > The other scope flags do not have corresponding access rights, so
> > far.
> >
> > If we were to add corresponding access rights for the other scope
> > flags, I would argue that we could apply a consistent logic there,
> > because IPC access within the same scope is always safe:
> >
> > - A hypothetical access right type for "signal numbers" would only
> > restrict signals that go beyond the current scope.
> >
> > - A hypothetical access right type for "abstract UNIX domain socket
> > names" would only restrict connections to abstract UNIX domain
> > servers that go beyond the current scope.
> >
> > I can not come up with a scenario where this doesn't work.
> >
> >
> > In conclusion, I think the approach has significant upsides:
> >
> > * Simpler UAPI: Users only have one access bit to deal with, in the
> > near future. Once we do add a scope flag for UNIX connections, it
> > does not interact in a surprising way with the corresponding FS
> > access right, because with either of these, scoped access is
> > allowed.
> >
> > If users absolutely need to restrict scoped access, they can
> > restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> > API, but in line with the "make easy things easy, make hard things
> > possible" API philosophy. And needing this should be the
> > exception rather than the norm, after all.
> >
> > * Consistent behaviour between scoped flags and regular access
> > rights, also for speculative access rights affecting the existing
> > scoped flags for signals and abstract UNIX domain sockets.
> >
> > [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
—Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-04 11:44 ` Günther Noack
@ 2026-02-04 16:36 ` Justin Suess
2026-02-04 18:28 ` Mickaël Salaün
2026-02-04 17:43 ` Mickaël Salaün
1 sibling, 1 reply; 41+ messages in thread
From: Justin Suess @ 2026-02-04 16:36 UTC (permalink / raw)
To: Günther Noack, Tingmao Wang, Mickaël Salaün
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, linux-security-module
On 2/4/26 06:44, Günther Noack wrote:
> On Tue, Feb 03, 2026 at 09:53:11PM +0000, Tingmao Wang wrote:
>> On 2/3/26 17:54, Günther Noack wrote:
>>> BTW, regarding the implementation: To have *OR* semantics for "within
>>> scope" and "allow-listed path", the implementation will be
>>> non-trivial, and I suspect we won't hit the merge window if we try to
>>> get them both in for 7.0. But in my mind, a simple UAPI is more
>>> important than trying to make it in time for the next merge window.
>>>
>>> (The implementation is difficult because the path-based and
>>> scope-based check currently happen in different LSM hooks, and none of
>>> the two hooks has enough information to make the decision alone. The
>>> second hook only gets called if the first returns 0. It'll require
>>> some further discussion to make it work together.)
>> Right. In that case, would it make sense to pass sk into the new
>> security_unix_find() hook, perhaps with the new argument named `struct
>> sock *other`? Then we can use this hook for the scope check as well by
>> using landlock_cred(other->sk_socket->file->f_cred)->domain etc.
Tingmao,
This seems like the best way to do it. Alternatively, I considered passing in just the
f_cred to the hook, but I'm leaning towards a more generic implementation like the one you
have below.
>>
>> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
>> index 227467236930..db9d279b3883 100644
>> --- a/net/unix/af_unix.c
>> +++ b/net/unix/af_unix.c
>> @@ -1223,24 +1223,24 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
>>
>> err = -ECONNREFUSED;
>> inode = d_backing_inode(path.dentry);
>> if (!S_ISSOCK(inode->i_mode))
>> goto path_put;
>>
>> + err = -ECONNREFUSED;
>> + sk = unix_find_socket_byinode(inode);
>> + if (!sk)
>> + goto path_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, type, flags);
>> + err = security_unix_find(&path, sk, flags);
>> if (err)
>> - goto path_put;
>> -
>> - err = -ECONNREFUSED;
>> - sk = unix_find_socket_byinode(inode);
>> - if (!sk)
>> - goto path_put;
>> + goto sock_put;
>>
>> err = -EPROTOTYPE;
>> if (sk->sk_type == type)
>> touch_atime(&path);
>> else
>> goto sock_put;
>>
>> By doing this we won't even need to pass `type` separately anymore. The
>> only change would be that now one can determine if a socket is bound or
>> not even without being allowed RESOLVE_UNIX access. I'm not sure how much
>> of an issue this is, but we could also call the hook anyway with a NULL in
>> place of the new argument, if unix_find_socket_byinode() fails. Other
>> LSMs can then decide what to do in that case (either return -ECONNREFUSED
>> or -EPERM).
I think landlock already allows checking of existence of files even when when the process
doesn't have rights on them, so there is precedent for this so in my mind this would probably be OK.
> Thank you for the suggestion.
>
> Small caveat is that the LSM interface is very central and we should
> be careful. We have previously gotten the advice from Paul to design
> the hooks in an LSM-independent way that ideally reflects the
> arguments to the unix_find_bsd() function, and this would now deviate
> (slightly) from that, but simplifying the implementation for us. In
> my personal opinion, this might be worth doing the trade-off, if
> AppArmor people also agree, but we should double check.
Gunther:
I'd be happy to send you an updated LSM hook patch with the sock parameter.
Just let me know.
>
> To keep the discussion of implementation and interface separate, I
> have raised this question in the pathname-restricted-UNIX patch set
> thread in [1].
>
> [1] https://lore.kernel.org/all/aYMenaSmBkAsFowd@google.com/
>
>
>
>>>> Furthermore, an application / Landlock config etc can always opt to not
>>>> use the scope bit at all, if it "knows" all the locations where the
>>>> application's sockets would be placed, and just use RESOLVE_UNIX access
>>>> right (or nothing if it is not supported).
>>>>
>>>> (The following is a bit of a side note, not terribly relevant if we're
>>>> deciding to go with the patch as is.)
>>>>
>>>>>> [...]
>>>>>> Another way to put it is that, if FS-based and scope-based controls
>>>>>> interacts in the above proposed way, both mechanisms feel like "poking
>>>>>> holes" in the other. But as Mickaël said, one can think of the two
>>>>>> mechanisms not as independent controls, but rather as two interfaces for
>>>>>> the same control. The socket access control is "enabled" if either the
>>>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
>>>>>> proposed in this patch is enabled.
>>>>>>
>>>>>> With that said, I can think of some alternative ways that might make this
>>>>>> API look "better" (from a subjective point of view, feedback welcome),
>>>>>> however it does mean more delays, and specifically, these will depend on
>>>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
>>>>>>
>>>>>> One possibility is to simply always allow a Landlock domain to connect to
>>>>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
>>>>>> handled, otherwise all sockets are allowed). This might be reasonable, as
>>>>>> one can only connect to a socket it creates if it has the permission to
>>>>>> create it in the first place, which is already controlled by
>>>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
>>>>>> flexibility here - if for some reason the sandboxer don't want to allow
>>>>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
>>>>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
>>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
>>>>> socket, not to connect. I guess you was thinking about
>>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>>>> In this "allow same-scope connect unconditionally" proposal, the
>>>> application would still be able to (bind to and) connect to its own
>>>> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
>>>> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
>>>> which for whatever reason doesn't want this "allow same scope" default can
>>>> still prevent the use of (pathname) sockets by restricting
>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
>>>> sockets it doesn't own, and can't create any sockets itself either, then
>>>> it effectively can't connect to any sockets at all.
>>>>
>>>> (Although on second thought, I guess there could be a case where an app
>>>> first creates some socket files before doing landlock_restrict_self(),
>>>> then it might still be able to bind to these even without
>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
>>> FWIW, I also really liked Tingmao's first of the two listed
>>> possibilities in [1], where she proposed to introduce both rights
>>> together. In my understanding, the arguments we have discussed so far
>>> for that are:
>>>
>>> IN FAVOR:
>>>
>>> (pro1) Connecting to a UNIX socket in the same scope is always safe,
>>> and it makes it possible to use named UNIX sockets between the
>>> processes within a Landlock domains. (Mickaël convinced me in
>>> discussion at FOSDEM that this is true.)
>>>
>>> If someone absolutely does not want that, they can restrict
>>> LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
>>> Tingmao said above).
>>>
>>> (pro2) The implementation of this is simpler.
>>>
>>> (I attempted to understand how the "or" semantics would be
>>> implemented, and I found it non-trivial when you try to do it
>>> for all layers at once. (Kernighan's Law applies, IMHO))
>> I think the logic would basically be:
>>
>> 1. if any layers deny the access due to handled RESOLVE_UNIX but does not
>> have the scope bit set, then we will deny rightaway, without calling
>> domain_is_scoped().
>>
>> 2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
>> there are RESOLVE_UNIX rules covering the socket being accessed, and
>> essentially ignore those layers in the scope violation check.
Tingmao:
For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
1. security_unix_find. (to look up the paths)
2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
Which for is called in DGRAM:
unix_dgram_connect OR unix_dgram_sendmsg
and for STREAM:
unix_stream_connect
IIRC, the path lookup only occurs in this order always, so in the logic as you have it the domain_is_scoped()
would be called twice, once from the security_unix_find when you call it in step two, and once from the
domain scope hooks. (If access was allowed from security_unix_find)
There are a couple of things to consider.
---
Audit blockers need special handling:
Here's an example:
1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
access bit (deny all for RESOLVE_UNIX).
2. Program A connects to /tmp/mysock.sock ran by program B, which is outside the domain.
2. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
/tmp/mysock.sock, so it calls domain_is_scoped
3. domain_is_scoped denies it as well, so now we must log an audit record.
When logging the denial, we have to include both blockers "scope.unix_socket" and "fs.resolve_unix" for the
denial, because it is the absence of both that caused the denial. I think the refer right has similar cases for auditing, so there is precedent for this (multiple blockers for an audit message).
---
Dual lookup for domain_is_scoped. Consider this case:
1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
access bit (deny all for RESOLVE_UNIX).
2. Program A connects to Program C's /tmp/foo.sock, which for the purposes of this example is in the domain of program A.
3. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
/tmp/mysock.sock, so it calls domain_is_scoped. Access is granted, and continues. (LSM hook complete)
4. The connection proceeds past the path lookup stage, and now security_unix_may_send, or security_unix_stream_connect gets called. This requires ANOTHER domain_is_scoped access check.
While I don't THINK this introduces a TOCTOU, it is a little confusing.
This does mean that we look up the domain twice, if this is implemented naively. I think we can then just
skip the task credential checks then for security_unix_may_send and security_unix_stream_connect **for
connecting to pathname sockets**, since the domain_is_scoped will already have been called in landlock's
security_unix_find hook, eliminating the need for handling pathname socket domain checks layer on.
>>
>> I definitely agree that it is tricky, but making same-scope access be
>> allowed (i.e. the suggested idea above) would only get rid of step 1,
>> which I think is the "simpler" bit. The extra logic in step 2 is still
>> needed.
>>
>> I definitely agree with pro1 tho.
> Yes, you are describing the logic for the variant where
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
> within the same scope. In that variant, there can be situations where
> the first hook can deny the action immediately.
>
> In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
> allow access from within the same scope, that shortcutting is not
> possible. On the upside however, there is no need to distinguish
> whether the scope flag is set when we are in the security_unix_find()
> hook, because access from within the same scope is always permitted.
> (That is the simplification I meant.)
>
>
>>> AGAINST:
>>>
>>> (con1) It would work differently than the other scoped access rights
>>> that we already have.
>>>
>>> A speculative feature that could potentially be built with the
>>> scoped access rights is that we could add a rule to permit IPC
>>> to other Landlock scopes, e.g. introducing a new rule type
>>>
>>> struct landlock_scope_attr {
>>> __u64 allowed_access; /* for "scoped" bits */
>>> /* some way to identify domains */
>>> }
>>>
>>> so that we could make IPC access to other Landlock domains
>>> configurable.
>>>
>>> If the scoped bit and the FS RESOLVE_UNIX bit were both
>>> conflated in RESOLVE_UNIX, it would not be possible to make
>>> UNIX connections configurable in such a way.
>> This exact API would no longer work, but if we give up the equivalence
>> between scope bits and the landlock_scope_attr struct, then we can do
>> something like:
>>
>> struct landlock_scope_attr {
>> __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
>> __u64 abstract_unix_socket:1;
>> __u64 pathname_unix_socket:1;
>> /* ... */
>>
>> __u64 allowed_signals;
>>
>> /*
>> * some way to identify domains, maybe we could use the audit domain
>> * ID, with 0 denoting "allow access to non-Landlocked processes?
>> */
>> }
> Yes, it would be possible to use such a struct for that scenario where
> IPC access gets allowed for other Landlock scopes. It would mean that
> we would not need to introduce a scoped flag for the pathname UNIX
> socket connections. But the relationship between that struct
> landlock_scope_attr and the flags and access rights in struct
> landlock_ruleset_attr would become less clear, which is a slight
> downside, and maybe error prone for users to work with.
>
> If we introduced an additional scoped flag, it would also be
> consistent though.
>
> (con1) was written under the assumption that we do not have an
> additional scoped flag. If that is lacking, it is not possible to
> express UNIX connect() access to other Landlock domains with that
> struct. But as outlined in the proposal below, if we *do* (later)
> introduce the additional scoped flag *in addition* to the FS access
> right, this *both* stays consistent in semantics with the signal and
> abstract UNIX support, *and* it starts working in a world where ICP
> access can be allowed to talk to other Landlock domains.
>
>>> (con2) Consistent behaviour between scoped flags and their
>>> interactions with other access rights:
>>>
>>> The existing scoped access rights (signal, abstract sockets)
>>> could hypothetically be extended with a related access right of
>>> another type. For instance, there could be an access right type
>>>
>>> __u64 handled_signal_number;
>>>
>>> and then you could add a rule to permit the use of certain
>>> signal numbers. The interaction between the scoped flags and
>>> other access rights should work the same.
>>>
>>>
>>> Constructive Proposal for consideration: Why not both?
>>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> I will think about the following a bit more but I'm afraid that I feel
>> like it might get slightly confusing. With this, the only reason for
>> having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
>> later enable allowing access to other domains (if I understood correctly),
>> in which case I personally feel like the suggestion on landlock_scope_attr
>> above, where we essentially accept that it is decoupled with the scope
>> bits in the ruleset, might be simpler...?
> Mickaël expressed the opinion to me that he would like to APIs to stay
> consistent between signals, abstract UNIX sockets, named UNIX sockets
> and other future "scoped" operations, in scenarios where:
>
> * the "scoped" (IPC) operations can be configured to give access to
> other Landlock domains (and that should work for UNIX connections too)
> * the existing "scoped" operations also start having matching access rights
>
> I think with the way I proposed, that would be consistent.
>
>
>>> Why not do both what Tingmao proposed in [1] **and** reserve the
>>> option to add the matching "scoped flag" later?
>>>
>>> * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
>>>
>>> If it is handled, UNIX connections are allowed either:
>>>
>>> (1) if the connection is to a service in the same scope, or
>>> (2) if the path was allow-listed with a "path beneath" rule.
>>>
>>> * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
>>>
>>>
>>> Let's go through the arguments again:
>>>
>>> We have observed that it is harmless to allow connections to services
>>> in the same scope (1), and that if users absolutely don't want that,
>>> they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
>>> (pro1).
>>>
>>> (con1): Can we still implement the feature idea where we poke a hole
>>> to get UNIX-connect() access to other Landlock domains?
>>>
>>> I think the answer is yes. The implementation strategy is:
>>>
>>> * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
>>> * The scoped bit can now be used to allow-list connections to
>>> other Landlock domains.
>>>
>>> For users, just setting the scoped bit on its own does the same as
>>> handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
>>> implementation can also stay simple. The only reason why the scoped
>>> bit is needed is because it makes it possible to allow-list
>>> connections to other Landlock domains, but at the same time, it is
>>> safe if libraries set the scoped bit once it exists, as it does not
>>> have any bad runtime impact either.
>>>
>>> (con2): Consistency: Do all the scoped flags interact with their
>>> corresponding access rights in the same way?
>>>
>>> The other scope flags do not have corresponding access rights, so
>>> far.
>>>
>>> If we were to add corresponding access rights for the other scope
>>> flags, I would argue that we could apply a consistent logic there,
>>> because IPC access within the same scope is always safe:
>>>
>>> - A hypothetical access right type for "signal numbers" would only
>>> restrict signals that go beyond the current scope.
>>>
>>> - A hypothetical access right type for "abstract UNIX domain socket
>>> names" would only restrict connections to abstract UNIX domain
>>> servers that go beyond the current scope.
>>>
>>> I can not come up with a scenario where this doesn't work.
Gunther / Tingmao / Mickaël:
I have a potential idea to make this concept cleaner.
The docs for landlock currently say:
IPC scoping does not support exceptions via landlock_add_rule(2).
If an operation is scoped within a domain, no rules can be added
to allow access to resources or processes outside of the scope.
So if we go with the solution where we are now saying IPC scoping DOES support exceptions
we will need to update the documentation, to say scoping for pathname unix sockets is an exception,
and have to have the "exemptible scopes" (like this one) alongside "non-exemptible" scopes
(ie the existing ones). This creates some friction for users.
If we foresee other "exempt-able scopes" (which are scopes that also support creating exemptions w/ corresponding access rights) in the future, maybe we should consider separating the two in the ruleset
attributes (I used scoped_fs as an example for the attribute name):
structlandlock_ruleset_attrruleset_attr={
.handled_access_fs=
LANDLOCK_ACCESS_FS_EXECUTE|
LANDLOCK_ACCESS_FS_WRITE_FILE|
LANDLOCK_ACCESS_FS_READ_FILE|
LANDLOCK_ACCESS_FS_READ_DIR|
LANDLOCK_ACCESS_FS_REMOVE_DIR|
LANDLOCK_ACCESS_FS_REMOVE_FILE|
LANDLOCK_ACCESS_FS_MAKE_CHAR|
LANDLOCK_ACCESS_FS_MAKE_DIR|
LANDLOCK_ACCESS_FS_MAKE_REG|
LANDLOCK_ACCESS_FS_MAKE_SOCK|
LANDLOCK_ACCESS_FS_MAKE_FIFO|
LANDLOCK_ACCESS_FS_MAKE_BLOCK|
LANDLOCK_ACCESS_FS_MAKE_SYM|
LANDLOCK_ACCESS_FS_REFER|
LANDLOCK_ACCESS_FS_TRUNCATE|
LANDLOCK_ACCESS_FS_IOCTL_DEV,
.handled_access_net=
LANDLOCK_ACCESS_NET_BIND_TCP|
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped=
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET|
LANDLOCK_SCOPE_SIGNAL,
.scoped_fs =
LANDLOCK_SCOPE_FS_PATHNAME_UNIX_SOCKET
};
This more clearly distinguishes between scopes that have exceptions/corresponding fs rights,
and ones that don't. Later we could add scoped_net, if needed. I feel like this would be more
intuitive and better categorize future scoping rights. An obvious con is increasing the size of
the ruleset attributes.
Of course this separation is only worth it if there are other "exempt-able" rights in the future.
I can think of a few potential future rights which COULD be scoped and have corresponding rights
(binder, sysv-ipc, pipes, tcp/udp between two local programs).
>>>
>>>
>>> In conclusion, I think the approach has significant upsides:
>>>
>>> * Simpler UAPI: Users only have one access bit to deal with, in the
>>> near future. Once we do add a scope flag for UNIX connections, it
>>> does not interact in a surprising way with the corresponding FS
>>> access right, because with either of these, scoped access is
>>> allowed.
>>>
>>> If users absolutely need to restrict scoped access, they can
>>> restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
>>> API, but in line with the "make easy things easy, make hard things
>>> possible" API philosophy. And needing this should be the
>>> exception rather than the norm, after all.
>>>
>>> * Consistent behaviour between scoped flags and regular access
>>> rights, also for speculative access rights affecting the existing
>>> scoped flags for signals and abstract UNIX domain sockets.
>>>
>>> [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
> —Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-03 1:26 ` Tingmao Wang
2026-02-03 17:54 ` Günther Noack
@ 2026-02-04 17:39 ` Mickaël Salaün
1 sibling, 0 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-04 17:39 UTC (permalink / raw)
To: Tingmao Wang
Cc: Justin Suess, Günther Noack, Demi Marie Obenour,
Günther Noack, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Tue, Feb 03, 2026 at 01:26:31AM +0000, Tingmao Wang wrote:
> Hi Mickaël,
>
> Thanks for the feedback and explanations :)
>
> On 2/2/26 20:32, Mickaël Salaün wrote:
> > On Sat, Jan 31, 2026 at 05:41:14PM +0000, Tingmao Wang wrote:
> >> [...]
> >> What do folks think?
> >
> > I'd like to keep a clean API, with a "scoped" field handling IPC
> > scoping, and an "handled_access_fs" field handling filesystem-related
> > accesses.
> >
> > One thing to keep in mind is that we could add a new kind of "handled"
> > field that would enable to add rules identifying e.g. processes,
> > cgroups, or Landlock domains, and that could be used to add exceptions
> > to the current scopes. This means that we need to have a generic way to
> > handle this case.
> >
> > What is the issue with two complementary interfaces (scope and access)
> > used to express a policy about connecting to UNIX sockets? We just need
> > to make sure that scopes and handled_access_fs dealing with UNIX sockets
> > are like binary OR: if the scope is set, then the domain can communicate
> > with peers which are in the same domain, and if the handled_access_fs
> > right is set, then the domain can only communicate with matching sockets
> > (OR scoped ones if the scope is set).
>
> Right, I see what you're saying, especially with the "additional access
> rules for other scopes" example, and I think I'm happy with this. I guess
> my attempt at trying to make the API more "elegant" would introduce
> complexity and also create future inconsistency if other existing scope
> bits are combined with handled_access rules.
>
> > [...]
> > Anyway, we need to decide if this should be merged in Linux 7.0 (next
> > week) or not. I'd prefer to merge it now because I think it works well
> > and it's not a new concept wrt the abstract UNIX socket scoping.
> > However, if there are any concern, I'd like to hear them now and I can
> > delay this merge if needed. This patch series still need a new version
> > but that should only be about cosmetic fixes. WDYT?
>
> I ended up being pretty busy today but I can definitely send the next
> version tomorrow with your formatting changes and comments. I'm happy
> with it going into the next merge window if you are. Justin raises a
> point about having these two mechanisms in the same ABI version - see
> below for consideration.
>
> >> [...]
> >
> > My main concern is about user space libraries and users that may want to
> > have conditional enforcement for compatibility reasons e.g., only
> > enforce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (let's say ABI v8) if it can
> > also set LANDLOCK_ACCESS_FS_RESOLVE_UNIX (let's say ABI v9). I see two
> > ways to deal with this case (if needed):
> > - add synthetic access right to easily let users "combine" two access
> > rigths or none;
> > - have a more generic way to AND and OR access rights. I'm thinking
> > about updating the Rust library in this direction.
>
> I'm not sure I fully understand the complexity here, but I think, assuming
> these land in separate kernel versions, it will have to be that if both
> the scope bit and LANDLOCK_ACCESS_FS_RESOLVE_UNIX is requested (maybe if
> the user actually adds rules containing RESOLVE_UNIX access), but only the
> scope bit is supported, then it will have to skip enforcing pathname UNIX
> socket restrictions altogether by skipping both the scope bit and the
> RESOLVE_UNIX access (if in best effort mode), or fail (if in hard
> requirement mode).
Yeah, this should be OK in theory but it might be confusing to
developers.
>
> I don't immediately see how further customization ability (e.g. synthetic
> access rights or other AND/OR combination) could be used - if an app needs
> access to a privileged socket and can't pre-open it before
> landlock_restrict_self(), then the only realistic choice is to not use the
> scope bits if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is not supported.
Yes, that's the main idea. The synthetic access right would just be
useful to avoid doing this check each time but let the library do it
instead. Anyway, that's mostly a (Rust) lib thing.
>
> On 2/2/26 22:03, Justin Suess wrote:
> > Regardless if you merge the patch series now in 7.0 or a later version, I think there is something to be said
> > about having the filesystem and scoped unix access right merged in the same ABI version / merge window.
> >
> > As you pointed out earlier, the combination of the two flags is much flexible and useful to userspace
> > consumers than one or the other, and if the features were merged separately, there would be an
> > awkward middle ABI where user space consumers may have to make compromises or changes to
> > sandbox between different versions or change application behavior.
> > [...]
>
> Given that the scope bit and RESOLVE_UNIX access right are in some sense
> part of the same system (they interact in an OR manner, after all), there
> is some positive for having them introduced in the same version, but on
> the other hand, with my above reasoning, I don't think these two
> mechanisms (scope bit and RESOLVE_UNIX access) being in different ABI
> versions would be too much of a problem. In either case, for applications
> which require access to more "privileged" sockets, when running on a
> kernel without the RESOLVE_UNIX access right support, no pathname socket
> restrictions can be applied (i.e. it won't use the scope bit either, there
> isn't much "compromise" it can make here). On the other hand, if
> RESOLVE_UNIX is supported, then it knows that the scope bit is also
> supported, and can just use it.
Yes
>
> Furthermore, an application / Landlock config etc can always opt to not
> use the scope bit at all, if it "knows" all the locations where the
> application's sockets would be placed, and just use RESOLVE_UNIX access
> right (or nothing if it is not supported).
>
> (The following is a bit of a side note, not terribly relevant if we're
> deciding to go with the patch as is.)
>
> >> [...]
> >> Another way to put it is that, if FS-based and scope-based controls
> >> interacts in the above proposed way, both mechanisms feel like "poking
> >> holes" in the other. But as Mickaël said, one can think of the two
> >> mechanisms not as independent controls, but rather as two interfaces for
> >> the same control. The socket access control is "enabled" if either the
> >> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> >> proposed in this patch is enabled.
> >>
> >> With that said, I can think of some alternative ways that might make this
> >> API look "better" (from a subjective point of view, feedback welcome),
> >> however it does mean more delays, and specifically, these will depend on
> >> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> >>
> >> One possibility is to simply always allow a Landlock domain to connect to
> >> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> >> handled, otherwise all sockets are allowed). This might be reasonable, as
> >> one can only connect to a socket it creates if it has the permission to
> >> create it in the first place, which is already controlled by
> >> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> >> flexibility here - if for some reason the sandboxer don't want to allow
> >> access to any (pathname) sockets, even the sandboxed app's own ones, it
> >> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> >
> > LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> > socket, not to connect. I guess you was thinking about
> > LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>
> In this "allow same-scope connect unconditionally" proposal, the
> application would still be able to (bind to and) connect to its own
> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> which for whatever reason doesn't want this "allow same scope" default can
> still prevent the use of (pathname) sockets by restricting
> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> sockets it doesn't own, and can't create any sockets itself either, then
> it effectively can't connect to any sockets at all.
>
> (Although on second thought, I guess there could be a case where an app
> first creates some socket files before doing landlock_restrict_self(),
> then it might still be able to bind to these even without
> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
That's good to keep in mind. There might be weird cases but developers
should be encouraged to set all the scopes bits and then potentially
allow specific sockets with FS_RESOLVE_UNIX.
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-04 11:44 ` Günther Noack
2026-02-04 16:36 ` Justin Suess
@ 2026-02-04 17:43 ` Mickaël Salaün
2026-02-05 8:02 ` Günther Noack
1 sibling, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-04 17:43 UTC (permalink / raw)
To: Günther Noack
Cc: Tingmao Wang, Justin Suess, Günther Noack,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Wed, Feb 04, 2026 at 12:44:29PM +0100, Günther Noack wrote:
> On Tue, Feb 03, 2026 at 09:53:11PM +0000, Tingmao Wang wrote:
> > On 2/3/26 17:54, Günther Noack wrote:
> > > BTW, regarding the implementation: To have *OR* semantics for "within
> > > scope" and "allow-listed path", the implementation will be
> > > non-trivial, and I suspect we won't hit the merge window if we try to
> > > get them both in for 7.0. But in my mind, a simple UAPI is more
> > > important than trying to make it in time for the next merge window.
> > >
> > > (The implementation is difficult because the path-based and
> > > scope-based check currently happen in different LSM hooks, and none of
> > > the two hooks has enough information to make the decision alone. The
> > > second hook only gets called if the first returns 0. It'll require
> > > some further discussion to make it work together.)
> >
> > Right. In that case, would it make sense to pass sk into the new
> > security_unix_find() hook, perhaps with the new argument named `struct
> > sock *other`? Then we can use this hook for the scope check as well by
> > using landlock_cred(other->sk_socket->file->f_cred)->domain etc.
> >
> > diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> > index 227467236930..db9d279b3883 100644
> > --- a/net/unix/af_unix.c
> > +++ b/net/unix/af_unix.c
> > @@ -1223,24 +1223,24 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
> >
> > err = -ECONNREFUSED;
> > inode = d_backing_inode(path.dentry);
> > if (!S_ISSOCK(inode->i_mode))
> > goto path_put;
> >
> > + err = -ECONNREFUSED;
> > + sk = unix_find_socket_byinode(inode);
> > + if (!sk)
> > + goto path_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, type, flags);
> > + err = security_unix_find(&path, sk, flags);
> > if (err)
> > - goto path_put;
> > -
> > - err = -ECONNREFUSED;
> > - sk = unix_find_socket_byinode(inode);
> > - if (!sk)
> > - goto path_put;
> > + goto sock_put;
> >
> > err = -EPROTOTYPE;
> > if (sk->sk_type == type)
> > touch_atime(&path);
> > else
> > goto sock_put;
> >
> > By doing this we won't even need to pass `type` separately anymore. The
> > only change would be that now one can determine if a socket is bound or
> > not even without being allowed RESOLVE_UNIX access. I'm not sure how much
> > of an issue this is, but we could also call the hook anyway with a NULL in
> > place of the new argument, if unix_find_socket_byinode() fails. Other
> > LSMs can then decide what to do in that case (either return -ECONNREFUSED
> > or -EPERM).
>
> Thank you for the suggestion.
>
> Small caveat is that the LSM interface is very central and we should
> be careful. We have previously gotten the advice from Paul to design
> the hooks in an LSM-independent way that ideally reflects the
> arguments to the unix_find_bsd() function, and this would now deviate
> (slightly) from that, but simplifying the implementation for us. In
> my personal opinion, this might be worth doing the trade-off, if
> AppArmor people also agree, but we should double check.
I think that's a good idea. The new hook would more generic and make it
possible to have more context to take an access control decision, which
is better for the LSM framework.
>
> To keep the discussion of implementation and interface separate, I
> have raised this question in the pathname-restricted-UNIX patch set
> thread in [1].
>
> [1] https://lore.kernel.org/all/aYMenaSmBkAsFowd@google.com/
>
>
>
> > >> Furthermore, an application / Landlock config etc can always opt to not
> > >> use the scope bit at all, if it "knows" all the locations where the
> > >> application's sockets would be placed, and just use RESOLVE_UNIX access
> > >> right (or nothing if it is not supported).
> > >>
> > >> (The following is a bit of a side note, not terribly relevant if we're
> > >> deciding to go with the patch as is.)
> > >>
> > >>>> [...]
> > >>>> Another way to put it is that, if FS-based and scope-based controls
> > >>>> interacts in the above proposed way, both mechanisms feel like "poking
> > >>>> holes" in the other. But as Mickaël said, one can think of the two
> > >>>> mechanisms not as independent controls, but rather as two interfaces for
> > >>>> the same control. The socket access control is "enabled" if either the
> > >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> > >>>> proposed in this patch is enabled.
> > >>>>
> > >>>> With that said, I can think of some alternative ways that might make this
> > >>>> API look "better" (from a subjective point of view, feedback welcome),
> > >>>> however it does mean more delays, and specifically, these will depend on
> > >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> > >>>>
> > >>>> One possibility is to simply always allow a Landlock domain to connect to
> > >>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> > >>>> handled, otherwise all sockets are allowed). This might be reasonable, as
> > >>>> one can only connect to a socket it creates if it has the permission to
> > >>>> create it in the first place, which is already controlled by
> > >>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> > >>>> flexibility here - if for some reason the sandboxer don't want to allow
> > >>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
> > >>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> > >>>
> > >>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> > >>> socket, not to connect. I guess you was thinking about
> > >>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
> > >>
> > >> In this "allow same-scope connect unconditionally" proposal, the
> > >> application would still be able to (bind to and) connect to its own
> > >> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> > >> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> > >> which for whatever reason doesn't want this "allow same scope" default can
> > >> still prevent the use of (pathname) sockets by restricting
> > >> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> > >> sockets it doesn't own, and can't create any sockets itself either, then
> > >> it effectively can't connect to any sockets at all.
> > >>
> > >> (Although on second thought, I guess there could be a case where an app
> > >> first creates some socket files before doing landlock_restrict_self(),
> > >> then it might still be able to bind to these even without
> > >> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
> > >
> > > FWIW, I also really liked Tingmao's first of the two listed
> > > possibilities in [1], where she proposed to introduce both rights
> > > together. In my understanding, the arguments we have discussed so far
> > > for that are:
> > >
> > > IN FAVOR:
> > >
> > > (pro1) Connecting to a UNIX socket in the same scope is always safe,
> > > and it makes it possible to use named UNIX sockets between the
> > > processes within a Landlock domains. (Mickaël convinced me in
> > > discussion at FOSDEM that this is true.)
> > >
> > > If someone absolutely does not want that, they can restrict
> > > LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
> > > Tingmao said above).
> > >
> > > (pro2) The implementation of this is simpler.
> > >
> > > (I attempted to understand how the "or" semantics would be
> > > implemented, and I found it non-trivial when you try to do it
> > > for all layers at once. (Kernighan's Law applies, IMHO))
> >
> > I think the logic would basically be:
> >
> > 1. if any layers deny the access due to handled RESOLVE_UNIX but does not
> > have the scope bit set, then we will deny rightaway, without calling
> > domain_is_scoped().
> >
> > 2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
> > there are RESOLVE_UNIX rules covering the socket being accessed, and
> > essentially ignore those layers in the scope violation check.
> >
> > I definitely agree that it is tricky, but making same-scope access be
> > allowed (i.e. the suggested idea above) would only get rid of step 1,
> > which I think is the "simpler" bit. The extra logic in step 2 is still
> > needed.
> >
> > I definitely agree with pro1 tho.
>
> Yes, you are describing the logic for the variant where
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
> within the same scope. In that variant, there can be situations where
> the first hook can deny the action immediately.
>
> In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
> allow access from within the same scope, that shortcutting is not
> possible. On the upside however, there is no need to distinguish
> whether the scope flag is set when we are in the security_unix_find()
> hook, because access from within the same scope is always permitted.
> (That is the simplification I meant.)
This proposal make sense, improve IPC restriction consistency (by always
be able to connect to peers from the same domain), and would simplify
some checks. What bother me the most is the implicit scoping induced
by the FS_RESOLVE_UNIX handling. A related issue is the mix of checks
for the FS_RESOLVE_UNIX implementation, which could make the code more
complex, but I don't see a better way. Landlock already has an implicit
ptrace scoping but it's not (and should never be) optional.
See my proposal below about implicit
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET. That would not change the kernel
checks you described though.
>
>
> > > AGAINST:
> > >
> > > (con1) It would work differently than the other scoped access rights
> > > that we already have.
> > >
> > > A speculative feature that could potentially be built with the
> > > scoped access rights is that we could add a rule to permit IPC
> > > to other Landlock scopes, e.g. introducing a new rule type
> > >
> > > struct landlock_scope_attr {
> > > __u64 allowed_access; /* for "scoped" bits */
> > > /* some way to identify domains */
> > > }
> > >
> > > so that we could make IPC access to other Landlock domains
> > > configurable.
> > >
> > > If the scoped bit and the FS RESOLVE_UNIX bit were both
> > > conflated in RESOLVE_UNIX, it would not be possible to make
> > > UNIX connections configurable in such a way.
> >
> > This exact API would no longer work, but if we give up the equivalence
> > between scope bits and the landlock_scope_attr struct, then we can do
> > something like:
> >
> > struct landlock_scope_attr {
> > __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
> > __u64 abstract_unix_socket:1;
> > __u64 pathname_unix_socket:1;
> > /* ... */
> >
> > __u64 allowed_signals;
> >
> > /*
> > * some way to identify domains, maybe we could use the audit domain
> > * ID, with 0 denoting "allow access to non-Landlocked processes?
> > */
> > }
>
> Yes, it would be possible to use such a struct for that scenario where
> IPC access gets allowed for other Landlock scopes. It would mean that
> we would not need to introduce a scoped flag for the pathname UNIX
> socket connections. But the relationship between that struct
> landlock_scope_attr and the flags and access rights in struct
> landlock_ruleset_attr would become less clear, which is a slight
> downside, and maybe error prone for users to work with.
>
> If we introduced an additional scoped flag, it would also be
> consistent though.
>
> (con1) was written under the assumption that we do not have an
> additional scoped flag. If that is lacking, it is not possible to
> express UNIX connect() access to other Landlock domains with that
> struct. But as outlined in the proposal below, if we *do* (later)
> introduce the additional scoped flag *in addition* to the FS access
> right, this *both* stays consistent in semantics with the signal and
> abstract UNIX support, *and* it starts working in a world where ICP
> access can be allowed to talk to other Landlock domains.
In this case, we need to assume the scoped flag is already there, and
because we have the implementation, to actually add it now.
As a side note, I don't really like this landlock_scope_attr interface.
A dedicated attr per kind of IPC would be much more flexible (e.g. the
handled_signal_number you described below). For instance, even if at
first we don't make it possible to describe different signals within the
attr struct, this struct will be able to grow. So it's better to have a
dedicated interface per IPC type than a generic scope interface that can
only express a subset of it.
Anyway, this proposal could work for pathname UNIX sockets, but a future
signal attr would be a bit inconsistent wrt to the pathname UNIX one.
>
> > > (con2) Consistent behaviour between scoped flags and their
> > > interactions with other access rights:
> > >
> > > The existing scoped access rights (signal, abstract sockets)
> > > could hypothetically be extended with a related access right of
> > > another type. For instance, there could be an access right type
> > >
> > > __u64 handled_signal_number;
> > >
> > > and then you could add a rule to permit the use of certain
> > > signal numbers. The interaction between the scoped flags and
> > > other access rights should work the same.
> > >
> > >
> > > Constructive Proposal for consideration: Why not both?
> > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >
> > I will think about the following a bit more but I'm afraid that I feel
> > like it might get slightly confusing. With this, the only reason for
> > having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
> > later enable allowing access to other domains (if I understood correctly),
> > in which case I personally feel like the suggestion on landlock_scope_attr
> > above, where we essentially accept that it is decoupled with the scope
> > bits in the ruleset, might be simpler...?
>
> Mickaël expressed the opinion to me that he would like to APIs to stay
> consistent between signals, abstract UNIX sockets, named UNIX sockets
> and other future "scoped" operations, in scenarios where:
>
> * the "scoped" (IPC) operations can be configured to give access to
> other Landlock domains (and that should work for UNIX connections too)
> * the existing "scoped" operations also start having matching access rights
>
> I think with the way I proposed, that would be consistent.
What about always implicitly set LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (in
the kernel) when LANDLOCK_ACCESS_FS_RESOLVE_UNIX is set?
*Requiring* users to set LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET would add a
new way to trigger an error, but setting
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET *implicitly* looks safer. This
might also help for the documentation BTW.
If we agree on this, I could merge the scope_pathname_unix in 7.0, and
merge the resolve_unix in 7.1 (or later if something unexpected happen).
Otherwise, I'll merge both at the same time with the same ABI version
(with the risk to postpone again). WDYT?
>
>
> > > Why not do both what Tingmao proposed in [1] **and** reserve the
> > > option to add the matching "scoped flag" later?
> > >
> > > * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
> > >
> > > If it is handled, UNIX connections are allowed either:
> > >
> > > (1) if the connection is to a service in the same scope, or
> > > (2) if the path was allow-listed with a "path beneath" rule.
> > >
> > > * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
> > >
> > >
> > > Let's go through the arguments again:
> > >
> > > We have observed that it is harmless to allow connections to services
> > > in the same scope (1), and that if users absolutely don't want that,
> > > they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> > > (pro1).
> > >
> > > (con1): Can we still implement the feature idea where we poke a hole
> > > to get UNIX-connect() access to other Landlock domains?
> > >
> > > I think the answer is yes. The implementation strategy is:
> > >
> > > * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> > > * The scoped bit can now be used to allow-list connections to
> > > other Landlock domains.
> > >
> > > For users, just setting the scoped bit on its own does the same as
> > > handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> > > implementation can also stay simple. The only reason why the scoped
> > > bit is needed is because it makes it possible to allow-list
> > > connections to other Landlock domains, but at the same time, it is
> > > safe if libraries set the scoped bit once it exists, as it does not
> > > have any bad runtime impact either.
> > >
> > > (con2): Consistency: Do all the scoped flags interact with their
> > > corresponding access rights in the same way?
> > >
> > > The other scope flags do not have corresponding access rights, so
> > > far.
> > >
> > > If we were to add corresponding access rights for the other scope
> > > flags, I would argue that we could apply a consistent logic there,
> > > because IPC access within the same scope is always safe:
> > >
> > > - A hypothetical access right type for "signal numbers" would only
> > > restrict signals that go beyond the current scope.
> > >
> > > - A hypothetical access right type for "abstract UNIX domain socket
> > > names" would only restrict connections to abstract UNIX domain
> > > servers that go beyond the current scope.
> > >
> > > I can not come up with a scenario where this doesn't work.
> > >
> > >
> > > In conclusion, I think the approach has significant upsides:
> > >
> > > * Simpler UAPI: Users only have one access bit to deal with, in the
> > > near future. Once we do add a scope flag for UNIX connections, it
> > > does not interact in a surprising way with the corresponding FS
> > > access right, because with either of these, scoped access is
> > > allowed.
> > >
> > > If users absolutely need to restrict scoped access, they can
> > > restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> > > API, but in line with the "make easy things easy, make hard things
> > > possible" API philosophy. And needing this should be the
> > > exception rather than the norm, after all.
> > >
> > > * Consistent behaviour between scoped flags and regular access
> > > rights, also for speculative access rights affecting the existing
> > > scoped flags for signals and abstract UNIX domain sockets.
> > >
> > > [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
Thanks for this summary Günther!
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-04 16:36 ` Justin Suess
@ 2026-02-04 18:28 ` Mickaël Salaün
2026-02-05 15:22 ` Justin Suess
[not found] ` <44d216aa-9680-4cf5-bbf0-173869111212@gmail.com>
0 siblings, 2 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-04 18:28 UTC (permalink / raw)
To: Justin Suess
Cc: Günther Noack, Tingmao Wang, Günther Noack,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Wed, Feb 04, 2026 at 11:36:06AM -0500, Justin Suess wrote:
>
> On 2/4/26 06:44, Günther Noack wrote:
> > On Tue, Feb 03, 2026 at 09:53:11PM +0000, Tingmao Wang wrote:
> >> On 2/3/26 17:54, Günther Noack wrote:
> >>> BTW, regarding the implementation: To have *OR* semantics for "within
> >>> scope" and "allow-listed path", the implementation will be
> >>> non-trivial, and I suspect we won't hit the merge window if we try to
> >>> get them both in for 7.0. But in my mind, a simple UAPI is more
> >>> important than trying to make it in time for the next merge window.
> >>>
> >>> (The implementation is difficult because the path-based and
> >>> scope-based check currently happen in different LSM hooks, and none of
> >>> the two hooks has enough information to make the decision alone. The
> >>> second hook only gets called if the first returns 0. It'll require
> >>> some further discussion to make it work together.)
> >> Right. In that case, would it make sense to pass sk into the new
> >> security_unix_find() hook, perhaps with the new argument named `struct
> >> sock *other`? Then we can use this hook for the scope check as well by
> >> using landlock_cred(other->sk_socket->file->f_cred)->domain etc.
>
> Tingmao,
>
> This seems like the best way to do it. Alternatively, I considered passing in just the
> f_cred to the hook, but I'm leaning towards a more generic implementation like the one you
> have below.
>
> >>
> >> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> >> index 227467236930..db9d279b3883 100644
> >> --- a/net/unix/af_unix.c
> >> +++ b/net/unix/af_unix.c
> >> @@ -1223,24 +1223,24 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
> >>
> >> err = -ECONNREFUSED;
> >> inode = d_backing_inode(path.dentry);
> >> if (!S_ISSOCK(inode->i_mode))
> >> goto path_put;
> >>
> >> + err = -ECONNREFUSED;
> >> + sk = unix_find_socket_byinode(inode);
> >> + if (!sk)
> >> + goto path_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, type, flags);
> >> + err = security_unix_find(&path, sk, flags);
> >> if (err)
> >> - goto path_put;
> >> -
> >> - err = -ECONNREFUSED;
> >> - sk = unix_find_socket_byinode(inode);
> >> - if (!sk)
> >> - goto path_put;
> >> + goto sock_put;
> >>
> >> err = -EPROTOTYPE;
> >> if (sk->sk_type == type)
> >> touch_atime(&path);
> >> else
> >> goto sock_put;
> >>
> >> By doing this we won't even need to pass `type` separately anymore. The
> >> only change would be that now one can determine if a socket is bound or
> >> not even without being allowed RESOLVE_UNIX access. I'm not sure how much
> >> of an issue this is, but we could also call the hook anyway with a NULL in
> >> place of the new argument, if unix_find_socket_byinode() fails. Other
> >> LSMs can then decide what to do in that case (either return -ECONNREFUSED
> >> or -EPERM).
> I think landlock already allows checking of existence of files even when when the process
> doesn't have rights on them, so there is precedent for this so in my mind this would probably be OK.
I agree, there are a lot of different side channels in Linux. Landlock
is about access control, like most LSMs, so this should not be an issue.
And if an LSM wants to hide this information, it can also return
-ECONNREFUSED or something else.
> > Thank you for the suggestion.
> >
> > Small caveat is that the LSM interface is very central and we should
> > be careful. We have previously gotten the advice from Paul to design
> > the hooks in an LSM-independent way that ideally reflects the
> > arguments to the unix_find_bsd() function, and this would now deviate
> > (slightly) from that, but simplifying the implementation for us. In
> > my personal opinion, this might be worth doing the trade-off, if
> > AppArmor people also agree, but we should double check.
>
> Gunther:
>
> I'd be happy to send you an updated LSM hook patch with the sock parameter.
>
> Just let me know.
>
> >
> > To keep the discussion of implementation and interface separate, I
> > have raised this question in the pathname-restricted-UNIX patch set
> > thread in [1].
> >
> > [1] https://lore.kernel.org/all/aYMenaSmBkAsFowd@google.com/
> >
> >
> >
> >>>> Furthermore, an application / Landlock config etc can always opt to not
> >>>> use the scope bit at all, if it "knows" all the locations where the
> >>>> application's sockets would be placed, and just use RESOLVE_UNIX access
> >>>> right (or nothing if it is not supported).
> >>>>
> >>>> (The following is a bit of a side note, not terribly relevant if we're
> >>>> deciding to go with the patch as is.)
> >>>>
> >>>>>> [...]
> >>>>>> Another way to put it is that, if FS-based and scope-based controls
> >>>>>> interacts in the above proposed way, both mechanisms feel like "poking
> >>>>>> holes" in the other. But as Mickaël said, one can think of the two
> >>>>>> mechanisms not as independent controls, but rather as two interfaces for
> >>>>>> the same control. The socket access control is "enabled" if either the
> >>>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> >>>>>> proposed in this patch is enabled.
> >>>>>>
> >>>>>> With that said, I can think of some alternative ways that might make this
> >>>>>> API look "better" (from a subjective point of view, feedback welcome),
> >>>>>> however it does mean more delays, and specifically, these will depend on
> >>>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> >>>>>>
> >>>>>> One possibility is to simply always allow a Landlock domain to connect to
> >>>>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> >>>>>> handled, otherwise all sockets are allowed). This might be reasonable, as
> >>>>>> one can only connect to a socket it creates if it has the permission to
> >>>>>> create it in the first place, which is already controlled by
> >>>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> >>>>>> flexibility here - if for some reason the sandboxer don't want to allow
> >>>>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
> >>>>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> >>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> >>>>> socket, not to connect. I guess you was thinking about
> >>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
> >>>> In this "allow same-scope connect unconditionally" proposal, the
> >>>> application would still be able to (bind to and) connect to its own
> >>>> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> >>>> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> >>>> which for whatever reason doesn't want this "allow same scope" default can
> >>>> still prevent the use of (pathname) sockets by restricting
> >>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> >>>> sockets it doesn't own, and can't create any sockets itself either, then
> >>>> it effectively can't connect to any sockets at all.
> >>>>
> >>>> (Although on second thought, I guess there could be a case where an app
> >>>> first creates some socket files before doing landlock_restrict_self(),
> >>>> then it might still be able to bind to these even without
> >>>> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
> >>> FWIW, I also really liked Tingmao's first of the two listed
> >>> possibilities in [1], where she proposed to introduce both rights
> >>> together. In my understanding, the arguments we have discussed so far
> >>> for that are:
> >>>
> >>> IN FAVOR:
> >>>
> >>> (pro1) Connecting to a UNIX socket in the same scope is always safe,
> >>> and it makes it possible to use named UNIX sockets between the
> >>> processes within a Landlock domains. (Mickaël convinced me in
> >>> discussion at FOSDEM that this is true.)
> >>>
> >>> If someone absolutely does not want that, they can restrict
> >>> LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
> >>> Tingmao said above).
> >>>
> >>> (pro2) The implementation of this is simpler.
> >>>
> >>> (I attempted to understand how the "or" semantics would be
> >>> implemented, and I found it non-trivial when you try to do it
> >>> for all layers at once. (Kernighan's Law applies, IMHO))
> >> I think the logic would basically be:
> >>
> >> 1. if any layers deny the access due to handled RESOLVE_UNIX but does not
> >> have the scope bit set, then we will deny rightaway, without calling
> >> domain_is_scoped().
> >>
> >> 2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
> >> there are RESOLVE_UNIX rules covering the socket being accessed, and
> >> essentially ignore those layers in the scope violation check.
>
> Tingmao:
>
> For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
>
> 1. security_unix_find. (to look up the paths)
>
> 2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
>
> Which for is called in DGRAM:
>
> unix_dgram_connect OR unix_dgram_sendmsg
>
> and for STREAM:
>
> unix_stream_connect
>
> IIRC, the path lookup only occurs in this order always, so in the logic as you have it the domain_is_scoped()
> would be called twice, once from the security_unix_find when you call it in step two, and once from the
> domain scope hooks. (If access was allowed from security_unix_find)
>
> There are a couple of things to consider.
>
> ---
>
> Audit blockers need special handling:
>
> Here's an example:
>
> 1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
> access bit (deny all for RESOLVE_UNIX).
>
> 2. Program A connects to /tmp/mysock.sock ran by program B, which is outside the domain.
>
> 2. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
> /tmp/mysock.sock, so it calls domain_is_scoped
>
> 3. domain_is_scoped denies it as well, so now we must log an audit record.
>
> When logging the denial, we have to include both blockers "scope.unix_socket" and "fs.resolve_unix" for the
> denial, because it is the absence of both that caused the denial. I think the refer right has similar cases for auditing, so there is precedent for this (multiple blockers for an audit message).
That's a good point, and it would give more informations to diagnose
issues. However, being able to identify if both accesses are denied
would require to check both, whereas the first is enough to know that
Landlock denies the access. So, if we can return both records without
continuing the security checks, that's good, otherwise we should stop
ASAP and return the error.
Anyway, that might not be needed if we end up with my latest proposal
about always setting scope.unix_socket when fs.resolve_unix is set.
>
> ---
>
> Dual lookup for domain_is_scoped. Consider this case:
>
> 1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
> access bit (deny all for RESOLVE_UNIX).
>
> 2. Program A connects to Program C's /tmp/foo.sock, which for the purposes of this example is in the domain of program A.
>
> 3. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
> /tmp/mysock.sock, so it calls domain_is_scoped. Access is granted, and continues. (LSM hook complete)
>
> 4. The connection proceeds past the path lookup stage, and now security_unix_may_send, or security_unix_stream_connect gets called. This requires ANOTHER domain_is_scoped access check.
>
> While I don't THINK this introduces a TOCTOU, it is a little confusing.
>
> This does mean that we look up the domain twice, if this is implemented naively. I think we can then just
> skip the task credential checks then for security_unix_may_send and security_unix_stream_connect **for
> connecting to pathname sockets**, since the domain_is_scoped will already have been called in landlock's
> security_unix_find hook, eliminating the need for handling pathname socket domain checks layer on.
>
> >>
> >> I definitely agree that it is tricky, but making same-scope access be
> >> allowed (i.e. the suggested idea above) would only get rid of step 1,
> >> which I think is the "simpler" bit. The extra logic in step 2 is still
> >> needed.
> >>
> >> I definitely agree with pro1 tho.
> > Yes, you are describing the logic for the variant where
> > LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
> > within the same scope. In that variant, there can be situations where
> > the first hook can deny the action immediately.
> >
> > In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
> > allow access from within the same scope, that shortcutting is not
> > possible. On the upside however, there is no need to distinguish
> > whether the scope flag is set when we are in the security_unix_find()
> > hook, because access from within the same scope is always permitted.
> > (That is the simplification I meant.)
> >
> >
> >>> AGAINST:
> >>>
> >>> (con1) It would work differently than the other scoped access rights
> >>> that we already have.
> >>>
> >>> A speculative feature that could potentially be built with the
> >>> scoped access rights is that we could add a rule to permit IPC
> >>> to other Landlock scopes, e.g. introducing a new rule type
> >>>
> >>> struct landlock_scope_attr {
> >>> __u64 allowed_access; /* for "scoped" bits */
> >>> /* some way to identify domains */
> >>> }
> >>>
> >>> so that we could make IPC access to other Landlock domains
> >>> configurable.
> >>>
> >>> If the scoped bit and the FS RESOLVE_UNIX bit were both
> >>> conflated in RESOLVE_UNIX, it would not be possible to make
> >>> UNIX connections configurable in such a way.
> >> This exact API would no longer work, but if we give up the equivalence
> >> between scope bits and the landlock_scope_attr struct, then we can do
> >> something like:
> >>
> >> struct landlock_scope_attr {
> >> __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
> >> __u64 abstract_unix_socket:1;
> >> __u64 pathname_unix_socket:1;
> >> /* ... */
> >>
> >> __u64 allowed_signals;
> >>
> >> /*
> >> * some way to identify domains, maybe we could use the audit domain
> >> * ID, with 0 denoting "allow access to non-Landlocked processes?
> >> */
> >> }
> > Yes, it would be possible to use such a struct for that scenario where
> > IPC access gets allowed for other Landlock scopes. It would mean that
> > we would not need to introduce a scoped flag for the pathname UNIX
> > socket connections. But the relationship between that struct
> > landlock_scope_attr and the flags and access rights in struct
> > landlock_ruleset_attr would become less clear, which is a slight
> > downside, and maybe error prone for users to work with.
> >
> > If we introduced an additional scoped flag, it would also be
> > consistent though.
> >
> > (con1) was written under the assumption that we do not have an
> > additional scoped flag. If that is lacking, it is not possible to
> > express UNIX connect() access to other Landlock domains with that
> > struct. But as outlined in the proposal below, if we *do* (later)
> > introduce the additional scoped flag *in addition* to the FS access
> > right, this *both* stays consistent in semantics with the signal and
> > abstract UNIX support, *and* it starts working in a world where ICP
> > access can be allowed to talk to other Landlock domains.
> >
> >>> (con2) Consistent behaviour between scoped flags and their
> >>> interactions with other access rights:
> >>>
> >>> The existing scoped access rights (signal, abstract sockets)
> >>> could hypothetically be extended with a related access right of
> >>> another type. For instance, there could be an access right type
> >>>
> >>> __u64 handled_signal_number;
> >>>
> >>> and then you could add a rule to permit the use of certain
> >>> signal numbers. The interaction between the scoped flags and
> >>> other access rights should work the same.
> >>>
> >>>
> >>> Constructive Proposal for consideration: Why not both?
> >>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >> I will think about the following a bit more but I'm afraid that I feel
> >> like it might get slightly confusing. With this, the only reason for
> >> having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
> >> later enable allowing access to other domains (if I understood correctly),
> >> in which case I personally feel like the suggestion on landlock_scope_attr
> >> above, where we essentially accept that it is decoupled with the scope
> >> bits in the ruleset, might be simpler...?
> > Mickaël expressed the opinion to me that he would like to APIs to stay
> > consistent between signals, abstract UNIX sockets, named UNIX sockets
> > and other future "scoped" operations, in scenarios where:
> >
> > * the "scoped" (IPC) operations can be configured to give access to
> > other Landlock domains (and that should work for UNIX connections too)
> > * the existing "scoped" operations also start having matching access rights
> >
> > I think with the way I proposed, that would be consistent.
> >
> >
> >>> Why not do both what Tingmao proposed in [1] **and** reserve the
> >>> option to add the matching "scoped flag" later?
> >>>
> >>> * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
> >>>
> >>> If it is handled, UNIX connections are allowed either:
> >>>
> >>> (1) if the connection is to a service in the same scope, or
> >>> (2) if the path was allow-listed with a "path beneath" rule.
> >>>
> >>> * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
> >>>
> >>>
> >>> Let's go through the arguments again:
> >>>
> >>> We have observed that it is harmless to allow connections to services
> >>> in the same scope (1), and that if users absolutely don't want that,
> >>> they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> >>> (pro1).
> >>>
> >>> (con1): Can we still implement the feature idea where we poke a hole
> >>> to get UNIX-connect() access to other Landlock domains?
> >>>
> >>> I think the answer is yes. The implementation strategy is:
> >>>
> >>> * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> >>> * The scoped bit can now be used to allow-list connections to
> >>> other Landlock domains.
> >>>
> >>> For users, just setting the scoped bit on its own does the same as
> >>> handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> >>> implementation can also stay simple. The only reason why the scoped
> >>> bit is needed is because it makes it possible to allow-list
> >>> connections to other Landlock domains, but at the same time, it is
> >>> safe if libraries set the scoped bit once it exists, as it does not
> >>> have any bad runtime impact either.
> >>>
> >>> (con2): Consistency: Do all the scoped flags interact with their
> >>> corresponding access rights in the same way?
> >>>
> >>> The other scope flags do not have corresponding access rights, so
> >>> far.
> >>>
> >>> If we were to add corresponding access rights for the other scope
> >>> flags, I would argue that we could apply a consistent logic there,
> >>> because IPC access within the same scope is always safe:
> >>>
> >>> - A hypothetical access right type for "signal numbers" would only
> >>> restrict signals that go beyond the current scope.
> >>>
> >>> - A hypothetical access right type for "abstract UNIX domain socket
> >>> names" would only restrict connections to abstract UNIX domain
> >>> servers that go beyond the current scope.
> >>>
> >>> I can not come up with a scenario where this doesn't work.
>
> Gunther / Tingmao / Mickaël:
>
> I have a potential idea to make this concept cleaner.
>
> The docs for landlock currently say:
>
>
> IPC scoping does not support exceptions via landlock_add_rule(2).
> If an operation is scoped within a domain, no rules can be added
> to allow access to resources or processes outside of the scope.
This part might indeed be confusing. The idea was to explain the
difference between scoped rights and handled access rights (which may
have rules).
>
> So if we go with the solution where we are now saying IPC scoping DOES support exceptions
> we will need to update the documentation, to say scoping for pathname unix sockets is an exception,
> and have to have the "exemptible scopes" (like this one) alongside "non-exemptible" scopes
> (ie the existing ones). This creates some friction for users.
The documentation will definitely require some updates. I think it can
be explained in a simple way.
>
> If we foresee other "exempt-able scopes" (which are scopes that also support creating exemptions w/ corresponding access rights) in the future, maybe we should consider separating the two in the ruleset
> attributes (I used scoped_fs as an example for the attribute name):
>
> structlandlock_ruleset_attrruleset_attr={
> .handled_access_fs=
> LANDLOCK_ACCESS_FS_EXECUTE|
> LANDLOCK_ACCESS_FS_WRITE_FILE|
> LANDLOCK_ACCESS_FS_READ_FILE|
> LANDLOCK_ACCESS_FS_READ_DIR|
> LANDLOCK_ACCESS_FS_REMOVE_DIR|
> LANDLOCK_ACCESS_FS_REMOVE_FILE|
> LANDLOCK_ACCESS_FS_MAKE_CHAR|
> LANDLOCK_ACCESS_FS_MAKE_DIR|
> LANDLOCK_ACCESS_FS_MAKE_REG|
> LANDLOCK_ACCESS_FS_MAKE_SOCK|
> LANDLOCK_ACCESS_FS_MAKE_FIFO|
> LANDLOCK_ACCESS_FS_MAKE_BLOCK|
> LANDLOCK_ACCESS_FS_MAKE_SYM|
> LANDLOCK_ACCESS_FS_REFER|
> LANDLOCK_ACCESS_FS_TRUNCATE|
> LANDLOCK_ACCESS_FS_IOCTL_DEV,
> .handled_access_net=
> LANDLOCK_ACCESS_NET_BIND_TCP|
> LANDLOCK_ACCESS_NET_CONNECT_TCP,
> .scoped=
> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET|
> LANDLOCK_SCOPE_SIGNAL,
> .scoped_fs =
> LANDLOCK_SCOPE_FS_PATHNAME_UNIX_SOCKET
> };
>
> This more clearly distinguishes between scopes that have exceptions/corresponding fs rights,
> and ones that don't. Later we could add scoped_net, if needed. I feel like this would be more
> intuitive and better categorize future scoping rights. An obvious con is increasing the size of
> the ruleset attributes.
I see your point but I don't think it would be worth it to add
sub-scoped fields. Each field has a clear semantic, and the scoped one
is related to the domain wrt other domains.
>
> Of course this separation is only worth it if there are other "exempt-able" rights in the future.
> I can think of a few potential future rights which COULD be scoped and have corresponding rights
> (binder, sysv-ipc, pipes, tcp/udp between two local programs).
Yes, it would definitely be useful to add exception for other kind of
IPCs. The idea would be to be able to describe the peer, either with a
file path, or PID, or cgroups, or a Landlock domain... The inet case
is an interesting idea but that might be a challenging task to
implement, if even possible.
>
> >>>
> >>>
> >>> In conclusion, I think the approach has significant upsides:
> >>>
> >>> * Simpler UAPI: Users only have one access bit to deal with, in the
> >>> near future. Once we do add a scope flag for UNIX connections, it
> >>> does not interact in a surprising way with the corresponding FS
> >>> access right, because with either of these, scoped access is
> >>> allowed.
> >>>
> >>> If users absolutely need to restrict scoped access, they can
> >>> restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> >>> API, but in line with the "make easy things easy, make hard things
> >>> possible" API philosophy. And needing this should be the
> >>> exception rather than the norm, after all.
> >>>
> >>> * Consistent behaviour between scoped flags and regular access
> >>> rights, also for speculative access rights affecting the existing
> >>> scoped flags for signals and abstract UNIX domain sockets.
> >>>
> >>> [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
> > —Günther
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-04 17:43 ` Mickaël Salaün
@ 2026-02-05 8:02 ` Günther Noack
2026-02-05 10:27 ` Mickaël Salaün
0 siblings, 1 reply; 41+ messages in thread
From: Günther Noack @ 2026-02-05 8:02 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Tingmao Wang, Justin Suess,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Wed, Feb 04, 2026 at 06:43:24PM +0100, Mickaël Salaün wrote:
> On Wed, Feb 04, 2026 at 12:44:29PM +0100, Günther Noack wrote:
> > On Tue, Feb 03, 2026 at 09:53:11PM +0000, Tingmao Wang wrote:
> > > On 2/3/26 17:54, Günther Noack wrote:
> > > >> Furthermore, an application / Landlock config etc can always opt to not
> > > >> use the scope bit at all, if it "knows" all the locations where the
> > > >> application's sockets would be placed, and just use RESOLVE_UNIX access
> > > >> right (or nothing if it is not supported).
> > > >>
> > > >> (The following is a bit of a side note, not terribly relevant if we're
> > > >> deciding to go with the patch as is.)
> > > >>
> > > >>>> [...]
> > > >>>> Another way to put it is that, if FS-based and scope-based controls
> > > >>>> interacts in the above proposed way, both mechanisms feel like "poking
> > > >>>> holes" in the other. But as Mickaël said, one can think of the two
> > > >>>> mechanisms not as independent controls, but rather as two interfaces for
> > > >>>> the same control. The socket access control is "enabled" if either the
> > > >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> > > >>>> proposed in this patch is enabled.
> > > >>>>
> > > >>>> With that said, I can think of some alternative ways that might make this
> > > >>>> API look "better" (from a subjective point of view, feedback welcome),
> > > >>>> however it does mean more delays, and specifically, these will depend on
> > > >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> > > >>>>
> > > >>>> One possibility is to simply always allow a Landlock domain to connect to
> > > >>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> > > >>>> handled, otherwise all sockets are allowed). This might be reasonable, as
> > > >>>> one can only connect to a socket it creates if it has the permission to
> > > >>>> create it in the first place, which is already controlled by
> > > >>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> > > >>>> flexibility here - if for some reason the sandboxer don't want to allow
> > > >>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
> > > >>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> > > >>>
> > > >>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> > > >>> socket, not to connect. I guess you was thinking about
> > > >>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
> > > >>
> > > >> In this "allow same-scope connect unconditionally" proposal, the
> > > >> application would still be able to (bind to and) connect to its own
> > > >> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> > > >> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> > > >> which for whatever reason doesn't want this "allow same scope" default can
> > > >> still prevent the use of (pathname) sockets by restricting
> > > >> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> > > >> sockets it doesn't own, and can't create any sockets itself either, then
> > > >> it effectively can't connect to any sockets at all.
> > > >>
> > > >> (Although on second thought, I guess there could be a case where an app
> > > >> first creates some socket files before doing landlock_restrict_self(),
> > > >> then it might still be able to bind to these even without
> > > >> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
> > > >
> > > > FWIW, I also really liked Tingmao's first of the two listed
> > > > possibilities in [1], where she proposed to introduce both rights
> > > > together. In my understanding, the arguments we have discussed so far
> > > > for that are:
> > > >
> > > > IN FAVOR:
> > > >
> > > > (pro1) Connecting to a UNIX socket in the same scope is always safe,
> > > > and it makes it possible to use named UNIX sockets between the
> > > > processes within a Landlock domains. (Mickaël convinced me in
> > > > discussion at FOSDEM that this is true.)
> > > >
> > > > If someone absolutely does not want that, they can restrict
> > > > LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
> > > > Tingmao said above).
> > > >
> > > > (pro2) The implementation of this is simpler.
> > > >
> > > > (I attempted to understand how the "or" semantics would be
> > > > implemented, and I found it non-trivial when you try to do it
> > > > for all layers at once. (Kernighan's Law applies, IMHO))
> > >
> > > I think the logic would basically be:
> > >
> > > 1. if any layers deny the access due to handled RESOLVE_UNIX but does not
> > > have the scope bit set, then we will deny rightaway, without calling
> > > domain_is_scoped().
> > >
> > > 2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
> > > there are RESOLVE_UNIX rules covering the socket being accessed, and
> > > essentially ignore those layers in the scope violation check.
> > >
> > > I definitely agree that it is tricky, but making same-scope access be
> > > allowed (i.e. the suggested idea above) would only get rid of step 1,
> > > which I think is the "simpler" bit. The extra logic in step 2 is still
> > > needed.
> > >
> > > I definitely agree with pro1 tho.
> >
> > Yes, you are describing the logic for the variant where
> > LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
> > within the same scope. In that variant, there can be situations where
> > the first hook can deny the action immediately.
> >
> > In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
> > allow access from within the same scope, that shortcutting is not
> > possible. On the upside however, there is no need to distinguish
> > whether the scope flag is set when we are in the security_unix_find()
> > hook, because access from within the same scope is always permitted.
> > (That is the simplification I meant.)
>
> This proposal make sense, improve IPC restriction consistency (by always
> be able to connect to peers from the same domain), and would simplify
> some checks.
> What bothers me the most is the implicit scoping induced
> by the FS_RESOLVE_UNIX handling.
I see this as similar to other decisions we have taken, e.g. on the
(in-review) socket type patch, we have also discussed at the time that
socketpair() should always be allowed, even though it creates sockets,
because this syscall only establishes IPC communication channels
within the same Landlock domain.
Admittedly, in this case it is a bit more clearly about the Landlock
domain boundaries, but still OK in my mind. I think we are aligned
though, see my answer to your proposal below.
> A related issue is the mix of checks
> for the FS_RESOLVE_UNIX implementation, which could make the code more
> complex, but I don't see a better way. Landlock already has an implicit
> ptrace scoping but it's not (and should never be) optional.
I am afraid that we don't have a better option. Even if we only
introduced one of the two patch sets in its original form now, we
would have to introduce that implementation as soon as the other is
added.
> See my proposal below about implicit
> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET. That would not change the kernel
> checks you described though.
> > > > AGAINST:
> > > >
> > > > (con1) It would work differently than the other scoped access rights
> > > > that we already have.
> > > >
> > > > A speculative feature that could potentially be built with the
> > > > scoped access rights is that we could add a rule to permit IPC
> > > > to other Landlock scopes, e.g. introducing a new rule type
> > > >
> > > > struct landlock_scope_attr {
> > > > __u64 allowed_access; /* for "scoped" bits */
> > > > /* some way to identify domains */
> > > > }
> > > >
> > > > so that we could make IPC access to other Landlock domains
> > > > configurable.
> > > >
> > > > If the scoped bit and the FS RESOLVE_UNIX bit were both
> > > > conflated in RESOLVE_UNIX, it would not be possible to make
> > > > UNIX connections configurable in such a way.
> > >
> > > This exact API would no longer work, but if we give up the equivalence
> > > between scope bits and the landlock_scope_attr struct, then we can do
> > > something like:
> > >
> > > struct landlock_scope_attr {
> > > __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
> > > __u64 abstract_unix_socket:1;
> > > __u64 pathname_unix_socket:1;
> > > /* ... */
> > >
> > > __u64 allowed_signals;
> > >
> > > /*
> > > * some way to identify domains, maybe we could use the audit domain
> > > * ID, with 0 denoting "allow access to non-Landlocked processes?
> > > */
> > > }
> >
> > Yes, it would be possible to use such a struct for that scenario where
> > IPC access gets allowed for other Landlock scopes. It would mean that
> > we would not need to introduce a scoped flag for the pathname UNIX
> > socket connections. But the relationship between that struct
> > landlock_scope_attr and the flags and access rights in struct
> > landlock_ruleset_attr would become less clear, which is a slight
> > downside, and maybe error prone for users to work with.
> >
> > If we introduced an additional scoped flag, it would also be
> > consistent though.
> >
> > (con1) was written under the assumption that we do not have an
> > additional scoped flag. If that is lacking, it is not possible to
> > express UNIX connect() access to other Landlock domains with that
> > struct. But as outlined in the proposal below, if we *do* (later)
> > introduce the additional scoped flag *in addition* to the FS access
> > right, this *both* stays consistent in semantics with the signal and
> > abstract UNIX support, *and* it starts working in a world where ICP
> > access can be allowed to talk to other Landlock domains.
>
> In this case, we need to assume the scoped flag is already there, and
> because we have the implementation, to actually add it now.
>
> As a side note, I don't really like this landlock_scope_attr interface.
> A dedicated attr per kind of IPC would be much more flexible (e.g. the
> handled_signal_number you described below). For instance, even if at
> first we don't make it possible to describe different signals within the
> attr struct, this struct will be able to grow. So it's better to have a
> dedicated interface per IPC type than a generic scope interface that can
> only express a subset of it.
>
> Anyway, this proposal could work for pathname UNIX sockets, but a future
> signal attr would be a bit inconsistent wrt to the pathname UNIX one.
See my answer to your proposal below.
> > > > (con2) Consistent behaviour between scoped flags and their
> > > > interactions with other access rights:
> > > >
> > > > The existing scoped access rights (signal, abstract sockets)
> > > > could hypothetically be extended with a related access right of
> > > > another type. For instance, there could be an access right type
> > > >
> > > > __u64 handled_signal_number;
> > > >
> > > > and then you could add a rule to permit the use of certain
> > > > signal numbers. The interaction between the scoped flags and
> > > > other access rights should work the same.
> > > >
> > > >
> > > > Constructive Proposal for consideration: Why not both?
> > > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > >
> > > I will think about the following a bit more but I'm afraid that I feel
> > > like it might get slightly confusing. With this, the only reason for
> > > having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
> > > later enable allowing access to other domains (if I understood correctly),
> > > in which case I personally feel like the suggestion on landlock_scope_attr
> > > above, where we essentially accept that it is decoupled with the scope
> > > bits in the ruleset, might be simpler...?
> >
> > Mickaël expressed the opinion to me that he would like to APIs to stay
> > consistent between signals, abstract UNIX sockets, named UNIX sockets
> > and other future "scoped" operations, in scenarios where:
> >
> > * the "scoped" (IPC) operations can be configured to give access to
> > other Landlock domains (and that should work for UNIX connections too)
> > * the existing "scoped" operations also start having matching access rights
> >
> > I think with the way I proposed, that would be consistent.
>
> What about always implicitly set LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (in
> the kernel) when LANDLOCK_ACCESS_FS_RESOLVE_UNIX is set?
>
> *Requiring* users to set LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET would add a
> new way to trigger an error, but setting
> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET *implicitly* looks safer. This
> might also help for the documentation BTW.
>
> If we agree on this, I could merge the scope_pathname_unix in 7.0, and
> merge the resolve_unix in 7.1 (or later if something unexpected happen).
> Otherwise, I'll merge both at the same time with the same ABI version
> (with the risk to postpone again). WDYT?
In my understanding, the only thing in which our two proposals differ
is the order in which we introduce the
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET and
LANDLOCK_ACCESS_FS_RESOLVE_UNIX bits to the UAPI.
As soon as both bits are added, our two proposals do the same thing.
My proposal (introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX first)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The reason why I have been arguing to introduce
LANDLOCK_ACCESS_FS_RESOLVE_UNIX first is:
(a) It is possible (which initially wasn't obvious to me):
When implementing this, handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX
implies the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET semantics(!), even
when that bit is not exposed in the UAPI yet. (When
LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled, UNIX-connect always
works within the same Landlock domain, even for the time when the
scoped flag is not exposed in the UAPI yet.)
That way, everything stays compatible even when the scoped flag is
later introduced.
(b) When introducing LANDLOCK_ACCESS_FS_RESOLVE_UNIX first, the UAPI
will initially only expose one of the two bits.
This simplifies the UAPI because users initially do not need to
deal with the fact that two bits in the UAPI "interact".
We can still introduce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later.
But the only ruleset-building feature for which this needed would
be if we want to have rules that allow outwards IPC interactions
with other Landlock domains.
Possibility of YAGNI: I am not sure how certain you are that the
speculative allowing-scope-access feature is needed; I mentioned
it here only because it is an option we want to keep open. If we
end up *not* needing this feature though, we will *not need to
introduce the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET bit at all*,
because LANDLOCK_ACCESS_FS_RESOLVE_UNIX can already do all that is
needed.
The implementation of this approach would be that we would have to
join the functionality from the scoped and FS-based patch set, but
without introducing the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET flag in
the UAPI.
Your proposal (introduce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET first)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Upside: users won't be surprised if UNIX-connect within
the same Landlock domain works, because the bit is there for it
* Downside: They'll have to understand that the FS bit implies the
scope flag.
But as I said in the beginning -- if we add both flags, these two
proposals end up being the same in the end.
Maybe the main point that is unclear to me is what I called the
"Possibility of YAGNI" above: it is at this point not 100% clear to me
that the feature for allowing IPC to other domains is going to happen
(we don't have a bug tracker issue for it either, AFAICT). So in the
case where this does not actually end up happening, I think that my
approach has the advantage of introducing one flag less, so the UAPI
stays simpler.
If you know that that feature will happen though, I think the two
approaches are equally good in the long run.
But either way, Mickaël, you are the maintainer here :); if you prefer
to do the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patch set in its current
form first, it's also OK with me. As I said, I think these two
approaches are the same.
P.S. Tingmao: One additional complication, in case the
LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patch set gets sent first -- your
current patch set uses a different LSM hook than the final state which
we are aiming for. We may need to double check that this will stay
compatible even if we move the scoped-check into the new LSM hook in
the future. (If we move the check into a different hook, there may be
some obscure scenarios in which a denied connect()/send() results in a
different error code.) I may comment on the patch set.
> > > > Why not do both what Tingmao proposed in [1] **and** reserve the
> > > > option to add the matching "scoped flag" later?
> > > >
> > > > * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
> > > >
> > > > If it is handled, UNIX connections are allowed either:
> > > >
> > > > (1) if the connection is to a service in the same scope, or
> > > > (2) if the path was allow-listed with a "path beneath" rule.
> > > >
> > > > * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
> > > >
> > > >
> > > > Let's go through the arguments again:
> > > >
> > > > We have observed that it is harmless to allow connections to services
> > > > in the same scope (1), and that if users absolutely don't want that,
> > > > they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> > > > (pro1).
> > > >
> > > > (con1): Can we still implement the feature idea where we poke a hole
> > > > to get UNIX-connect() access to other Landlock domains?
> > > >
> > > > I think the answer is yes. The implementation strategy is:
> > > >
> > > > * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> > > > * The scoped bit can now be used to allow-list connections to
> > > > other Landlock domains.
> > > >
> > > > For users, just setting the scoped bit on its own does the same as
> > > > handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> > > > implementation can also stay simple. The only reason why the scoped
> > > > bit is needed is because it makes it possible to allow-list
> > > > connections to other Landlock domains, but at the same time, it is
> > > > safe if libraries set the scoped bit once it exists, as it does not
> > > > have any bad runtime impact either.
> > > >
> > > > (con2): Consistency: Do all the scoped flags interact with their
> > > > corresponding access rights in the same way?
> > > >
> > > > The other scope flags do not have corresponding access rights, so
> > > > far.
> > > >
> > > > If we were to add corresponding access rights for the other scope
> > > > flags, I would argue that we could apply a consistent logic there,
> > > > because IPC access within the same scope is always safe:
> > > >
> > > > - A hypothetical access right type for "signal numbers" would only
> > > > restrict signals that go beyond the current scope.
> > > >
> > > > - A hypothetical access right type for "abstract UNIX domain socket
> > > > names" would only restrict connections to abstract UNIX domain
> > > > servers that go beyond the current scope.
> > > >
> > > > I can not come up with a scenario where this doesn't work.
> > > >
> > > >
> > > > In conclusion, I think the approach has significant upsides:
> > > >
> > > > * Simpler UAPI: Users only have one access bit to deal with, in the
> > > > near future. Once we do add a scope flag for UNIX connections, it
> > > > does not interact in a surprising way with the corresponding FS
> > > > access right, because with either of these, scoped access is
> > > > allowed.
> > > >
> > > > If users absolutely need to restrict scoped access, they can
> > > > restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> > > > API, but in line with the "make easy things easy, make hard things
> > > > possible" API philosophy. And needing this should be the
> > > > exception rather than the norm, after all.
> > > >
> > > > * Consistent behaviour between scoped flags and regular access
> > > > rights, also for speculative access rights affecting the existing
> > > > scoped flags for signals and abstract UNIX domain sockets.
> > > >
> > > > [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
>
> Thanks for this summary Günther!
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-05 8:02 ` Günther Noack
@ 2026-02-05 10:27 ` Mickaël Salaün
2026-02-08 2:57 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-05 10:27 UTC (permalink / raw)
To: Günther Noack, Paul Moore, John Johansen
Cc: Günther Noack, Tingmao Wang, Justin Suess,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Thu, Feb 05, 2026 at 09:02:19AM +0100, Günther Noack wrote:
> On Wed, Feb 04, 2026 at 06:43:24PM +0100, Mickaël Salaün wrote:
> > On Wed, Feb 04, 2026 at 12:44:29PM +0100, Günther Noack wrote:
> > > On Tue, Feb 03, 2026 at 09:53:11PM +0000, Tingmao Wang wrote:
> > > > On 2/3/26 17:54, Günther Noack wrote:
> > > > >> Furthermore, an application / Landlock config etc can always opt to not
> > > > >> use the scope bit at all, if it "knows" all the locations where the
> > > > >> application's sockets would be placed, and just use RESOLVE_UNIX access
> > > > >> right (or nothing if it is not supported).
> > > > >>
> > > > >> (The following is a bit of a side note, not terribly relevant if we're
> > > > >> deciding to go with the patch as is.)
> > > > >>
> > > > >>>> [...]
> > > > >>>> Another way to put it is that, if FS-based and scope-based controls
> > > > >>>> interacts in the above proposed way, both mechanisms feel like "poking
> > > > >>>> holes" in the other. But as Mickaël said, one can think of the two
> > > > >>>> mechanisms not as independent controls, but rather as two interfaces for
> > > > >>>> the same control. The socket access control is "enabled" if either the
> > > > >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
> > > > >>>> proposed in this patch is enabled.
> > > > >>>>
> > > > >>>> With that said, I can think of some alternative ways that might make this
> > > > >>>> API look "better" (from a subjective point of view, feedback welcome),
> > > > >>>> however it does mean more delays, and specifically, these will depend on
> > > > >>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
> > > > >>>>
> > > > >>>> One possibility is to simply always allow a Landlock domain to connect to
> > > > >>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
> > > > >>>> handled, otherwise all sockets are allowed). This might be reasonable, as
> > > > >>>> one can only connect to a socket it creates if it has the permission to
> > > > >>>> create it in the first place, which is already controlled by
> > > > >>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
> > > > >>>> flexibility here - if for some reason the sandboxer don't want to allow
> > > > >>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
> > > > >>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
> > > > >>>
> > > > >>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
> > > > >>> socket, not to connect. I guess you was thinking about
> > > > >>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
> > > > >>
> > > > >> In this "allow same-scope connect unconditionally" proposal, the
> > > > >> application would still be able to (bind to and) connect to its own
> > > > >> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
> > > > >> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access. But a sandboxer
> > > > >> which for whatever reason doesn't want this "allow same scope" default can
> > > > >> still prevent the use of (pathname) sockets by restricting
> > > > >> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
> > > > >> sockets it doesn't own, and can't create any sockets itself either, then
> > > > >> it effectively can't connect to any sockets at all.
> > > > >>
> > > > >> (Although on second thought, I guess there could be a case where an app
> > > > >> first creates some socket files before doing landlock_restrict_self(),
> > > > >> then it might still be able to bind to these even without
> > > > >> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
> > > > >
> > > > > FWIW, I also really liked Tingmao's first of the two listed
> > > > > possibilities in [1], where she proposed to introduce both rights
> > > > > together. In my understanding, the arguments we have discussed so far
> > > > > for that are:
> > > > >
> > > > > IN FAVOR:
> > > > >
> > > > > (pro1) Connecting to a UNIX socket in the same scope is always safe,
> > > > > and it makes it possible to use named UNIX sockets between the
> > > > > processes within a Landlock domains. (Mickaël convinced me in
> > > > > discussion at FOSDEM that this is true.)
> > > > >
> > > > > If someone absolutely does not want that, they can restrict
> > > > > LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
> > > > > Tingmao said above).
> > > > >
> > > > > (pro2) The implementation of this is simpler.
> > > > >
> > > > > (I attempted to understand how the "or" semantics would be
> > > > > implemented, and I found it non-trivial when you try to do it
> > > > > for all layers at once. (Kernighan's Law applies, IMHO))
> > > >
> > > > I think the logic would basically be:
> > > >
> > > > 1. if any layers deny the access due to handled RESOLVE_UNIX but does not
> > > > have the scope bit set, then we will deny rightaway, without calling
> > > > domain_is_scoped().
> > > >
> > > > 2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
> > > > there are RESOLVE_UNIX rules covering the socket being accessed, and
> > > > essentially ignore those layers in the scope violation check.
> > > >
> > > > I definitely agree that it is tricky, but making same-scope access be
> > > > allowed (i.e. the suggested idea above) would only get rid of step 1,
> > > > which I think is the "simpler" bit. The extra logic in step 2 is still
> > > > needed.
> > > >
> > > > I definitely agree with pro1 tho.
> > >
> > > Yes, you are describing the logic for the variant where
> > > LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
> > > within the same scope. In that variant, there can be situations where
> > > the first hook can deny the action immediately.
> > >
> > > In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
> > > allow access from within the same scope, that shortcutting is not
> > > possible. On the upside however, there is no need to distinguish
> > > whether the scope flag is set when we are in the security_unix_find()
> > > hook, because access from within the same scope is always permitted.
> > > (That is the simplification I meant.)
> >
> > This proposal make sense, improve IPC restriction consistency (by always
> > be able to connect to peers from the same domain), and would simplify
> > some checks.
>
> > What bothers me the most is the implicit scoping induced
> > by the FS_RESOLVE_UNIX handling.
>
> I see this as similar to other decisions we have taken, e.g. on the
> (in-review) socket type patch, we have also discussed at the time that
> socketpair() should always be allowed, even though it creates sockets,
> because this syscall only establishes IPC communication channels
> within the same Landlock domain.
>
> Admittedly, in this case it is a bit more clearly about the Landlock
> domain boundaries, but still OK in my mind. I think we are aligned
> though, see my answer to your proposal below.
>
>
> > A related issue is the mix of checks
> > for the FS_RESOLVE_UNIX implementation, which could make the code more
> > complex, but I don't see a better way. Landlock already has an implicit
> > ptrace scoping but it's not (and should never be) optional.
>
> I am afraid that we don't have a better option. Even if we only
> introduced one of the two patch sets in its original form now, we
> would have to introduce that implementation as soon as the other is
> added.
>
>
> > See my proposal below about implicit
> > LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET. That would not change the kernel
> > checks you described though.
>
>
> > > > > AGAINST:
> > > > >
> > > > > (con1) It would work differently than the other scoped access rights
> > > > > that we already have.
> > > > >
> > > > > A speculative feature that could potentially be built with the
> > > > > scoped access rights is that we could add a rule to permit IPC
> > > > > to other Landlock scopes, e.g. introducing a new rule type
> > > > >
> > > > > struct landlock_scope_attr {
> > > > > __u64 allowed_access; /* for "scoped" bits */
> > > > > /* some way to identify domains */
> > > > > }
> > > > >
> > > > > so that we could make IPC access to other Landlock domains
> > > > > configurable.
> > > > >
> > > > > If the scoped bit and the FS RESOLVE_UNIX bit were both
> > > > > conflated in RESOLVE_UNIX, it would not be possible to make
> > > > > UNIX connections configurable in such a way.
> > > >
> > > > This exact API would no longer work, but if we give up the equivalence
> > > > between scope bits and the landlock_scope_attr struct, then we can do
> > > > something like:
> > > >
> > > > struct landlock_scope_attr {
> > > > __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
> > > > __u64 abstract_unix_socket:1;
> > > > __u64 pathname_unix_socket:1;
> > > > /* ... */
> > > >
> > > > __u64 allowed_signals;
> > > >
> > > > /*
> > > > * some way to identify domains, maybe we could use the audit domain
> > > > * ID, with 0 denoting "allow access to non-Landlocked processes?
> > > > */
> > > > }
> > >
> > > Yes, it would be possible to use such a struct for that scenario where
> > > IPC access gets allowed for other Landlock scopes. It would mean that
> > > we would not need to introduce a scoped flag for the pathname UNIX
> > > socket connections. But the relationship between that struct
> > > landlock_scope_attr and the flags and access rights in struct
> > > landlock_ruleset_attr would become less clear, which is a slight
> > > downside, and maybe error prone for users to work with.
> > >
> > > If we introduced an additional scoped flag, it would also be
> > > consistent though.
> > >
> > > (con1) was written under the assumption that we do not have an
> > > additional scoped flag. If that is lacking, it is not possible to
> > > express UNIX connect() access to other Landlock domains with that
> > > struct. But as outlined in the proposal below, if we *do* (later)
> > > introduce the additional scoped flag *in addition* to the FS access
> > > right, this *both* stays consistent in semantics with the signal and
> > > abstract UNIX support, *and* it starts working in a world where ICP
> > > access can be allowed to talk to other Landlock domains.
> >
> > In this case, we need to assume the scoped flag is already there, and
> > because we have the implementation, to actually add it now.
> >
> > As a side note, I don't really like this landlock_scope_attr interface.
> > A dedicated attr per kind of IPC would be much more flexible (e.g. the
> > handled_signal_number you described below). For instance, even if at
> > first we don't make it possible to describe different signals within the
> > attr struct, this struct will be able to grow. So it's better to have a
> > dedicated interface per IPC type than a generic scope interface that can
> > only express a subset of it.
> >
> > Anyway, this proposal could work for pathname UNIX sockets, but a future
> > signal attr would be a bit inconsistent wrt to the pathname UNIX one.
>
> See my answer to your proposal below.
>
>
> > > > > (con2) Consistent behaviour between scoped flags and their
> > > > > interactions with other access rights:
> > > > >
> > > > > The existing scoped access rights (signal, abstract sockets)
> > > > > could hypothetically be extended with a related access right of
> > > > > another type. For instance, there could be an access right type
> > > > >
> > > > > __u64 handled_signal_number;
> > > > >
> > > > > and then you could add a rule to permit the use of certain
> > > > > signal numbers. The interaction between the scoped flags and
> > > > > other access rights should work the same.
> > > > >
> > > > >
> > > > > Constructive Proposal for consideration: Why not both?
> > > > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > > >
> > > > I will think about the following a bit more but I'm afraid that I feel
> > > > like it might get slightly confusing. With this, the only reason for
> > > > having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
> > > > later enable allowing access to other domains (if I understood correctly),
> > > > in which case I personally feel like the suggestion on landlock_scope_attr
> > > > above, where we essentially accept that it is decoupled with the scope
> > > > bits in the ruleset, might be simpler...?
> > >
> > > Mickaël expressed the opinion to me that he would like to APIs to stay
> > > consistent between signals, abstract UNIX sockets, named UNIX sockets
> > > and other future "scoped" operations, in scenarios where:
> > >
> > > * the "scoped" (IPC) operations can be configured to give access to
> > > other Landlock domains (and that should work for UNIX connections too)
> > > * the existing "scoped" operations also start having matching access rights
> > >
> > > I think with the way I proposed, that would be consistent.
> >
> > What about always implicitly set LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET (in
> > the kernel) when LANDLOCK_ACCESS_FS_RESOLVE_UNIX is set?
> >
> > *Requiring* users to set LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET would add a
> > new way to trigger an error, but setting
> > LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET *implicitly* looks safer. This
> > might also help for the documentation BTW.
> >
> > If we agree on this, I could merge the scope_pathname_unix in 7.0, and
> > merge the resolve_unix in 7.1 (or later if something unexpected happen).
> > Otherwise, I'll merge both at the same time with the same ABI version
> > (with the risk to postpone again). WDYT?
>
> In my understanding, the only thing in which our two proposals differ
> is the order in which we introduce the
> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET and
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX bits to the UAPI.
>
> As soon as both bits are added, our two proposals do the same thing.
>
> My proposal (introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX first)
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> The reason why I have been arguing to introduce
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX first is:
>
> (a) It is possible (which initially wasn't obvious to me):
>
> When implementing this, handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX
> implies the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET semantics(!), even
> when that bit is not exposed in the UAPI yet. (When
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled, UNIX-connect always
> works within the same Landlock domain, even for the time when the
> scoped flag is not exposed in the UAPI yet.)
>
> That way, everything stays compatible even when the scoped flag is
> later introduced.
>
> (b) When introducing LANDLOCK_ACCESS_FS_RESOLVE_UNIX first, the UAPI
> will initially only expose one of the two bits.
>
> This simplifies the UAPI because users initially do not need to
> deal with the fact that two bits in the UAPI "interact".
It simplifies but users can just ignore the bits for which they don't
care. They just have to set the one they want (either scope or resolve)
and Landlock will do the right thing. :)
The only corner case is that setting scope_pathname_unix or not when
resolve_unix is set will result to the same restrictions. In other
words, resolve_unix is a superset of scope_pathname_unix, so this scope
UAPI bit will be useless when resolve_unix will be introduced.
>
> We can still introduce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later.
> But the only ruleset-building feature for which this needed would
> be if we want to have rules that allow outwards IPC interactions
> with other Landlock domains.
>
> Possibility of YAGNI: I am not sure how certain you are that the
> speculative allowing-scope-access feature is needed; I mentioned
> it here only because it is an option we want to keep open. If we
> end up *not* needing this feature though, we will *not need to
> introduce the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET bit at all*,
> because LANDLOCK_ACCESS_FS_RESOLVE_UNIX can already do all that is
> needed.
Indeed, I get your point. Please add this rationale in the "Design
choices" section in Documentation/security/landlock.rst
>
> The implementation of this approach would be that we would have to
> join the functionality from the scoped and FS-based patch set, but
> without introducing the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET flag in
> the UAPI.
Right, this looks good to me. We'll need to sync both patch series and
remove the scope flag from UAPI. I'll let you and Tingmao work together
for the next series. The "IPC scoping" documentation section should
mention LANDLOCK_ACCESS_FS_RESOLVE_UNIX even if it's not a scope flag.
>
> Your proposal (introduce LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET first)
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> * Upside: users won't be surprised if UNIX-connect within
> the same Landlock domain works, because the bit is there for it
>
> * Downside: They'll have to understand that the FS bit implies the
> scope flag.
Yes, but this would be the same with your approach. Not sure which one
would be the less confusing though.
>
>
>
> But as I said in the beginning -- if we add both flags, these two
> proposals end up being the same in the end.
>
> Maybe the main point that is unclear to me is what I called the
> "Possibility of YAGNI" above: it is at this point not 100% clear to me
> that the feature for allowing IPC to other domains is going to happen
> (we don't have a bug tracker issue for it either, AFAICT). So in the
> case where this does not actually end up happening, I think that my
> approach has the advantage of introducing one flag less, so the UAPI
> stays simpler.
As I said earlier, I'd definitely prefer to have an IPC-specific attr
instead of a generic scope attr. So yes, this looks good to me.
>
> If you know that that feature will happen though, I think the two
> approaches are equally good in the long run.
>
> But either way, Mickaël, you are the maintainer here :); if you prefer
> to do the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patch set in its current
> form first, it's also OK with me. As I said, I think these two
> approaches are the same.
You convinced me. Let's go for the hybrid approach you proposed with
the implicit scope.
>
>
> P.S. Tingmao: One additional complication, in case the
> LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patch set gets sent first -- your
> current patch set uses a different LSM hook than the final state which
> we are aiming for. We may need to double check that this will stay
> compatible even if we move the scoped-check into the new LSM hook in
> the future. (If we move the check into a different hook, there may be
> some obscure scenarios in which a denied connect()/send() results in a
> different error code.) I may comment on the patch set.
That's also a good point. I'd be curious to know if this changes
anything, but it should not except maybe some error codes.
Anyway, we'll need this new LSM hook.
>
> > > > > Why not do both what Tingmao proposed in [1] **and** reserve the
> > > > > option to add the matching "scoped flag" later?
> > > > >
> > > > > * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
> > > > >
> > > > > If it is handled, UNIX connections are allowed either:
> > > > >
> > > > > (1) if the connection is to a service in the same scope, or
> > > > > (2) if the path was allow-listed with a "path beneath" rule.
> > > > >
> > > > > * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
> > > > >
> > > > >
> > > > > Let's go through the arguments again:
> > > > >
> > > > > We have observed that it is harmless to allow connections to services
> > > > > in the same scope (1), and that if users absolutely don't want that,
> > > > > they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> > > > > (pro1).
> > > > >
> > > > > (con1): Can we still implement the feature idea where we poke a hole
> > > > > to get UNIX-connect() access to other Landlock domains?
> > > > >
> > > > > I think the answer is yes. The implementation strategy is:
> > > > >
> > > > > * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> > > > > * The scoped bit can now be used to allow-list connections to
> > > > > other Landlock domains.
> > > > >
> > > > > For users, just setting the scoped bit on its own does the same as
> > > > > handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> > > > > implementation can also stay simple. The only reason why the scoped
> > > > > bit is needed is because it makes it possible to allow-list
> > > > > connections to other Landlock domains, but at the same time, it is
> > > > > safe if libraries set the scoped bit once it exists, as it does not
> > > > > have any bad runtime impact either.
> > > > >
> > > > > (con2): Consistency: Do all the scoped flags interact with their
> > > > > corresponding access rights in the same way?
> > > > >
> > > > > The other scope flags do not have corresponding access rights, so
> > > > > far.
> > > > >
> > > > > If we were to add corresponding access rights for the other scope
> > > > > flags, I would argue that we could apply a consistent logic there,
> > > > > because IPC access within the same scope is always safe:
> > > > >
> > > > > - A hypothetical access right type for "signal numbers" would only
> > > > > restrict signals that go beyond the current scope.
> > > > >
> > > > > - A hypothetical access right type for "abstract UNIX domain socket
> > > > > names" would only restrict connections to abstract UNIX domain
> > > > > servers that go beyond the current scope.
> > > > >
> > > > > I can not come up with a scenario where this doesn't work.
> > > > >
> > > > >
> > > > > In conclusion, I think the approach has significant upsides:
> > > > >
> > > > > * Simpler UAPI: Users only have one access bit to deal with, in the
> > > > > near future. Once we do add a scope flag for UNIX connections, it
> > > > > does not interact in a surprising way with the corresponding FS
> > > > > access right, because with either of these, scoped access is
> > > > > allowed.
> > > > >
> > > > > If users absolutely need to restrict scoped access, they can
> > > > > restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> > > > > API, but in line with the "make easy things easy, make hard things
> > > > > possible" API philosophy. And needing this should be the
> > > > > exception rather than the norm, after all.
> > > > >
> > > > > * Consistent behaviour between scoped flags and regular access
> > > > > rights, also for speculative access rights affecting the existing
> > > > > scoped flags for signals and abstract UNIX domain sockets.
> > > > >
> > > > > [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
> >
> > Thanks for this summary Günther!
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-04 18:28 ` Mickaël Salaün
@ 2026-02-05 15:22 ` Justin Suess
[not found] ` <44d216aa-9680-4cf5-bbf0-173869111212@gmail.com>
1 sibling, 0 replies; 41+ messages in thread
From: Justin Suess @ 2026-02-05 15:22 UTC (permalink / raw)
To: mic
Cc: demiobenour, fahimitahera, gnoack3000, gnoack, hi, jannh,
linux-security-module, m, matthieu, utilityemal77
On 2/4/26 13:28, Mickaël Salaün wrote:
>> [...]
>> Tingmao:
>>
>> For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
>>
>> 1. security_unix_find. (to look up the paths)
>>
>> 2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
>>
>> Which for is called in DGRAM:
>>
>> unix_dgram_connect OR unix_dgram_sendmsg
>>
>> and for STREAM:
>>
>> unix_stream_connect
>>
>> IIRC, the path lookup only occurs in this order always, so in the logic as you have it the domain_is_scoped()
>> would be called twice, once from the security_unix_find when you call it in step two, and once from the
>> domain scope hooks. (If access was allowed from security_unix_find)
>>
>> There are a couple of things to consider.
>>
>> ---
>>
>> Audit blockers need special handling:
>>
>> Here's an example:
>>
>> 1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
>> access bit (deny all for RESOLVE_UNIX).
>>
>> 2. Program A connects to /tmp/mysock.sock ran by program B, which is outside the domain.
>>
>> 2. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
>> /tmp/mysock.sock, so it calls domain_is_scoped
>>
>> 3. domain_is_scoped denies it as well, so now we must log an audit record.
>>
>> When logging the denial, we have to include both blockers "scope.unix_socket" and "fs.resolve_unix" for the
>> denial, because it is the absence of both that caused the denial. I think the refer right has similar cases for auditing, so there is precedent for this (multiple blockers for an audit message).
> That's a good point, and it would give more informations to diagnose
> issues. However, being able to identify if both accesses are denied
> would require to check both, whereas the first is enough to know that
> Landlock denies the access. So, if we can return both records without
> continuing the security checks, that's good, otherwise we should stop
> ASAP and return the error.
Maybe I'm missing something, but if the flags interact in an "OR" manner
wouldn't we need to check both? In your proposal where RESOLVE_UNIX
implies the scoped flag, if a program connects to a unix socket that is within
the domain but does not have an explicit RESOLVE_UNIX exception, we must
still check for the case that the access is scoped.
---
(Given LANDLOCK_ACCESS_FS_RESOLVE_UNIX and LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
are set)
case 1: access to socket within domain and no RESOLVE_UNIX rule covers the access
We check first in security_unix_find hook and find there is no rule allowing the access.
After the check fails, because LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is set
we then check is_domain_scoped, the check allows the access.
case 2: access to socket outside domain but RESOLVE_UNIX rule covers the access
We check first in security_unix_find hook and find there is a rule allowing the access.
We can allow the access early (short-circuit eval) without calling is_domain_scoped.
case 4: access to socket inside domain and RESOLVE_UNIX covers the access
We check first in security_unix_find hook and find there is a rule allowing the access.
We can allow the access early (short-circuit eval) without calling is_domain_scoped. (same as case 2)
case 4: access to socket outside domain and no RESOLVE_UNIX covers the access
We check first in security_unix_find hook and find there is no rule allowing the access.
After the check fails, because LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is set
we then check is_domain_scoped, the check does not allow the access. (it is the combination
of the two checks failing that denied the access).
---
Case 4 is what I'm specifically considering would need to have both blockers listed in a denial audit.
We can't short circuit in that case because we have to check the scoping before denying.
Let me know if I'm misunderstanding this.
(PS: IIRC the hooks used by the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
will never be hit if the check in security_unix_find fails. So some logic to check this
access in security_unix_find will be needed).
> Anyway, that might not be needed if we end up with my latest proposal
> about always setting scope.unix_socket when fs.resolve_unix is set.
>
>> ---
>>
>> Dual lookup for domain_is_scoped. Consider this case:
>>
>> 1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
>> access bit (deny all for RESOLVE_UNIX).
>>
>> 2. Program A connects to Program C's /tmp/foo.sock, which for the purposes of this example is in the domain of program A.
>>
>> 3. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
>> /tmp/mysock.sock, so it calls domain_is_scoped. Access is granted, and continues. (LSM hook complete)
>>
>> 4. The connection proceeds past the path lookup stage, and now security_unix_may_send, or security_unix_stream_connect gets called. This requires ANOTHER domain_is_scoped access check.
>>
>> While I don't THINK this introduces a TOCTOU, it is a little confusing.
>>
>> This does mean that we look up the domain twice, if this is implemented naively. I think we can then just
>> skip the task credential checks then for security_unix_may_send and security_unix_stream_connect **for
>> connecting to pathname sockets**, since the domain_is_scoped will already have been called in landlock's
>> security_unix_find hook, eliminating the need for handling pathname socket domain checks layer on.
>>
>>>> I definitely agree that it is tricky, but making same-scope access be
>>>> allowed (i.e. the suggested idea above) would only get rid of step 1,
>>>> which I think is the "simpler" bit. The extra logic in step 2 is still
>>>> needed.
>>>>
>>>> I definitely agree with pro1 tho.
>>> Yes, you are describing the logic for the variant where
>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
>>> within the same scope. In that variant, there can be situations where
>>> the first hook can deny the action immediately.
>>>
>>> In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
>>> allow access from within the same scope, that shortcutting is not
>>> possible. On the upside however, there is no need to distinguish
>>> whether the scope flag is set when we are in the security_unix_find()
>>> hook, because access from within the same scope is always permitted.
>>> (That is the simplification I meant.)
>>>
>>>
>>>>> AGAINST:
>>>>>
>>>>> (con1) It would work differently than the other scoped access rights
>>>>> that we already have.
>>>>>
>>>>> A speculative feature that could potentially be built with the
>>>>> scoped access rights is that we could add a rule to permit IPC
>>>>> to other Landlock scopes, e.g. introducing a new rule type
>>>>>
>>>>> struct landlock_scope_attr {
>>>>> __u64 allowed_access; /* for "scoped" bits */
>>>>> /* some way to identify domains */
>>>>> }
>>>>>
>>>>> so that we could make IPC access to other Landlock domains
>>>>> configurable.
>>>>>
>>>>> If the scoped bit and the FS RESOLVE_UNIX bit were both
>>>>> conflated in RESOLVE_UNIX, it would not be possible to make
>>>>> UNIX connections configurable in such a way.
>>>> This exact API would no longer work, but if we give up the equivalence
>>>> between scope bits and the landlock_scope_attr struct, then we can do
>>>> something like:
>>>>
>>>> struct landlock_scope_attr {
>>>> __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
>>>> __u64 abstract_unix_socket:1;
>>>> __u64 pathname_unix_socket:1;
>>>> /* ... */
>>>>
>>>> __u64 allowed_signals;
>>>>
>>>> /*
>>>> * some way to identify domains, maybe we could use the audit domain
>>>> * ID, with 0 denoting "allow access to non-Landlocked processes?
>>>> */
>>>> }
>>> Yes, it would be possible to use such a struct for that scenario where
>>> IPC access gets allowed for other Landlock scopes. It would mean that
>>> we would not need to introduce a scoped flag for the pathname UNIX
>>> socket connections. But the relationship between that struct
>>> landlock_scope_attr and the flags and access rights in struct
>>> landlock_ruleset_attr would become less clear, which is a slight
>>> downside, and maybe error prone for users to work with.
>>>
>>> If we introduced an additional scoped flag, it would also be
>>> consistent though.
>>>
>>> (con1) was written under the assumption that we do not have an
>>> additional scoped flag. If that is lacking, it is not possible to
>>> express UNIX connect() access to other Landlock domains with that
>>> struct. But as outlined in the proposal below, if we *do* (later)
>>> introduce the additional scoped flag *in addition* to the FS access
>>> right, this *both* stays consistent in semantics with the signal and
>>> abstract UNIX support, *and* it starts working in a world where ICP
>>> access can be allowed to talk to other Landlock domains.
>>>
>>>>> (con2) Consistent behaviour between scoped flags and their
>>>>> interactions with other access rights:
>>>>>
>>>>> The existing scoped access rights (signal, abstract sockets)
>>>>> could hypothetically be extended with a related access right of
>>>>> another type. For instance, there could be an access right type
>>>>>
>>>>> __u64 handled_signal_number;
>>>>>
>>>>> and then you could add a rule to permit the use of certain
>>>>> signal numbers. The interaction between the scoped flags and
>>>>> other access rights should work the same.
>>>>>
>>>>>
>>>>> Constructive Proposal for consideration: Why not both?
>>>>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>>> I will think about the following a bit more but I'm afraid that I feel
>>>> like it might get slightly confusing. With this, the only reason for
>>>> having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
>>>> later enable allowing access to other domains (if I understood correctly),
>>>> in which case I personally feel like the suggestion on landlock_scope_attr
>>>> above, where we essentially accept that it is decoupled with the scope
>>>> bits in the ruleset, might be simpler...?
>>> Mickaël expressed the opinion to me that he would like to APIs to stay
>>> consistent between signals, abstract UNIX sockets, named UNIX sockets
>>> and other future "scoped" operations, in scenarios where:
>>>
>>> * the "scoped" (IPC) operations can be configured to give access to
>>> other Landlock domains (and that should work for UNIX connections too)
>>> * the existing "scoped" operations also start having matching access rights
>>>
>>> I think with the way I proposed, that would be consistent.
>>>
>>>
>>>>> Why not do both what Tingmao proposed in [1] **and** reserve the
>>>>> option to add the matching "scoped flag" later?
>>>>>
>>>>> * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
>>>>>
>>>>> If it is handled, UNIX connections are allowed either:
>>>>>
>>>>> (1) if the connection is to a service in the same scope, or
>>>>> (2) if the path was allow-listed with a "path beneath" rule.
>>>>>
>>>>> * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
>>>>>
>>>>>
>>>>> Let's go through the arguments again:
>>>>>
>>>>> We have observed that it is harmless to allow connections to services
>>>>> in the same scope (1), and that if users absolutely don't want that,
>>>>> they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
>>>>> (pro1).
>>>>>
>>>>> (con1): Can we still implement the feature idea where we poke a hole
>>>>> to get UNIX-connect() access to other Landlock domains?
>>>>>
>>>>> I think the answer is yes. The implementation strategy is:
>>>>>
>>>>> * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
>>>>> * The scoped bit can now be used to allow-list connections to
>>>>> other Landlock domains.
>>>>>
>>>>> For users, just setting the scoped bit on its own does the same as
>>>>> handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
>>>>> implementation can also stay simple. The only reason why the scoped
>>>>> bit is needed is because it makes it possible to allow-list
>>>>> connections to other Landlock domains, but at the same time, it is
>>>>> safe if libraries set the scoped bit once it exists, as it does not
>>>>> have any bad runtime impact either.
>>>>>
>>>>> (con2): Consistency: Do all the scoped flags interact with their
>>>>> corresponding access rights in the same way?
>>>>>
>>>>> The other scope flags do not have corresponding access rights, so
>>>>> far.
>>>>>
>>>>> If we were to add corresponding access rights for the other scope
>>>>> flags, I would argue that we could apply a consistent logic there,
>>>>> because IPC access within the same scope is always safe:
>>>>>
>>>>> - A hypothetical access right type for "signal numbers" would only
>>>>> restrict signals that go beyond the current scope.
>>>>>
>>>>> - A hypothetical access right type for "abstract UNIX domain socket
>>>>> names" would only restrict connections to abstract UNIX domain
>>>>> servers that go beyond the current scope.
>>>>>
>>>>> I can not come up with a scenario where this doesn't work.
>> Gunther / Tingmao / Mickaël:
>>
>> I have a potential idea to make this concept cleaner.
>>
>> The docs for landlock currently say:
>>
>>
>> IPC scoping does not support exceptions via landlock_add_rule(2).
>> If an operation is scoped within a domain, no rules can be added
>> to allow access to resources or processes outside of the scope.
> This part might indeed be confusing. The idea was to explain the
> difference between scoped rights and handled access rights (which may
> have rules).
>
>> So if we go with the solution where we are now saying IPC scoping DOES support exceptions
>> we will need to update the documentation, to say scoping for pathname unix sockets is an exception,
>> and have to have the "exemptible scopes" (like this one) alongside "non-exemptible" scopes
>> (ie the existing ones). This creates some friction for users.
> The documentation will definitely require some updates. I think it can
> be explained in a simple way.
>
>> If we foresee other "exempt-able scopes" (which are scopes that also support creating exemptions w/ corresponding access rights) in the future, maybe we should consider separating the two in the ruleset
>> attributes (I used scoped_fs as an example for the attribute name):
>>
>> structlandlock_ruleset_attrruleset_attr={
>> .handled_access_fs=
>> LANDLOCK_ACCESS_FS_EXECUTE|
>> LANDLOCK_ACCESS_FS_WRITE_FILE|
>> LANDLOCK_ACCESS_FS_READ_FILE|
>> LANDLOCK_ACCESS_FS_READ_DIR|
>> LANDLOCK_ACCESS_FS_REMOVE_DIR|
>> LANDLOCK_ACCESS_FS_REMOVE_FILE|
>> LANDLOCK_ACCESS_FS_MAKE_CHAR|
>> LANDLOCK_ACCESS_FS_MAKE_DIR|
>> LANDLOCK_ACCESS_FS_MAKE_REG|
>> LANDLOCK_ACCESS_FS_MAKE_SOCK|
>> LANDLOCK_ACCESS_FS_MAKE_FIFO|
>> LANDLOCK_ACCESS_FS_MAKE_BLOCK|
>> LANDLOCK_ACCESS_FS_MAKE_SYM|
>> LANDLOCK_ACCESS_FS_REFER|
>> LANDLOCK_ACCESS_FS_TRUNCATE|
>> LANDLOCK_ACCESS_FS_IOCTL_DEV,
>> .handled_access_net=
>> LANDLOCK_ACCESS_NET_BIND_TCP|
>> LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> .scoped=
>> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET|
>> LANDLOCK_SCOPE_SIGNAL,
>> .scoped_fs =
>> LANDLOCK_SCOPE_FS_PATHNAME_UNIX_SOCKET
>> };
>>
>> This more clearly distinguishes between scopes that have exceptions/corresponding fs rights,
>> and ones that don't. Later we could add scoped_net, if needed. I feel like this would be more
>> intuitive and better categorize future scoping rights. An obvious con is increasing the size of
>> the ruleset attributes.
> I see your point but I don't think it would be worth it to add
> sub-scoped fields. Each field has a clear semantic, and the scoped one
> is related to the domain wrt other domains.
As long as it's documented clearly, and future IPCs have similar behavior
I agree that a separate field probably isn't needed.
>> Of course this separation is only worth it if there are other "exempt-able" rights in the future.
>> I can think of a few potential future rights which COULD be scoped and have corresponding rights
>> (binder, sysv-ipc, pipes, tcp/udp between two local programs).
> Yes, it would definitely be useful to add exception for other kind of
> IPCs. The idea would be to be able to describe the peer, either with a
> file path, or PID, or cgroups, or a Landlock domain... The inet case
> is an interesting idea but that might be a challenging task to
> implement, if even possible.
>>>>> In conclusion, I think the approach has significant upsides:
>>>>>
>>>>> * Simpler UAPI: Users only have one access bit to deal with, in the
>>>>> near future. Once we do add a scope flag for UNIX connections, it
>>>>> does not interact in a surprising way with the corresponding FS
>>>>> access right, because with either of these, scoped access is
>>>>> allowed.
>>>>>
>>>>> If users absolutely need to restrict scoped access, they can
>>>>> restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
>>>>> API, but in line with the "make easy things easy, make hard things
>>>>> possible" API philosophy. And needing this should be the
>>>>> exception rather than the norm, after all.
>>>>>
>>>>> * Consistent behaviour between scoped flags and regular access
>>>>> rights, also for speculative access rights affecting the existing
>>>>> scoped flags for signals and abstract UNIX domain sockets.
>>>>>
>>>>> [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
>>> —Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
[not found] ` <44d216aa-9680-4cf5-bbf0-173869111212@gmail.com>
@ 2026-02-05 19:15 ` Mickaël Salaün
2026-02-08 2:57 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-05 19:15 UTC (permalink / raw)
To: Justin Suess
Cc: Günther Noack, Tingmao Wang, Günther Noack,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Thu, Feb 05, 2026 at 10:18:54AM -0500, Justin Suess wrote:
>
> On 2/4/26 13:28, Mickaël Salaün wrote:
>
> >> [...]
> >> Tingmao:
> >>
> >> For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
> >>
> >> 1. security_unix_find. (to look up the paths)
> >>
> >> 2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
> >>
> >> Which for is called in DGRAM:
> >>
> >> unix_dgram_connect OR unix_dgram_sendmsg
> >>
> >> and for STREAM:
> >>
> >> unix_stream_connect
> >>
> >> IIRC, the path lookup only occurs in this order always, so in the logic as you have it the domain_is_scoped()
> >> would be called twice, once from the security_unix_find when you call it in step two, and once from the
> >> domain scope hooks. (If access was allowed from security_unix_find)
> >>
> >> There are a couple of things to consider.
> >>
> >> ---
> >>
> >> Audit blockers need special handling:
> >>
> >> Here's an example:
> >>
> >> 1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
> >> access bit (deny all for RESOLVE_UNIX).
> >>
> >> 2. Program A connects to /tmp/mysock.sock ran by program B, which is outside the domain.
> >>
> >> 2. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
> >> /tmp/mysock.sock, so it calls domain_is_scoped
> >>
> >> 3. domain_is_scoped denies it as well, so now we must log an audit record.
> >>
> >> When logging the denial, we have to include both blockers "scope.unix_socket" and "fs.resolve_unix" for the
> >> denial, because it is the absence of both that caused the denial. I think the refer right has similar cases for auditing, so there is precedent for this (multiple blockers for an audit message).
> > That's a good point, and it would give more informations to diagnose
> > issues. However, being able to identify if both accesses are denied
> > would require to check both, whereas the first is enough to know that
> > Landlock denies the access. So, if we can return both records without
> > continuing the security checks, that's good, otherwise we should stop
> > ASAP and return the error.
> Maybe I'm missing something, but if the flags interact in an "OR" manner
> wouldn't we need to check both?
Yes, but my point is that as soon as one or the other *denies* an
access, there is no need to check the other access type.
> In your proposal where RESOLVE_UNIX
> implies the scoped flag, if a program connects to a unix socket that is within
> the domain but does not have an explicit RESOLVE_UNIX exception, we must
> still check for the case that the access is scoped.
>
> ---
>
> (Given LANDLOCK_ACCESS_FS_RESOLVE_UNIX and LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> are set)
>
> case 1: access to socket within domain and no RESOLVE_UNIX rule covers the access
>
> We check first in security_unix_find hook and find there is no rule allowing the access.
> After the check fails, because LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is set
> we then check is_domain_scoped, the check allows the access.
>
> case 2: access to socket outside domain but RESOLVE_UNIX rule covers the access
>
> We check first in security_unix_find hook and find there is a rule allowing the access.
> We can allow the access early (short-circuit eval) without calling is_domain_scoped.
>
> case 4: access to socket inside domain and RESOLVE_UNIX covers the access
>
> We check first in security_unix_find hook and find there is a rule allowing the access.
> We can allow the access early (short-circuit eval) without calling is_domain_scoped. (same as case 2)
>
>
> case 4: access to socket outside domain and no RESOLVE_UNIX covers the access
>
> We check first in security_unix_find hook and find there is no rule allowing the access.
> After the check fails, because LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is set
> we then check is_domain_scoped, the check does not allow the access. (it is the combination
> of the two checks failing that denied the access).
BTW, we should first check is_domain_scoped() because it is quicker than
the FS checks.
>
> ---
>
> Case 4 is what I'm specifically considering would need to have both blockers listed in a denial audit.
> We can't short circuit in that case because we have to check the scoping before denying.
> Let me know if I'm misunderstanding this.
Indeed, if both deny the request, both should be listed.
Anyway, we're now going to only have one access flag that would merge
both semantic, so this should not be an issue.
However, this specific case will be relevant when we'll add e.g., a
signal attr that would then be complementary to the signal scope. At
this point, I think it would be enough to only log the signal.* record
because it would be a superset of the scope.signal
>
> (PS: IIRC the hooks used by the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> will never be hit if the check in security_unix_find fails. So some logic to check this
> access in security_unix_find will be needed).
>
> >
> > Anyway, that might not be needed if we end up with my latest proposal
> > about always setting scope.unix_socket when fs.resolve_unix is set.
> >
> >> ---
> >>
> >> Dual lookup for domain_is_scoped. Consider this case:
> >>
> >> 1. Program A enforces a ruleset with RESOLVE_UNIX and the unix pathname scope bit, with no rules with that
> >> access bit (deny all for RESOLVE_UNIX).
> >>
> >> 2. Program A connects to Program C's /tmp/foo.sock, which for the purposes of this example is in the domain of program A.
> >>
> >> 3. security_unix_find is hit to lookup the path, and the RESOLVE_UNIX code doesn't grant access to
> >> /tmp/mysock.sock, so it calls domain_is_scoped. Access is granted, and continues. (LSM hook complete)
> >>
> >> 4. The connection proceeds past the path lookup stage, and now security_unix_may_send, or security_unix_stream_connect gets called. This requires ANOTHER domain_is_scoped access check.
> >>
> >> While I don't THINK this introduces a TOCTOU, it is a little confusing.
> >>
> >> This does mean that we look up the domain twice, if this is implemented naively. I think we can then just
> >> skip the task credential checks then for security_unix_may_send and security_unix_stream_connect **for
> >> connecting to pathname sockets**, since the domain_is_scoped will already have been called in landlock's
> >> security_unix_find hook, eliminating the need for handling pathname socket domain checks layer on.
> >>
> >>>> I definitely agree that it is tricky, but making same-scope access be
> >>>> allowed (i.e. the suggested idea above) would only get rid of step 1,
> >>>> which I think is the "simpler" bit. The extra logic in step 2 is still
> >>>> needed.
> >>>>
> >>>> I definitely agree with pro1 tho.
> >>> Yes, you are describing the logic for the variant where
> >>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX does not implicitly permit access from
> >>> within the same scope. In that variant, there can be situations where
> >>> the first hook can deny the action immediately.
> >>>
> >>> In the variant where LANDLOCK_ACCESS_FS_RESOLVE_UNIX *does* implicitly
> >>> allow access from within the same scope, that shortcutting is not
> >>> possible. On the upside however, there is no need to distinguish
> >>> whether the scope flag is set when we are in the security_unix_find()
> >>> hook, because access from within the same scope is always permitted.
> >>> (That is the simplification I meant.)
> >>>
> >>>
> >>>>> AGAINST:
> >>>>>
> >>>>> (con1) It would work differently than the other scoped access rights
> >>>>> that we already have.
> >>>>>
> >>>>> A speculative feature that could potentially be built with the
> >>>>> scoped access rights is that we could add a rule to permit IPC
> >>>>> to other Landlock scopes, e.g. introducing a new rule type
> >>>>>
> >>>>> struct landlock_scope_attr {
> >>>>> __u64 allowed_access; /* for "scoped" bits */
> >>>>> /* some way to identify domains */
> >>>>> }
> >>>>>
> >>>>> so that we could make IPC access to other Landlock domains
> >>>>> configurable.
> >>>>>
> >>>>> If the scoped bit and the FS RESOLVE_UNIX bit were both
> >>>>> conflated in RESOLVE_UNIX, it would not be possible to make
> >>>>> UNIX connections configurable in such a way.
> >>>> This exact API would no longer work, but if we give up the equivalence
> >>>> between scope bits and the landlock_scope_attr struct, then we can do
> >>>> something like:
> >>>>
> >>>> struct landlock_scope_attr {
> >>>> __u64 ptrace:1; /* Note that this is not a (user controllable) scope bit! */
> >>>> __u64 abstract_unix_socket:1;
> >>>> __u64 pathname_unix_socket:1;
> >>>> /* ... */
> >>>>
> >>>> __u64 allowed_signals;
> >>>>
> >>>> /*
> >>>> * some way to identify domains, maybe we could use the audit domain
> >>>> * ID, with 0 denoting "allow access to non-Landlocked processes?
> >>>> */
> >>>> }
> >>> Yes, it would be possible to use such a struct for that scenario where
> >>> IPC access gets allowed for other Landlock scopes. It would mean that
> >>> we would not need to introduce a scoped flag for the pathname UNIX
> >>> socket connections. But the relationship between that struct
> >>> landlock_scope_attr and the flags and access rights in struct
> >>> landlock_ruleset_attr would become less clear, which is a slight
> >>> downside, and maybe error prone for users to work with.
> >>>
> >>> If we introduced an additional scoped flag, it would also be
> >>> consistent though.
> >>>
> >>> (con1) was written under the assumption that we do not have an
> >>> additional scoped flag. If that is lacking, it is not possible to
> >>> express UNIX connect() access to other Landlock domains with that
> >>> struct. But as outlined in the proposal below, if we *do* (later)
> >>> introduce the additional scoped flag *in addition* to the FS access
> >>> right, this *both* stays consistent in semantics with the signal and
> >>> abstract UNIX support, *and* it starts working in a world where ICP
> >>> access can be allowed to talk to other Landlock domains.
> >>>
> >>>>> (con2) Consistent behaviour between scoped flags and their
> >>>>> interactions with other access rights:
> >>>>>
> >>>>> The existing scoped access rights (signal, abstract sockets)
> >>>>> could hypothetically be extended with a related access right of
> >>>>> another type. For instance, there could be an access right type
> >>>>>
> >>>>> __u64 handled_signal_number;
> >>>>>
> >>>>> and then you could add a rule to permit the use of certain
> >>>>> signal numbers. The interaction between the scoped flags and
> >>>>> other access rights should work the same.
> >>>>>
> >>>>>
> >>>>> Constructive Proposal for consideration: Why not both?
> >>>>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >>>> I will think about the following a bit more but I'm afraid that I feel
> >>>> like it might get slightly confusing. With this, the only reason for
> >>>> having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
> >>>> later enable allowing access to other domains (if I understood correctly),
> >>>> in which case I personally feel like the suggestion on landlock_scope_attr
> >>>> above, where we essentially accept that it is decoupled with the scope
> >>>> bits in the ruleset, might be simpler...?
> >>> Mickaël expressed the opinion to me that he would like to APIs to stay
> >>> consistent between signals, abstract UNIX sockets, named UNIX sockets
> >>> and other future "scoped" operations, in scenarios where:
> >>>
> >>> * the "scoped" (IPC) operations can be configured to give access to
> >>> other Landlock domains (and that should work for UNIX connections too)
> >>> * the existing "scoped" operations also start having matching access rights
> >>>
> >>> I think with the way I proposed, that would be consistent.
> >>>
> >>>
> >>>>> Why not do both what Tingmao proposed in [1] **and** reserve the
> >>>>> option to add the matching "scoped flag" later?
> >>>>>
> >>>>> * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
> >>>>>
> >>>>> If it is handled, UNIX connections are allowed either:
> >>>>>
> >>>>> (1) if the connection is to a service in the same scope, or
> >>>>> (2) if the path was allow-listed with a "path beneath" rule.
> >>>>>
> >>>>> * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
> >>>>>
> >>>>>
> >>>>> Let's go through the arguments again:
> >>>>>
> >>>>> We have observed that it is harmless to allow connections to services
> >>>>> in the same scope (1), and that if users absolutely don't want that,
> >>>>> they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> >>>>> (pro1).
> >>>>>
> >>>>> (con1): Can we still implement the feature idea where we poke a hole
> >>>>> to get UNIX-connect() access to other Landlock domains?
> >>>>>
> >>>>> I think the answer is yes. The implementation strategy is:
> >>>>>
> >>>>> * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
> >>>>> * The scoped bit can now be used to allow-list connections to
> >>>>> other Landlock domains.
> >>>>>
> >>>>> For users, just setting the scoped bit on its own does the same as
> >>>>> handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX. That way, the kernel-side
> >>>>> implementation can also stay simple. The only reason why the scoped
> >>>>> bit is needed is because it makes it possible to allow-list
> >>>>> connections to other Landlock domains, but at the same time, it is
> >>>>> safe if libraries set the scoped bit once it exists, as it does not
> >>>>> have any bad runtime impact either.
> >>>>>
> >>>>> (con2): Consistency: Do all the scoped flags interact with their
> >>>>> corresponding access rights in the same way?
> >>>>>
> >>>>> The other scope flags do not have corresponding access rights, so
> >>>>> far.
> >>>>>
> >>>>> If we were to add corresponding access rights for the other scope
> >>>>> flags, I would argue that we could apply a consistent logic there,
> >>>>> because IPC access within the same scope is always safe:
> >>>>>
> >>>>> - A hypothetical access right type for "signal numbers" would only
> >>>>> restrict signals that go beyond the current scope.
> >>>>>
> >>>>> - A hypothetical access right type for "abstract UNIX domain socket
> >>>>> names" would only restrict connections to abstract UNIX domain
> >>>>> servers that go beyond the current scope.
> >>>>>
> >>>>> I can not come up with a scenario where this doesn't work.
> >> Gunther / Tingmao / Mickaël:
> >>
> >> I have a potential idea to make this concept cleaner.
> >>
> >> The docs for landlock currently say:
> >>
> >>
> >> IPC scoping does not support exceptions via landlock_add_rule(2).
> >> If an operation is scoped within a domain, no rules can be added
> >> to allow access to resources or processes outside of the scope.
> > This part might indeed be confusing. The idea was to explain the
> > difference between scoped rights and handled access rights (which may
> > have rules).
> >
> >> So if we go with the solution where we are now saying IPC scoping DOES support exceptions
> >> we will need to update the documentation, to say scoping for pathname unix sockets is an exception,
> >> and have to have the "exemptible scopes" (like this one) alongside "non-exemptible" scopes
> >> (ie the existing ones). This creates some friction for users.
> > The documentation will definitely require some updates. I think it can
> > be explained in a simple way.
> >
> >> If we foresee other "exempt-able scopes" (which are scopes that also support creating exemptions w/ corresponding access rights) in the future, maybe we should consider separating the two in the ruleset
> >> attributes (I used scoped_fs as an example for the attribute name):
> >>
> >> structlandlock_ruleset_attrruleset_attr={
> >> .handled_access_fs=
> >> LANDLOCK_ACCESS_FS_EXECUTE|
> >> LANDLOCK_ACCESS_FS_WRITE_FILE|
> >> LANDLOCK_ACCESS_FS_READ_FILE|
> >> LANDLOCK_ACCESS_FS_READ_DIR|
> >> LANDLOCK_ACCESS_FS_REMOVE_DIR|
> >> LANDLOCK_ACCESS_FS_REMOVE_FILE|
> >> LANDLOCK_ACCESS_FS_MAKE_CHAR|
> >> LANDLOCK_ACCESS_FS_MAKE_DIR|
> >> LANDLOCK_ACCESS_FS_MAKE_REG|
> >> LANDLOCK_ACCESS_FS_MAKE_SOCK|
> >> LANDLOCK_ACCESS_FS_MAKE_FIFO|
> >> LANDLOCK_ACCESS_FS_MAKE_BLOCK|
> >> LANDLOCK_ACCESS_FS_MAKE_SYM|
> >> LANDLOCK_ACCESS_FS_REFER|
> >> LANDLOCK_ACCESS_FS_TRUNCATE|
> >> LANDLOCK_ACCESS_FS_IOCTL_DEV,
> >> .handled_access_net=
> >> LANDLOCK_ACCESS_NET_BIND_TCP|
> >> LANDLOCK_ACCESS_NET_CONNECT_TCP,
> >> .scoped=
> >> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET|
> >> LANDLOCK_SCOPE_SIGNAL,
> >> .scoped_fs =
> >> LANDLOCK_SCOPE_FS_PATHNAME_UNIX_SOCKET
> >> };
> >>
> >> This more clearly distinguishes between scopes that have exceptions/corresponding fs rights,
> >> and ones that don't. Later we could add scoped_net, if needed. I feel like this would be more
> >> intuitive and better categorize future scoping rights. An obvious con is increasing the size of
> >> the ruleset attributes.
> > I see your point but I don't think it would be worth it to add
> > sub-scoped fields. Each field has a clear semantic, and the scoped one
> > is related to the domain wrt other domains.
> As long as it's documented clearly, and future IPCs have similar behavior
> I agree that a separate field probably isn't needed.
> >
> >> Of course this separation is only worth it if there are other "exempt-able" rights in the future.
> >> I can think of a few potential future rights which COULD be scoped and have corresponding rights
> >> (binder, sysv-ipc, pipes, tcp/udp between two local programs).
> > Yes, it would definitely be useful to add exception for other kind of
> > IPCs. The idea would be to be able to describe the peer, either with a
> > file path, or PID, or cgroups, or a Landlock domain... The inet case
> > is an interesting idea but that might be a challenging task to
> > implement, if even possible.
>
> >
> >>>>>
> >>>>> In conclusion, I think the approach has significant upsides:
> >>>>>
> >>>>> * Simpler UAPI: Users only have one access bit to deal with, in the
> >>>>> near future. Once we do add a scope flag for UNIX connections, it
> >>>>> does not interact in a surprising way with the corresponding FS
> >>>>> access right, because with either of these, scoped access is
> >>>>> allowed.
> >>>>>
> >>>>> If users absolutely need to restrict scoped access, they can
> >>>>> restrict LANDLOCK_ACCESS_FS_MAKE_SOCK. It is a slightly obscure
> >>>>> API, but in line with the "make easy things easy, make hard things
> >>>>> possible" API philosophy. And needing this should be the
> >>>>> exception rather than the norm, after all.
> >>>>>
> >>>>> * Consistent behaviour between scoped flags and regular access
> >>>>> rights, also for speculative access rights affecting the existing
> >>>>> scoped flags for signals and abstract UNIX domain sockets.
> >>>>>
> >>>>> [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/
> >>> —Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-05 10:27 ` Mickaël Salaün
@ 2026-02-08 2:57 ` Tingmao Wang
2026-02-08 20:37 ` Günther Noack
0 siblings, 1 reply; 41+ messages in thread
From: Tingmao Wang @ 2026-02-08 2:57 UTC (permalink / raw)
To: Mickaël Salaün, Günther Noack
Cc: Günther Noack, Justin Suess, Paul Moore, John Johansen,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
Matthieu Buffet, linux-security-module
On 2/5/26 10:27, Mickaël Salaün wrote:
> On Thu, Feb 05, 2026 at 09:02:19AM +0100, Günther Noack wrote:
>> [...]
>>
>> The implementation of this approach would be that we would have to
>> join the functionality from the scoped and FS-based patch set, but
>> without introducing the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET flag in
>> the UAPI.
>
> Right, this looks good to me. We'll need to sync both patch series and
> remove the scope flag from UAPI. I'll let you and Tingmao work together
> for the next series. The "IPC scoping" documentation section should
> mention LANDLOCK_ACCESS_FS_RESOLVE_UNIX even if it's not a scope flag.
This sounds good to me. I'm not sure how much code we can reuse out of
the existing LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patchset - but I think
the selftest patches could still largely be useful (after changing e.g.
create_scoped_domain() to use the RESOLVE_UNIX fs access instead of the
scope bit for pathname sockets). The fs-based rules (i.e. "exceptions")
can then be tested separately from the scope tests (and would also check
for things like path being different across mount namespaces etc).
Günther, feel free to take anything out of the existing scope series, if
you feel it would be useful. Also let me know if you would like me to
help with any part of the RESOLVE_UNIX series if you feel that would be
useful (but you don't have to if not).
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-05 19:15 ` Mickaël Salaün
@ 2026-02-08 2:57 ` Tingmao Wang
2026-02-08 13:44 ` Günther Noack
2026-02-08 13:49 ` Günther Noack
0 siblings, 2 replies; 41+ messages in thread
From: Tingmao Wang @ 2026-02-08 2:57 UTC (permalink / raw)
To: Mickaël Salaün, Justin Suess, Günther Noack
Cc: Günther Noack, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, linux-security-module, Matthieu Buffet
On 2/5/26 19:15, Mickaël Salaün wrote:
> On Thu, Feb 05, 2026 at 10:18:54AM -0500, Justin Suess wrote:
>>
>> On 2/4/26 13:28, Mickaël Salaün wrote:
>>
>>>> [...]
>>>> Tingmao:
>>>>
>>>> For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
>>>>
>>>> 1. security_unix_find. (to look up the paths)
>>>>
>>>> 2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
btw, ideally for pathname sockets we can leave all the checking in the
security_unix_find() hook (as newly proposed, with the struct sock *other
param), and not have to e.g. call domain_is_scoped() again in
security_unix_may_send and security_unix_stream_connect, right?
(Although if this changes error codes, we might have to "delay" the denial
until the may_send/connect hooks...? Hopefully not but not checked.)
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-08 2:57 ` Tingmao Wang
@ 2026-02-08 13:44 ` Günther Noack
2026-02-08 13:49 ` Günther Noack
1 sibling, 0 replies; 41+ messages in thread
From: Günther Noack @ 2026-02-08 13:44 UTC (permalink / raw)
To: Tingmao Wang, g
Cc: Mickaël Salaün, Justin Suess, Günther Noack,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Sun, Feb 08, 2026 at 02:57:16AM +0000, Tingmao Wang wrote:
> On 2/5/26 19:15, Mickaël Salaün wrote:
> > On Thu, Feb 05, 2026 at 10:18:54AM -0500, Justin Suess wrote:
> >> On 2/4/26 13:28, Mickaël Salaün wrote:
> >>>> Tingmao:
> >>>>
> >>>> For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
> >>>>
> >>>> 1. security_unix_find. (to look up the paths)
> >>>>
> >>>> 2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
>
> btw, ideally for pathname sockets we can leave all the checking in the
> security_unix_find() hook (as newly proposed, with the struct sock *other
> param), and not have to e.g. call domain_is_scoped() again in
> security_unix_may_send and security_unix_stream_connect, right?
>
> (Although if this changes error codes, we might have to "delay" the denial
> until the may_send/connect hooks...? Hopefully not but not checked.)
Yes, absolutely. I have had a stab at it and will send it soon.
Justin adopted your suggestion from [1] and created an updated LSM
hook patch based on it. With that, I am doing both checks in the
security_unix_find() hook, based on the resulting struct sock.
[1] https://lore.kernel.org/all/e6b6b069-384c-4c45-a56b-fa54b26bc72a@maowtm.org/#t
–Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-08 2:57 ` Tingmao Wang
2026-02-08 13:44 ` Günther Noack
@ 2026-02-08 13:49 ` Günther Noack
1 sibling, 0 replies; 41+ messages in thread
From: Günther Noack @ 2026-02-08 13:49 UTC (permalink / raw)
To: Tingmao Wang
Cc: Mickaël Salaün, Justin Suess, Günther Noack,
Demi Marie Obenour, Alyssa Ross, Jann Horn, Tahera Fahimi,
linux-security-module, Matthieu Buffet
On Sun, Feb 08, 2026 at 02:57:16AM +0000, Tingmao Wang wrote:
> On 2/5/26 19:15, Mickaël Salaün wrote:
> > On Thu, Feb 05, 2026 at 10:18:54AM -0500, Justin Suess wrote:
> >> On 2/4/26 13:28, Mickaël Salaün wrote:
> >>>> Tingmao:
> >>>>
> >>>> For connecting a pathname unix socket, the order of the hooks landlock sees is something like:
> >>>>
> >>>> 1. security_unix_find. (to look up the paths)
> >>>>
> >>>> 2. security_unix_may_send, security_unix_stream_connect (after the path is looked up)
>
> btw, ideally for pathname sockets we can leave all the checking in the
> security_unix_find() hook (as newly proposed, with the struct sock *other
> param), and not have to e.g. call domain_is_scoped() again in
> security_unix_may_send and security_unix_stream_connect, right?
>
> (Although if this changes error codes, we might have to "delay" the denial
> until the may_send/connect hooks...? Hopefully not but not checked.)
Thank you, Tingmao!
So far, the selftests that I already had in fs_test.c were
straightforward to extend so that they cover the new cases, but I'll
definitely have a look through your patch set and see if there are
parts that we can reuse or that I missed to cover. Either way, I'll
make sure that you'll get appropriate credit for it. :)
–Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-08 2:57 ` Tingmao Wang
@ 2026-02-08 20:37 ` Günther Noack
2026-02-08 20:48 ` Tingmao Wang
0 siblings, 1 reply; 41+ messages in thread
From: Günther Noack @ 2026-02-08 20:37 UTC (permalink / raw)
To: Tingmao Wang
Cc: Mickaël Salaün, Günther Noack, Justin Suess,
Paul Moore, John Johansen, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Matthieu Buffet, linux-security-module
On Sun, Feb 08, 2026 at 02:57:10AM +0000, Tingmao Wang wrote:
> On 2/5/26 10:27, Mickaël Salaün wrote:
> > On Thu, Feb 05, 2026 at 09:02:19AM +0100, Günther Noack wrote:
> >> [...]
> >>
> >> The implementation of this approach would be that we would have to
> >> join the functionality from the scoped and FS-based patch set, but
> >> without introducing the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET flag in
> >> the UAPI.
> >
> > Right, this looks good to me. We'll need to sync both patch series and
> > remove the scope flag from UAPI. I'll let you and Tingmao work together
> > for the next series. The "IPC scoping" documentation section should
> > mention LANDLOCK_ACCESS_FS_RESOLVE_UNIX even if it's not a scope flag.
>
> This sounds good to me. I'm not sure how much code we can reuse out of
> the existing LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patchset - but I think
> the selftest patches could still largely be useful (after changing e.g.
> create_scoped_domain() to use the RESOLVE_UNIX fs access instead of the
> scope bit for pathname sockets). The fs-based rules (i.e. "exceptions")
> can then be tested separately from the scope tests (and would also check
> for things like path being different across mount namespaces etc).
>
> Günther, feel free to take anything out of the existing scope series, if
> you feel it would be useful. Also let me know if you would like me to
> help with any part of the RESOLVE_UNIX series if you feel that would be
> useful (but you don't have to if not).
Thank you, Tingmao!
So far, the selftests that I already had in fs_test.c were
straightforward to extend so that they cover the new cases. I had a
look at your patch set, but found the scoping tests difficult to port
to fs_test.c, but I'll double check that we don't miss anything.
Either way, I'll make sure that you'll get appropriate credit for
it. :)
–Günther
(P.S. If this mail looks familiar, it's because I accidentally replied
with an earlier version of that to the wrong mail earlier today
(https://lore.kernel.org/all/20260208.b25c4105bc03@gnoack.org/) –
Replying here again so that this answer makes more sense.)
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-08 20:37 ` Günther Noack
@ 2026-02-08 20:48 ` Tingmao Wang
2026-02-08 23:21 ` Günther Noack
2026-02-09 20:20 ` Mickaël Salaün
0 siblings, 2 replies; 41+ messages in thread
From: Tingmao Wang @ 2026-02-08 20:48 UTC (permalink / raw)
To: Günther Noack
Cc: Mickaël Salaün, Günther Noack, Justin Suess,
Paul Moore, John Johansen, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Matthieu Buffet, linux-security-module
On 2/8/26 20:37, Günther Noack wrote:
> On Sun, Feb 08, 2026 at 02:57:10AM +0000, Tingmao Wang wrote:
>> On 2/5/26 10:27, Mickaël Salaün wrote:
>>> On Thu, Feb 05, 2026 at 09:02:19AM +0100, Günther Noack wrote:
>>>> [...]
>>>>
>>>> The implementation of this approach would be that we would have to
>>>> join the functionality from the scoped and FS-based patch set, but
>>>> without introducing the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET flag in
>>>> the UAPI.
>>>
>>> Right, this looks good to me. We'll need to sync both patch series and
>>> remove the scope flag from UAPI. I'll let you and Tingmao work together
>>> for the next series. The "IPC scoping" documentation section should
>>> mention LANDLOCK_ACCESS_FS_RESOLVE_UNIX even if it's not a scope flag.
>>
>> This sounds good to me. I'm not sure how much code we can reuse out of
>> the existing LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patchset - but I think
>> the selftest patches could still largely be useful (after changing e.g.
>> create_scoped_domain() to use the RESOLVE_UNIX fs access instead of the
>> scope bit for pathname sockets). The fs-based rules (i.e. "exceptions")
>> can then be tested separately from the scope tests (and would also check
>> for things like path being different across mount namespaces etc).
>>
>> Günther, feel free to take anything out of the existing scope series, if
>> you feel it would be useful. Also let me know if you would like me to
>> help with any part of the RESOLVE_UNIX series if you feel that would be
>> useful (but you don't have to if not).
>
> Thank you, Tingmao!
>
> So far, the selftests that I already had in fs_test.c were
> straightforward to extend so that they cover the new cases. I had a
> look at your patch set, but found the scoping tests difficult to port
> to fs_test.c
I was thinking that the tests in scoped_abstract_unix_test.c could be
extended to test scoping of pathname UNIX sockets as well (otherwise
wouldn't you have to write another instance of the scoped_domains test
based on scoped_base_variants.h, whether you put it in fs_test.c or
somewhere else?)
And if you think that is sensible, then I'm hoping that patch 4,5,6 of the
series would be mostly useful. But it's up to you :)
> , but I'll double check that we don't miss anything.
> Either way, I'll make sure that you'll get appropriate credit for
> it. :)
Thanks!
Tingmao
> ...
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-08 20:48 ` Tingmao Wang
@ 2026-02-08 23:21 ` Günther Noack
2026-02-09 20:20 ` Mickaël Salaün
1 sibling, 0 replies; 41+ messages in thread
From: Günther Noack @ 2026-02-08 23:21 UTC (permalink / raw)
To: Tingmao Wang
Cc: Mickaël Salaün, Günther Noack, Justin Suess,
Paul Moore, John Johansen, Demi Marie Obenour, Alyssa Ross,
Jann Horn, Tahera Fahimi, Matthieu Buffet, linux-security-module
On Sun, Feb 08, 2026 at 08:48:22PM +0000, Tingmao Wang wrote:
> On 2/8/26 20:37, Günther Noack wrote:
> > Thank you, Tingmao!
> >
> > So far, the selftests that I already had in fs_test.c were
> > straightforward to extend so that they cover the new cases. I had a
> > look at your patch set, but found the scoping tests difficult to port
> > to fs_test.c
>
> I was thinking that the tests in scoped_abstract_unix_test.c could be
> extended to test scoping of pathname UNIX sockets as well (otherwise
> wouldn't you have to write another instance of the scoped_domains test
> based on scoped_base_variants.h, whether you put it in fs_test.c or
> somewhere else?)
>
> And if you think that is sensible, then I'm hoping that patch 4,5,6 of the
> series would be mostly useful. But it's up to you :)
I maybe have not wrapped my head around the scoped_test enough; for
now I sent a tentative V4 patch set to the list, so that we can
discuss something concrete.
If you spot things that are missing, or you feel inspired to port your
tests on top, I am still happy to accept that. (But for today it is
too late in the evening here %-))
–Günther
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets
2026-02-08 20:48 ` Tingmao Wang
2026-02-08 23:21 ` Günther Noack
@ 2026-02-09 20:20 ` Mickaël Salaün
1 sibling, 0 replies; 41+ messages in thread
From: Mickaël Salaün @ 2026-02-09 20:20 UTC (permalink / raw)
To: Tingmao Wang
Cc: Günther Noack, Günther Noack, Justin Suess, Paul Moore,
John Johansen, Demi Marie Obenour, Alyssa Ross, Jann Horn,
Tahera Fahimi, Matthieu Buffet, linux-security-module
On Sun, Feb 08, 2026 at 08:48:22PM +0000, Tingmao Wang wrote:
> On 2/8/26 20:37, Günther Noack wrote:
> > On Sun, Feb 08, 2026 at 02:57:10AM +0000, Tingmao Wang wrote:
> >> On 2/5/26 10:27, Mickaël Salaün wrote:
> >>> On Thu, Feb 05, 2026 at 09:02:19AM +0100, Günther Noack wrote:
> >>>> [...]
> >>>>
> >>>> The implementation of this approach would be that we would have to
> >>>> join the functionality from the scoped and FS-based patch set, but
> >>>> without introducing the LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET flag in
> >>>> the UAPI.
> >>>
> >>> Right, this looks good to me. We'll need to sync both patch series and
> >>> remove the scope flag from UAPI. I'll let you and Tingmao work together
> >>> for the next series. The "IPC scoping" documentation section should
> >>> mention LANDLOCK_ACCESS_FS_RESOLVE_UNIX even if it's not a scope flag.
> >>
> >> This sounds good to me. I'm not sure how much code we can reuse out of
> >> the existing LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET patchset - but I think
> >> the selftest patches could still largely be useful (after changing e.g.
> >> create_scoped_domain() to use the RESOLVE_UNIX fs access instead of the
> >> scope bit for pathname sockets). The fs-based rules (i.e. "exceptions")
> >> can then be tested separately from the scope tests (and would also check
> >> for things like path being different across mount namespaces etc).
> >>
> >> Günther, feel free to take anything out of the existing scope series, if
> >> you feel it would be useful. Also let me know if you would like me to
> >> help with any part of the RESOLVE_UNIX series if you feel that would be
> >> useful (but you don't have to if not).
> >
> > Thank you, Tingmao!
> >
> > So far, the selftests that I already had in fs_test.c were
> > straightforward to extend so that they cover the new cases. I had a
> > look at your patch set, but found the scoping tests difficult to port
> > to fs_test.c
>
> I was thinking that the tests in scoped_abstract_unix_test.c could be
> extended to test scoping of pathname UNIX sockets as well (otherwise
> wouldn't you have to write another instance of the scoped_domains test
> based on scoped_base_variants.h, whether you put it in fs_test.c or
> somewhere else?)
>
> And if you think that is sensible, then I'm hoping that patch 4,5,6 of the
> series would be mostly useful. But it's up to you :)
I agree that these 3 patches should be integrated (see my other reply on
Günther's v4).
>
> > , but I'll double check that we don't miss anything.
> > Either way, I'll make sure that you'll get appropriate credit for
> > it. :)
>
> Thanks!
>
> Tingmao
>
> > ...
>
^ permalink raw reply [flat|nested] 41+ messages in thread
end of thread, other threads:[~2026-02-09 20:20 UTC | newest]
Thread overview: 41+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-30 17:20 [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 1/6] landlock: Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET scope bit to uAPI Tingmao Wang
2026-01-29 21:27 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
2026-01-29 21:27 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 3/6] samples/landlock: Support LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET Tingmao Wang
2026-01-29 21:27 ` Mickaël Salaün
2026-01-31 17:48 ` Tingmao Wang
2026-02-02 20:14 ` Mickaël Salaün
2025-12-30 17:20 ` [PATCH v2 4/6] selftests/landlock: Support pathname socket path in set_unix_address Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 5/6] selftests/landlock: Repurpose scoped_abstract_unix_test.c for pathname sockets too Tingmao Wang
2026-01-29 21:28 ` Mickaël Salaün
2026-02-02 0:06 ` Tingmao Wang
2025-12-30 17:20 ` [PATCH v2 6/6] selftests/landlock: Add pathname socket variants for more tests Tingmao Wang
2026-01-29 21:28 ` Mickaël Salaün
2025-12-30 23:16 ` [PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets Günther Noack
2025-12-31 16:54 ` Demi Marie Obenour
2026-01-09 12:01 ` Mickaël Salaün
2026-01-31 17:41 ` Tingmao Wang
2026-02-02 20:32 ` Mickaël Salaün
2026-02-02 22:03 ` Justin Suess
2026-02-03 1:26 ` Tingmao Wang
2026-02-03 17:54 ` Günther Noack
2026-02-03 21:53 ` Tingmao Wang
2026-02-04 11:44 ` Günther Noack
2026-02-04 16:36 ` Justin Suess
2026-02-04 18:28 ` Mickaël Salaün
2026-02-05 15:22 ` Justin Suess
[not found] ` <44d216aa-9680-4cf5-bbf0-173869111212@gmail.com>
2026-02-05 19:15 ` Mickaël Salaün
2026-02-08 2:57 ` Tingmao Wang
2026-02-08 13:44 ` Günther Noack
2026-02-08 13:49 ` Günther Noack
2026-02-04 17:43 ` Mickaël Salaün
2026-02-05 8:02 ` Günther Noack
2026-02-05 10:27 ` Mickaël Salaün
2026-02-08 2:57 ` Tingmao Wang
2026-02-08 20:37 ` Günther Noack
2026-02-08 20:48 ` Tingmao Wang
2026-02-08 23:21 ` Günther Noack
2026-02-09 20:20 ` Mickaël Salaün
2026-02-04 17:39 ` Mickaël Salaün
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox