From: Lennart Poettering Date: Tue, 2 Jun 2026 16:22:50 +0000 (+0200) Subject: core: add socket xattr settings for socket unit X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cc174775f582ed08f6fed37a55f9786ed32b156c;p=thirdparty%2Fsystemd.git core: add socket xattr settings for socket unit --- diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 83748c6dd81..e7e56466d2a 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -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 { + + + + + + @@ -7299,6 +7311,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { FlushPending specifies whether to flush the socket just before entering the listening state. This setting only applies to sockets with Accept= set to no. + + XAttrEntryPoint, XAttrListen and + XAttrAccept each contain a list of extended attributes (as + NAME/VALUE + pairs) to set on socket inodes. XAttrEntryPoint applies to the file-system inode an + AF_UNIX socket is bound to, XAttrListen applies to the listening + socket, and XAttrAccept applies to connection sockets accepted off the listening + socket. See the XAttrEntryPoint=, XAttrListen= and + XAttrAccept= settings in + systemd.socket5 for + details. @@ -12977,6 +13000,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ IOPressureWatch, CPUSetPartition, and OOMRules were added in version 261. + XAttrEntryPoint, + XAttrListen, and + XAttrAccept were added in version 262. Mount Unit Objects diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index 0de281f02ec..6816b073b36 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -675,6 +675,40 @@ + + XAttrEntryPoint= + XAttrListen= + XAttrAccept= + Set an extended attribute on the socket, in the form + NAME=VALUE. The extended + attribute's name must be in the user. namespace, i.e. begin with the four + characters user.. Specifiers are expanded in both the name and the value, see + systemd.unit5 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. + + The three variables control where the extended attributes are applied: + XAttrEntryPoint= sets them on the file-system inode an + AF_UNIX socket is bound to (i.e. the socket node visible in the file system at + the specified listening path; this only applies to AF_UNIX sockets), + XAttrListen= sets them on the listening socket, and + XAttrAccept= sets them on the connection sockets accepted off the listening socket + (and is thus only relevant in combination with + Accept=yes). + + 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 + user.varlink=entrypoint via XAttrEntryPoint=, which makes them + discoverable via varlinkctl list-sockets, see + varlinkctl1. + + 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. + + + + SELinuxContextFromNet= Takes a boolean argument. When true, systemd diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index ecb4df4dfda..26612015f05 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -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; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 2b8d2296f09..aa95bd49920 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -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) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index ff8bded569e..3fa92aff56b 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -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, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 48129f96029..15dd411cc5a 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -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); diff --git a/src/core/socket.c b/src/core/socket.c index a33a8d6359c..a7aa34caeb4 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -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); } diff --git a/src/core/socket.h b/src/core/socket.h index b9010bac49f..d374bec9b70 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -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; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 8b618bf5001..48a48f71b21 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -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 }, diff --git a/src/shared/socket-label.c b/src/shared/socket-label.c index ea1116983ef..b06b00e0857 100644 --- a/src/shared/socket-label.c +++ b/src/shared/socket-label.c @@ -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; diff --git a/src/shared/socket-label.h b/src/shared/socket-label.h index 48a500c0818..15dc9d86fe7 100644 --- a/src/shared/socket-label.h +++ b/src/shared/socket-label.h @@ -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); diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 1112a090840..a9b31744180 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -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; diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index edef1d25759..82b85688133 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -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 index 00000000000..3832fb3f820 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.socket-xattr.sh @@ -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 </run/systemd/system/test-socket-xattr.socket </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 diff --git a/test/units/util.sh b/test/units/util.sh index 248e676eae0..14bc29af119 100755 --- a/test/units/util.sh +++ b/test/units/util.sh @@ -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:?}"