From 97df75d7bd13ae9843fb55dd7285bf113adc2bd2 Mon Sep 17 00:00:00 2001 From: Jakub Sitnicki Date: Thu, 15 Feb 2024 18:02:50 +0100 Subject: [PATCH] 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 --- man/org.freedesktop.systemd1.xml | 11 ++++++++-- man/systemd.socket.xml | 14 ++++++++++++ src/core/dbus-socket.c | 4 ++++ src/core/load-fragment-gperf.gperf.in | 1 + src/core/socket.c | 22 +++++++++++++++++++ src/core/socket.h | 1 + src/shared/bus-unit-util.c | 1 + .../fuzz-unit-file/directives-all.service | 1 + 8 files changed, 53 insertions(+), 2 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 3a37a636b3..4e43b4ba20 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 c7166e4f64..50871f7a74 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 e77e9e5ccd..03c5b4ad2a 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 c5ea99726a..27aa27b55a 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 45656cbda7..5dbbd5a1d2 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 973a697f86..5e3929c5fa 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 2fcfb1d3b9..19cebb0cfe 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 b05b0a4973..670e589bab 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=