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:
+
+
+ user.varlink 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;