sd-bus: Make sure we can connect to user machines as well (#40698)

Don't unconditionally look into /run/systemd/machines. If we're a
connected to a session bus, look at the machines for the current user
instead.
This commit is contained in:
Daan De Meyer
2026-02-17 19:13:40 +01:00
committed by GitHub
9 changed files with 204 additions and 160 deletions

View File

@@ -68,6 +68,11 @@ static int pidref_namespace_open_by_type_internal(const PidRef *pidref, Namespac
if (pidref->fd >= 0) {
r = pidfd_get_namespace(pidref->fd, namespace_info[type].pidfd_get_ns_ioctl_cmd);
if (r == -ENOPKG)
return log_debug_errno(
r,
"Cannot open %s namespace for PID "PID_FMT" as the namespace type is not supported by the kernel",
namespace_info[type].proc_name, pidref->pid);
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
return r;
}
@@ -83,10 +88,17 @@ static int pidref_namespace_open_by_type_internal(const PidRef *pidref, Namespac
if (nsfd == -ENOENT) {
r = proc_mounted();
if (r == 0)
return -ENOSYS; /* /proc/ is not available or not set up properly, we're most likely
in some chroot environment. */
/* /proc/ is not available or not set up properly, we're most likely in some chroot environment. */
return log_debug_errno(
SYNTHETIC_ERRNO(ENOSYS),
"Cannot open %s namespace for PID "PID_FMT" as /proc is not mounted",
namespace_info[type].proc_name, pidref->pid);
if (r > 0)
return -ENOPKG; /* If /proc/ is definitely around then this means the namespace type is not supported */
/* If /proc/ is definitely around then this means the namespace type is not supported */
return log_debug_errno(
SYNTHETIC_ERRNO(ENOPKG),
"Cannot open %s namespace for PID "PID_FMT" via /proc as the namespace type is not supported by the kernel",
namespace_info[type].proc_name, pidref->pid);
/* can't determine? then propagate original error */
}
@@ -216,33 +228,39 @@ int namespace_open(
return pidref_namespace_open(&pidref, ret_pidns_fd, ret_mntns_fd, ret_netns_fd, ret_userns_fd, ret_root_fd);
}
static int namespace_enter_one_idempotent(int nsfd, NamespaceType type) {
int r;
/* Join a namespace, but only if we're not part of it already. This is important if we don't necessarily
* own the namespace in question, as kernel would unconditionally return EPERM otherwise. */
assert(nsfd >= 0);
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
r = is_our_namespace(nsfd, type);
if (r < 0)
return r;
if (r > 0)
return 0;
if (setns(nsfd, namespace_info[type].clone_flag) < 0)
return -errno;
return 1;
}
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
int r;
/* Block dlopen() now, to avoid us inadvertently loading shared library from another namespace */
block_dlopen();
/* Join namespaces, but only if we're not part of them already. This is important if we don't
* necessarily own the namespace in question, as kernel would unconditionally return EPERM otherwise. */
if (pidns_fd >= 0) {
r = is_our_namespace(pidns_fd, NAMESPACE_PID);
if (r < 0)
return r;
if (r > 0)
pidns_fd = -EBADF;
}
if (mntns_fd >= 0) {
r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return r;
if (r > 0)
mntns_fd = -EBADF;
}
if (netns_fd >= 0) {
r = is_our_namespace(netns_fd, NAMESPACE_NET);
if (r < 0)
return r;
if (r > 0)
netns_fd = -EBADF;
}
if (userns_fd >= 0) {
/* Can't setns to your own userns, since then you could escalate from non-root to root in
* your own namespace, so check if namespaces are equal before attempting to enter. */
@@ -275,23 +293,17 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
return -errno;
}
if (pidns_fd >= 0) {
r = namespace_enter_one_idempotent(pidns_fd, NAMESPACE_PID);
if (r < 0)
return r;
}
if (pidns_fd >= 0)
if (setns(pidns_fd, CLONE_NEWPID) < 0)
return -errno;
if (mntns_fd >= 0) {
r = namespace_enter_one_idempotent(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return r;
}
if (mntns_fd >= 0)
if (setns(mntns_fd, CLONE_NEWNS) < 0)
return -errno;
if (netns_fd >= 0) {
r = namespace_enter_one_idempotent(netns_fd, NAMESPACE_NET);
if (r < 0)
return r;
}
if (netns_fd >= 0)
if (setns(netns_fd, CLONE_NEWNET) < 0)
return -errno;
if (userns_fd >= 0 && have_cap_sys_admin)
if (setns(userns_fd, CLONE_NEWUSER) < 0)
@@ -361,6 +373,42 @@ int is_our_namespace(int fd, NamespaceType type) {
return fd_inode_same(fd, our_ns);
}
int are_our_namespaces(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
int r;
if (pidns_fd >= 0) {
r = is_our_namespace(pidns_fd, NAMESPACE_PID);
if (r <= 0)
return r;
}
if (mntns_fd >= 0) {
r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT);
if (r <= 0)
return r;
}
if (netns_fd >= 0) {
r = is_our_namespace(netns_fd, NAMESPACE_NET);
if (r <= 0)
return r;
}
if (userns_fd >= 0) {
r = is_our_namespace(userns_fd, NAMESPACE_USER);
if (r <= 0)
return r;
}
if (root_fd >= 0) {
r = dir_fd_is_root(root_fd);
if (r <= 0)
return r;
}
return true;
}
int namespace_is_init(NamespaceType type) {
int r;

View File

@@ -50,6 +50,7 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
int fd_is_namespace(int fd, NamespaceType type);
int is_our_namespace(int fd, NamespaceType type);
int are_our_namespaces(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
int namespace_is_init(NamespaceType type);

View File

@@ -25,13 +25,11 @@
#include "cgroup-util.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "env-file.h"
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "io-util.h"
#include "iovec-util.h"
#include "locale-util.h"
@@ -53,6 +51,7 @@
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "user-util.h"
@@ -349,47 +348,6 @@ int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char *
return 0;
}
int container_get_leader(const char *machine, pid_t *pid) {
_cleanup_free_ char *s = NULL, *class = NULL;
const char *p;
pid_t leader;
int r;
assert(machine);
assert(pid);
if (streq(machine, ".host")) {
*pid = 1;
return 0;
}
if (!hostname_is_valid(machine, 0))
return -EINVAL;
p = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(NULL, p,
"LEADER", &s,
"CLASS", &class);
if (r == -ENOENT)
return -EHOSTDOWN;
if (r < 0)
return r;
if (!s)
return -EIO;
if (!streq_ptr(class, "container"))
return -EIO;
r = parse_pid(s, &leader);
if (r < 0)
return r;
if (leader <= 1)
return -EIO;
*pid = leader;
return 0;
}
int pid_is_kernel_thread(pid_t pid) {
int r;

View File

@@ -54,8 +54,6 @@ int pid_get_start_time(pid_t pid, usec_t *ret);
int pidref_get_start_time(const PidRef *pid, usec_t *ret);
int get_process_umask(pid_t pid, mode_t *ret);
int container_get_leader(const char *machine, pid_t *pid);
static inline bool SIGINFO_CODE_IS_DEAD(int code) {
return IN_SET(code, CLD_EXITED, CLD_KILLED, CLD_DUMPED);
}

View File

@@ -241,7 +241,7 @@ int coredump_send_to_container(CoredumpContext *context) {
_cleanup_(pidref_done) PidRef leader_pid = PIDREF_NULL;
r = namespace_get_leader(&context->pidref, NAMESPACE_PID, &leader_pid);
if (r < 0)
return log_debug_errno(r, "Failed to get namespace leader: %m");
return log_error_errno(r, "Failed to get namespace leader: %m");
r = can_forward_coredump(&context->pidref, &leader_pid);
if (r <= 0)
@@ -258,33 +258,33 @@ int coredump_send_to_container(CoredumpContext *context) {
r = RET_NERRNO(socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair));
if (r < 0)
return log_debug_errno(r, "Failed to create socket pair: %m");
return log_error_errno(r, "Failed to create socket pair: %m");
r = setsockopt_int(pair[1], SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return log_debug_errno(r, "Failed to set SO_PASSCRED: %m");
return log_error_errno(r, "Failed to set SO_PASSCRED: %m");
r = pidref_namespace_open(&leader_pid, &pidnsfd, &mntnsfd, &netnsfd, &usernsfd, &rootfd);
if (r < 0)
return log_debug_errno(r, "Failed to open namespaces of PID " PID_FMT ": %m", leader_pid.pid);
return log_error_errno(r, "Failed to open namespaces of PID " PID_FMT ": %m", leader_pid.pid);
r = namespace_fork("(sd-coredumpns)", "(sd-coredump)",
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM,
pidnsfd, mntnsfd, netnsfd, usernsfd, rootfd, &child);
if (r < 0)
return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid.pid);
return log_error_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid.pid);
if (r == 0) {
pair[0] = safe_close(pair[0]);
r = access_nofollow("/run/systemd/coredump", W_OK);
if (r < 0) {
log_debug_errno(r, "Cannot find coredump socket, exiting: %m");
log_error_errno(r, "Cannot find coredump socket, exiting: %m");
_exit(EXIT_FAILURE);
}
r = receive_ucred(pair[1], &ucred);
if (r < 0) {
log_debug_errno(r, "Failed to receive ucred and fd: %m");
log_error_errno(r, "Failed to receive ucred and fd: %m");
_exit(EXIT_FAILURE);
}
@@ -309,7 +309,7 @@ int coredump_send_to_container(CoredumpContext *context) {
r = coredump_send(context);
if (r < 0) {
log_debug_errno(r, "Failed to send iovec to coredump socket: %m");
log_error_errno(r, "Failed to send iovec to coredump socket: %m");
_exit(EXIT_FAILURE);
}
@@ -324,13 +324,13 @@ int coredump_send_to_container(CoredumpContext *context) {
* container. The kernel will perform the translation for us. */
r = send_ucred(pair[0], &ucred);
if (r < 0)
return log_debug_errno(r, "Failed to send metadata to container: %m");
return log_error_errno(r, "Failed to send metadata to container: %m");
r = pidref_wait_for_terminate_and_check("(sd-coredumpns)", &child, 0);
r = pidref_wait_for_terminate_and_check("(sd-coredumpns)", &child, WAIT_LOG);
if (r < 0)
return log_debug_errno(r, "Failed to wait for child to terminate: %m");
return r;
if (r != EXIT_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to process coredump in container.");
return -EPROTO;
return 1; /* sent */
}

View File

@@ -5,20 +5,99 @@
#include "bus-container.h"
#include "bus-internal.h"
#include "bus-socket.h"
#include "env-file.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "hostname-util.h"
#include "log.h"
#include "namespace-util.h"
#include "pidref.h"
#include "parse-util.h"
#include "path-lookup.h"
#include "path-util.h"
#include "process-util.h"
#include "string-util.h"
int container_get_leader(RuntimeScope scope, const char *machine, pid_t *ret) {
_cleanup_free_ char *p = NULL, *s = NULL, *class = NULL;
pid_t leader;
int r;
assert(machine);
assert(ret);
if (streq(machine, ".host")) {
if (scope == RUNTIME_SCOPE_USER)
return -EHOSTDOWN;
*ret = 1;
return 0;
}
if (!hostname_is_valid(machine, 0))
return -EINVAL;
r = runtime_directory_generic(scope, "systemd/machines", &p);
if (r < 0)
return r;
if (!path_extend(&p, machine))
return -ENOMEM;
r = parse_env_file(NULL, p,
"LEADER", &s,
"CLASS", &class);
if (r == -ENOENT)
return -EHOSTDOWN;
if (r < 0)
return r;
if (!s)
return -ESRCH;
if (!streq_ptr(class, "container"))
return -EMEDIUMTYPE;
r = parse_pid(s, &leader);
if (r < 0)
return r;
if (leader <= 1)
return -EBADMSG;
*ret = leader;
return 0;
}
static int bus_container_connect_namespace(sd_bus *b, int pidnsfd, int mntnsfd, int usernsfd, int rootfd) {
_cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR;
int r;
if (pipe2(errno_pipe_fd, O_CLOEXEC) < 0)
return log_debug_errno(errno, "Failed to create pipe: %m");
r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT,
pidnsfd, mntnsfd, /* netns_fd= */ -EBADF, usernsfd, rootfd, /* ret= */ NULL);
if (r == -EPROTO) {
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
int k = read_errno(errno_pipe_fd[0]);
if (k < 0 && k != -EIO)
return k;
}
if (r < 0)
return log_debug_errno(r, "Failed to create namespace for (sd-buscntr): %m");
if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = RET_NERRNO(connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size));
report_errno_and_exit(errno_pipe_fd[1], r);
}
return 0;
}
int bus_container_connect_socket(sd_bus *b) {
_cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF;
_cleanup_(pidref_done) PidRef child = PIDREF_NULL;
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
int r, error_buf = 0;
ssize_t n;
int r;
assert(b);
assert(b->input_fd < 0);
@@ -29,7 +108,9 @@ int bus_container_connect_socket(sd_bus *b) {
log_debug("sd-bus: connecting bus%s%s to machine %s...",
b->description ? " " : "", strempty(b->description), b->machine);
r = container_get_leader(b->machine, &b->nspid);
r = container_get_leader(RUNTIME_SCOPE_USER, b->machine, &b->nspid);
if (IN_SET(r, -EHOSTDOWN, -ENXIO))
r = container_get_leader(RUNTIME_SCOPE_SYSTEM, b->machine, &b->nspid);
if (r < 0)
return r;
} else
@@ -50,56 +131,17 @@ int bus_container_connect_socket(sd_bus *b) {
bus_socket_setup(b);
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
return log_debug_errno(errno, "Failed to create a socket pair: %m");
r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child);
r = are_our_namespaces(pidnsfd, mntnsfd, /* netns_fd= */ -EBADF, usernsfd, rootfd);
if (r < 0)
return log_debug_errno(r, "Failed to create namespace for (sd-buscntr): %m");
if (r == 0) {
pair[0] = safe_close(pair[0]);
r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size);
if (r < 0) {
/* Try to send error up */
error_buf = errno;
(void) write(pair[1], &error_buf, sizeof(error_buf));
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
pair[1] = safe_close(pair[1]);
r = pidref_wait_for_terminate_and_check("(sd-buscntrns)", &child, 0);
return log_debug_errno(r, "Failed to check if already in PID "PID_FMT" namespaces: %m", b->nspid);
if (r > 0)
r = RET_NERRNO(connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size));
else
r = bus_container_connect_namespace(b, pidnsfd, mntnsfd, usernsfd, rootfd);
if (r == -EINPROGRESS)
return 1;
if (r < 0)
return r;
bool nonzero_exit_status = r != EXIT_SUCCESS;
n = read(pair[0], &error_buf, sizeof(error_buf));
if (n < 0)
return log_debug_errno(errno, "Failed to read error status from (sd-buscntr): %m");
if (n > 0) {
if (n != sizeof(error_buf))
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Read error status of unexpected length %zd from (sd-buscntr).", n);
if (error_buf < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Got unexpected error status from (sd-buscntr).");
if (error_buf == EINPROGRESS)
return 1;
if (error_buf > 0)
return log_debug_errno(error_buf, "(sd-buscntr) failed to connect to D-Bus socket: %m");
}
if (nonzero_exit_status)
return -EPROTO;
return log_debug_errno(r, "Failed to connect to D-Bus socket in namespaces of PID "PID_FMT": %m", b->nspid);
return bus_socket_start_auth(b);
}

View File

@@ -3,4 +3,6 @@
#include "sd-forward.h"
int container_get_leader(RuntimeScope scope, const char *machine, pid_t *ret);
int bus_container_connect_socket(sd_bus *b);

View File

@@ -5,6 +5,7 @@
#include <sys/socket.h>
#include <unistd.h>
#include "bus-container.h"
#include "fd-util.h"
#include "fs-util.h"
#include "hash-funcs.h"
@@ -13,6 +14,7 @@
#include "namespace-util.h"
#include "pidref.h"
#include "process-util.h"
#include "runtime-scope.h"
#include "sha256.h"
#include "siphash24.h"
#include "string-util.h"
@@ -287,7 +289,7 @@ int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret) {
if (isempty(machine))
return sd_id128_get_boot(ret);
r = container_get_leader(machine, &pid);
r = container_get_leader(RUNTIME_SCOPE_SYSTEM, machine, &pid);
if (r < 0)
return r;

View File

@@ -4368,15 +4368,8 @@ static int outer_child(
/* The inner child has all namespaces that are requested, so that we all are owned by the
* user if user namespaces are turned on. */
if (arg_network_namespace_path) {
r = namespace_enter(/* pidns_fd= */ -EBADF,
/* mntns_fd= */ -EBADF,
netns_fd,
/* userns_fd= */ -EBADF,
/* root_fd= */ -EBADF);
if (r < 0)
return log_error_errno(r, "Failed to join network namespace: %m");
}
if (arg_network_namespace_path && setns(netns_fd, CLONE_NEWNET) < 0)
return log_error_errno(errno, "Failed to join network namespace: %m");
if (arg_userns_mode == USER_NAMESPACE_MANAGED) {
/* In managed usernamespace operation, sysfs + procfs are special, we'll have to