diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 3a37a636b3c..4e43b4ba20d 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -4821,6 +4821,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PassCredentials = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly b PassFileDescriptorsToExec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PassSecurity = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PassPacketInfo = ...; @@ -5488,6 +5490,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6100,6 +6104,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -12061,8 +12067,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ MemoryZSwapCurrent were added in version 255. EffectiveMemoryHigh, EffectiveMemoryMax, - EffectiveTasksMax, and - MemoryZSwapWriteback were added in version 256. + EffectiveTasksMax, + MemoryZSwapWriteback, and + PassFileDescriptorsToExec were added in version 256. Mount Unit Objects diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index c7166e4f643..50871f7a749 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -922,6 +922,20 @@ + + PassFileDescriptorsToExec= + + Takes a boolean argument. Defaults to off. If enabled, file descriptors created by + the socket unit are passed to ExecStartPost=, ExecStopPre=, and + ExecStopPost= commands from the socket unit. The passed file descriptors can be + accessed with + sd_listen_fds3 as + if the commands were invoked from the associated service units. Note that + ExecStartPre= command cannot access socket file descriptors. + + + + diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index e77e9e5ccd2..03c5b4ad2ae 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -86,6 +86,7 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("Transparent", "b", bus_property_get_bool, offsetof(Socket, transparent), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassFileDescriptorsToExec", "b", bus_property_get_bool, offsetof(Socket, pass_fds_to_exec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PassPacketInfo", "b", bus_property_get_bool, offsetof(Socket, pass_pktinfo), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Timestamping", "s", property_get_timestamping, offsetof(Socket, timestamping), SD_BUS_VTABLE_PROPERTY_CONST), @@ -190,6 +191,9 @@ static int bus_socket_set_transient_property( if (streq(name, "PassCredentials")) return bus_set_transient_bool(u, name, &s->pass_cred, message, flags, error); + if (streq(name, "PassFileDescriptorsToExec")) + return bus_set_transient_bool(u, name, &s->pass_fds_to_exec, message, flags, error); + if (streq(name, "PassSecurity")) return bus_set_transient_bool(u, name, &s->pass_sec, message, flags, error); diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index c5ea99726a4..27aa27b55a9 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -500,6 +500,7 @@ Socket.FreeBind, config_parse_bool, Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) +Socket.PassFileDescriptorsToExec, config_parse_bool, 0, offsetof(Socket, pass_fds_to_exec) Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) Socket.PassPacketInfo, config_parse_bool, 0, offsetof(Socket, pass_pktinfo) Socket.Timestamping, config_parse_socket_timestamping, 0, offsetof(Socket, timestamping) diff --git a/src/core/socket.c b/src/core/socket.c index 45656cbda77..5dbbd5a1d25 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -590,6 +590,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { "%sTransparent: %s\n" "%sBroadcast: %s\n" "%sPassCredentials: %s\n" + "%sPassFileDescriptorsToExec: %s\n" "%sPassSecurity: %s\n" "%sPassPacketInfo: %s\n" "%sTCPCongestion: %s\n" @@ -610,6 +611,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(s->transparent), prefix, yes_no(s->broadcast), prefix, yes_no(s->pass_cred), + prefix, yes_no(s->pass_fds_to_exec), prefix, yes_no(s->pass_sec), prefix, yes_no(s->pass_pktinfo), prefix, strna(s->tcp_congestion), @@ -1921,6 +1923,26 @@ static int socket_spawn(Socket *s, ExecCommand *c, PidRef *ret_pid) { if (r < 0) return r; + /* Note that ExecStartPre= command doesn't inherit any FDs. It runs before we open listen FDs. */ + if (s->pass_fds_to_exec) { + _cleanup_strv_free_ char **fd_names = NULL; + _cleanup_free_ int *fds = NULL; + int n_fds; + + n_fds = socket_collect_fds(s, &fds); + if (n_fds < 0) + return n_fds; + + r = strv_extend_n(&fd_names, socket_fdname(s), n_fds); + if (r < 0) + return r; + + exec_params.flags |= EXEC_PASS_FDS; + exec_params.fds = TAKE_PTR(fds); + exec_params.fd_names = TAKE_PTR(fd_names); + exec_params.n_socket_fds = n_fds; + } + r = exec_spawn(UNIT(s), c, &s->exec_context, diff --git a/src/core/socket.h b/src/core/socket.h index 973a697f861..5e3929c5fa7 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -129,6 +129,7 @@ struct Socket { bool transparent; bool broadcast; bool pass_cred; + bool pass_fds_to_exec; bool pass_sec; bool pass_pktinfo; SocketTimestamping timestamping; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 2fcfb1d3b96..19cebb0cfe1 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2450,6 +2450,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons "Transparent", "Broadcast", "PassCredentials", + "PassFileDescriptorsToExec", "PassSecurity", "PassPacketInfo", "ReusePort", diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index b05b0a49731..670e589babe 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -185,6 +185,7 @@ PAMName= PIDFile= PartOf= PassCredentials= +PassFileDescriptorsToExec= PassSecurity= PassPacketInfo= PathChanged= diff --git a/test/testsuite-07.units/pass-fds-to-exec-no.socket b/test/testsuite-07.units/pass-fds-to-exec-no.socket new file mode 100644 index 00000000000..8b7964b6648 --- /dev/null +++ b/test/testsuite-07.units/pass-fds-to-exec-no.socket @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test if ExecXYZ= commands don't inherit listen FDs when PassFileDescriptorsToExec= is unset + +[Socket] +# With Accept= set we don't need a corresponding service unit +Accept=yes +FileDescriptorName=foo +ListenStream=127.0.0.1:1234 +ListenStream=[::1]:1234 +PassFileDescriptorsToExec=no +ExecStartPre=\ + test ExecStartPre -a \ + -z ${LISTEN_FDS} -a \ + -z ${LISTEN_FDNAMES} -a \ + ! -e /dev/fd/3 -a \ + ! -e /dev/fd/4 +ExecStartPost=\ + test ExecStartPost -a \ + -z ${LISTEN_FDS} -a \ + -z ${LISTEN_FDNAMES} -a \ + ! -e /dev/fd/3 -a \ + ! -e /dev/fd/4 +ExecStopPre=\ + test ExecStopPre -a \ + -z ${LISTEN_FDS} -a \ + -z ${LISTEN_FDNAMES} -a \ + ! -e /dev/fd/3 -a \ + ! -e /dev/fd/4 +ExecStopPost=\ + test ExecStopPost -a \ + -z ${LISTEN_FDS} -a \ + -z ${LISTEN_FDNAMES} -a \ + ! -e /dev/fd/3 -a \ + ! -e /dev/fd/4 diff --git a/test/testsuite-07.units/pass-fds-to-exec-yes.socket b/test/testsuite-07.units/pass-fds-to-exec-yes.socket new file mode 100644 index 00000000000..bff192d559d --- /dev/null +++ b/test/testsuite-07.units/pass-fds-to-exec-yes.socket @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test if ExecXYZ= commands inherit listen FDs when PassFileDescriptorsToExec= is set + +[Socket] +# With Accept= set we don't need a corresponding service unit +Accept=yes +FileDescriptorName=foo +ListenStream=127.0.0.1:1234 +ListenStream=[::1]:1234 +PassFileDescriptorsToExec=yes +# ExecStartPre runs before we create sockets. Nothing to pass. +ExecStartPre=\ + test ExecStartPre -a \ + -z ${LISTEN_FDS} -a \ + -z ${LISTEN_FDNAMES} -a \ + ! -e /dev/fd/3 -a \ + ! -e /dev/fd/4 +ExecStartPost=\ + test ExecStartPost -a \ + ${LISTEN_FDS} = 2 -a \ + ${LISTEN_FDNAMES} = foo:foo -a \ + -S /dev/fd/3 -a \ + -S /dev/fd/4 +ExecStopPre=\ + test "ExecStopPre" -a \ + ${LISTEN_FDS} = 2 -a \ + ${LISTEN_FDNAMES} = foo:foo -a \ + -S /dev/fd/3 -a \ + -S /dev/fd/4 +ExecStopPost=\ + test "ExecStopPost" -a \ + ${LISTEN_FDS} = 2 -a \ + ${LISTEN_FDNAMES} = foo:foo -a \ + -S /dev/fd/3 -a \ + -S /dev/fd/4 diff --git a/test/units/testsuite-07.socket-pass-fds.sh b/test/units/testsuite-07.socket-pass-fds.sh new file mode 100755 index 00000000000..a61b1c01f14 --- /dev/null +++ b/test/units/testsuite-07.socket-pass-fds.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Test PassFileDescriptorsToExec= option in socket units + +for u in pass-fds-to-exec-{no,yes}.socket; do + systemctl start "$u" + systemctl stop "$u" +done