sd-varlink: mark varlink sockets and entrypoint inodes as varlink via xattrs
This commit is contained in:
@@ -55,6 +55,84 @@
|
||||
makes the functionality implemented by sd-varlink available from the command line.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Recognizing Varlink Socket Inodes</title>
|
||||
|
||||
<para>On kernels that support extended attributes on socket inodes (available since Linux 7.0),
|
||||
sd-varlink automatically tags the <constant>AF_UNIX</constant> socket inodes it creates and manages with
|
||||
the <varname>user.varlink</varname> 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 <command>varlinkctl list-sockets</command> (see
|
||||
<citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para>
|
||||
|
||||
<para>Four distinct roles are defined:</para>
|
||||
|
||||
<table>
|
||||
<title><varname>user.varlink</varname> extended attribute roles</title>
|
||||
|
||||
<tgroup cols='2'>
|
||||
<colspec colname='value' />
|
||||
<colspec colname='meaning' />
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Attribute value</entry>
|
||||
<entry>Meaning</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>client</literal></entry>
|
||||
<entry>Set on the connecting end of a connection, i.e. a socket created via <function>socket()</function> + <function>connect()</function>. Only for inodes on the anonymous socket file system (<literal>sockfs</literal>).</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>server</literal></entry>
|
||||
<entry>Set on the serving end of a connection, i.e. a socket obtained via <function>accept()</function> on a listening socket. Only for inodes on the anonymous socket file system (<literal>sockfs</literal>).</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>listen</literal></entry>
|
||||
<entry>Set on a listening socket, i.e. a socket created via <function>socket()</function> + <function>listen()</function>. Only for inodes on the anonymous socket file system (<literal>sockfs</literal>).</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>entrypoint</literal></entry>
|
||||
<entry>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 <literal>sockfs</literal>) and is hence visible to any process that can resolve the socket path.</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>Note that for socket activated Varlink services it is recommended to set these extended attributes
|
||||
via <varname>XAttrEntryPoint=</varname>, <varname>XAttrListen=</varname> and
|
||||
<varname>XAttrAccept=</varname> settings on the socket units, see
|
||||
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details. Specifically, for socket units for which <varname>Accept=no</varname> is set:</para>
|
||||
|
||||
<programlisting>…
|
||||
[Socket]
|
||||
SocketMode=0644
|
||||
XAttrEntryPoint=user.varlink=entrypoint
|
||||
XAttrListen=user.varlink=listen
|
||||
…</programlisting>
|
||||
|
||||
<para>For socket units which have <varname>Accept=yes</varname>:</para>
|
||||
|
||||
<programlisting>…
|
||||
[Socket]
|
||||
Accept=yes
|
||||
SocketMode=0644
|
||||
XAttrEntryPoint=user.varlink=entrypoint
|
||||
XAttrListen=user.varlink=listen
|
||||
XAttrAccept=user.varlink=server
|
||||
…</programlisting>
|
||||
|
||||
<para>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 <function>connect()</function> 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 <literal>SocketMode=0644</literal> for services that shall not be
|
||||
accessible to arbitrary users, rather than <literal>SocketMode=0600</literal>.</para>
|
||||
</refsect1>
|
||||
|
||||
<xi:include href="libsystemd-pkgconfig.xml" />
|
||||
|
||||
<refsect1>
|
||||
@@ -87,6 +165,7 @@
|
||||
<member><citerefentry><refentrytitle>sd-event</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd-json</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd-bus</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project='die-net'><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user