socket: pass socket FDs to all ExecXYZ= commands but ExecStartPre=

Today listen file descriptors created by socket unit don't get passed to
commands in Exec{Start,Stop}{Pre,Post}= socket options.

This prevents ExecXYZ= commands from accessing the created socket FDs to do
any kind of system setup which involves the socket but is not covered by
existing socket unit options.

One concrete example is to insert a socket FD into a BPF map capable of
holding socket references, such as BPF sockmap/sockhash [1] or
reuseport_sockarray [2]. Or, similarly, send the file descriptor with
SCM_RIGHTS to another process, which has access to a BPF map for storing
sockets.

To unblock this use case, pass ListenXYZ= file descriptors to ExecXYZ=
commands as listen FDs [4]. As an exception, ExecStartPre= command does not
inherit any file descriptors because it gets invoked before the listen FDs
are created.

This new behavior can potentially break existing configurations. Commands
invoked from ExecXYZ= might not expect to inherit file descriptors through
sd_listen_fds protocol.

To prevent breakage, add a new socket unit parameter,
PassFileDescriptorsToExec=, to control whether ExecXYZ= programs inherit
listen FDs.

[1] https://docs.kernel.org/bpf/map_sockmap.html
[2] https://lore.kernel.org/r/20180808075917.3009181-1-kafai@fb.com
[3] https://man.archlinux.org/man/socket.7#SO_INCOMING_CPU
[4] https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html
This commit is contained in:
Jakub Sitnicki 2024-02-15 18:02:50 +01:00 committed by Mike Yuan
parent d30d0b04ae
commit 97df75d7bd
8 changed files with 53 additions and 2 deletions

View file

@ -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 {
<!--property PassCredentials is not documented!-->
<!--property PassFileDescriptorsToExec is not documented!-->
<!--property PassSecurity is not documented!-->
<!--property PassPacketInfo is not documented!-->
@ -6100,6 +6104,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="PassCredentials"/>
<variablelist class="dbus-property" generated="True" extra-ref="PassFileDescriptorsToExec"/>
<variablelist class="dbus-property" generated="True" extra-ref="PassSecurity"/>
<variablelist class="dbus-property" generated="True" extra-ref="PassPacketInfo"/>
@ -12061,8 +12067,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>MemoryZSwapCurrent</varname> were added in version 255.</para>
<para><varname>EffectiveMemoryHigh</varname>,
<varname>EffectiveMemoryMax</varname>,
<varname>EffectiveTasksMax</varname>, and
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
<varname>EffectiveTasksMax</varname>,
<varname>MemoryZSwapWriteback</varname>, and
<varname>PassFileDescriptorsToExec</varname> were added in version 256.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>

View file

@ -922,6 +922,20 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>PassFileDescriptorsToExec=</varname></term>
<listitem><para>Takes a boolean argument. Defaults to off. If enabled, file descriptors created by
the socket unit are passed to <varname>ExecStartPost=</varname>, <varname>ExecStopPre=</varname>, and
<varname>ExecStopPost=</varname> commands from the socket unit. The passed file descriptors can be
accessed with
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry> as
if the commands were invoked from the associated service units. Note that
<varname>ExecStartPre=</varname> command cannot access socket file descriptors.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
</variablelist>
<xi:include href="systemd.service.xml" xpointer="shared-unit-options" />

View file

@ -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);

View file

@ -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)

View file

@ -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,

View file

@ -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;

View file

@ -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",

View file

@ -185,6 +185,7 @@ PAMName=
PIDFile=
PartOf=
PassCredentials=
PassFileDescriptorsToExec=
PassSecurity=
PassPacketInfo=
PathChanged=