| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| |
| #include "fd-util.h" |
| #include "missing_fs.h" |
| #include "missing_magic.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 = TAKE_FD(pidnsfd); |
| |
| if (mntns_fd) |
| *mntns_fd = TAKE_FD(mntnsfd); |
| |
| if (netns_fd) |
| *netns_fd = TAKE_FD(netnsfd); |
| |
| if (userns_fd) |
| *userns_fd = TAKE_FD(usernsfd); |
| |
| if (root_fd) |
| *root_fd = TAKE_FD(rfd); |
| |
| 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; |
| } |
| |
| int detach_mount_namespace(void) { |
| |
| /* Detaches the mount namespace, disabling propagation from our namespace to the host */ |
| |
| if (unshare(CLONE_NEWNS) < 0) |
| return -errno; |
| |
| if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) |
| return -errno; |
| |
| return 0; |
| } |