From: Lennart Poettering Date: Tue, 2 Jun 2026 15:36:52 +0000 (+0200) Subject: sd-varlink: mark varlink sockets and entrypoint inodes as varlink via xattrs X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96a37e3493bd5679539c99cfd6151c2a38ed8d71;p=thirdparty%2Fsystemd.git sd-varlink: mark varlink sockets and entrypoint inodes as varlink via xattrs --- diff --git a/man/sd-varlink.xml b/man/sd-varlink.xml index 576bdf1b7f8..bd2b3556523 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 f484c9da449..9f06f692655 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;