sd-varlink: mark varlink sockets via xattrs (#42454)

Linux 7.0 added the ability to mark socket inodes with xattrs. Let's use
that to clearly mark all our Varlink sockets as being varlink related.
This is then used to implement a very useful new command "varlinkctl
list-sockets" which lists all varlink entrypoint sockets marked this
way.

By marking not just the entrypoint inodes but also the connection
sockets properly, we can one day add an ebpf based "varlinkctl trace"
command that watches varlink sockets for traffic. but that's material
for a later PR.
This commit is contained in:
Lennart Poettering
2026-06-24 06:16:39 +02:00
committed by GitHub
78 changed files with 1358 additions and 57 deletions
+2 -6
View File
@@ -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:**
@@ -229,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
+26
View File
@@ -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 {
<variablelist class="dbus-property" generated="True" extra-ref="Symlinks"/>
<variablelist class="dbus-property" generated="True" extra-ref="XAttrEntryPoint"/>
<variablelist class="dbus-property" generated="True" extra-ref="XAttrListen"/>
<variablelist class="dbus-property" generated="True" extra-ref="XAttrAccept"/>
<variablelist class="dbus-property" generated="True" extra-ref="Mark"/>
<variablelist class="dbus-property" generated="True" extra-ref="MaxConnections"/>
@@ -7299,6 +7311,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<para><varname>FlushPending</varname> specifies whether to flush the socket
just before entering the listening state. This setting only applies to sockets with
<varname>Accept=</varname> set to <literal>no</literal>.</para>
<para><varname>XAttrEntryPoint</varname>, <varname>XAttrListen</varname> and
<varname>XAttrAccept</varname> each contain a list of extended attributes (as
<literal><replaceable>NAME</replaceable></literal>/<literal><replaceable>VALUE</replaceable></literal>
pairs) to set on socket inodes. <varname>XAttrEntryPoint</varname> applies to the file-system inode an
<constant>AF_UNIX</constant> socket is bound to, <varname>XAttrListen</varname> applies to the listening
socket, and <varname>XAttrAccept</varname> applies to connection sockets accepted off the listening
socket. See the <varname>XAttrEntryPoint=</varname>, <varname>XAttrListen=</varname> and
<varname>XAttrAccept=</varname> settings in
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details.</para>
</refsect2>
</refsect1>
@@ -12977,6 +13000,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>IOPressureWatch</varname>,
<varname>CPUSetPartition</varname>, and
<varname>OOMRules</varname> were added in version 261.</para>
<para><varname>XAttrEntryPoint</varname>,
<varname>XAttrListen</varname>, and
<varname>XAttrAccept</varname> were added in version 262.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>
+6
View File
@@ -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', [], ''],
+79
View File
@@ -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>
+181
View File
@@ -0,0 +1,181 @@
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="sd_varlink_server_listen_address" xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>sd_varlink_server_listen_address</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>sd_varlink_server_listen_address</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>sd_varlink_server_listen_address</refname>
<refname>sd_varlink_server_listen_fd</refname>
<refname>sd_varlink_server_listen_name</refname>
<refname>sd_varlink_server_listen_auto</refname>
<refpurpose>Bind a Varlink server object to listening sockets</refpurpose>
</refnamediv>
<refsynopsisdiv>
<funcsynopsis>
<funcsynopsisinfo>#include &lt;systemd/sd-varlink.h&gt;</funcsynopsisinfo>
<funcsynopsisinfo>#define SD_VARLINK_SERVER_MODE_MKDIR_0755 …</funcsynopsisinfo>
<funcprototype>
<funcdef>int <function>sd_varlink_server_listen_address</function></funcdef>
<paramdef>sd_varlink_server* <parameter>server</parameter></paramdef>
<paramdef>const char* <parameter>address</parameter></paramdef>
<paramdef>mode_t <parameter>mode</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_varlink_server_listen_fd</function></funcdef>
<paramdef>sd_varlink_server* <parameter>server</parameter></paramdef>
<paramdef>int <parameter>fd</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_varlink_server_listen_name</function></funcdef>
<paramdef>sd_varlink_server* <parameter>server</parameter></paramdef>
<paramdef>const char* <parameter>name</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_varlink_server_listen_auto</function></funcdef>
<paramdef>sd_varlink_server* <parameter>server</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>These functions add listening sockets to a Varlink server object, as previously allocated with
<citerefentry><refentrytitle>sd_varlink_server_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
Once the server object is attached to an event loop (see
<function>sd_varlink_server_attach_event()</function>) it will accept incoming connections on the
configured sockets and dispatch the registered method calls.</para>
<para><function>sd_varlink_server_listen_address()</function> allocates a new
<constant>AF_UNIX</constant> socket, binds it to the specified <parameter>address</parameter>, and starts
listening on it. The <parameter>address</parameter> is a file system path of the socket to bind to. (Use
<function>sd_varlink_server_listen_fd()</function> below for sockets in the abstract namespace, in foreign
address families, or that are otherwise already allocated.)</para>
<para>The <parameter>mode</parameter> parameter specifies the file system access mode to apply to the
socket inode, i.e. a bit mask of permission bits as in
<citerefentry project='man-pages'><refentrytitle>chmod</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
Only the lower nine bits (<constant>0777</constant>) are permitted. The special value
<constant>(mode_t) -1</constant> may be specified to pick a suitable default: this is
<constant>0666</constant> in the general case, but <constant>0644</constant> if the server was allocated
with the <constant>SD_VARLINK_SERVER_ROOT_ONLY</constant> or
<constant>SD_VARLINK_SERVER_MYSELF_ONLY</constant> flags (the reason being that it is the
<literal>w</literal> bit that controls
<citerefentry project='man-pages'><refentrytitle>connect</refentrytitle><manvolnum>2</manvolnum></citerefentry>
access, while leaving the <literal>r</literal> bit on permits other users to read the socket's extended
attributes, which makes the socket recognizable as a Varlink entrypoint, see
<citerefentry><refentrytitle>sd-varlink</refentrytitle><manvolnum>3</manvolnum></citerefentry>).</para>
<para>The constant <constant>SD_VARLINK_SERVER_MODE_MKDIR_0755</constant> may be OR-ed into the
<parameter>mode</parameter> parameter (this is not supported if the mode is specified as
<constant>(mode_t) -1</constant>). If specified, and <parameter>address</parameter> is an absolute path,
the leading directories of the socket path are automatically created (with access mode
<constant>0755</constant>) if they are missing.</para>
<para><function>sd_varlink_server_listen_fd()</function> adds an already allocated and listening socket to
the server object, referenced by the file descriptor <parameter>fd</parameter>. This is typically used in
conjunction with socket activation, to make use of a listening socket passed in from the service manager,
see
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>. The
file descriptor is automatically switched to non-blocking mode and the close-on-exec flag is set on
it.</para>
<para><function>sd_varlink_server_listen_name()</function> picks up sockets passed in via the socket
activation protocol whose name (as set via <varname>$LISTEN_FDNAMES</varname>, i.e. via the
<varname>FileDescriptorName=</varname> setting of the
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> unit)
matches the specified <parameter>name</parameter>. For each matching file descriptor that refers to a
listening socket <function>sd_varlink_server_listen_fd()</function> is invoked; file descriptors that
refer to an already accepted connection are added as connections via
<function>sd_varlink_server_add_connection()</function> instead.</para>
<para><function>sd_varlink_server_listen_auto()</function> is a convenience wrapper that automatically
determines the sockets to listen on. It first picks up all socket activation file descriptors named
<literal>varlink</literal> (by calling <function>sd_varlink_server_listen_name()</function> with that
name). In addition, if the <varname>$SYSTEMD_VARLINK_LISTEN</varname> environment variable is set, it is
used as an additional address to listen on: if set to <literal>-</literal> the Varlink protocol is spoken
on standard input and output (see <function>sd_varlink_server_add_connection_stdio()</function>),
otherwise the value is passed to <function>sd_varlink_server_listen_address()</function>. 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).</para>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>On success, <function>sd_varlink_server_listen_address()</function> and
<function>sd_varlink_server_listen_fd()</function> return a non-negative integer.
<function>sd_varlink_server_listen_name()</function> and
<function>sd_varlink_server_listen_auto()</function> 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.</para>
<refsect2>
<title>Errors</title>
<para>Returned errors may indicate the following problems:</para>
<variablelist>
<varlistentry>
<term><constant>-EINVAL</constant></term>
<listitem><para>An argument is invalid. This is also returned by
<function>sd_varlink_server_listen_address()</function> if the <parameter>mode</parameter> parameter
contains bits other than the permission bits and
<constant>SD_VARLINK_SERVER_MODE_MKDIR_0755</constant>.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-EBADF</constant></term>
<listitem><para>The file descriptor passed to <function>sd_varlink_server_listen_fd()</function> is
negative.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
<xi:include href="libsystemd-pkgconfig.xml" />
<refsect1>
<title>History</title>
<para><function>sd_varlink_server_listen_address()</function>,
<function>sd_varlink_server_listen_fd()</function>, and
<function>sd_varlink_server_listen_auto()</function> were added in version 257.</para>
<para><function>sd_varlink_server_listen_name()</function> was added in version 258.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd-varlink</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd_varlink_server_new</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+34
View File
@@ -675,6 +675,40 @@
<xi:include href="version-info.xml" xpointer="v196"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>XAttrEntryPoint=</varname></term>
<term><varname>XAttrListen=</varname></term>
<term><varname>XAttrAccept=</varname></term>
<listitem><para>Set an extended attribute on the socket, in the form
<literal><replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></literal>. The extended
attribute's name must be in the <literal>user.</literal> namespace, i.e. begin with the four
characters <literal>user.</literal>. Specifiers are expanded in both the name and the value, see
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> 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.</para>
<para>The three variables control where the extended attributes are applied:
<varname>XAttrEntryPoint=</varname> sets them on the file-system inode an
<constant>AF_UNIX</constant> socket is bound to (i.e. the socket node visible in the file system at
the specified listening path; this only applies to <constant>AF_UNIX</constant> sockets),
<varname>XAttrListen=</varname> sets them on the listening socket, and
<varname>XAttrAccept=</varname> sets them on the connection sockets accepted off the listening socket
(and is thus only relevant in combination with
<varname>Accept=</varname><literal>yes</literal>).</para>
<para>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
<literal>user.varlink=entrypoint</literal> via <varname>XAttrEntryPoint=</varname>, which makes them
discoverable via <command>varlinkctl list-sockets</command>, see
<citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
<para>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.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>SELinuxContextFromNet=</varname></term>
<listitem><para>Takes a boolean argument. When true, systemd
+29
View File
@@ -81,6 +81,18 @@
<arg choice="req" rep="repeat"><replaceable>CMDLINE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">list-registry</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">list-sockets</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
@@ -221,6 +233,23 @@
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><command>list-sockets</command></term>
<listitem><para>Shows a list of listening <constant>AF_UNIX</constant> 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
<varname>user.varlink</varname> extended attribute set to <literal>entrypoint</literal>.</para>
<para>Sockets are enumerated via the kernel's <constant>sock_diag</constant> netlink interface, and
hence this is not restricted to sockets in <filename>/run/varlink/registry/</filename> (as
<command>list-registry</command> 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.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><command>validate-idl</command> [<replaceable>FILE</replaceable>]</term>
+1 -1
View File
@@ -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'
+1
View File
@@ -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[@]" )
+1
View File
@@ -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. */
+6
View File
@@ -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))
+42
View File
@@ -13,6 +13,7 @@
#include <poll.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#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);
}
+2
View File
@@ -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);
+1 -2
View File
@@ -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 */
+1
View File
@@ -5,6 +5,7 @@
#include <sys/stat.h>
#include "bpf-restrict-fsaccess.h"
#include "devnum-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "initrd-util.h"
-8
View File
@@ -1,8 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/sysmacros.h>
#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 {
+96
View File
@@ -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;
+3
View File
@@ -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)
+60
View File
@@ -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,
+1
View File
@@ -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);
+8 -1
View File
@@ -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);
}
+4
View File
@@ -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;
@@ -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;
}
@@ -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);
@@ -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);
+86 -14
View File
@@ -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,10 +3446,19 @@ _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;
}
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;
@@ -3407,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
@@ -3431,6 +3498,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 +3511,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;
@@ -3577,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;
+55
View File
@@ -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 },
+36 -1
View File
@@ -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;
+5 -1
View File
@@ -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);
+3 -1
View File
@@ -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;
+19
View File
@@ -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;
+1
View File
@@ -25,6 +25,7 @@
#include <unistd.h>
#include "bpf-restrict-fsaccess.h"
#include "devnum-util.h"
#include "fd-util.h"
#include "log.h"
#include "string-util.h"
+13
View File
@@ -4,6 +4,7 @@
#include <grp.h>
#include <linux/pkt_sched.h>
#include <netinet/ip.h>
#include <sys/stat.h>
#include <unistd.h>
#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);
+1 -1
View File
@@ -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)
+193 -13
View File
@@ -1,24 +1,33 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <linux/unix_diag.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#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"
@@ -32,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"
@@ -40,6 +50,7 @@
#include "varlink-util.h"
#include "verbs.h"
#include "version.h"
#include "xattr-util.h"
typedef struct PushFds {
int *fds;
@@ -71,14 +82,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 +97,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 +116,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;
}
@@ -1159,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). */
+1 -1
View File
@@ -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);
+108
View File
@@ -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 <<EOF
[Service]
ExecStart=/bin/true
EOF
cat >/run/systemd/system/test-socket-xattr.socket <<EOF
[Unit]
Description=Socket xattr test (on-disk unit)
[Socket]
ListenStream=$UNIT_SOCKET_PATH
SocketMode=0666
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
RemoveOnStop=true
EOF
systemctl daemon-reload
systemctl start test-socket-xattr.socket
# The socket node bound into the file system must carry the entrypoint xattr.
test -S "$UNIT_SOCKET_PATH"
[[ "$(read_role "$UNIT_SOCKET_PATH")" == "entrypoint" ]]
# The configured settings must be exposed again via the manager's D-Bus properties.
systemctl show -P XAttrEntryPoint test-socket-xattr.socket | grep "user.varlink=entrypoint" >/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
+76
View File
@@ -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"
+18
View File
@@ -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:?}"
+3
View File
@@ -20,3 +20,6 @@ FileDescriptorName=varlink
SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
+4 -1
View File
@@ -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
+3
View File
@@ -20,3 +20,6 @@ FileDescriptorName=varlink
SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
+3
View File
@@ -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
+2
View File
@@ -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
+3
View File
@@ -20,3 +20,6 @@ SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
RemoveOnStop=yes
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
+3
View File
@@ -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.
+2
View File
@@ -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
+4 -1
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+4 -1
View File
@@ -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
@@ -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
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+4 -1
View File
@@ -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
+4 -1
View File
@@ -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
+4 -1
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3 -1
View File
@@ -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
+2
View File
@@ -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
+3
View File
@@ -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
+2
View File
@@ -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
+3
View File
@@ -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
+2
View File
@@ -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
+3
View File
@@ -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