blob: 833a18a20499d012a6c3f7c5b089bb86e6132925 [file] [log] [blame] [raw]
/* 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;
}