1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-26 17:27:41 +03:00

core: pin notify sender through pidfd (potentially SCM_PIDFD)

This commit is contained in:
Mike Yuan 2024-07-14 17:19:50 +02:00
parent 31d76a1702
commit 19eccb7667
No known key found for this signature in database
GPG Key ID: 417471C0A40F58B3
4 changed files with 53 additions and 21 deletions

1
TODO
View File

@ -579,7 +579,6 @@ Features:
- cg_pid_get_xyz() - cg_pid_get_xyz()
- pid_from_same_root_fs() - pid_from_same_root_fs()
- get_ctty_devnr() - get_ctty_devnr()
- pid1: sd_notify() receiver should use SCM_PIDFD to authenticate client
- actually wait for POLLIN on pidref's pidfd in service logic - actually wait for POLLIN on pidref's pidfd in service logic
- openpt_allocate_in_namespace() - openpt_allocate_in_namespace()
- unit_attach_pid_to_cgroup_via_bus() - unit_attach_pid_to_cgroup_via_bus()

View File

@ -1104,6 +1104,11 @@ static int manager_setup_notify(Manager *m) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to enable SO_PASSCRED for notify socket: %m"); return log_error_errno(r, "Failed to enable SO_PASSCRED for notify socket: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0 && r != -ENOPROTOOPT)
log_warning_errno(r, "Failed to enable SO_PASSPIDFD for notify socket, ignoring: %m");
// TODO: maybe enforce SO_PASSPIDFD?
m->notify_fd = TAKE_FD(fd); m->notify_fd = TAKE_FD(fd);
log_debug("Using notification socket %s", m->notify_socket); log_debug("Using notification socket %s", m->notify_socket);
@ -2638,13 +2643,16 @@ static bool manager_process_barrier_fd(char * const *tags, FDSet *fds) {
static void manager_invoke_notify_message( static void manager_invoke_notify_message(
Manager *m, Manager *m,
Unit *u, Unit *u,
PidRef *pidref,
const struct ucred *ucred, const struct ucred *ucred,
char * const *tags, char * const *tags,
FDSet *fds) { FDSet *fds) {
assert(m); assert(m);
assert(u); assert(u);
assert(pidref_is_set(pidref));
assert(ucred); assert(ucred);
assert(pidref->pid == ucred->pid);
assert(tags); assert(tags);
if (u->notifygen == m->notifygen) /* Already invoked on this same unit in this same iteration? */ if (u->notifygen == m->notifygen) /* Already invoked on this same unit in this same iteration? */
@ -2652,7 +2660,7 @@ static void manager_invoke_notify_message(
u->notifygen = m->notifygen; u->notifygen = m->notifygen;
if (UNIT_VTABLE(u)->notify_message) if (UNIT_VTABLE(u)->notify_message)
UNIT_VTABLE(u)->notify_message(u, ucred, tags, fds); UNIT_VTABLE(u)->notify_message(u, pidref, ucred, tags, fds);
else if (DEBUG_LOGGING) { else if (DEBUG_LOGGING) {
_cleanup_free_ char *joined = strv_join(tags, ", "); _cleanup_free_ char *joined = strv_join(tags, ", ");
@ -2723,7 +2731,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
.iov_base = buf, .iov_base = buf,
.iov_len = sizeof(buf)-1, .iov_len = sizeof(buf)-1,
}; };
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int)) /* SCM_PIDFD */ +
CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)) control; CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)) control;
struct msghdr msghdr = { struct msghdr msghdr = {
.msg_iov = &iovec, .msg_iov = &iovec,
@ -2755,6 +2763,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
* socket. */ * socket. */
return log_error_errno(n, "Failed to receive notification message: %m"); return log_error_errno(n, "Failed to receive notification message: %m");
_cleanup_close_ int pidfd = -EBADF;
struct ucred *ucred = NULL; struct ucred *ucred = NULL;
int *fd_array = NULL; int *fd_array = NULL;
size_t n_fds = 0; size_t n_fds = 0;
@ -2773,6 +2782,9 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
assert(!ucred); assert(!ucred);
ucred = CMSG_TYPED_DATA(cmsg, struct ucred); ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
} else if (cmsg->cmsg_type == SCM_PIDFD) {
assert(pidfd < 0);
pidfd = *CMSG_TYPED_DATA(cmsg, int);
} }
} }
@ -2794,6 +2806,28 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
return 0; return 0;
} }
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
if (pidfd >= 0)
r = pidref_set_pidfd_consume(&pidref, TAKE_FD(pidfd));
else
r = pidref_set_pid(&pidref, ucred->pid);
if (r < 0) {
if (r == -ESRCH)
log_debug_errno(r, "Notify sender died before message is processed. Ignoring.");
else
log_warning_errno(r, "Failed to pin notify sender, ignoring message: %m");
return 0;
}
if (pidref.pid != ucred->pid) {
assert(pidref.fd >= 0);
log_warning("Got SCM_PIDFD for process " PID_FMT " but SCM_CREDENTIALS for process " PID_FMT ". Ignoring.",
pidref.pid, ucred->pid);
return 0;
}
if ((size_t) n >= sizeof(buf) || (msghdr.msg_flags & MSG_TRUNC)) { if ((size_t) n >= sizeof(buf) || (msghdr.msg_flags & MSG_TRUNC)) {
log_warning("Received notify message exceeded maximum size. Ignoring."); log_warning("Received notify message exceeded maximum size. Ignoring.");
return 0; return 0;
@ -2824,9 +2858,6 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
/* Increase the generation counter used for filtering out duplicate unit invocations. */ /* Increase the generation counter used for filtering out duplicate unit invocations. */
m->notifygen++; m->notifygen++;
/* Generate lookup key from the PID (we have no pidfd here, after all) */
PidRef pidref = PIDREF_MAKE_FROM_PID(ucred->pid);
/* Notify every unit that might be interested, which might be multiple. */ /* Notify every unit that might be interested, which might be multiple. */
_cleanup_free_ Unit **array = NULL; _cleanup_free_ Unit **array = NULL;
@ -2841,7 +2872,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
/* And now invoke the per-unit callbacks. Note that manager_invoke_notify_message() will handle /* And now invoke the per-unit callbacks. Note that manager_invoke_notify_message() will handle
* duplicate units making sure we only invoke each unit's handler once. */ * duplicate units making sure we only invoke each unit's handler once. */
FOREACH_ARRAY(u, array, n_array) FOREACH_ARRAY(u, array, n_array)
manager_invoke_notify_message(m, *u, ucred, tags, fds); manager_invoke_notify_message(m, *u, &pidref, ucred, tags, fds);
if (!fdset_isempty(fds)) if (!fdset_isempty(fds))
log_warning("Got extra auxiliary fds with notification message, closing them."); log_warning("Got extra auxiliary fds with notification message, closing them.");

View File

@ -4341,44 +4341,44 @@ static void service_force_watchdog(Service *s) {
service_enter_signal(s, SERVICE_STOP_WATCHDOG, SERVICE_FAILURE_WATCHDOG); service_enter_signal(s, SERVICE_STOP_WATCHDOG, SERVICE_FAILURE_WATCHDOG);
} }
static bool service_notify_message_authorized(Service *s, pid_t pid) { static bool service_notify_message_authorized(Service *s, PidRef *pid) {
assert(s); assert(s);
assert(pid_is_valid(pid)); assert(pidref_is_set(pid));
switch (service_get_notify_access(s)) { switch (service_get_notify_access(s)) {
case NOTIFY_NONE: case NOTIFY_NONE:
/* Warn level only if no notifications are expected */ /* Warn level only if no notifications are expected */
log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception is disabled", pid); log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception is disabled", pid->pid);
return false; return false;
case NOTIFY_ALL: case NOTIFY_ALL:
return true; return true;
case NOTIFY_MAIN: case NOTIFY_MAIN:
if (pid == s->main_pid.pid) if (pidref_equal(pid, &s->main_pid))
return true; return true;
if (pidref_is_set(&s->main_pid)) if (pidref_is_set(&s->main_pid))
log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid.pid); log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid->pid, s->main_pid.pid);
else else
log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid->pid);
return false; return false;
case NOTIFY_EXEC: case NOTIFY_EXEC:
if (pid == s->main_pid.pid || pid == s->control_pid.pid) if (pidref_equal(pid, &s->main_pid) || pidref_equal(pid, &s->control_pid))
return true; return true;
if (pidref_is_set(&s->main_pid) && pidref_is_set(&s->control_pid)) if (pidref_is_set(&s->main_pid) && pidref_is_set(&s->control_pid))
log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT, log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT,
pid, s->main_pid.pid, s->control_pid.pid); pid->pid, s->main_pid.pid, s->control_pid.pid);
else if (pidref_is_set(&s->main_pid)) else if (pidref_is_set(&s->main_pid))
log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid.pid); log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid->pid, s->main_pid.pid);
else if (pidref_is_set(&s->control_pid)) else if (pidref_is_set(&s->control_pid))
log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid.pid); log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid->pid, s->control_pid.pid);
else else
log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid); log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid->pid);
return false; return false;
@ -4389,6 +4389,7 @@ static bool service_notify_message_authorized(Service *s, pid_t pid) {
static void service_notify_message( static void service_notify_message(
Unit *u, Unit *u,
PidRef *pidref,
const struct ucred *ucred, const struct ucred *ucred,
char * const *tags, char * const *tags,
FDSet *fds) { FDSet *fds) {
@ -4396,14 +4397,15 @@ static void service_notify_message(
Service *s = ASSERT_PTR(SERVICE(u)); Service *s = ASSERT_PTR(SERVICE(u));
int r; int r;
assert(pidref_is_set(pidref));
assert(ucred); assert(ucred);
if (!service_notify_message_authorized(s, ucred->pid)) if (!service_notify_message_authorized(s, pidref))
return; return;
if (DEBUG_LOGGING) { if (DEBUG_LOGGING) {
_cleanup_free_ char *cc = strv_join(tags, ", "); _cleanup_free_ char *cc = strv_join(tags, ", ");
log_unit_debug(u, "Got notification message from PID "PID_FMT": %s", ucred->pid, empty_to_na(cc)); log_unit_debug(u, "Got notification message from PID "PID_FMT": %s", pidref->pid, empty_to_na(cc));
} }
usec_t monotonic_usec = USEC_INFINITY; usec_t monotonic_usec = USEC_INFINITY;

View File

@ -630,7 +630,7 @@ typedef struct UnitVTable {
void (*notify_cgroup_oom)(Unit *u, bool managed_oom); void (*notify_cgroup_oom)(Unit *u, bool managed_oom);
/* Called whenever a process of this unit sends us a message */ /* Called whenever a process of this unit sends us a message */
void (*notify_message)(Unit *u, const struct ucred *ucred, char * const *tags, FDSet *fds); void (*notify_message)(Unit *u, PidRef *pidref, const struct ucred *ucred, char * const *tags, FDSet *fds);
/* Called whenever we learn a handoff timestamp */ /* Called whenever we learn a handoff timestamp */
void (*notify_handoff_timestamp)(Unit *u, const struct ucred *ucred, const dual_timestamp *ts); void (*notify_handoff_timestamp)(Unit *u, const struct ucred *ucred, const dual_timestamp *ts);