/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "fd-util.h" #include "missing.h" #include "namespace-util.h" #include "process-util.h" #include "stat-util.h" #include "user-util.h" int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) { _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1; int rfd = -1; assert(pid >= 0); if (mntns_fd) { const char *mntns; mntns = procfs_file_alloca(pid, "ns/mnt"); mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); if (mntnsfd < 0) return -errno; } if (pidns_fd) { const char *pidns; pidns = procfs_file_alloca(pid, "ns/pid"); pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); if (pidnsfd < 0) return -errno; } if (netns_fd) { const char *netns; netns = procfs_file_alloca(pid, "ns/net"); netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); if (netnsfd < 0) return -errno; } if (userns_fd) { const char *userns; userns = procfs_file_alloca(pid, "ns/user"); usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); if (usernsfd < 0 && errno != ENOENT) return -errno; } if (root_fd) { const char *root; root = procfs_file_alloca(pid, "root"); rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); if (rfd < 0) return -errno; } if (pidns_fd) *pidns_fd = pidnsfd; if (mntns_fd) *mntns_fd = mntnsfd; if (netns_fd) *netns_fd = netnsfd; if (userns_fd) *userns_fd = usernsfd; if (root_fd) *root_fd = rfd; pidnsfd = mntnsfd = netnsfd = usernsfd = -1; return 0; } int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { 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 equal before attempting to enter. */ _cleanup_free_ char *userns_fd_path = NULL; int r; if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0) return -ENOMEM; r = files_same(userns_fd_path, "/proc/self/ns/user", 0); if (r < 0) return r; if (r) userns_fd = -1; } if (pidns_fd >= 0) if (setns(pidns_fd, CLONE_NEWPID) < 0) return -errno; if (mntns_fd >= 0) if (setns(mntns_fd, CLONE_NEWNS) < 0) return -errno; if (netns_fd >= 0) if (setns(netns_fd, CLONE_NEWNET) < 0) return -errno; if (userns_fd >= 0) if (setns(userns_fd, CLONE_NEWUSER) < 0) return -errno; if (root_fd >= 0) { if (fchdir(root_fd) < 0) return -errno; if (chroot(".") < 0) return -errno; } return reset_uid_gid(); } int fd_is_network_ns(int fd) { struct statfs s; int r; /* Checks whether the specified file descriptor refers to a network namespace. On old kernels there's no nice * way to detect that, hence on those we'll return a recognizable error (EUCLEAN), so that callers can handle * this somewhat nicely. * * This function returns > 0 if the fd definitely refers to a network namespace, 0 if it definitely does not * refer to a network namespace, -EUCLEAN if we can't determine, and other negative error codes on error. */ if (fstatfs(fd, &s) < 0) return -errno; if (!is_fs_type(&s, NSFS_MAGIC)) { /* On really old kernels, there was no "nsfs", and network namespace sockets belonged to procfs * instead. Handle that in a somewhat smart way. */ if (is_fs_type(&s, PROC_SUPER_MAGIC)) { struct statfs t; /* OK, so it is procfs. Let's see if our own network namespace is procfs, too. If so, then the * passed fd might refer to a network namespace, but we can't know for sure. In that case, * return a recognizable error. */ if (statfs("/proc/self/ns/net", &t) < 0) return -errno; if (s.f_type == t.f_type) return -EUCLEAN; /* It's possible, we simply don't know */ } return 0; /* No! */ } r = ioctl(fd, NS_GET_NSTYPE); if (r < 0) { if (errno == ENOTTY) /* Old kernels didn't know this ioctl, let's also return a recognizable error in that case */ return -EUCLEAN; return -errno; } return r == CLONE_NEWNET; }