]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add socket xattr settings for socket unit
authorLennart Poettering <lennart@amutable.com>
Tue, 2 Jun 2026 16:22:50 +0000 (18:22 +0200)
committerLennart Poettering <lennart@amutable.com>
Tue, 23 Jun 2026 20:55:23 +0000 (22:55 +0200)
15 files changed:
man/org.freedesktop.systemd1.xml
man/systemd.socket.xml
src/core/dbus-socket.c
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/socket.c
src/core/socket.h
src/shared/bus-unit-util.c
src/shared/socket-label.c
src/shared/socket-label.h
src/shared/socket-netlink.c
src/systemctl/systemctl-show.c
test/units/TEST-74-AUX-UTILS.socket-xattr.sh [new file with mode: 0755]
test/units/util.sh

index 83748c6dd8196c386cf5ab2820c9ca26ba724137..e7e56466d2a51a4ad622c10e7bf634419d6bc8d8 100644 (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>
index 0de281f02eca78276929abdd250475c793675845..6816b073b36524a8f16b7782a9b36f2868c59237 100644 (file)
         <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
index ecb4df4dfda84dcf84ae5f3978710f36f8835523..26612015f05380db165a4318c1ea259f69343297 100644 (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;
 
index 2b8d2296f0922b18dd7a75b967edff08200c8324..aa95bd49920011c7e79f77321e47d0bf94394b5f 100644 (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)
index ff8bded569e274b27bf9419062493f6bd1448bdf..3fa92aff56bf20041917f091075de373090cb433 100644 (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,
index 48129f960299ad1dff4e9dcb4d4f93c4bc6dc635..15dd411cc5a93268e05ae7a7db8954839a8d63df 100644 (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);
index a33a8d6359c98cee35949023a6144d7636b31d4e..a7aa34caeb43f02563a8de5db27309c01d59d4d9 100644 (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);
         }
 
index b9010bac49f52d23da59bdd10091a79c70b1f87e..d374bec9b70e57e5bf69319bed760970d5b6c2b2 100644 (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;
index 8b618bf5001d444163ed4a2057a0b4548fd11008..48a48f71b21707e0e197f96223fd24d7ae49166e 100644 (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                             },
index ea1116983efff7867eca2419ff790d611e8c5fa8..b06b00e08577bd319332a071201c251999f76129 100644 (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;
index 48a500c081830ebe550c516e60c2c7266ed4bd83..15dc9d86fe7c2f0cb13bd172b1d9005878bede9c 100644 (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);
index 1112a090840ecfc99a9001c36b2074cf1dcbc7ee..a9b31744180f291a3cd18fef31940c66c6367d14 100644 (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;
 
index edef1d25759c62d1d5493323d72aa1ab4dd1f5fd..82b85688133653c10f4c5b45a1bdff2dfc2c7308 100644 (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;
diff --git a/test/units/TEST-74-AUX-UTILS.socket-xattr.sh b/test/units/TEST-74-AUX-UTILS.socket-xattr.sh
new file mode 100755 (executable)
index 0000000..3832fb3
--- /dev/null
@@ -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
index 248e676eae062f06fe3d58582e44bd877624be89..14bc29af1193e94a53b04a4e4bfdb274b274cd1a 100755 (executable)
@@ -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:?}"