* [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
* 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
* [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
* 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
* [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
* 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 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 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
* [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
* 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 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
* [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 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 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 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 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-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 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
[parent not found: <44d216aa-9680-4cf5-bbf0-173869111212@gmail.com>]
* 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 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-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 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-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-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
* 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
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