core/service: support sd_notify() MAINPIDFD=1 and MAINPIDFDID=

These serve as race-free alternatives for MAINPID= notification.
This commit is contained in:
Mike Yuan
2024-10-26 00:49:18 +02:00
parent 68d9aa7ede
commit 695323d90a
3 changed files with 115 additions and 31 deletions

3
TODO
View File

@@ -131,8 +131,7 @@ Features:
* $LISTEN_PID, $MAINPID and $SYSTEMD_EXECPID env vars that the service manager
sets should be augmented with $LISTEN_PIDFDID, $MAINPIDFDID and
$SYSTEMD_EXECPIDFD (and similar for other env vars we might send). Also,
MAINPID= in sd_notify() should be augmented with MAINPIDFDID=, and so on.
$SYSTEMD_EXECPIDFD (and similar for other env vars we might send).
* port copy.c over to use LabelOps for all labelling.

View File

@@ -291,12 +291,35 @@
<varlistentry>
<term>MAINPID=…</term>
<listitem><para>The main process ID (PID) of the service, in case the service manager did not fork
off the process itself. Example: <literal>MAINPID=4711</literal>.</para>
<listitem><para>Change the main process ID (PID) of the service. This is especially useful in the case
where the real main process isn't directly forked off by the service manager.
Example: <literal>MAINPID=4711</literal>.</para>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
</varlistentry>
<varlistentry>
<term>MAINPIDFDID=…</term>
<listitem><para>The pidfd inode number of the new main process (specified through <varname>MAINPID=</varname>).
This information can be acquired through
<citerefentry project='man-pages'><refentrytitle>fstat</refentrytitle><manvolnum>2</manvolnum></citerefentry>
on the pidfd and is used to identify the process in a race-free fashion. Alternatively,
a pidfd can be sent directly to the service manager (see <varname>MAINPIDFD=1</varname> below).</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term>MAINPIDFD=1</term>
<listitem><para>Similar to <varname>MAINPID=</varname> with <varname>MAINPIDFDID=</varname>, but
the process is referenced directly by the pidfd passed to the service manager. This is useful
if pidfd id is not supported on the system. Exactly one fd is expected for this notification.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG=1</term>

View File

@@ -4540,6 +4540,72 @@ static bool service_notify_message_authorized(Service *s, PidRef *pid) {
}
}
static int service_notify_message_parse_new_pid(
Unit *u,
char * const *tags,
FDSet *fds,
PidRef *ret) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
const char *e;
int r;
assert(u);
assert(ret);
/* MAINPIDFD=1 always takes precedence */
if (strv_contains(tags, "MAINPIDFD=1")) {
unsigned n_fds = fdset_size(fds);
if (n_fds != 1)
return log_unit_warning_errno(u, SYNTHETIC_ERRNO(EINVAL),
"Got MAINPIDFD=1 with %s fd, ignoring.", n_fds == 0 ? "no" : "more than one");
r = pidref_set_pidfd_consume(&pidref, ASSERT_FD(fdset_steal_first(fds)));
if (r < 0)
return log_unit_warning_errno(u, r, "Failed to create reference to received new main pidfd: %m");
goto finish;
}
e = strv_find_startswith(tags, "MAINPID=");
if (!e) {
*ret = PIDREF_NULL;
return 0;
}
r = pidref_set_pidstr(&pidref, e);
if (r < 0)
return log_unit_warning_errno(u, r, "Failed to parse MAINPID=%s field in notification message, ignoring: %m", e);
e = strv_find_startswith(tags, "MAINPIDFDID=");
if (!e)
goto finish;
uint64_t pidfd_id;
r = safe_atou64(e, &pidfd_id);
if (r < 0)
return log_unit_warning_errno(u, r, "Failed to parse MAINPIDFDID= in notification message, refusing: %s", e);
r = pidref_acquire_pidfd_id(&pidref);
if (r < 0) {
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_unit_warning_errno(u, r,
"Failed to acquire pidfd id of process " PID_FMT ", not validating MAINPIDFDID=%" PRIu64 ": %m",
pidref.pid, pidfd_id);
goto finish;
}
if (pidref.fd_id != pidfd_id)
return log_unit_warning_errno(u, SYNTHETIC_ERRNO(ESRCH),
"PIDFD ID of process " PID_FMT " (%" PRIu64 ") mismatches with received MAINPIDFDID=%" PRIu64 ", not changing main PID.",
pidref.pid, pidref.fd_id, pidfd_id);
finish:
*ret = TAKE_PIDREF(pidref);
return 1;
}
static void service_notify_message(
Unit *u,
PidRef *pidref,
@@ -4565,38 +4631,34 @@ static void service_notify_message(
bool notify_dbus = false;
const char *e;
/* Interpret MAINPID= */
e = strv_find_startswith(tags, "MAINPID=");
if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
SERVICE_STOP, SERVICE_STOP_SIGTERM)) {
/* Interpret MAINPID= (+ MAINPIDFDID=) / MAINPIDFD=1 */
_cleanup_(pidref_done) PidRef new_main_pid = PIDREF_NULL;
_cleanup_(pidref_done) PidRef new_main_pid = PIDREF_NULL;
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
(!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) {
r = pidref_set_pidstr(&new_main_pid, e);
if (r < 0)
log_unit_warning_errno(u, r, "Failed to parse MAINPID=%s field in notification message, ignoring: %m", e);
else if (!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid)) {
r = service_is_suitable_main_pid(s, &new_main_pid, LOG_WARNING);
if (r == 0) {
/* The new main PID is a bit suspicious, which is OK if the sender is privileged. */
r = service_is_suitable_main_pid(s, &new_main_pid, LOG_WARNING);
if (r == 0) {
/* The new main PID is a bit suspicious, which is OK if the sender is privileged. */
if (ucred->uid == 0) {
log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, but we'll accept it as the request to change it came from a privileged process.", new_main_pid.pid);
r = 1;
} else
log_unit_warning(u, "New main PID "PID_FMT" does not belong to service, refusing.", new_main_pid.pid);
}
if (r > 0) {
(void) service_set_main_pidref(s, TAKE_PIDREF(new_main_pid), /* start_timestamp = */ NULL);
if (ucred->uid == 0) {
log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, but we'll accept it as the request to change it came from a privileged process.", new_main_pid.pid);
r = 1;
} else
log_unit_warning(u, "New main PID "PID_FMT" does not belong to service, refusing.", new_main_pid.pid);
}
if (r > 0) {
(void) service_set_main_pidref(s, TAKE_PIDREF(new_main_pid), /* start_timestamp = */ NULL);
r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false);
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to watch new main PID "PID_FMT" for service: %m", s->main_pid.pid);
r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false);
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to watch new main PID "PID_FMT" for service: %m", s->main_pid.pid);
notify_dbus = true;
}
notify_dbus = true;
}
}