From e1bdd512edde138047992ff64591fbc66d9552e1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 17:35:49 +0200 Subject: [PATCH 01/12] xattr-util: use empty_to_null() where appropriate --- src/basic/xattr-util.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index fb886f6ae8..417e8650e5 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -36,8 +36,7 @@ static int normalize_and_maybe_pin_inode( assert(ret_tfd); assert(ret_opath); - if (isempty(*path)) - *path = NULL; /* Normalize "" to NULL */ + *path = empty_to_null(*path); /* Normalize "" to NULL */ if (*fd == AT_FDCWD) { if (!*path) /* Both unspecified? Then operate on current working directory */ From f304250315f8f282dffbe22e57ae3e086bfb99e7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 Jun 2026 11:06:23 +0200 Subject: [PATCH 02/12] socket-util: add new helper socket_xattr_supported() --- TODO.md | 2 ++ src/basic/socket-util.c | 42 +++++++++++++++++++++++++++++++++++++ src/basic/socket-util.h | 2 ++ src/test/test-socket-util.c | 13 ++++++++++++ 4 files changed, 59 insertions(+) diff --git a/TODO.md b/TODO.md index 1ebad3fcf8..4d6668dace 100644 --- a/TODO.md +++ b/TODO.md @@ -126,6 +126,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later - remove any trace of "cpuacct" cgroup controller, it's a cgroupv1 thing. similar "devices" +- drop socket_xattr_supported() once our baseline is kernel 7.0 + ## Features - **report:** diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index d8f41f0f9c..c25e41d771 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -21,6 +22,7 @@ #include "fd-util.h" #include "format-ifname.h" #include "format-util.h" +#include "fs-util.h" #include "in-addr-util.h" #include "io-util.h" #include "log.h" @@ -36,6 +38,8 @@ #include "string-util.h" #include "strv.h" #include "sysctl-util.h" +#include "tmpfile-util.h" +#include "xattr-util.h" #if ENABLE_IDN # define IDN_FLAGS NI_IDN @@ -1891,3 +1895,41 @@ int tos_to_priority(uint8_t tos) { return TC_PRIO_BESTEFFORT; } } + +int socket_xattr_supported(void) { + int r; + + // FIXME: Drop this check once Linux 7.0 becomes our baseline + + /* Checks if socket inodes may have xattrs on this kernel. This should pass on kernel 7.0, fail on + * older kernels */ + + static int cached = -1; + if (cached >= 0) + return cached; + + const char *t; + r = tmp_dir(&t); + if (r < 0) + return r; + + _cleanup_free_ char *sp = NULL; + r = tempfn_random_child(t, "sockxattrtest", &sp); + if (r < 0) + return r; + + if (mknod(sp, S_IFSOCK | 0600, /* dev= */ 0) < 0) + return -errno; + + _cleanup_(unlink_and_freep) char *sp_destroy = TAKE_PTR(sp); + + /* Old kernels return EPERM. But let's also check for more appropriate error codes, to be friendly to + * seccomp policies */ + r = xsetxattr(AT_FDCWD, sp_destroy, /* at_flags= */ 0, "user.testxxx", "1"); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EPERM) + return (cached = false); + if (r < 0) + return log_debug_errno(r, "Failed to set test xattr on socket inode '%s': %m", sp_destroy); + + return (cached = true); +} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index b3a2733b74..f9d6ddaaad 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -268,3 +268,5 @@ int socket_get_cookie(int fd, uint64_t *ret); void cmsg_close_all(struct msghdr *mh); int tos_to_priority(uint8_t tos); + +int socket_xattr_supported(void); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 713844b09b..038140d4c5 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -550,4 +551,16 @@ TEST(tos_to_priority) { ASSERT_EQ(tos_to_priority(0xff), TC_PRIO_CONTROL); } +TEST(socket_xattr_supported) { + int r; + + r = socket_xattr_supported(); + ASSERT_OK(r); + + log_info("Extended attributes on socket inodes supported: %s", yes_no(r)); + + /* A second call must agree with the first. */ + ASSERT_EQ(socket_xattr_supported(), r); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 96a37e3493bd5679539c99cfd6151c2a38ed8d71 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 17:36:52 +0200 Subject: [PATCH 03/12] sd-varlink: mark varlink sockets and entrypoint inodes as varlink via xattrs --- man/sd-varlink.xml | 79 ++++++++++++++++++++++++ src/libsystemd/sd-varlink/sd-varlink.c | 85 ++++++++++++++++++++++---- 2 files changed, 151 insertions(+), 13 deletions(-) diff --git a/man/sd-varlink.xml b/man/sd-varlink.xml index 576bdf1b7f..bd2b355652 100644 --- a/man/sd-varlink.xml +++ b/man/sd-varlink.xml @@ -55,6 +55,84 @@ makes the functionality implemented by sd-varlink available from the command line. + + Recognizing Varlink Socket Inodes + + On kernels that support extended attributes on socket inodes (available since Linux 7.0), + sd-varlink automatically tags the AF_UNIX socket inodes it creates and manages with + the user.varlink extended attribute. The attribute records the role the socket plays + in the Varlink communication, allowing other processes to discover and classify Varlink sockets, for + example via varlinkctl list-sockets (see + varlinkctl1). + + Four distinct roles are defined: + + + <varname>user.varlink</varname> extended attribute roles + + + + + + + Attribute value + Meaning + + + + + client + Set on the connecting end of a connection, i.e. a socket created via socket() + connect(). Only for inodes on the anonymous socket file system (sockfs). + + + server + Set on the serving end of a connection, i.e. a socket obtained via accept() on a listening socket. Only for inodes on the anonymous socket file system (sockfs). + + + listen + Set on a listening socket, i.e. a socket created via socket() + listen(). Only for inodes on the anonymous socket file system (sockfs). + + + entrypoint + Set on the entrypoint socket inode, i.e. the socket node bound into the regular file system that clients connect to. Unlike the other three, this attribute lives on a real file-system inode (not on sockfs) and is hence visible to any process that can resolve the socket path. + + + +
+ + Note that for socket activated Varlink services it is recommended to set these extended attributes + via XAttrEntryPoint=, XAttrListen= and + XAttrAccept= settings on the socket units, see + systemd.socket5 for + details. Specifically, for socket units for which Accept=no is set: + + … +[Socket] +SocketMode=0644 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +… + + For socket units which have Accept=yes: + + … +[Socket] +Accept=yes +SocketMode=0644 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server +… + + Note that for Varlink sockets that shall not be world-accessible it is recommended to unset the + relevant "w" bits in the socket access mode, but to keep the relevant "r" bits set. As the access check + done by connect() looks for the "w" bits, this does not make the service + unnecessarily accessible, however it has the benefit that the extended attributes that identify the + socket as Varlink-related may still be read by other users (as that's restricted by the "r" bit). Or in + other words, it's recommended to set SocketMode=0644 for services that shall not be + accessible to arbitrary users, rather than SocketMode=0600. +
+ @@ -87,6 +165,7 @@ sd-event3 sd-json3 varlinkctl1 + systemd.socket5 sd-bus3 pkg-config1 diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index f484c9da44..9f06f69265 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -39,6 +39,7 @@ #include "varlink-io.systemd.h" #include "varlink-org.varlink.service.h" #include "varlink-util.h" +#include "xattr-util.h" #define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U #define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 128U @@ -154,6 +155,33 @@ static int varlink_new(sd_varlink **ret) { return 0; } +static int mark_varlink_socket(int fd, const char *path, const char *role) { + int r; + + assert(wildcard_fd_is_valid(fd)); + assert(role); + + /* We define four roles: + * + * 1. "client" → this socket was created via socket()+connect() [sockfs] + * 2. "server" → this socket was created via accept() [sockfs] + * 3. "listen" → this socket was created via socket()+listen() [sockfs] + * 4. "entrypoint" → this is the entrypoint socket inode [not sockfs] + */ + + r = socket_xattr_supported(); /* Let's check for the feature directly, to make use of the cache */ + if (r < 0) + return r; + if (r == 0) + return -EOPNOTSUPP; + + r = xsetxattr(fd, path, AT_EMPTY_PATH, "user.varlink", role); + if (r < 0) + return log_debug_errno(r, "Failed to set 'user.varlink' to '%s': %m", role); + + return 0; +} + _public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) { _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; int r; @@ -169,14 +197,31 @@ _public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) { if (r < 0) return r; + (void) mark_varlink_socket(v->stream.input_fd, /* path= */ NULL, "client"); + varlink_set_state(v, VARLINK_IDLE_CLIENT); *ret = TAKE_PTR(v); return 0; } -_public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, char **_argv) { +static int varlink_socketpair(int *ret_client_fd, int *ret_server_fd) { + assert(ret_client_fd); + assert(ret_server_fd); + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + + (void) mark_varlink_socket(pair[0], /* path= */ NULL, "client"); + (void) mark_varlink_socket(pair[1], /* path= */ NULL, "server"); + + *ret_client_fd = TAKE_FD(pair[0]); + *ret_server_fd = TAKE_FD(pair[1]); + return 0; +} + +_public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, char **_argv) { _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; _cleanup_free_ char *command = NULL; _cleanup_strv_free_ char **argv = NULL; @@ -200,17 +245,19 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha log_debug("Forking off Varlink child process '%s'.", command); - if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) - return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + _cleanup_close_ int client_fd = -EBADF, server_fd = -EBADF; + r = varlink_socketpair(&client_fd, &server_fd); + if (r < 0) + return r; - r = fd_nonblock(pair[1], false); + r = fd_nonblock(server_fd, false); if (r < 0) return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m"); r = pidref_safe_fork_full( "(sd-vlexec)", /* stdio_fds= */ NULL, - /* except_fds= */ (int[]) { pair[1] }, + /* except_fds= */ (int[]) { server_fd }, /* n_except_fds= */ 1, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_PACK_FDS|FORK_CLOEXEC_OFF|FORK_REOPEN_LOG|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE, &pidref); @@ -249,14 +296,14 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha _exit(EXIT_FAILURE); } - pair[1] = safe_close(pair[1]); + server_fd = safe_close(server_fd); sd_varlink *v; r = varlink_new(&v); if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - int conn_fd = TAKE_FD(pair[0]); + int conn_fd = TAKE_FD(client_fd); r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd); if (r < 0) return r; @@ -280,7 +327,6 @@ static int ssh_path(const char **ret) { } static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; int r; @@ -316,12 +362,14 @@ static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h); - if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) - return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + _cleanup_close_ int client_fd = -EBADF, server_fd = -EBADF; + r = varlink_socketpair(&client_fd, &server_fd); + if (r < 0) + return r; r = pidref_safe_fork_full( "(sd-vlssh)", - /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO }, + /* stdio_fds= */ (int[]) { server_fd, server_fd, STDERR_FILENO }, /* except_fds= */ NULL, /* n_except_fds= */ 0, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, @@ -336,14 +384,14 @@ static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { _exit(EXIT_FAILURE); } - pair[1] = safe_close(pair[1]); + server_fd = safe_close(server_fd); sd_varlink *v; r = varlink_new(&v); if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - int conn_fd = TAKE_FD(pair[0]); + int conn_fd = TAKE_FD(client_fd); r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd); if (r < 0) return r; @@ -562,6 +610,8 @@ _public_ int sd_varlink_connect_fd_pair(sd_varlink **ret, int input_fd, int outp if (r < 0) return r; + (void) mark_varlink_socket(v->stream.input_fd, /* path= */ NULL, "client"); + if (override_ucred) json_stream_set_peer_ucred(&v->stream, override_ucred); @@ -3283,6 +3333,8 @@ _public_ int sd_varlink_server_add_connection_pair( } } + (void) mark_varlink_socket(input_fd, /* path= */ NULL, "server"); + if (ret) *ret = v; @@ -3394,6 +3446,8 @@ _public_ int sd_varlink_server_listen_fd(sd_varlink_server *s, int fd) { if (r < 0) return r; + (void) mark_varlink_socket(ss->fd, /* path= */ NULL, "listen"); + LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss)); return 0; } @@ -3431,6 +3485,8 @@ _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char * fd = fd_move_above_stdio(fd); + (void) mark_varlink_socket(fd, /* path= */ NULL, "listen"); + /* See the comment in sd_varlink_server_listen_fd() */ if (FLAGS_SET(s->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT)) (void) setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, FLAGS_SET(s->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT)); @@ -3442,6 +3498,9 @@ _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char * if (r < 0) return r; + if (path_is_absolute(address)) + (void) mark_varlink_socket(AT_FDCWD, address, "entrypoint"); + if (listen(fd, SOMAXCONN_DELUXE) < 0) return -errno; From ce97f9f6e0678f4d2053ce2c02478e70dfb6aa04 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 17:49:00 +0200 Subject: [PATCH 04/12] varlinkctl: port to new help-util.[ch] apis --- src/varlinkctl/varlinkctl.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 94a4bc40b3..8f580350a5 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -16,6 +16,7 @@ #include "fileio.h" #include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "memfd-util.h" @@ -71,14 +72,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_push_fds, push_fds_done); static int help(void) { - _cleanup_free_ char *link = NULL; _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; - r = terminal_urlify_man("varlinkctl", "1", &link); - if (r < 0) - return log_oom(); - r = option_parser_get_help_table(&options); if (r < 0) return r; @@ -91,18 +87,16 @@ static int help(void) { pager_open(arg_pager_flags); - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sIntrospect Varlink Services.%s\n" - "\nCommands:\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Introspect Varlink Services."); + + help_section("Commands"); r = table_print_or_warn(verbs); if (r < 0) return r; - printf("\nOptions:\n"); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) @@ -112,7 +106,7 @@ static int help(void) { " %s --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n", program_invocation_short_name); - printf("\nSee the %s for details.\n", link); + help_man_page_reference("varlinkctl", "1"); return 0; } From cc174775f582ed08f6fed37a55f9786ed32b156c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 18:22:50 +0200 Subject: [PATCH 05/12] core: add socket xattr settings for socket unit --- man/org.freedesktop.systemd1.xml | 26 +++++ man/systemd.socket.xml | 34 ++++++ src/core/dbus-socket.c | 96 +++++++++++++++++ src/core/load-fragment-gperf.gperf.in | 3 + src/core/load-fragment.c | 60 +++++++++++ src/core/load-fragment.h | 1 + src/core/socket.c | 9 +- src/core/socket.h | 4 + src/shared/bus-unit-util.c | 55 ++++++++++ src/shared/socket-label.c | 37 ++++++- src/shared/socket-label.h | 6 +- src/shared/socket-netlink.c | 4 +- src/systemctl/systemctl-show.c | 19 ++++ test/units/TEST-74-AUX-UTILS.socket-xattr.sh | 108 +++++++++++++++++++ test/units/util.sh | 18 ++++ 15 files changed, 476 insertions(+), 4 deletions(-) create mode 100755 test/units/TEST-74-AUX-UTILS.socket-xattr.sh diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 83748c6dd8..e7e56466d2 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -5245,6 +5245,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as Symlinks = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) XAttrEntryPoint = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) XAttrListen = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) XAttrAccept = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i Mark = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u MaxConnections = ...; @@ -6616,6 +6622,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + @@ -7299,6 +7311,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { FlushPending specifies whether to flush the socket just before entering the listening state. This setting only applies to sockets with Accept= set to no. + + XAttrEntryPoint, XAttrListen and + XAttrAccept each contain a list of extended attributes (as + NAME/VALUE + pairs) to set on socket inodes. XAttrEntryPoint applies to the file-system inode an + AF_UNIX socket is bound to, XAttrListen applies to the listening + socket, and XAttrAccept applies to connection sockets accepted off the listening + socket. See the XAttrEntryPoint=, XAttrListen= and + XAttrAccept= settings in + systemd.socket5 for + details. @@ -12977,6 +13000,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ IOPressureWatch, CPUSetPartition, and OOMRules were added in version 261. + XAttrEntryPoint, + XAttrListen, and + XAttrAccept were added in version 262. Mount Unit Objects diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index 0de281f02e..6816b073b3 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -675,6 +675,40 @@ + + XAttrEntryPoint= + XAttrListen= + XAttrAccept= + Set an extended attribute on the socket, in the form + NAME=VALUE. The extended + attribute's name must be in the user. namespace, i.e. begin with the four + characters user.. Specifiers are expanded in both the name and the value, see + systemd.unit5 for + details on the available specifiers. These settings may be used more than once to set multiple + extended attributes; assigning the empty string resets the list. + + The three variables control where the extended attributes are applied: + XAttrEntryPoint= sets them on the file-system inode an + AF_UNIX socket is bound to (i.e. the socket node visible in the file system at + the specified listening path; this only applies to AF_UNIX sockets), + XAttrListen= sets them on the listening socket, and + XAttrAccept= sets them on the connection sockets accepted off the listening socket + (and is thus only relevant in combination with + Accept=yes). + + This is primarily useful to tag sockets so that they can be discovered and classified by other + tools. For example, Varlink entrypoint sockets are supposed to be tagged with + user.varlink=entrypoint via XAttrEntryPoint=, which makes them + discoverable via varlinkctl list-sockets, see + varlinkctl1. + + These settings require kernel support for extended attributes on socket inodes (available since + Linux 7.0). Extended attributes that cannot be applied are logged at debug level and otherwise + ignored. + + + + SELinuxContextFromNet= Takes a boolean argument. When true, systemd diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index ecb4df4dfd..26612015f0 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -58,6 +58,33 @@ static int property_get_listen( return sd_bus_message_close_container(reply); } +static int property_get_xattr( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *reterr_error) { + + char ***xattr = ASSERT_PTR(userdata); + int r; + + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(name, value, *xattr) { + r = sd_bus_message_append(reply, "(ss)", *name, *value); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), @@ -95,6 +122,9 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XAttrEntryPoint", "a(ss)", property_get_xattr, offsetof(Socket, xattr_entrypoint), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XAttrListen", "a(ss)", property_get_xattr, offsetof(Socket, xattr_listen), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XAttrAccept", "a(ss)", property_get_xattr, offsetof(Socket, xattr_accept), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MaxConnectionsPerSource", "u", bus_property_get_unsigned, offsetof(Socket, max_connections_per_source), SD_BUS_VTABLE_PROPERTY_CONST), @@ -153,6 +183,63 @@ static BUS_DEFINE_SET_TRANSIENT_TO_STRING(socket_protocol, "i", int32_t, int, "% static BUS_DEFINE_SET_TRANSIENT_PARSE(socket_timestamping, SocketTimestamping, socket_timestamping_from_string_harder); static BUS_DEFINE_SET_TRANSIENT_PARSE(socket_defer_trigger, SocketDeferTrigger, socket_defer_trigger_from_string); +static int bus_socket_set_transient_xattr( + Unit *u, + const char *name, + char ***p, + sd_bus_message *message, + UnitWriteFlags flags, + sd_bus_error *reterr_error) { + + _cleanup_strv_free_ char **pairs = NULL; + const char *xname, *xvalue; + bool empty = true; + int r; + + assert(u); + assert(name); + assert(p); + assert(message); + + r = sd_bus_message_enter_container(message, 'a', "(ss)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(ss)", &xname, &xvalue)) > 0) { + if (!startswith(xname, "user.")) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Extended attribute name does not begin with 'user.': %s", xname); + + r = strv_extend_many(&pairs, xname, xvalue); + if (r < 0) + return -ENOMEM; + + empty = false; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (empty) { + *p = strv_free(*p); + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=", name); + } else { + r = strv_extend_strv(p, pairs, /* filter_duplicates= */ false); + if (r < 0) + return -ENOMEM; + + STRV_FOREACH_PAIR(n, v, pairs) + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s=%s", name, *n, *v); + } + } + + return 1; +} + static int bus_socket_set_transient_property( Socket *s, const char *name, @@ -335,6 +422,15 @@ static int bus_socket_set_transient_property( &s->exec_command[ci], message, flags, reterr_error); + if (streq(name, "XAttrEntryPoint")) + return bus_socket_set_transient_xattr(u, name, &s->xattr_entrypoint, message, flags, reterr_error); + + if (streq(name, "XAttrListen")) + return bus_socket_set_transient_xattr(u, name, &s->xattr_listen, message, flags, reterr_error); + + if (streq(name, "XAttrAccept")) + return bus_socket_set_transient_xattr(u, name, &s->xattr_accept, message, flags, reterr_error); + if (streq(name, "Symlinks")) { _cleanup_strv_free_ char **l = NULL; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 2b8d2296f0..aa95bd4992 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -561,6 +561,9 @@ Socket.PollLimitIntervalSec, config_parse_sec, Socket.PollLimitBurst, config_parse_unsigned, 0, offsetof(Socket, poll_limit.burst) Socket.DeferTrigger, config_parse_socket_defer_trigger, 0, offsetof(Socket, defer_trigger) Socket.DeferTriggerMaxSec, config_parse_sec_fix_0, 0, offsetof(Socket, defer_trigger_max_usec) +Socket.XAttrEntryPoint, config_parse_xattr, 0, offsetof(Socket, xattr_entrypoint) +Socket.XAttrListen, config_parse_xattr, 0, offsetof(Socket, xattr_listen) +Socket.XAttrAccept, config_parse_xattr, 0, offsetof(Socket, xattr_accept) {% if ENABLE_SMACK %} Socket.SmackLabel, config_parse_unit_string_printf, 0, offsetof(Socket, smack) Socket.SmackLabelIPIn, config_parse_unit_string_printf, 0, offsetof(Socket, smack_ip_in) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index ff8bded569..3fa92aff56 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2247,6 +2247,66 @@ int config_parse_socket_service( return 0; } +int config_parse_xattr( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***lp = ASSERT_PTR(data); + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *lp = strv_free(*lp); + return 0; + } + + _cleanup_free_ char *name = NULL, *value = NULL; + r = split_pair(rvalue, "=", &name, &value); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse extended attribute expression, ignoring: %s", rvalue); + return 0; + } + + _cleanup_free_ char *expanded_name = NULL; + r = unit_full_printf(u, name, &expanded_name); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in extended attribute expression, ignoring: %s", name); + return 0; + } + + if (!startswith(expanded_name, "user.")) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Extended attribute name does not begin with 'user.', ignoring: %s", expanded_name); + return 0; + } + + _cleanup_free_ char *expanded_value = NULL; + r = unit_full_printf(u, value, &expanded_value); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in extended attribute expression, ignoring: %s", value); + return 0; + } + + if (strv_push_pair(lp, expanded_name, expanded_value) < 0) + return log_oom(); + + TAKE_PTR(expanded_name); + TAKE_PTR(expanded_value); + + return 0; +} + int config_parse_fdname( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 48129f9602..15dd411cc5 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -173,6 +173,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface); CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp); CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_partition); CONFIG_PARSER_PROTOTYPE(config_parse_luo_sessions); +CONFIG_PARSER_PROTOTYPE(config_parse_xattr); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/core/socket.c b/src/core/socket.c index a33a8d6359..a7aa34caeb 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -210,6 +210,10 @@ static void socket_done(Unit *u) { strv_free(s->symlinks); + strv_free(s->xattr_entrypoint); + strv_free(s->xattr_listen); + strv_free(s->xattr_accept); + s->user = mfree(s->user); s->group = mfree(s->group); @@ -1510,7 +1514,9 @@ static int socket_address_listen_do( s->directory_mode, s->socket_mode, selinux_label, - s->smack); + s->smack, + s->xattr_entrypoint, + s->xattr_listen); } #define log_address_error_errno(u, address, error, fmt) \ @@ -3208,6 +3214,7 @@ static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, if (cfd < 0) goto fail; + (void) socket_set_xattrs(cfd, /* path= */ NULL, p->socket->xattr_accept); socket_apply_socket_options(p->socket, p, cfd); } diff --git a/src/core/socket.h b/src/core/socket.h index b9010bac49..d374bec9b7 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -126,6 +126,10 @@ typedef struct Socket { char **symlinks; + char **xattr_entrypoint; + char **xattr_listen; + char **xattr_accept; + bool accept; bool remove_on_stop; bool writable; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 8b618bf500..48a48f71b2 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -224,6 +224,58 @@ static int bus_append_strv_colon(sd_bus_message *m, const char *field, const cha return bus_append_strv_full(m, field, eq, ":" WHITESPACE, EXTRACT_UNQUOTE); } +static int bus_append_xattr(sd_bus_message *m, const char *field, const char *eq) { + int r; + + assert(m); + assert(field); + + /* Sends an extended attribute assignment of the form "name=value" as an a(ss) array of one + * name/value pair (or as an empty array if the value is empty, which resets the list). */ + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(m, 's', field); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(ss)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(ss)"); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(eq)) { + _cleanup_free_ char *name = NULL, *value = NULL; + + r = split_pair(eq, "=", &name, &value); + if (r < 0) + return log_error_errno(r, "Failed to parse extended attribute expression '%s': %m", eq); + + r = sd_bus_message_append(m, "(ss)", name, value); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 1; +} + static int bus_append_byte_array(sd_bus_message *m, const char *field, const void *buf, size_t n) { int r; @@ -2786,6 +2838,9 @@ static const BusProperty socket_properties[] = { { "Timestamping", bus_append_string }, { "DeferTrigger", bus_append_string }, { "Symlinks", bus_append_strv }, + { "XAttrEntryPoint", bus_append_xattr }, + { "XAttrListen", bus_append_xattr }, + { "XAttrAccept", bus_append_xattr }, { "SocketProtocol", bus_append_parse_ip_protocol }, { "ListenStream", bus_append_listen }, { "ListenDatagram", bus_append_listen }, diff --git a/src/shared/socket-label.c b/src/shared/socket-label.c index ea1116983e..b06b00e085 100644 --- a/src/shared/socket-label.c +++ b/src/shared/socket-label.c @@ -14,7 +14,9 @@ #include "socket-label.h" #include "socket-util.h" #include "string-table.h" +#include "strv.h" #include "umask-util.h" +#include "xattr-util.h" static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { [SOCKET_ADDRESS_DEFAULT] = "default", @@ -36,6 +38,32 @@ SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(cons return socket_address_bind_ipv6_only_from_string(s); } +int socket_set_xattrs(int fd, const char *path, char **xattrs) { + int r; + + assert(wildcard_fd_is_valid(fd)); + + if (strv_isempty(xattrs)) + return 0; + + r = socket_xattr_supported(); + if (r <= 0) + return r; + + int c = 0; + STRV_FOREACH_PAIR(name, value, xattrs) { + r = xsetxattr(fd, path, AT_EMPTY_PATH, *name, *value); + if (r < 0) { + log_debug_errno(r, "Failed to set extended attribute '%s' on socket, ignoring: %m", *name); + continue; + } + + c++; + } + + return c; +} + int socket_address_listen( const SocketAddress *a, int flags, @@ -48,7 +76,9 @@ int socket_address_listen( mode_t directory_mode, mode_t socket_mode, const char *selinux_label, - const char *smack_label) { + const char *smack_label, + char **xattr_entrypoint, + char **xattr_listen) { _cleanup_close_ int fd = -EBADF; const char *p; @@ -119,6 +149,8 @@ int socket_address_listen( if (r < 0) return r; + (void) socket_set_xattrs(fd, /* path= */ NULL, xattr_listen); + p = socket_address_get_path(a); if (p) { /* Create parents */ @@ -138,11 +170,14 @@ int socket_address_listen( if (r < 0) return r; } + if (smack_label) { r = mac_smack_apply(p, SMACK_ATTR_ACCESS, smack_label); if (r < 0) log_warning_errno(r, "Failed to apply SMACK label for socket path, ignoring: %m"); } + + (void) socket_set_xattrs(AT_FDCWD, p, xattr_entrypoint); } else { if (bind(fd, &a->sockaddr.sa, a->size) < 0) return -errno; diff --git a/src/shared/socket-label.h b/src/shared/socket-label.h index 48a500c081..15dc9d86fe 100644 --- a/src/shared/socket-label.h +++ b/src/shared/socket-label.h @@ -26,4 +26,8 @@ int socket_address_listen( mode_t directory_mode, mode_t socket_mode, const char *selinux_label, - const char *smack_label); + const char *smack_label, + char **xattr_entrypoint, + char **xattr_listen); + +int socket_set_xattrs(int fd, const char *path, char **xattrs); diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 1112a09084..a9b3174418 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -198,7 +198,9 @@ int make_socket_fd(int log_level, const char* address, int type, int flags) { 0755, 0644, /* selinux_label= */ NULL, - /* smack_label= */ NULL); + /* smack_label= */ NULL, + /* xattr_entrypoint= */ NULL, + /* xattr_listen= */ NULL); if (fd < 0 || log_get_max_level() >= log_level) { _cleanup_free_ char *p = NULL; diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index edef1d2575..82b8568813 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -1467,6 +1467,25 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m return 1; + } else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && + STR_IN_SET(name, "XAttrEntryPoint", "XAttrListen", "XAttrAccept")) { + const char *xname, *xvalue; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &xname, &xvalue)) > 0) + bus_print_property_valuef(name, expected_value, flags, "%s=%s", xname, xvalue); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 1; + } else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "TimersMonotonic")) { const char *base; uint64_t v, next_elapse; diff --git a/test/units/TEST-74-AUX-UTILS.socket-xattr.sh b/test/units/TEST-74-AUX-UTILS.socket-xattr.sh new file mode 100755 index 0000000000..3832fb3f82 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.socket-xattr.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Extended attributes on socket inodes (and hence the XAttr*= socket settings and +# "varlinkctl list-sockets") require a sufficiently new kernel. Probe for actual +# support and skip if unavailable, since the kernel version alone is not a reliable +# indicator. +if ! socket_inode_supports_user_xattrs; then + echo "Socket inode extended attributes unsupported on this kernel, skipping." >&2 + exit 0 +fi + +UNIT_SOCKET_PATH="/run/test-socket-xattr.sock" +RUN_SOCKET_PATH="/run/test-socket-xattr-run.sock" + +at_exit() { + set +e + systemctl stop test-socket-xattr.socket test-socket-xattr-run.socket + systemctl reset-failed test-socket-xattr-run.socket test-socket-xattr-run.service + rm -f /run/systemd/system/test-socket-xattr.socket + rm -f /run/systemd/system/test-socket-xattr.service + systemctl daemon-reload +} +trap at_exit EXIT + +# Read the single user.varlink extended attribute value off the given path. +read_role() { + getfattr --absolute-names --only-values --name=user.varlink "$1" +} + +# ------------------------------------------------------------------------------ +# 1) XAttr*= configured in a unit file on disk +# ------------------------------------------------------------------------------ + +# A matching .service unit is required to be able to start the .socket unit. It is +# never actually triggered here (we only check the listening socket), so a trivial +# service is sufficient. +cat >/run/systemd/system/test-socket-xattr.service </run/systemd/system/test-socket-xattr.socket </dev/null +systemctl show -P XAttrListen test-socket-xattr.socket | grep "user.varlink=listen" >/dev/null +systemctl show -P XAttrAccept test-socket-xattr.socket | grep "user.varlink=server" >/dev/null + +# ------------------------------------------------------------------------------ +# 2) XAttr*= set via "systemd-run --socket-property=" +# ------------------------------------------------------------------------------ + +rm -f "$RUN_SOCKET_PATH" + +# systemd-run synthesizes the matching test-socket-xattr-run.service for us. +systemd-run \ + --unit=test-socket-xattr-run \ + --service-type=oneshot \ + --remain-after-exit \ + --socket-property=ListenStream="$RUN_SOCKET_PATH" \ + --socket-property=SocketMode=0666 \ + --socket-property=XAttrEntryPoint=user.varlink=entrypoint \ + --socket-property=XAttrListen=user.varlink=listen \ + --socket-property=XAttrAccept=user.varlink=server \ + --socket-property=RemoveOnStop=true \ + true + +systemctl cat test-socket-xattr-run.socket + +# The XAttr*= settings must have been serialized into the transient socket unit. +grep "^XAttrEntryPoint=user.varlink=entrypoint$" /run/systemd/transient/test-socket-xattr-run.socket >/dev/null +grep "^XAttrListen=user.varlink=listen$" /run/systemd/transient/test-socket-xattr-run.socket >/dev/null +grep "^XAttrAccept=user.varlink=server$" /run/systemd/transient/test-socket-xattr-run.socket >/dev/null + +# And the transient socket must validate cleanly. +systemd-analyze verify --recursive-errors=no "/run/systemd/transient/test-socket-xattr-run.socket" + +# Wait for the transient socket to be bound, then check the entrypoint xattr. +test -S "$RUN_SOCKET_PATH" +[[ "$(read_role "$RUN_SOCKET_PATH")" == "entrypoint" ]] + +systemctl show -P XAttrEntryPoint test-socket-xattr-run.socket | grep "user.varlink=entrypoint" >/dev/null +systemctl show -P XAttrListen test-socket-xattr-run.socket | grep "user.varlink=listen" >/dev/null +systemctl show -P XAttrAccept test-socket-xattr-run.socket | grep "user.varlink=server" >/dev/null diff --git a/test/units/util.sh b/test/units/util.sh index 248e676eae..14bc29af11 100755 --- a/test/units/util.sh +++ b/test/units/util.sh @@ -251,6 +251,24 @@ cgroupfs_supports_user_xattrs() { [[ "$(getfattr --name="$xattr" --absolute-names --only-values /sys/fs/cgroup)" -eq 254 ]] } +socket_inode_supports_user_xattrs() { + local socket xattr + + # The XAttr*= socket settings and "varlinkctl list-sockets" rely on extended + # attributes on socket inodes. This needs a sufficiently new kernel, but the + # kernel version alone is not a reliable indicator: some kernels that report + # >= 7.0 still reject user.* xattrs on socket inodes (with EPERM/EOPNOTSUPP). + # Hence probe for actual support, mirroring socket_xattr_supported() in the + # C code, by binding a throwaway socket and trying to tag it. + socket="$(mktemp -u /run/socket-xattr-probe.XXXXXX.sock)" + xattr="user.probe_$RANDOM" + # shellcheck disable=SC2064 + trap "rm -f '$socket'" RETURN + + python3 -c 'import socket, sys; socket.socket(socket.AF_UNIX).bind(sys.argv[1])' "$socket" || return 1 + setfattr --name="$xattr" --value=1 "$socket" 2>/dev/null +} + tpm_has_pcr() { local algorithm="${1:?}" local pcr="${2:?}" From ce72c94accaaa9c4b056df05258d9b4bd51d1dff Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 18:51:13 +0200 Subject: [PATCH 06/12] bpf-restrict-fsaccess: move STAT_DEV_TO_KERNEL into generic code We want to reuse it when processing sock-diag messages, hence let's generalize this. --- src/basic/devnum-util.h | 6 ++++++ src/core/bpf-restrict-fsaccess.c | 1 + src/core/bpf-restrict-fsaccess.h | 8 -------- src/test/test-bpf-restrict-fsaccess.c | 1 + 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h index 8588b434fb..b5ae0dee57 100644 --- a/src/basic/devnum-util.h +++ b/src/basic/devnum-util.h @@ -56,3 +56,9 @@ static inline bool devnum_is_zero(dev_t d) { #define DEVNUM_TO_PTR(u) ((void*) (uintptr_t) (u)) #define PTR_TO_DEVNUM(p) ((dev_t) ((uintptr_t) (p))) + +/* Convert a userspace dev_t (as returned by stat()) to the kernel's internal dev_t encoding. stat() returns + * new_encode_dev(s_dev), while various kernel interfaces (e.g. the BPF sb helpers, or unix_diag's + * udiag_vfs_dev) report s_dev directly, which uses MKDEV(major, minor) = (major << 20) | minor. */ +#define STAT_DEV_TO_KERNEL(dev) \ + ((uint32_t) major(dev) << 20 | (uint32_t) minor(dev)) diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index 4e0af5596a..daa64e6afd 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -5,6 +5,7 @@ #include #include "bpf-restrict-fsaccess.h" +#include "devnum-util.h" #include "fd-util.h" #include "fileio.h" #include "initrd-util.h" diff --git a/src/core/bpf-restrict-fsaccess.h b/src/core/bpf-restrict-fsaccess.h index a39f602539..39b79353be 100644 --- a/src/core/bpf-restrict-fsaccess.h +++ b/src/core/bpf-restrict-fsaccess.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "core-forward.h" #include "macro.h" #include "shared-forward.h" @@ -33,12 +31,6 @@ enum { /* Maximum number of dm-verity devices tracked in the BPF hash map. */ #define DMVERITY_DEVICES_MAX (16U*1024U) -/* Convert userspace dev_t (from stat()) to kernel dev_t encoding (MKDEV). - * stat() returns new_encode_dev(s_dev); the BPF program reads s_dev directly - * which uses MKDEV(major, minor) = (major << 20) | minor. */ -#define STAT_DEV_TO_KERNEL(dev) \ - ((uint32_t)major(dev) << 20 | (uint32_t)minor(dev)) - /* Mirrors the BPF program's .bss section layout for read-modify-write via * bpf_map_lookup_elem/bpf_map_update_elem on the serialized .bss map FD. */ struct restrict_fsaccess_bss { diff --git a/src/test/test-bpf-restrict-fsaccess.c b/src/test/test-bpf-restrict-fsaccess.c index 80d448815c..776d75eaec 100644 --- a/src/test/test-bpf-restrict-fsaccess.c +++ b/src/test/test-bpf-restrict-fsaccess.c @@ -25,6 +25,7 @@ #include #include "bpf-restrict-fsaccess.h" +#include "devnum-util.h" #include "fd-util.h" #include "log.h" #include "string-util.h" From daae289f1024f2203c38ff2074f2392305eab90d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 18:52:00 +0200 Subject: [PATCH 07/12] sd-netlink: beef up sock-diag code a bit Let's make it useful to enumerate AF_UNIX sockets. --- src/basic/basic-forward.h | 1 + src/libsystemd/sd-netlink/netlink-sock-diag.c | 48 +++++++++++++++++++ src/libsystemd/sd-netlink/netlink-sock-diag.h | 2 + .../sd-netlink/netlink-types-sdnl.c | 2 + 4 files changed, 53 insertions(+) diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 5f1bef4a3f..f02801a77d 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -56,6 +56,7 @@ struct statx; struct termios; struct tm; struct ucred; +struct unix_diag_msg; /* To forward declare FILE and DIR, we have to declare the internal struct names for them. Since these are * used for C++ symbol name mangling, they're effectively part of the ABI and won't actually change. */ diff --git a/src/libsystemd/sd-netlink/netlink-sock-diag.c b/src/libsystemd/sd-netlink/netlink-sock-diag.c index 177be0c2bc..c76cef4632 100644 --- a/src/libsystemd/sd-netlink/netlink-sock-diag.c +++ b/src/libsystemd/sd-netlink/netlink-sock-diag.c @@ -40,3 +40,51 @@ int sd_sock_diag_message_new_unix( *ret = TAKE_PTR(m); return 0; } + +int sd_sock_diag_message_new_unix_dump( + sd_netlink *sdnl, + sd_netlink_message **ret, + uint32_t states, + uint32_t show) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert_return(sdnl, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(sdnl, &m, SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST | NLM_F_DUMP); + if (r < 0) + return r; + + /* Unlike sd_sock_diag_message_new_unix() this requests a dump of all AF_UNIX sockets matching the + * specified state mask, rather than looking up a single socket by inode/cookie. The kernel's dump + * handler ignores udiag_ino/udiag_cookie, hence we leave them zeroed. */ + + *(struct unix_diag_req*) NLMSG_DATA(m->hdr) = (struct unix_diag_req) { + .sdiag_family = AF_UNIX, + .udiag_states = states, + .udiag_show = show, + }; + + *ret = TAKE_PTR(m); + return 0; +} + +int sd_sock_diag_message_get_unix(sd_netlink_message *m, struct unix_diag_msg *ret) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(ret, -EINVAL); + + if (m->hdr->nlmsg_type != SOCK_DIAG_BY_FAMILY) + return -EINVAL; + + if (NLMSG_PAYLOAD(m->hdr, 0) < sizeof(struct unix_diag_msg)) + return -EBADMSG; + + /* Reads out the fixed-size unix_diag_msg header that precedes the attributes in a reply. There's no + * sd-netlink attribute for this leading family header, hence we read it directly. */ + + *ret = *(struct unix_diag_msg*) NLMSG_DATA(m->hdr); + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-sock-diag.h b/src/libsystemd/sd-netlink/netlink-sock-diag.h index e12e55a285..7253193f10 100644 --- a/src/libsystemd/sd-netlink/netlink-sock-diag.h +++ b/src/libsystemd/sd-netlink/netlink-sock-diag.h @@ -7,3 +7,5 @@ int sd_sock_diag_socket_open(sd_netlink **ret); int sd_sock_diag_message_new_unix(sd_netlink *sdnl, sd_netlink_message **ret, ino_t inode, uint64_t cookie, uint32_t show); +int sd_sock_diag_message_new_unix_dump(sd_netlink *sdnl, sd_netlink_message **ret, uint32_t states, uint32_t show); +int sd_sock_diag_message_get_unix(sd_netlink_message *m, struct unix_diag_msg *ret); diff --git a/src/libsystemd/sd-netlink/netlink-types-sdnl.c b/src/libsystemd/sd-netlink/netlink-types-sdnl.c index 6b72efb86b..6e08825513 100644 --- a/src/libsystemd/sd-netlink/netlink-types-sdnl.c +++ b/src/libsystemd/sd-netlink/netlink-types-sdnl.c @@ -18,6 +18,8 @@ static const NLAPolicy sdnl_req_policies[] = { DEFINE_POLICY_SET(sdnl_req); static const NLAPolicy unix_diag_msg_policies[] = { + [UNIX_DIAG_NAME] = BUILD_POLICY(STRING), /* Note: not NUL-terminated on the wire, read via sd_netlink_message_read_data() */ + [UNIX_DIAG_VFS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct unix_diag_vfs)), [UNIX_DIAG_RQLEN] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct unix_diag_rqlen)), }; DEFINE_POLICY_SET(unix_diag_msg); From 52e6c185df3d44cfc4dc62d57d0502a19fb43112 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 Jun 2026 18:53:07 +0200 Subject: [PATCH 08/12] varlinkctl: add 'list-sockets' verb --- man/varlinkctl.xml | 29 +++ shell-completion/bash/varlinkctl | 2 +- shell-completion/zsh/_varlinkctl | 1 + src/varlinkctl/varlinkctl.c | 186 ++++++++++++++++++ ...ST-74-AUX-UTILS.varlinkctl-list-sockets.sh | 76 +++++++ 5 files changed, 293 insertions(+), 1 deletion(-) create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-list-sockets.sh diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index 72f1983c9e..539b0ea33d 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -81,6 +81,18 @@ CMDLINE + + varlinkctl + OPTIONS + list-registry + + + + varlinkctl + OPTIONS + list-sockets + + varlinkctl OPTIONS @@ -221,6 +233,23 @@ + + list-sockets + + Shows a list of listening AF_UNIX stream sockets on the local + system that are marked as Varlink entrypoints, along with whether the calling user has write (i.e. + connect) access to each of them. A socket is considered a Varlink entrypoint if its inode carries the + user.varlink extended attribute set to entrypoint. + + Sockets are enumerated via the kernel's sock_diag netlink interface, and + hence this is not restricted to sockets in /run/varlink/registry/ (as + list-registry is), but covers all file-system bound listening Varlink sockets, + regardless of their location. This requires kernel support for extended attributes on socket inodes, + which is available since Linux 7.0. + + + + validate-idl [FILE] diff --git a/shell-completion/bash/varlinkctl b/shell-completion/bash/varlinkctl index 53c62dbae5..cfde556840 100644 --- a/shell-completion/bash/varlinkctl +++ b/shell-completion/bash/varlinkctl @@ -63,7 +63,7 @@ _varlinkctl() { fi local -A VERBS=( - [STANDALONE]='help list-registry' + [STANDALONE]='help list-registry list-sockets' [CALL]='call' [FILE]='info list-interfaces validate-idl' [ADDRESS_INTERFACES]='list-methods introspect' diff --git a/shell-completion/zsh/_varlinkctl b/shell-completion/zsh/_varlinkctl index a071f6ec2e..aa44beaeb3 100644 --- a/shell-completion/zsh/_varlinkctl +++ b/shell-completion/zsh/_varlinkctl @@ -34,6 +34,7 @@ _regex_words varlink-commands 'varlink command' \ 'introspect:show an interface definition:$varlink_interface' \ 'call:invoke a method:$varlink_call' \ 'validate-idl:validate an interface description:$varlink_idl' \ + 'list-sockets:List listening Varlink entrypoint sockets' \ 'help:show a help message' local -a varlinkcmd=( /$'[^\0]#\0'/ "$reply[@]" ) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 8f580350a5..48c500f7c4 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1,25 +1,33 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include #include #include #include #include "sd-daemon.h" +#include "sd-netlink.h" #include "sd-varlink.h" #include "build.h" #include "bus-util.h" #include "chase.h" +#include "devnum-util.h" #include "env-util.h" +#include "errno-list.h" +#include "errno-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" #include "format-util.h" +#include "fs-util.h" #include "help-util.h" #include "log.h" #include "main-func.h" #include "memfd-util.h" +#include "netlink-sock-diag.h" #include "options.h" #include "pager.h" #include "parse-argument.h" @@ -33,6 +41,7 @@ #include "recurse-dir.h" #include "runtime-scope.h" #include "socket-forward.h" +#include "socket-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -41,6 +50,7 @@ #include "varlink-util.h" #include "verbs.h" #include "version.h" +#include "xattr-util.h" typedef struct PushFds { int *fds; @@ -1153,6 +1163,182 @@ static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *use return 0; } +VERB_NOARG(verb_list_sockets, "list-sockets", "List listening Varlink entrypoint sockets"); +static int verb_list_sockets(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + assert(argc <= 1); + + /* Enumerates listening, file-system bound AF_UNIX SOCK_STREAM sockets via the sock_diag netlink API, + * and lists those that are marked as Varlink entrypoints (i.e. carry the "user.varlink" xattr set to + * "entrypoint"). */ + + r = socket_xattr_supported(); + if (r < 0) + return log_error_errno(r, "Failed to check if S_IFSOCK inodes support xattrs: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "This kernel does not support extended attributes on socket inodes, cannot enumerate Varlink sockets."); + + _cleanup_(sd_netlink_unrefp) sd_netlink *nl = NULL; + r = sd_sock_diag_socket_open(&nl); + if (r < 0) + return log_error_errno(r, "Failed to open sock_diag netlink socket: %m"); + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + r = sd_sock_diag_message_new_unix_dump(nl, &req, 1U << TCP_LISTEN, UDIAG_SHOW_NAME|UDIAG_SHOW_VFS); + if (r < 0) + return log_error_errno(r, "Failed to allocate AF_UNIX socket dump request: %m"); + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *reply = NULL; + r = sd_netlink_call(nl, req, /* timeout= */ 0, &reply); + if (r < 0) + return log_error_errno(r, "Failed to issue AF_UNIX socket dump: %m"); + + _cleanup_(table_unrefp) Table *table = table_new("path", "access"); + if (!table) + return log_oom(); + + (void) table_set_sort(table, (size_t) 0); + + for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_warning_errno(r, "Error in AF_UNIX socket dump entry, ignoring: %m"); + continue; + } + + struct unix_diag_msg udm; + r = sd_sock_diag_message_get_unix(m, &udm); + if (r < 0) { + log_warning_errno(r, "Failed to read AF_UNIX socket dump header, ignoring: %m"); + continue; + } + + /* We only care about listening stream sockets. The kernel already filtered by state, but + * there's no way to filter by type in the request, so we do that here (and double check the + * state for good measure). */ + if (udm.udiag_type != SOCK_STREAM) + continue; + if (udm.udiag_state != TCP_LISTEN) + continue; + + /* Read the bound name. This is not NUL terminated on the wire, hence read it as raw data. */ + _cleanup_free_ void *name = NULL; + size_t name_size = 0; + r = sd_netlink_message_read_data(m, UNIX_DIAG_NAME, &name_size, &name); + if (r == -ENODATA) /* unnamed socket */ + continue; + if (r < 0) + return log_error_errno(r, "Failed to read AF_UNIX socket name: %m"); + + /* Safely turn the raw, not necessarily NUL-terminated, name into a C string. This also + * rejects any name with embedded NUL bytes. */ + _cleanup_free_ char *path = NULL; + r = make_cstring(name, name_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &path); + if (r < 0) { + log_debug_errno(r, "Failed to convert AF_UNIX socket name to string, skipping: %m"); + continue; + } + if (!path_is_absolute(path)) { + log_debug("Got non-absolute AF_UNIX socket path '%s', skipping.", path); + continue; + } + + /* The kernel also reports the backing VFS inode/device, but only for file-system bound + * sockets. We require it, both as a filter and to validate the path below. */ + _cleanup_free_ void *vfs = NULL; + size_t vfs_size = 0; + r = sd_netlink_message_read_data(m, UNIX_DIAG_VFS, &vfs_size, &vfs); + if (r == -ENODATA) + continue; /* not fs bound */ + if (r < 0) + return log_error_errno(r, "Failed to read AF_UNIX socket VFS data: %m"); + if (vfs_size != sizeof(struct unix_diag_vfs)) { + log_warning("Got AF_UNIX socket VFS data of unexpected size, skipping."); + continue; + } + const struct unix_diag_vfs *uv = vfs; + + /* Validate the path the kernel reported: open it (without following a final-component + * symlink), and verify it really is a socket whose inode and backing device match what + * netlink told us. This guards against the path having been unlinked/replaced in the + * meantime, so that we only ever read xattrs off the right inode. */ + _cleanup_close_ int fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) { + log_debug_errno(errno, "Failed to open reported AF_UNIX socket path '%s', skipping: %m", path); + continue; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + log_debug_errno(errno, "Failed to stat reported AF_UNIX socket path '%s', skipping: %m", path); + continue; + } + + if (!S_ISSOCK(st.st_mode)) { + log_debug("Reported AF_UNIX socket path '%s' is not a socket, skipping.", path); + continue; + } + + /* the unix_diag_vfs structure only gives us 32bit inode numbers, which it truncates. hence lets truncate the value before comparison */ + if (((st.st_ino ^ uv->udiag_vfs_ino) & UINT32_MAX) != 0) { + log_debug("Inode of reported AF_UNIX socket path '%s' does not match netlink data, skipping.", path); + continue; + } + + /* udiag_vfs_dev carries the kernel-internal dev_t encoding, which differs from the userspace + * dev_t in st_dev — hence translate before comparing. */ + if (STAT_DEV_TO_KERNEL(st.st_dev) != uv->udiag_vfs_dev) { + log_debug("Backing device of reported AF_UNIX socket path '%s' does not match netlink data, skipping.", path); + continue; + } + + /* The path is validated now, hence we may safely read the Varlink role xattr off the fd. We + * only list sockets that are marked as Varlink entrypoints. */ + _cleanup_free_ char *role = NULL; + r = fgetxattr_malloc(fd, "user.varlink", &role, /* ret_size= */ NULL); + if (r < 0) { + if (!ERRNO_IS_NEG_XATTR_ABSENT(r)) + log_debug_errno(r, "Failed to read 'user.varlink' xattr of '%s', skipping: %m", path); + continue; + } + if (!streq(role, "entrypoint")) + continue; + + _cleanup_free_ char *no = NULL; + r = access_fd(fd, W_OK); + if (r < 0) { + no = strjoin("no (", ERRNO_NAME(r), ")"); + if (!no) + return log_oom(); + } + + r = table_add_many( + table, + TABLE_PATH, path, + TABLE_STRING, no ?: "yes", + TABLE_SET_COLOR, ansi_highlight_green_red(!no)); + if (r < 0) + return r; + } + + if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ true); + if (r < 0) + return log_error_errno(r, "Failed to output table: %m"); + } + + if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { + if (table_isempty(table)) + printf("No sockets found.\n"); + else + printf("\n%zu entrypoint sockets listed.\n", table_get_rows(table) - 1); + } + + return 0; +} + /* Build a minimal IDL from a qualified method name so that introspection works. The parsed interface is * returned to the caller who must keep it alive for the lifetime of the server * (sd_varlink_server_add_interface() borrows the pointer). */ diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-list-sockets.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-list-sockets.sh new file mode 100755 index 0000000000..2097d00fb1 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-list-sockets.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# "varlinkctl list-sockets" relies on extended attributes on socket inodes, which +# require a sufficiently new kernel. Probe for actual support and skip if +# unavailable, since the kernel version alone is not a reliable indicator. +if ! socket_inode_supports_user_xattrs; then + echo "Socket inode extended attributes unsupported on this kernel, skipping." >&2 + exit 0 +fi + +ENTRYPOINT_PATH="/run/test-list-sockets-entrypoint.sock" +PLAIN_PATH="/run/test-list-sockets-plain.sock" + +at_exit() { + set +e + systemctl stop test-list-sockets-entrypoint.socket + systemctl reset-failed test-list-sockets-entrypoint.socket test-list-sockets-entrypoint.service + systemctl stop test-list-sockets-plain.socket + systemctl reset-failed test-list-sockets-plain.socket test-list-sockets-plain.service + rm -f "$ENTRYPOINT_PATH" "$PLAIN_PATH" +} +trap at_exit EXIT + +rm -f "$ENTRYPOINT_PATH" "$PLAIN_PATH" + +# A listening socket tagged as a Varlink entrypoint. +systemd-run \ + --unit=test-list-sockets-entrypoint \ + --service-type=oneshot \ + --remain-after-exit \ + --socket-property=ListenStream="$ENTRYPOINT_PATH" \ + --socket-property=SocketMode=0666 \ + --socket-property=XAttrEntryPoint=user.varlink=entrypoint \ + --socket-property=RemoveOnStop=true \ + true + +# A listening socket *without* the entrypoint marker, which must be ignored. +systemd-run \ + --unit=test-list-sockets-plain \ + --service-type=oneshot \ + --remain-after-exit \ + --socket-property=ListenStream="$PLAIN_PATH" \ + --socket-property=SocketMode=0666 \ + --socket-property=RemoveOnStop=true \ + true + +test -S "$ENTRYPOINT_PATH" +test -S "$PLAIN_PATH" + +# Plain text output should run cleanly and mention the entrypoint socket. +varlinkctl list-sockets +varlinkctl list-sockets | grep "$ENTRYPOINT_PATH" >/dev/null + +# JSON output is an array of {path, access} objects. The entrypoint socket must be +# present... +json="$(varlinkctl --json=short list-sockets)" +echo "$json" | jq -e --arg p "$ENTRYPOINT_PATH" 'any(.[]; .path == $p)' >/dev/null +# ...and carry an "access" field (either "yes" or "No (…)"). +echo "$json" | jq -e --arg p "$ENTRYPOINT_PATH" 'any(.[]; .path == $p and (.access | type == "string"))' >/dev/null + +# The socket without the entrypoint xattr must NOT be listed. +(! echo "$json" | jq -e --arg p "$PLAIN_PATH" 'any(.[]; .path == $p)' >/dev/null) + +# Stopping the socket unit must make the entrypoint disappear from the listing again. +systemctl stop test-list-sockets-entrypoint.socket +test ! -S "$ENTRYPOINT_PATH" +(! varlinkctl --json=short list-sockets | jq -e --arg p "$ENTRYPOINT_PATH" 'any(.[]; .path == $p)' >/dev/null) + +systemctl stop test-list-sockets-plain.socket +test ! -S "$PLAIN_PATH" From 53fc4c48e7d40293e8f79392e2da91323dd50268 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 Jun 2026 08:37:03 +0200 Subject: [PATCH 09/12] units: tag all .varlink sockets with the right xattrs This also relaxes the inode access modes a bit, in case they were set to 0600: we now set the "r" bit too, i.e. use 0644. This is beneficial since it permits unpriv code to read the xattrs of the entrypoints (which require read access). Note that in order to be able to connect() to a socket inode you need write access, hence this shouldn't compromise security in any way. --- units/systemd-ask-password.socket | 3 +++ units/systemd-bootctl.socket | 5 ++++- units/systemd-creds.socket | 3 +++ units/systemd-factory-reset.socket | 3 +++ units/systemd-hostnamed.socket | 2 ++ units/systemd-imds-metrics.socket | 3 +++ units/systemd-imdsd.socket | 3 +++ units/systemd-importd.socket | 2 ++ units/systemd-journalctl.socket | 5 ++++- units/systemd-journald-varlink@.socket | 2 ++ units/systemd-logind-varlink.socket | 2 ++ units/systemd-machined.socket | 2 ++ units/systemd-mountfsd.socket | 2 ++ units/systemd-mute-console.socket | 5 ++++- units/systemd-networkd-resolve-hook.socket | 2 ++ units/systemd-networkd-varlink-metrics.socket | 2 ++ units/systemd-networkd-varlink.socket | 2 ++ units/systemd-nsresourced.socket | 2 ++ units/systemd-oomd.socket | 2 ++ units/systemd-pcrextend.socket | 5 ++++- units/systemd-pcrlock.socket | 5 ++++- units/systemd-repart.socket | 5 ++++- units/systemd-report-basic.socket | 3 +++ units/systemd-report-cgroup.socket | 3 +++ units/systemd-report-files.socket | 3 +++ units/systemd-report-sign-plain.socket | 3 +++ units/systemd-report-sign-tsm.socket | 3 +++ units/systemd-resolved-monitor.socket | 2 ++ units/systemd-resolved-varlink.socket | 2 ++ units/systemd-storage-block.socket | 3 +++ units/systemd-storage-fs.socket | 3 +++ units/systemd-sysext.socket | 3 +++ units/systemd-udevd-varlink.socket | 4 +++- units/systemd-userdbd.socket | 2 ++ units/user/systemd-ask-password.socket | 3 +++ units/user/systemd-importd.socket | 2 ++ units/user/systemd-journalctl.socket | 3 +++ units/user/systemd-machined.socket | 2 ++ units/user/systemd-storage-fs.socket | 3 +++ 39 files changed, 107 insertions(+), 7 deletions(-) diff --git a/units/systemd-ask-password.socket b/units/systemd-ask-password.socket index df5eaffc22..4251ecabf4 100644 --- a/units/systemd-ask-password.socket +++ b/units/systemd-ask-password.socket @@ -20,3 +20,6 @@ FileDescriptorName=varlink SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-bootctl.socket b/units/systemd-bootctl.socket index e720f24f54..64b42e3ce1 100644 --- a/units/systemd-bootctl.socket +++ b/units/systemd-bootctl.socket @@ -18,6 +18,9 @@ Before=sockets.target ListenStream=/run/systemd/io.systemd.BootControl Symlinks=/run/varlink/registry/io.systemd.BootControl FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0644 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-creds.socket b/units/systemd-creds.socket index 3ea3ca5b05..452c4d2bcb 100644 --- a/units/systemd-creds.socket +++ b/units/systemd-creds.socket @@ -20,3 +20,6 @@ FileDescriptorName=varlink SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-factory-reset.socket b/units/systemd-factory-reset.socket index 467517ec24..bba00e0fa9 100644 --- a/units/systemd-factory-reset.socket +++ b/units/systemd-factory-reset.socket @@ -20,6 +20,9 @@ FileDescriptorName=varlink SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-hostnamed.socket b/units/systemd-hostnamed.socket index f84853ade8..da938ba019 100644 --- a/units/systemd-hostnamed.socket +++ b/units/systemd-hostnamed.socket @@ -21,3 +21,5 @@ ListenStream=/run/systemd/io.systemd.Hostname Symlinks=/run/varlink/registry/io.systemd.Hostname FileDescriptorName=varlink SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/systemd-imds-metrics.socket b/units/systemd-imds-metrics.socket index f71a31e5b9..e828864010 100644 --- a/units/systemd-imds-metrics.socket +++ b/units/systemd-imds-metrics.socket @@ -20,3 +20,6 @@ SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-imdsd.socket b/units/systemd-imdsd.socket index daeb7840b3..20d8eb3903 100644 --- a/units/systemd-imdsd.socket +++ b/units/systemd-imdsd.socket @@ -21,6 +21,9 @@ SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server # Note that this is typically pulled in automatically by # systemd-imds-generator, but you can also enable it manually if you like. diff --git a/units/systemd-importd.socket b/units/systemd-importd.socket index a538ef0d0e..0fe7edce66 100644 --- a/units/systemd-importd.socket +++ b/units/systemd-importd.socket @@ -23,3 +23,5 @@ ListenStream=/run/systemd/io.systemd.Import Symlinks=/run/varlink/registry/io.systemd.Import FileDescriptorName=varlink SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/systemd-journalctl.socket b/units/systemd-journalctl.socket index 59d0af3449..7284b539aa 100644 --- a/units/systemd-journalctl.socket +++ b/units/systemd-journalctl.socket @@ -19,6 +19,9 @@ ListenStream=/run/systemd/io.systemd.JournalAccess Symlinks=/run/varlink/registry/io.systemd.JournalAccess FileDescriptorName=varlink SocketGroup=systemd-journal -SocketMode=0660 +SocketMode=0664 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-journald-varlink@.socket b/units/systemd-journald-varlink@.socket index e48a93c202..c8d09b93d4 100644 --- a/units/systemd-journald-varlink@.socket +++ b/units/systemd-journald-varlink@.socket @@ -16,3 +16,5 @@ StopWhenUnneeded=yes Service=systemd-journald@%i.service ListenStream=/run/systemd/journal.%i/io.systemd.journal SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/systemd-logind-varlink.socket b/units/systemd-logind-varlink.socket index 377eac7006..83cd5ece8c 100644 --- a/units/systemd-logind-varlink.socket +++ b/units/systemd-logind-varlink.socket @@ -17,3 +17,5 @@ Symlinks=/run/varlink/registry/io.systemd.Login /run/varlink/registry/io.systemd FileDescriptorName=varlink SocketMode=0666 Service=systemd-logind.service +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/systemd-machined.socket b/units/systemd-machined.socket index 75a91bb0cc..dbabd9376c 100644 --- a/units/systemd-machined.socket +++ b/units/systemd-machined.socket @@ -16,3 +16,5 @@ ListenStream=/run/systemd/machine/io.systemd.Machine Symlinks=/run/systemd/machine/io.systemd.MachineImage /run/varlink/registry/io.systemd.Machine /run/varlink/registry/io.systemd.MachineImage FileDescriptorName=varlink SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/systemd-mountfsd.socket b/units/systemd-mountfsd.socket index a3e19cc418..46dfb021dd 100644 --- a/units/systemd-mountfsd.socket +++ b/units/systemd-mountfsd.socket @@ -20,6 +20,8 @@ ListenStream=/run/systemd/io.systemd.MountFileSystem Symlinks=/run/varlink/registry/io.systemd.MountFileSystem FileDescriptorName=varlink SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-mute-console.socket b/units/systemd-mute-console.socket index 5eae6d5acd..1b7ce2bfdb 100644 --- a/units/systemd-mute-console.socket +++ b/units/systemd-mute-console.socket @@ -19,6 +19,9 @@ Before=shutdown.target ListenStream=/run/systemd/io.systemd.MuteConsole Symlinks=/run/varlink/registry/io.systemd.MuteConsole FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0644 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-networkd-resolve-hook.socket b/units/systemd-networkd-resolve-hook.socket index 07b596319b..e56697e77f 100644 --- a/units/systemd-networkd-resolve-hook.socket +++ b/units/systemd-networkd-resolve-hook.socket @@ -22,6 +22,8 @@ FileDescriptorName=resolve-hook SocketMode=0666 Service=systemd-networkd.service RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-networkd-varlink-metrics.socket b/units/systemd-networkd-varlink-metrics.socket index 562cc6b7f2..3ae15c13bc 100644 --- a/units/systemd-networkd-varlink-metrics.socket +++ b/units/systemd-networkd-varlink-metrics.socket @@ -20,6 +20,8 @@ ListenStream=/run/systemd/report/io.systemd.Network FileDescriptorName=varlink-metrics SocketMode=0666 Service=systemd-networkd.service +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-networkd-varlink.socket b/units/systemd-networkd-varlink.socket index 1f4db858bc..264e81f602 100644 --- a/units/systemd-networkd-varlink.socket +++ b/units/systemd-networkd-varlink.socket @@ -21,6 +21,8 @@ Symlinks=/run/varlink/registry/io.systemd.Network FileDescriptorName=varlink SocketMode=0666 Service=systemd-networkd.service +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-nsresourced.socket b/units/systemd-nsresourced.socket index 6b4a883df3..2840be43e2 100644 --- a/units/systemd-nsresourced.socket +++ b/units/systemd-nsresourced.socket @@ -20,6 +20,8 @@ ListenStream=/run/systemd/io.systemd.NamespaceResource Symlinks=/run/systemd/userdb/io.systemd.NamespaceResource /run/varlink/registry/io.systemd.NamespaceResource FileDescriptorName=varlink SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-oomd.socket b/units/systemd-oomd.socket index 4e24342f58..9b0f822d9e 100644 --- a/units/systemd-oomd.socket +++ b/units/systemd-oomd.socket @@ -22,6 +22,8 @@ ConditionPathExists=/proc/pressure/memory ListenStream=/run/systemd/oom/io.systemd.ManagedOOM SocketMode=0666 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-pcrextend.socket b/units/systemd-pcrextend.socket index 0f4ab11e2f..2be4e51a25 100644 --- a/units/systemd-pcrextend.socket +++ b/units/systemd-pcrextend.socket @@ -19,9 +19,12 @@ ConditionSecurity=measured-os ListenStream=/run/systemd/io.systemd.PCRExtend Symlinks=/run/varlink/registry/io.systemd.PCRExtend FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0644 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-pcrlock.socket b/units/systemd-pcrlock.socket index efb6818683..637462bb41 100644 --- a/units/systemd-pcrlock.socket +++ b/units/systemd-pcrlock.socket @@ -19,6 +19,9 @@ ConditionSecurity=measured-uki ListenStream=/run/systemd/io.systemd.PCRLock Symlinks=/run/varlink/registry/io.systemd.PCRLock FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0644 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-repart.socket b/units/systemd-repart.socket index ecd275414d..b79505fe07 100644 --- a/units/systemd-repart.socket +++ b/units/systemd-repart.socket @@ -19,6 +19,9 @@ Before=shutdown.target ListenStream=/run/systemd/io.systemd.Repart Symlinks=/run/varlink/registry/io.systemd.Repart FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0644 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/systemd-report-basic.socket b/units/systemd-report-basic.socket index ba5d88c8e7..f31dab50e2 100644 --- a/units/systemd-report-basic.socket +++ b/units/systemd-report-basic.socket @@ -18,6 +18,9 @@ SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-report-cgroup.socket b/units/systemd-report-cgroup.socket index 39a867cd40..2168129a7a 100644 --- a/units/systemd-report-cgroup.socket +++ b/units/systemd-report-cgroup.socket @@ -20,6 +20,9 @@ SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-report-files.socket b/units/systemd-report-files.socket index f37c508cf6..b5e6491a10 100644 --- a/units/systemd-report-files.socket +++ b/units/systemd-report-files.socket @@ -21,6 +21,9 @@ SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-report-sign-plain.socket b/units/systemd-report-sign-plain.socket index 4a6dea580d..7c3c501709 100644 --- a/units/systemd-report-sign-plain.socket +++ b/units/systemd-report-sign-plain.socket @@ -28,6 +28,9 @@ SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-report-sign-tsm.socket b/units/systemd-report-sign-tsm.socket index 1756dde8cf..5817075fa7 100644 --- a/units/systemd-report-sign-tsm.socket +++ b/units/systemd-report-sign-tsm.socket @@ -22,6 +22,9 @@ SocketMode=0600 Accept=yes MaxConnectionsPerSource=16 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-resolved-monitor.socket b/units/systemd-resolved-monitor.socket index 3674a1f876..9bb4dd646f 100644 --- a/units/systemd-resolved-monitor.socket +++ b/units/systemd-resolved-monitor.socket @@ -20,6 +20,8 @@ ListenStream=/run/systemd/resolve/io.systemd.Resolve.Monitor Symlinks=/run/varlink/registry/io.systemd.Resolve.Monitor FileDescriptorName=varlink-monitor SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-resolved-varlink.socket b/units/systemd-resolved-varlink.socket index a570168373..65593df1d0 100644 --- a/units/systemd-resolved-varlink.socket +++ b/units/systemd-resolved-varlink.socket @@ -20,6 +20,8 @@ ListenStream=/run/systemd/resolve/io.systemd.Resolve Symlinks=/run/varlink/registry/io.systemd.Resolve FileDescriptorName=varlink SocketMode=0666 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/systemd-storage-block.socket b/units/systemd-storage-block.socket index 1d18b481a3..c8097e705b 100644 --- a/units/systemd-storage-block.socket +++ b/units/systemd-storage-block.socket @@ -19,6 +19,9 @@ FileDescriptorName=varlink SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-storage-fs.socket b/units/systemd-storage-fs.socket index c83cf0a11f..d34e39d059 100644 --- a/units/systemd-storage-fs.socket +++ b/units/systemd-storage-fs.socket @@ -20,6 +20,9 @@ FileDescriptorName=varlink SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-sysext.socket b/units/systemd-sysext.socket index 61d7268f37..209592134b 100644 --- a/units/systemd-sysext.socket +++ b/units/systemd-sysext.socket @@ -22,6 +22,9 @@ FileDescriptorName=varlink SocketMode=0666 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target diff --git a/units/systemd-udevd-varlink.socket b/units/systemd-udevd-varlink.socket index c2b7652e5e..b1094b9c9b 100644 --- a/units/systemd-udevd-varlink.socket +++ b/units/systemd-udevd-varlink.socket @@ -19,6 +19,8 @@ Service=systemd-udevd.service ListenStream=/run/udev/io.systemd.Udev Symlinks=/run/varlink/registry/io.systemd.Udev FileDescriptorName=varlink -SocketMode=0600 +SocketMode=0644 RemoveOnStop=yes DeferTrigger=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/systemd-userdbd.socket b/units/systemd-userdbd.socket index 6793d1b41d..83f3d7e560 100644 --- a/units/systemd-userdbd.socket +++ b/units/systemd-userdbd.socket @@ -19,6 +19,8 @@ Symlinks=/run/systemd/userdb/io.systemd.NameServiceSwitch /run/systemd/userdb/io FileDescriptorName=varlink SocketMode=0666 RemoveOnStop=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen [Install] WantedBy=sockets.target diff --git a/units/user/systemd-ask-password.socket b/units/user/systemd-ask-password.socket index 56492fae54..13a2379557 100644 --- a/units/user/systemd-ask-password.socket +++ b/units/user/systemd-ask-password.socket @@ -19,3 +19,6 @@ Symlinks=%t/varlink/registry/io.systemd.AskPassword FileDescriptorName=varlink SocketMode=0600 Accept=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/user/systemd-importd.socket b/units/user/systemd-importd.socket index 67ea62bdb0..35108341f5 100644 --- a/units/user/systemd-importd.socket +++ b/units/user/systemd-importd.socket @@ -17,3 +17,5 @@ ListenStream=%t/systemd/io.systemd.Import Symlinks=%t/varlink/registry/io.systemd.Import FileDescriptorName=varlink SocketMode=0600 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/user/systemd-journalctl.socket b/units/user/systemd-journalctl.socket index b8a504f0f3..a711ca2127 100644 --- a/units/user/systemd-journalctl.socket +++ b/units/user/systemd-journalctl.socket @@ -17,3 +17,6 @@ Symlinks=%t/varlink/registry/io.systemd.JournalAccess FileDescriptorName=varlink SocketMode=0600 Accept=yes +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server diff --git a/units/user/systemd-machined.socket b/units/user/systemd-machined.socket index 17e552a7dc..ffb4ff1ce3 100644 --- a/units/user/systemd-machined.socket +++ b/units/user/systemd-machined.socket @@ -16,3 +16,5 @@ ListenStream=%t/systemd/machine/io.systemd.Machine Symlinks=%t/systemd/machine/io.systemd.MachineImage %t/varlink/registry/io.systemd.Machine %t/varlink/registry/io.systemd.MachineImage FileDescriptorName=varlink SocketMode=0600 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen diff --git a/units/user/systemd-storage-fs.socket b/units/user/systemd-storage-fs.socket index fa8018b2e8..23af4d79e6 100644 --- a/units/user/systemd-storage-fs.socket +++ b/units/user/systemd-storage-fs.socket @@ -18,6 +18,9 @@ FileDescriptorName=varlink SocketMode=0600 Accept=yes MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server [Install] WantedBy=sockets.target From 941a9ff40cabd28bdb52aee6ea339f754c050897 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 Jun 2026 12:10:49 +0200 Subject: [PATCH 10/12] tree-wide: relax access mode of private Varlink sockets a bit --- src/libsystemd/sd-varlink/sd-varlink.c | 15 ++++++++++++++- src/udev/udev-varlink.c | 2 +- src/vmspawn/vmspawn-varlink.c | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 9f06f69265..048dbb4747 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -3452,6 +3452,13 @@ _public_ int sd_varlink_server_listen_fd(sd_varlink_server *s, int fd) { return 0; } +static mode_t default_listen_mode(sd_varlink_server_flags_t flags) { + /* NB: we use 0644 rather than 0600 here, because it's the "w" flag that controls connect() + * privileges, but leaving the "r" flag on allows others to read our xattrs, which is good because it + * makes our sockets recognizable as varlink, even if not connectible. */ + return (flags & (SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY)) != 0 ? 0644 : 0666; +} + _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char *address, mode_t m) { _cleanup_(varlink_server_socket_freep) VarlinkServerSocket *ss = NULL; union sockaddr_union sockaddr; @@ -3461,6 +3468,12 @@ _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char * assert_return(s, -EINVAL); assert_return(address, -EINVAL); + + /* NB: we resolve m being MODE_INVALID before checking SD_VARLINK_SERVER_MODE_MKDIR_0755, since that + * flag is not defined for MODE_INVALID (if we'd check we'd see it always set...) */ + if (m == MODE_INVALID) + m = default_listen_mode(s->flags); + assert_return((m & ~(0777|SD_VARLINK_SERVER_MODE_MKDIR_0755)) == 0, -EINVAL); /* Validate that the definition of our flag doesn't collide with the official mode_t bits. Thankfully @@ -3636,7 +3649,7 @@ _public_ int sd_varlink_server_listen_auto(sd_varlink_server *s) { if (streq(e, "-")) r = sd_varlink_server_add_connection_stdio(s, /* ret= */ NULL); else - r = sd_varlink_server_listen_address(s, e, FLAGS_SET(s->flags, SD_VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666); + r = sd_varlink_server_listen_address(s, e, default_listen_mode(s->flags)); if (r < 0) return r; diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c index 355e5a7860..183265fc53 100644 --- a/src/udev/udev-varlink.c +++ b/src/udev/udev-varlink.c @@ -189,7 +189,7 @@ int manager_start_varlink_server(Manager *manager, int fd) { return log_error_errno(r, "Failed to attach Varlink connection to event loop: %m"); if (fd < 0) - r = sd_varlink_server_listen_address(v, UDEV_VARLINK_ADDRESS, 0600); + r = sd_varlink_server_listen_address(v, UDEV_VARLINK_ADDRESS, 0644); else r = sd_varlink_server_listen_fd(v, fd); if (r < 0) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 57230c8e8f..b75b8daa99 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -551,7 +551,7 @@ int vmspawn_varlink_setup( if (!listen_address) return log_oom(); - r = sd_varlink_server_listen_address(ctx->varlink_server, listen_address, 0600); + r = sd_varlink_server_listen_address(ctx->varlink_server, listen_address, 0644); if (r < 0) return log_error_errno(r, "Failed to listen on %s: %m", listen_address); From 627655b2c09988b97b5493b449a42f5011f6b5df Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 Jun 2026 15:30:56 +0200 Subject: [PATCH 11/12] man: document sd_varlink_server_listen_address() and friends --- man/rules/meson.build | 6 + man/sd_varlink_server_listen_address.xml | 181 +++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 man/sd_varlink_server_listen_address.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index b37eb5f19f..974d9eee4b 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -977,6 +977,12 @@ manpages = [ ['sd_varlink_is_connected', '3', ['sd_varlink_is_idle'], ''], ['sd_varlink_push_fd', '3', ['sd_varlink_push_dup_fd'], ''], ['sd_varlink_send', '3', ['sd_varlink_sendb', 'sd_varlink_sendbo'], ''], + ['sd_varlink_server_listen_address', + '3', + ['sd_varlink_server_listen_auto', + 'sd_varlink_server_listen_fd', + 'sd_varlink_server_listen_name'], + ''], ['sd_varlink_server_new', '3', [], ''], ['sd_varlink_set_description', '3', ['sd_varlink_get_description'], ''], ['sd_varlink_set_relative_timeout', '3', [], ''], diff --git a/man/sd_varlink_server_listen_address.xml b/man/sd_varlink_server_listen_address.xml new file mode 100644 index 0000000000..55858c1c99 --- /dev/null +++ b/man/sd_varlink_server_listen_address.xml @@ -0,0 +1,181 @@ + + + + + + + + sd_varlink_server_listen_address + systemd + + + + sd_varlink_server_listen_address + 3 + + + + sd_varlink_server_listen_address + sd_varlink_server_listen_fd + sd_varlink_server_listen_name + sd_varlink_server_listen_auto + + Bind a Varlink server object to listening sockets + + + + + #include <systemd/sd-varlink.h> + + #define SD_VARLINK_SERVER_MODE_MKDIR_0755 … + + + int sd_varlink_server_listen_address + sd_varlink_server* server + const char* address + mode_t mode + + + + int sd_varlink_server_listen_fd + sd_varlink_server* server + int fd + + + + int sd_varlink_server_listen_name + sd_varlink_server* server + const char* name + + + + int sd_varlink_server_listen_auto + sd_varlink_server* server + + + + + + + Description + + These functions add listening sockets to a Varlink server object, as previously allocated with + sd_varlink_server_new3. + Once the server object is attached to an event loop (see + sd_varlink_server_attach_event()) it will accept incoming connections on the + configured sockets and dispatch the registered method calls. + + sd_varlink_server_listen_address() allocates a new + AF_UNIX socket, binds it to the specified address, and starts + listening on it. The address is a file system path of the socket to bind to. (Use + sd_varlink_server_listen_fd() below for sockets in the abstract namespace, in foreign + address families, or that are otherwise already allocated.) + + The mode parameter specifies the file system access mode to apply to the + socket inode, i.e. a bit mask of permission bits as in + chmod2. + Only the lower nine bits (0777) are permitted. The special value + (mode_t) -1 may be specified to pick a suitable default: this is + 0666 in the general case, but 0644 if the server was allocated + with the SD_VARLINK_SERVER_ROOT_ONLY or + SD_VARLINK_SERVER_MYSELF_ONLY flags (the reason being that it is the + w bit that controls + connect2 + access, while leaving the r bit on permits other users to read the socket's extended + attributes, which makes the socket recognizable as a Varlink entrypoint, see + sd-varlink3). + + The constant SD_VARLINK_SERVER_MODE_MKDIR_0755 may be OR-ed into the + mode parameter (this is not supported if the mode is specified as + (mode_t) -1). If specified, and address is an absolute path, + the leading directories of the socket path are automatically created (with access mode + 0755) if they are missing. + + sd_varlink_server_listen_fd() adds an already allocated and listening socket to + the server object, referenced by the file descriptor fd. This is typically used in + conjunction with socket activation, to make use of a listening socket passed in from the service manager, + see + sd_listen_fds3. The + file descriptor is automatically switched to non-blocking mode and the close-on-exec flag is set on + it. + + sd_varlink_server_listen_name() picks up sockets passed in via the socket + activation protocol whose name (as set via $LISTEN_FDNAMES, i.e. via the + FileDescriptorName= setting of the + systemd.socket5 unit) + matches the specified name. For each matching file descriptor that refers to a + listening socket sd_varlink_server_listen_fd() is invoked; file descriptors that + refer to an already accepted connection are added as connections via + sd_varlink_server_add_connection() instead. + + sd_varlink_server_listen_auto() is a convenience wrapper that automatically + determines the sockets to listen on. It first picks up all socket activation file descriptors named + varlink (by calling sd_varlink_server_listen_name() with that + name). In addition, if the $SYSTEMD_VARLINK_LISTEN environment variable is set, it is + used as an additional address to listen on: if set to - the Varlink protocol is spoken + on standard input and output (see sd_varlink_server_add_connection_stdio()), + otherwise the value is passed to sd_varlink_server_listen_address(). This is the + recommended way for a Varlink service to set up its listening sockets, as it transparently supports both + socket activation and direct invocation (for testing and debugging). + + + + Return Value + + On success, sd_varlink_server_listen_address() and + sd_varlink_server_listen_fd() return a non-negative integer. + sd_varlink_server_listen_name() and + sd_varlink_server_listen_auto() return the number of sockets and file descriptors + they added to the server object (which may be zero). On failure, these calls return a negative errno-style + error code. + + + Errors + + Returned errors may indicate the following problems: + + + + -EINVAL + + An argument is invalid. This is also returned by + sd_varlink_server_listen_address() if the mode parameter + contains bits other than the permission bits and + SD_VARLINK_SERVER_MODE_MKDIR_0755. + + + + -EBADF + + The file descriptor passed to sd_varlink_server_listen_fd() is + negative. + + + + + + + + + History + sd_varlink_server_listen_address(), + sd_varlink_server_listen_fd(), and + sd_varlink_server_listen_auto() were added in version 257. + sd_varlink_server_listen_name() was added in version 258. + + + + See Also + + + systemd1 + sd-varlink3 + sd_varlink_server_new3 + sd_listen_fds3 + systemd.socket5 + varlinkctl1 + + + + From b0688a6f5e28fac8206829e665e9d1aaffc4da6e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 Jun 2026 11:12:22 +0200 Subject: [PATCH 12/12] update TODO --- TODO.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/TODO.md b/TODO.md index 4d6668dace..a302415b84 100644 --- a/TODO.md +++ b/TODO.md @@ -231,12 +231,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later sequences of this type, so that every step of the way we get the right behaviour. -- now that the kernel supports xattrs on sockets: mark varlink entrypoint - sockets, server side of varlink sockets, and client sides of valrink sockets - with distinct xattrs to make them recognizable (similar maybe for our other - protocols, such as syslog, journal native entry point). For entrypoints might - require new .socket unit setting. - - implement "varlinkctl trace" or so, that watches socket traffic on a group of processes (select by pid, select by cgroup, select by all machine), and shows traffic of all sockets marked via the new varlink socket xattrs. Use BPF for