mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
nsresourced: restrict Btrfs subvolume creation ioctls
Btrfs subvolume creation bypasses the path and inode creation hooks. A managed user namespace can therefore leave a subvolume root owned by its transient range on a file system that outlives the namespace. Let's apply the existing creation restrictions to the native and compat ioctl calls for subvolume creation. The tests check persistent and idmapped mounts, nested user namespaces, unrelated ioctls and other file systems. The compat ioctl hook is absent on older kernels. Let's preserve the native policy when that hook isn't available. (cherry picked from commitbdd20e5ff6) (cherry picked from commitebe9b22a27)
This commit is contained in:
1 parent
98c440d91a
commit
1ced75f76e
5 files changed
+120
No files matched your search
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "vmlinux.h"
|
||||
|
||||
#include <asm/ioctl.h>
|
||||
#include <errno.h> /* IWYU pragma: keep */
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
@@ -66,6 +67,7 @@ void* bpf_rdonly_cast(const void *, __u32) __ksym;
|
||||
#define UID_GID_INVALID ((uid_t) -1)
|
||||
|
||||
/* From include/uapi/linux/magic.h, which vmlinux.h does not provide. */
|
||||
#define BTRFS_SUPER_MAGIC 0x9123683E
|
||||
#define OVERLAYFS_SUPER_MAGIC 0x794c7630
|
||||
#define CGROUP2_SUPER_MAGIC 0x63677270
|
||||
|
||||
@@ -75,6 +77,10 @@ void* bpf_rdonly_cast(const void *, __u32) __ksym;
|
||||
/* From include/uapi/asm-generic/fcntl.h. Only archs without BPF trampolines (no LSM programs) deviate. */
|
||||
#define __O_TMPFILE (1U << 22) /* O_TMPFILE without O_DIRECTORY */
|
||||
|
||||
/* The Btrfs UAPI header conflicts with vmlinux.h, so define the subvolume creation commands here. */
|
||||
#define BTRFS_IOC_SUBVOL_CREATE _IOC(_IOC_WRITE, 0x94, 14, 4096)
|
||||
#define BTRFS_IOC_SUBVOL_CREATE_V2 _IOC(_IOC_WRITE, 0x94, 24, 4096)
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 1); /* placeholder, configured otherwise by nsresourced */
|
||||
@@ -429,6 +435,27 @@ int BPF_PROG(userns_restrict_path_rename, const struct path *old_dir, struct den
|
||||
return validate_mount(old_dir->mnt, ret);
|
||||
}
|
||||
|
||||
/* Btrfs creates subvolume root inodes without calling the path or inode creation hooks. */
|
||||
static int validate_ioctl(struct file *file, unsigned cmd, int ret) {
|
||||
if (cmd != BTRFS_IOC_SUBVOL_CREATE && cmd != BTRFS_IOC_SUBVOL_CREATE_V2)
|
||||
return ret;
|
||||
|
||||
if (file->f_path.mnt->mnt_sb->s_magic != BTRFS_SUPER_MAGIC)
|
||||
return ret;
|
||||
|
||||
return validate_mount(file->f_path.mnt, ret);
|
||||
}
|
||||
|
||||
SEC("lsm/file_ioctl")
|
||||
int BPF_PROG(userns_restrict_file_ioctl, struct file *file, unsigned cmd, unsigned long arg, int ret) {
|
||||
return validate_ioctl(file, cmd, ret);
|
||||
}
|
||||
|
||||
SEC("lsm/file_ioctl_compat")
|
||||
int BPF_PROG(userns_restrict_file_ioctl_compat, struct file *file, unsigned cmd, unsigned long arg, int ret) {
|
||||
return validate_ioctl(file, cmd, ret);
|
||||
}
|
||||
|
||||
/* overlayfs' struct ovl_fs and struct ovl_layer are private to the module and hence absent from vmlinux.h
|
||||
* whenever overlayfs is built as a module. We declare the two fields we need as CO-RE "type flavors": libbpf
|
||||
* strips the ___local suffix and relocates these against the kernel's real ovl_fs/ovl_layer. This both
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <grp.h>
|
||||
#include <linux/btrfs.h>
|
||||
#include <linux/magic.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "btrfs-util.h"
|
||||
#include "cgroup-setup.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "errno-util.h"
|
||||
@@ -19,7 +23,9 @@
|
||||
#include "pidref.h"
|
||||
#include "process-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "tests.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "uid-classification.h"
|
||||
@@ -213,6 +219,81 @@ TEST(userns_restrict) {
|
||||
ASSERT_OK(pidref_wait_for_terminate_and_check("(test)", &pidref, WAIT_LOG));
|
||||
}
|
||||
|
||||
TEST(userns_restrict_btrfs) {
|
||||
_cleanup_(rm_rf_subvolume_and_freep) char *t = NULL;
|
||||
_cleanup_close_ int dir_fd = -EBADF, host_fd = -EBADF, mapped_fd = -EBADF,
|
||||
mount_fd = -EBADF, idmapped_fd = -EBADF, userns_fd = -EBADF,
|
||||
tmpfs_mount = -EBADF, tmpfs_fd = -EBADF;
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
|
||||
_cleanup_free_ char *idmap = NULL;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
ASSERT_OK(mkdtemp_malloc("/var/tmp/test-userns-restrict-btrfs.XXXXXX", &t));
|
||||
dir_fd = ASSERT_OK(xopenat(AT_FDCWD, t, O_DIRECTORY));
|
||||
if (ASSERT_OK(fd_is_fs_type(dir_fd, BTRFS_SUPER_MAGIC)) == 0)
|
||||
return (void) log_tests_skipped("Temporary directory is not on Btrfs");
|
||||
|
||||
host_fd = ASSERT_OK(xopenat_full(dir_fd, "host", O_DIRECTORY|O_CREAT, /* xopen_flags= */ 0, 0700));
|
||||
mapped_fd = ASSERT_OK(xopenat_full(dir_fd, "mapped", O_DIRECTORY|O_CREAT, /* xopen_flags= */ 0, 0700));
|
||||
ASSERT_OK_ERRNO(fchown(host_fd, CONTAINER_UID_MIN, CONTAINER_UID_MIN));
|
||||
|
||||
ASSERT_OK(btrfs_subvol_make(host_fd, "probe"));
|
||||
ASSERT_OK(btrfs_subvol_remove_at(host_fd, "probe", /* flags= */ 0));
|
||||
|
||||
ASSERT_OK(asprintf(&idmap, "0 "UID_FMT" 1", CONTAINER_UID_MIN));
|
||||
userns_fd = ASSERT_OK(userns_acquire(idmap, idmap, /* setgroups_deny= */ true));
|
||||
|
||||
mount_fd = ASSERT_OK_ERRNO(open_tree(mapped_fd, "", OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_EMPTY_PATH));
|
||||
ASSERT_OK_ERRNO(mount_setattr(mount_fd, "", AT_EMPTY_PATH,
|
||||
&(struct mount_attr) {
|
||||
.attr_set = MOUNT_ATTR_IDMAP,
|
||||
.userns_fd = userns_fd,
|
||||
}, sizeof(struct mount_attr)));
|
||||
idmapped_fd = ASSERT_OK(xopenat(mount_fd, ".", O_DIRECTORY));
|
||||
|
||||
tmpfs_mount = ASSERT_OK(make_tmpfs_fsmount());
|
||||
tmpfs_fd = ASSERT_OK(xopenat(tmpfs_mount, ".", O_DIRECTORY));
|
||||
|
||||
ASSERT_OK(userns_restrict_register_by_fd(bpf_obj, userns_fd));
|
||||
|
||||
r = ASSERT_OK(pidref_safe_fork("(btrfs)", FORK_DEATHSIG_SIGKILL, &pidref));
|
||||
if (r == 0) {
|
||||
struct btrfs_ioctl_vol_args legacy = { .name = "legacy" };
|
||||
struct btrfs_ioctl_vol_args_v2 modern = { .name = "modern" };
|
||||
struct btrfs_ioctl_fs_info_args info = {};
|
||||
|
||||
ASSERT_OK(namespace_enter(-EBADF, -EBADF, -EBADF, userns_fd, -EBADF));
|
||||
ASSERT_OK_ERRNO(unshare(CLONE_NEWNS));
|
||||
|
||||
ASSERT_ERROR_ERRNO(ioctl(host_fd, BTRFS_IOC_SUBVOL_CREATE, &legacy), EPERM);
|
||||
ASSERT_ERROR_ERRNO(ioctl(host_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &modern), EPERM);
|
||||
ASSERT_OK_ERRNO(ioctl(idmapped_fd, BTRFS_IOC_SUBVOL_CREATE, &legacy));
|
||||
ASSERT_OK_ERRNO(ioctl(idmapped_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &modern));
|
||||
ASSERT_OK_ERRNO(ioctl(host_fd, BTRFS_IOC_FS_INFO, &info));
|
||||
|
||||
ASSERT_ERROR_ERRNO(ioctl(tmpfs_fd, BTRFS_IOC_SUBVOL_CREATE, &legacy), ENOTTY);
|
||||
ASSERT_ERROR_ERRNO(ioctl(tmpfs_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &modern), ENOTTY);
|
||||
|
||||
ASSERT_OK_ERRNO(unshare(CLONE_NEWUSER));
|
||||
ASSERT_ERROR_ERRNO(ioctl(host_fd, BTRFS_IOC_SUBVOL_CREATE, &legacy), EPERM);
|
||||
ASSERT_ERROR_ERRNO(ioctl(host_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &modern), EPERM);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
ASSERT_OK(pidref_wait_for_terminate_and_check("(btrfs)", &pidref, WAIT_LOG));
|
||||
|
||||
FOREACH_STRING(name, "legacy", "modern") {
|
||||
ASSERT_OK_ERRNO(fstatat(mapped_fd, name, &st, 0));
|
||||
ASSERT_EQ(st.st_uid, 0U);
|
||||
ASSERT_EQ(st.st_gid, 0U);
|
||||
}
|
||||
|
||||
ASSERT_OK_ERRNO(fstat(userns_fd, &st));
|
||||
ASSERT_OK(userns_restrict_reset_by_inode(bpf_obj, st.st_ino));
|
||||
}
|
||||
|
||||
TEST(userns_restrict_overlayfs) {
|
||||
_cleanup_close_ int userns_fd = -EBADF, idmapped_upper = -EBADF, bad_userns_fd = -EBADF,
|
||||
bad_upper = -EBADF;
|
||||
|
||||
@@ -68,6 +68,12 @@ int userns_restrict_install(
|
||||
if (!obj)
|
||||
return log_error_errno(errno, "Failed to open userns_restrict BPF object: %m");
|
||||
|
||||
/* Kernels without the compat ioctl hook cannot load a program attached to it. */
|
||||
r = sym_bpf_program__set_autoload(obj->progs.userns_restrict_file_ioctl_compat,
|
||||
bpf_kernel_has_kfunc("bpf_lsm_file_ioctl_compat"));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to select compat ioctl BPF program: %m");
|
||||
|
||||
if (pin) {
|
||||
struct bpf_map *map;
|
||||
|
||||
@@ -159,6 +165,9 @@ int userns_restrict_attach(struct userns_restrict_bpf *obj, bool pin) {
|
||||
bool linked = false;
|
||||
const char *e;
|
||||
|
||||
if (sym_bpf_program__fd(*ps->prog) < 0)
|
||||
continue;
|
||||
|
||||
e = startswith(ps->name, "userns_restrict_");
|
||||
assert(e);
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL;
|
||||
DLSYM_PROTOTYPE(bpf_program__attach) = NULL;
|
||||
DLSYM_PROTOTYPE(bpf_program__attach_cgroup) = NULL;
|
||||
DLSYM_PROTOTYPE(bpf_program__attach_lsm) = NULL;
|
||||
DLSYM_PROTOTYPE(bpf_program__fd) = NULL;
|
||||
DLSYM_PROTOTYPE(bpf_program__name) = NULL;
|
||||
DLSYM_PROTOTYPE(bpf_program__set_autoload) = NULL;
|
||||
static int missing_bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts) {
|
||||
@@ -128,6 +129,7 @@ int dlopen_bpf(int log_level) {
|
||||
DLSYM_ARG_FORCE(bpf_program__attach_cgroup),
|
||||
DLSYM_ARG_FORCE(bpf_program__attach_lsm),
|
||||
#endif
|
||||
DLSYM_ARG(bpf_program__fd),
|
||||
DLSYM_ARG(bpf_program__name),
|
||||
DLSYM_ARG(bpf_program__set_autoload),
|
||||
DLSYM_ARG(libbpf_get_error),
|
||||
|
||||
@@ -62,6 +62,7 @@ extern DLSYM_PROTOTYPE(bpf_object__pin_maps);
|
||||
extern DLSYM_PROTOTYPE(bpf_program__attach);
|
||||
extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup);
|
||||
extern DLSYM_PROTOTYPE(bpf_program__attach_lsm);
|
||||
extern DLSYM_PROTOTYPE(bpf_program__fd);
|
||||
extern DLSYM_PROTOTYPE(bpf_program__name);
|
||||
extern DLSYM_PROTOTYPE(bpf_program__set_autoload);
|
||||
extern DLSYM_PROTOTYPE(bpf_token_create);
|
||||
|
||||
Reference in new issue
Block a user