From: Christian Brauner Date: Thu, 12 May 2022 16:52:48 +0000 (+0200) Subject: vfstest: split out btrfs idmapped mounts test X-Git-Tag: v2022.05.15~2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=3bc939bccca554980b999b25720648ab211cc21b;p=xfstests-dev.git vfstest: split out btrfs idmapped mounts test Split the btrfs specific idmapped mounts test into a separate source file. Acked-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Zorro Lang --- diff --git a/src/vfs/Makefile b/src/vfs/Makefile index fa0f1621..1b0b364b 100644 --- a/src/vfs/Makefile +++ b/src/vfs/Makefile @@ -4,10 +4,10 @@ TOPDIR = ../.. include $(TOPDIR)/include/builddefs TARGETS = vfstest mount-idmapped -CFILES_VFSTEST = vfstest.c idmapped-mounts.c utils.c +CFILES_VFSTEST = vfstest.c btrfs-idmapped-mounts.c idmapped-mounts.c utils.c CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c -HFILES = missing.h utils.h idmapped-mounts.h +HFILES = missing.h utils.h btrfs-idmapped-mounts.h idmapped-mounts.h LLDLIBS += -pthread LDIRT = $(TARGETS) diff --git a/src/vfs/btrfs-idmapped-mounts.c b/src/vfs/btrfs-idmapped-mounts.c new file mode 100644 index 00000000..7c8f73be --- /dev/null +++ b/src/vfs/btrfs-idmapped-mounts.c @@ -0,0 +1,3854 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "../global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_BTRFS_H +# ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID +# define btrfs_ioctl_vol_args_v2 override_btrfs_ioctl_vol_args_v2 +# endif +#include +# undef btrfs_ioctl_vol_args_v2 +#endif + +#ifdef HAVE_LINUX_BTRFS_TREE_H +#include +#endif + +#include "missing.h" +#include "utils.h" + +static char t_buf[PATH_MAX]; + +#ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS + +#ifndef BTRFS_PATH_NAME_MAX +#define BTRFS_PATH_NAME_MAX 4087 +#endif + +struct btrfs_ioctl_vol_args { + __s64 fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; +#endif + +#ifndef HAVE_STRUCT_BTRFS_QGROUP_LIMIT +struct btrfs_qgroup_limit { + __u64 flags; + __u64 max_rfer; + __u64 max_excl; + __u64 rsv_rfer; + __u64 rsv_excl; +}; +#endif + +#ifndef HAVE_STRUCT_BTRFS_QGROUP_INHERIT +struct btrfs_qgroup_inherit { + __u64 flags; + __u64 num_qgroups; + __u64 num_ref_copies; + __u64 num_excl_copies; + struct btrfs_qgroup_limit lim; + __u64 qgroups[0]; +}; +#endif + +#if !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2) || !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID) + +#ifndef BTRFS_SUBVOL_NAME_MAX +#define BTRFS_SUBVOL_NAME_MAX 4039 +#endif + +struct btrfs_ioctl_vol_args_v2 { + __s64 fd; + __u64 transid; + __u64 flags; + union { + struct { + __u64 size; + struct btrfs_qgroup_inherit *qgroup_inherit; + }; + __u64 unused[4]; + }; + union { + char name[BTRFS_SUBVOL_NAME_MAX + 1]; + __u64 devid; + __u64 subvolid; + }; +}; +#endif + +#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_ARGS + +#ifndef BTRFS_INO_LOOKUP_PATH_MAX +#define BTRFS_INO_LOOKUP_PATH_MAX 4080 +#endif +struct btrfs_ioctl_ino_lookup_args { + __u64 treeid; + __u64 objectid; + char name[BTRFS_INO_LOOKUP_PATH_MAX]; +}; +#endif + +#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_USER_ARGS + +#ifndef BTRFS_VOL_NAME_MAX +#define BTRFS_VOL_NAME_MAX 255 +#endif + +#ifndef BTRFS_INO_LOOKUP_USER_PATH_MAX +#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080 - BTRFS_VOL_NAME_MAX - 1) +#endif + +struct btrfs_ioctl_ino_lookup_user_args { + __u64 dirid; + __u64 treeid; + char name[BTRFS_VOL_NAME_MAX + 1]; + char path[BTRFS_INO_LOOKUP_USER_PATH_MAX]; +}; +#endif + +#ifndef HAVE_STRUCT_BTRFS_IOCTL_GET_SUBVOL_ROOTREF_ARGS + +#ifndef BTRFS_MAX_ROOTREF_BUFFER_NUM +#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255 +#endif + +struct btrfs_ioctl_get_subvol_rootref_args { + __u64 min_treeid; + struct { + __u64 treeid; + __u64 dirid; + } rootref[BTRFS_MAX_ROOTREF_BUFFER_NUM]; + __u8 num_items; + __u8 align[7]; +}; +#endif + +#ifndef BTRFS_IOCTL_MAGIC +#define BTRFS_IOCTL_MAGIC 0x94 +#endif + +#ifndef BTRFS_IOC_SNAP_DESTROY +#define BTRFS_IOC_SNAP_DESTROY \ + _IOW(BTRFS_IOCTL_MAGIC, 15, struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_SNAP_DESTROY_V2 +#define BTRFS_IOC_SNAP_DESTROY_V2 \ + _IOW(BTRFS_IOCTL_MAGIC, 63, struct btrfs_ioctl_vol_args_v2) +#endif + +#ifndef BTRFS_IOC_SNAP_CREATE_V2 +#define BTRFS_IOC_SNAP_CREATE_V2 \ + _IOW(BTRFS_IOCTL_MAGIC, 23, struct btrfs_ioctl_vol_args_v2) +#endif + +#ifndef BTRFS_IOC_SUBVOL_CREATE_V2 +#define BTRFS_IOC_SUBVOL_CREATE_V2 \ + _IOW(BTRFS_IOCTL_MAGIC, 24, struct btrfs_ioctl_vol_args_v2) +#endif + +#ifndef BTRFS_IOC_SUBVOL_GETFLAGS +#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) +#endif + +#ifndef BTRFS_IOC_SUBVOL_SETFLAGS +#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) +#endif + +#ifndef BTRFS_IOC_INO_LOOKUP +#define BTRFS_IOC_INO_LOOKUP \ + _IOWR(BTRFS_IOCTL_MAGIC, 18, struct btrfs_ioctl_ino_lookup_args) +#endif + +#ifndef BTRFS_IOC_INO_LOOKUP_USER +#define BTRFS_IOC_INO_LOOKUP_USER \ + _IOWR(BTRFS_IOCTL_MAGIC, 62, struct btrfs_ioctl_ino_lookup_user_args) +#endif + +#ifndef BTRFS_IOC_GET_SUBVOL_ROOTREF +#define BTRFS_IOC_GET_SUBVOL_ROOTREF \ + _IOWR(BTRFS_IOCTL_MAGIC, 61, struct btrfs_ioctl_get_subvol_rootref_args) +#endif + +#ifndef BTRFS_SUBVOL_RDONLY +#define BTRFS_SUBVOL_RDONLY (1ULL << 1) +#endif + +#ifndef BTRFS_SUBVOL_SPEC_BY_ID +#define BTRFS_SUBVOL_SPEC_BY_ID (1ULL << 4) +#endif + +#ifndef BTRFS_FIRST_FREE_OBJECTID +#define BTRFS_FIRST_FREE_OBJECTID 256ULL +#endif + +static int btrfs_delete_subvolume(int parent_fd, const char *name) +{ + struct btrfs_ioctl_vol_args args = {}; + size_t len; + int ret; + + len = strlen(name); + if (len >= sizeof(args.name)) + return -ENAMETOOLONG; + + memcpy(args.name, name, len); + args.name[len] = '\0'; + + ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY, &args); + if (ret < 0) + return -1; + + return 0; +} + +static int btrfs_delete_subvolume_id(int parent_fd, uint64_t subvolid) +{ + struct btrfs_ioctl_vol_args_v2 args = {}; + int ret; + + args.flags = BTRFS_SUBVOL_SPEC_BY_ID; + args.subvolid = subvolid; + + ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY_V2, &args); + if (ret < 0) + return -1; + + return 0; +} + +static int btrfs_create_subvolume(int parent_fd, const char *name) +{ + struct btrfs_ioctl_vol_args_v2 args = {}; + size_t len; + int ret; + + len = strlen(name); + if (len >= sizeof(args.name)) + return -ENAMETOOLONG; + + memcpy(args.name, name, len); + args.name[len] = '\0'; + + ret = ioctl(parent_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args); + if (ret < 0) + return -1; + + return 0; +} + +static int btrfs_create_snapshot(int fd, int parent_fd, const char *name, + int flags) +{ + struct btrfs_ioctl_vol_args_v2 args = { + .fd = fd, + }; + size_t len; + int ret; + + if (flags & ~BTRFS_SUBVOL_RDONLY) + return -EINVAL; + + len = strlen(name); + if (len >= sizeof(args.name)) + return -ENAMETOOLONG; + memcpy(args.name, name, len); + args.name[len] = '\0'; + + if (flags & BTRFS_SUBVOL_RDONLY) + args.flags |= BTRFS_SUBVOL_RDONLY; + ret = ioctl(parent_fd, BTRFS_IOC_SNAP_CREATE_V2, &args); + if (ret < 0) + return -1; + + return 0; +} + +static int btrfs_get_subvolume_ro(int fd, bool *read_only_ret) +{ + uint64_t flags; + int ret; + + ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); + if (ret < 0) + return -1; + + *read_only_ret = flags & BTRFS_SUBVOL_RDONLY; + return 0; +} + +static int btrfs_set_subvolume_ro(int fd, bool read_only) +{ + uint64_t flags; + int ret; + + ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); + if (ret < 0) + return -1; + + if (read_only) + flags |= BTRFS_SUBVOL_RDONLY; + else + flags &= ~BTRFS_SUBVOL_RDONLY; + + ret = ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags); + if (ret < 0) + return -1; + + return 0; +} + +static int btrfs_get_subvolume_id(int fd, uint64_t *id_ret) +{ + struct btrfs_ioctl_ino_lookup_args args = { + .treeid = 0, + .objectid = BTRFS_FIRST_FREE_OBJECTID, + }; + int ret; + + ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args); + if (ret < 0) + return -1; + + *id_ret = args.treeid; + + return 0; +} + +/* + * The following helpers are adapted from the btrfsutils library. We can't use + * the library directly since we need full control over how the subvolume + * iteration happens. We need to be able to check whether unprivileged + * subvolume iteration is possible, i.e. whether BTRFS_IOC_INO_LOOKUP_USER is + * available and also ensure that it is actually used when looking up paths. + */ +struct btrfs_stack { + uint64_t tree_id; + struct btrfs_ioctl_get_subvol_rootref_args rootref_args; + size_t items_pos; + size_t path_len; +}; + +struct btrfs_iter { + int fd; + int cur_fd; + + struct btrfs_stack *search_stack; + size_t stack_len; + size_t stack_capacity; + + char *cur_path; + size_t cur_path_capacity; +}; + +static struct btrfs_stack *top_stack_entry(struct btrfs_iter *iter) +{ + return &iter->search_stack[iter->stack_len - 1]; +} + +static int pop_stack(struct btrfs_iter *iter) +{ + struct btrfs_stack *top, *parent; + int fd, parent_fd; + size_t i; + + if (iter->stack_len == 1) { + iter->stack_len--; + return 0; + } + + top = top_stack_entry(iter); + iter->stack_len--; + parent = top_stack_entry(iter); + + fd = iter->cur_fd; + for (i = parent->path_len; i < top->path_len; i++) { + if (i == 0 || iter->cur_path[i] == '/') { + parent_fd = openat(fd, "..", O_RDONLY); + if (fd != iter->cur_fd) + close(fd); + if (parent_fd == -1) + return -1; + fd = parent_fd; + } + } + if (iter->cur_fd != iter->fd) + close(iter->cur_fd); + iter->cur_fd = fd; + + return 0; +} + +static int append_stack(struct btrfs_iter *iter, uint64_t tree_id, size_t path_len) +{ + struct btrfs_stack *entry; + + if (iter->stack_len >= iter->stack_capacity) { + size_t new_capacity = iter->stack_capacity * 2; + struct btrfs_stack *new_search_stack; +#ifdef HAVE_REALLOCARRAY + new_search_stack = reallocarray(iter->search_stack, new_capacity, + sizeof(*iter->search_stack)); +#else + new_search_stack = realloc(iter->search_stack, new_capacity * sizeof(*iter->search_stack)); +#endif + if (!new_search_stack) + return -ENOMEM; + + iter->stack_capacity = new_capacity; + iter->search_stack = new_search_stack; + } + + entry = &iter->search_stack[iter->stack_len]; + + memset(entry, 0, sizeof(*entry)); + entry->path_len = path_len; + entry->tree_id = tree_id; + + if (iter->stack_len) { + struct btrfs_stack *top; + char *path; + int fd; + + top = top_stack_entry(iter); + path = &iter->cur_path[top->path_len]; + if (*path == '/') + path++; + fd = openat(iter->cur_fd, path, O_RDONLY); + if (fd == -1) + return -errno; + + close(iter->cur_fd); + iter->cur_fd = fd; + } + + iter->stack_len++; + + return 0; +} + +static int btrfs_iterator_start(int fd, uint64_t top, struct btrfs_iter **ret) +{ + struct btrfs_iter *iter; + int err; + + iter = malloc(sizeof(*iter)); + if (!iter) + return -ENOMEM; + + iter->fd = fd; + iter->cur_fd = fd; + + iter->stack_len = 0; + iter->stack_capacity = 4; + iter->search_stack = malloc(sizeof(*iter->search_stack) * + iter->stack_capacity); + if (!iter->search_stack) { + err = -ENOMEM; + goto out_iter; + } + + iter->cur_path_capacity = 256; + iter->cur_path = malloc(iter->cur_path_capacity); + if (!iter->cur_path) { + err = -ENOMEM; + goto out_search_stack; + } + + err = append_stack(iter, top, 0); + if (err) + goto out_cur_path; + + *ret = iter; + + return 0; + +out_cur_path: + free(iter->cur_path); +out_search_stack: + free(iter->search_stack); +out_iter: + free(iter); + return err; +} + +static void btrfs_iterator_end(struct btrfs_iter *iter) +{ + if (iter) { + free(iter->cur_path); + free(iter->search_stack); + if (iter->cur_fd != iter->fd) + close(iter->cur_fd); + close(iter->fd); + free(iter); + } +} + +static int __append_path(struct btrfs_iter *iter, const char *name, + size_t name_len, const char *dir, size_t dir_len, + size_t *path_len_ret) +{ + struct btrfs_stack *top = top_stack_entry(iter); + size_t path_len; + char *p; + + path_len = top->path_len; + /* + * We need a joining slash if we have a current path and a subdirectory. + */ + if (top->path_len && dir_len) + path_len++; + path_len += dir_len; + /* + * We need another joining slash if we have a current path and a name, + * but not if we have a subdirectory, because the lookup ioctl includes + * a trailing slash. + */ + if (top->path_len && !dir_len && name_len) + path_len++; + path_len += name_len; + + /* We need one extra character for the NUL terminator. */ + if (path_len + 1 > iter->cur_path_capacity) { + char *tmp = realloc(iter->cur_path, path_len + 1); + + if (!tmp) + return -ENOMEM; + iter->cur_path = tmp; + iter->cur_path_capacity = path_len + 1; + } + + p = iter->cur_path + top->path_len; + if (top->path_len && dir_len) + *p++ = '/'; + memcpy(p, dir, dir_len); + p += dir_len; + if (top->path_len && !dir_len && name_len) + *p++ = '/'; + memcpy(p, name, name_len); + p += name_len; + *p = '\0'; + + *path_len_ret = path_len; + + return 0; +} + +static int get_subvolume_path(struct btrfs_iter *iter, uint64_t treeid, + uint64_t dirid, size_t *path_len_ret) +{ + struct btrfs_ioctl_ino_lookup_user_args args = { + .treeid = treeid, + .dirid = dirid, + }; + int ret; + + ret = ioctl(iter->cur_fd, BTRFS_IOC_INO_LOOKUP_USER, &args); + if (ret == -1) + return -1; + + return __append_path(iter, args.name, strlen(args.name), args.path, + strlen(args.path), path_len_ret); +} + +static int btrfs_iterator_next(struct btrfs_iter *iter, char **path_ret, + uint64_t *id_ret) +{ + struct btrfs_stack *top; + uint64_t treeid, dirid; + size_t path_len; + int ret, err; + + for (;;) { + for (;;) { + if (iter->stack_len == 0) + return 1; + + top = top_stack_entry(iter); + if (top->items_pos < top->rootref_args.num_items) { + break; + } else { + ret = ioctl(iter->cur_fd, + BTRFS_IOC_GET_SUBVOL_ROOTREF, + &top->rootref_args); + if (ret == -1 && errno != EOVERFLOW) + return -1; + top->items_pos = 0; + + if (top->rootref_args.num_items == 0) { + err = pop_stack(iter); + if (err) + return err; + } + } + } + + treeid = top->rootref_args.rootref[top->items_pos].treeid; + dirid = top->rootref_args.rootref[top->items_pos].dirid; + top->items_pos++; + err = get_subvolume_path(iter, treeid, dirid, &path_len); + if (err) { + /* Skip the subvolume if we can't access it. */ + if (errno == EACCES) + continue; + return err; + } + + err = append_stack(iter, treeid, path_len); + if (err) { + /* + * Skip the subvolume if it does not exist (which can + * happen if there is another filesystem mounted over a + * parent directory) or we don't have permission to + * access it. + */ + if (errno == ENOENT || errno == EACCES) + continue; + return err; + } + + top = top_stack_entry(iter); + goto out; + } + +out: + if (path_ret) { + *path_ret = malloc(top->path_len + 1); + if (!*path_ret) + return -ENOMEM; + memcpy(*path_ret, iter->cur_path, top->path_len); + (*path_ret)[top->path_len] = '\0'; + } + if (id_ret) + *id_ret = top->tree_id; + return 0; +} + +#define BTRFS_SUBVOLUME1 "subvol1" +#define BTRFS_SUBVOLUME1_SNAPSHOT1 "subvol1_snapshot1" +#define BTRFS_SUBVOLUME1_SNAPSHOT1_RO "subvol1_snapshot1_ro" +#define BTRFS_SUBVOLUME1_RENAME "subvol1_rename" +#define BTRFS_SUBVOLUME2 "subvol2" + +static int btrfs_subvolumes_fsids_mapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_up()) + die("failure: raise caps"); + + /* + * The caller's fsids now have mappings in the idmapped mount so + * any file creation must succeed. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: check ownership"); + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!caps_down()) + die("failure: lower caps"); + + /* + * The filesystem is not mounted with user_subvol_rm_allowed so + * subvolume deletion must fail. + */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + if (errno != EPERM) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: check ownership"); + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_fsids_mapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* The caller's fsids now have mappings in the idmapped mount so + * any file creation must fail. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) + die("failure: check ownership"); + + /* remove subvolume */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_fsids_unmapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + /* create directory for rename test */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + /* change ownership of all files to uid 0 */ + if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + if (!switch_fsids(0, 0)) { + log_stderr("failure: switch_fsids"); + goto out; + } + + /* + * The caller's fsids don't have a mappings in the idmapped mount so + * any file creation must fail. + */ + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + if (errno != EOVERFLOW) { + log_stderr("failure: errno"); + goto out; + } + + /* try to rename a subvolume */ + if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, + BTRFS_SUBVOLUME1_RENAME)) { + log_stderr("failure: renameat"); + goto out; + } + if (errno != EOVERFLOW) { + log_stderr("failure: errno"); + goto out; + } + + /* The caller is privileged over the inode so file deletion must work. */ + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_fsids_unmapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + /* create directory for rename test */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + /* change ownership of all files to uid 0 */ + if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + userns_fd = get_userns_fd(0, 30000, 10000); + if (userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + /* + * The caller's fsids don't have a mappings in the idmapped mount so + * any file creation must fail. + */ + + /* create subvolume */ + if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) + die("failure: btrfs_create_subvolume"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* try to rename a subvolume */ + if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, + BTRFS_SUBVOLUME1_RENAME)) + die("failure: renameat"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* + * The caller is not privileged over the inode so subvolume + * deletion must fail. + */ + + /* remove subvolume */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + safe_close(userns_fd); + + return fret; +} + +static int btrfs_snapshots_fsids_mapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_up()) + die("failure: raise caps"); + + /* The caller's fsids now have mappings in the idmapped mount so + * any file creation must fail. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + /* create read-only snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + safe_close(subvolume_fd); + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + /* remove read-write snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) + die("failure: btrfs_delete_subvolume"); + + /* remove read-only snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) + die("failure: btrfs_delete_subvolume"); + + /* create directory */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + /* create read-only snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + /* remove read-write snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + /* remove read-only snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_fsids_mapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + /* create read-only snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0)) + die("failure: expected_uid_gid"); + + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + /* remove read-write snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + /* remove read-only snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_fsids_unmapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* create directory for rename test */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + /* change ownership of all files to uid 0 */ + if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + + if (!switch_fsids(0, 0)) { + log_stderr("failure: switch_fsids"); + goto out; + } + + /* + * The caller's fsids don't have a mappings in the idmapped + * mount so any file creation must fail. + */ + + /* + * The open_tree() syscall returns an O_PATH file descriptor + * which we can't use with ioctl(). So let's reopen it as a + * proper file descriptor. + */ + tree_fd = openat(open_tree_fd, ".", + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) + die("failure: openat"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create directory */ + if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) + die("failure: btrfs_create_subvolume"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* create read-write snapshot */ + if (!btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* create read-only snapshot */ + if (!btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* try to rename a directory */ + if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, + BTRFS_SUBVOLUME1_RENAME)) + die("failure: renameat"); + if (errno != EOVERFLOW) + die("failure: errno"); + + if (!caps_down()) + die("failure: caps_down"); + + /* create read-write snapshot */ + if (!btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + if (errno != EPERM) + die("failure: errno"); + + /* create read-only snapshot */ + if (!btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + if (errno != EPERM) + die("failure: errno"); + + /* + * The caller is not privileged over the inode so subvolume + * deletion must fail. + */ + + /* remove directory */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + if (errno != EPERM) + die("failure: errno"); + + if (!caps_up()) + die("failure: caps_down"); + + /* + * The caller is privileged over the inode so subvolume + * deletion must work. + */ + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_fsids_unmapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF, + userns_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* create directory for rename test */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + /* change ownership of all files to uid 0 */ + if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + userns_fd = get_userns_fd(0, 30000, 10000); + if (userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor + * which we can't use with ioctl(). So let's reopen it as a + * proper file descriptor. + */ + tree_fd = openat(open_tree_fd, ".", + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + /* + * The caller's fsids don't have a mappings in the idmapped + * mount so any file creation must fail. + */ + + /* create directory */ + if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) + die("failure: btrfs_create_subvolume"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* create read-write snapshot */ + if (!btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + if (errno != EPERM) + die("failure: errno"); + + /* create read-only snapshot */ + if (!btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + if (errno != EPERM) + die("failure: errno"); + + /* try to rename a directory */ + if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, + BTRFS_SUBVOLUME1_RENAME)) + die("failure: renameat"); + if (errno != EOVERFLOW) + die("failure: errno"); + + /* + * The caller is not privileged over the inode so subvolume + * deletion must fail. + */ + + /* remove directory */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + if (errno != EPERM) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(subvolume_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: raise caps"); + + /* + * The caller's fsids now have mappings in the idmapped mount so + * any file creation must succedd. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: check ownership"); + + /* + * The scratch device is mounted with user_subvol_rm_allowed so + * subvolume deletion must succeed. + */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* The caller's fsids now have mappings in the idmapped mount so + * any file creation must fail. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) + die("failure: check ownership"); + + /* + * The scratch device is mounted with user_subvol_rm_allowed so + * subvolume deletion must succeed. + */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: raise caps"); + + /* + * The caller's fsids now have mappings in the idmapped mount so + * any file creation must succeed. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + /* create read-only snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + safe_close(subvolume_fd); + + /* remove subvolume */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + /* remove read-write snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) + die("failure: btrfs_delete_subvolume"); + + /* remove read-only snapshot */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) + die("failure: btrfs_delete_subvolume"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + if (btrfs_set_subvolume_ro(subvolume_fd, false)) + die("failure: btrfs_set_subvolume_ro"); + + safe_close(subvolume_fd); + + /* remove read-only snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + /* create read-only snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + BTRFS_SUBVOL_RDONLY)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0)) + die("failure: expected_uid_gid"); + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_delete_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + /* remove read-write snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) + die("failure: btrfs_delete_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0)) + die("failure: expected_uid_gid"); + + /* remove read-only snapshot */ + if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) + die("failure: btrfs_delete_subvolume"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + if (btrfs_set_subvolume_ro(subvolume_fd, false)) + die("failure: btrfs_set_subvolume_ro"); + + safe_close(subvolume_fd); + + /* remove read-only snapshot */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) + die("failure: btrfs_delete_subvolume"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_delete_by_spec_id(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF; + uint64_t subvolume_id1 = -EINVAL, subvolume_id2 = -EINVAL; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "A")) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "B")) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + subvolume_fd = openat(info->t_mnt_scratch_fd, "B", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(subvolume_fd, "C")) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + safe_close(subvolume_fd); + + subvolume_fd = openat(info->t_mnt_scratch_fd, "A", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id1)) { + log_stderr("failure: btrfs_get_subvolume_id"); + goto out; + } + + subvolume_fd = openat(info->t_mnt_scratch_fd, "B/C", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id2)) { + log_stderr("failure: btrfs_get_subvolume_id"); + goto out; + } + + if (sys_mount(info->t_device_scratch, info->t_mountpoint, "btrfs", 0, "subvol=B/C")) { + log_stderr("failure: mount"); + goto out; + } + + open_tree_fd = sys_open_tree(-EBADF, info->t_mountpoint, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + /* + * The subvolume isn't exposed in the idmapped mount so + * delation via spec id must fail. + */ + if (!btrfs_delete_subvolume_id(tree_fd, subvolume_id1)) + die("failure: btrfs_delete_subvolume_id"); + if (errno != EOPNOTSUPP) + die("failure: errno"); + + if (btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id1)) + die("failure: btrfs_delete_subvolume_id"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + sys_umount2(info->t_mountpoint, MNT_DETACH); + btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id2); + btrfs_delete_subvolume(info->t_mnt_scratch_fd, "B"); + + return fret; +} + +static int btrfs_subvolumes_setflags_fsids_mapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + bool read_only = false; + + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: raise caps"); + + /* The caller's fsids now have mappings in the idmapped mount so + * any file creation must fail. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (btrfs_set_subvolume_ro(subvolume_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (!read_only) + die("failure: not read_only"); + + if (btrfs_set_subvolume_ro(subvolume_fd, false)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_setflags_fsids_mapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + bool read_only = false; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* The caller's fsids now have mappings in the idmapped mount so + * any file creation must fail. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (btrfs_set_subvolume_ro(subvolume_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (!read_only) + die("failure: not read_only"); + + if (btrfs_set_subvolume_ro(subvolume_fd, false)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_setflags_fsids_unmapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + bool read_only = false; + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + if (!switch_fsids(0, 0)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: raise caps"); + + /* + * The caller's fsids don't have mappings in the idmapped mount + * so any file creation must fail. + */ + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (!btrfs_set_subvolume_ro(subvolume_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + if (errno != EPERM) + die("failure: errno"); + + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_subvolumes_setflags_fsids_unmapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + userns_fd = get_userns_fd(0, 30000, 10000); + if (userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int subvolume_fd = -EBADF; + bool read_only = false; + + /* + * The caller's fsids don't have mappings in the idmapped mount + * so any file creation must fail. + */ + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + if (!switch_userns(userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (!btrfs_set_subvolume_ro(subvolume_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + if (errno != EPERM) + die("failure: errno"); + + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + safe_close(userns_fd); + + return fret; +} + +static int btrfs_snapshots_setflags_fsids_mapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int snapshot_fd = -EBADF, subvolume_fd = -EBADF; + bool read_only = false; + + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: raise caps"); + + /* + * The caller's fsids now have mappings in the idmapped mount + * so any file creation must succeed. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) + die("failure: expected_uid_gid"); + + snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (snapshot_fd < 0) + die("failure: openat"); + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (btrfs_set_subvolume_ro(snapshot_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (!read_only) + die("failure: not read_only"); + + if (btrfs_set_subvolume_ro(snapshot_fd, false)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + safe_close(snapshot_fd); + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_setflags_fsids_mapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int snapshot_fd = -EBADF, subvolume_fd = -EBADF; + bool read_only = false; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* + * The caller's fsids now have mappings in the idmapped mount so + * any file creation must succeed. + */ + + /* create subvolume */ + if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) + die("failure: btrfs_create_subvolume"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) + die("failure: openat"); + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, tree_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) + die("failure: btrfs_create_snapshot"); + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (snapshot_fd < 0) + die("failure: openat"); + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (btrfs_set_subvolume_ro(snapshot_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (!read_only) + die("failure: not read_only"); + + if (btrfs_set_subvolume_ro(snapshot_fd, false)) + die("failure: btrfs_set_subvolume_ro"); + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + safe_close(snapshot_fd); + safe_close(subvolume_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_setflags_fsids_unmapped(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) { + log_stderr("failure: btrfs_create_snapshot"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int snapshot_fd = -EBADF; + bool read_only = false; + + snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (snapshot_fd < 0) + die("failure: openat"); + + if (!switch_fsids(0, 0)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: raise caps"); + + /* + * The caller's fsids don't have mappings in the idmapped mount + * so any file creation must fail. + */ + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (!btrfs_set_subvolume_ro(snapshot_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + if (errno != EPERM) + die("failure: errno"); + + safe_close(snapshot_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(subvolume_fd); + safe_close(tree_fd); + + return fret; +} + +static int btrfs_snapshots_setflags_fsids_unmapped_userns(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF, + userns_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + userns_fd = get_userns_fd(0, 30000, 10000); + if (userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (subvolume_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create read-write snapshot */ + if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd, + BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) { + log_stderr("failure: btrfs_create_snapshot"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int snapshot_fd = -EBADF; + bool read_only = false; + + /* + * The caller's fsids don't have mappings in the idmapped mount + * so any file creation must fail. + */ + + snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, + O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (snapshot_fd < 0) + die("failure: openat"); + + + if (!switch_userns(userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, + info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + /* + * The caller's fsids don't have mappings in the idmapped mount + * so any file creation must fail. + */ + + if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) + die("failure: btrfs_get_subvolume_ro"); + + if (read_only) + die("failure: read_only"); + + if (!btrfs_set_subvolume_ro(snapshot_fd, true)) + die("failure: btrfs_set_subvolume_ro"); + if (errno != EPERM) + die("failure: errno"); + + safe_close(snapshot_fd); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + /* remove directory */ + if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { + log_stderr("failure: btrfs_delete_subvolume"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(open_tree_fd); + safe_close(subvolume_fd); + safe_close(tree_fd); + safe_close(userns_fd); + + return fret; +} + +#define BTRFS_SUBVOLUME_SUBVOL1 "subvol1" +#define BTRFS_SUBVOLUME_SUBVOL2 "subvol2" +#define BTRFS_SUBVOLUME_SUBVOL3 "subvol3" +#define BTRFS_SUBVOLUME_SUBVOL4 "subvol4" + +#define BTRFS_SUBVOLUME_SUBVOL1_ID 0 +#define BTRFS_SUBVOLUME_SUBVOL2_ID 1 +#define BTRFS_SUBVOLUME_SUBVOL3_ID 2 +#define BTRFS_SUBVOLUME_SUBVOL4_ID 3 + +#define BTRFS_SUBVOLUME_DIR1 "dir1" +#define BTRFS_SUBVOLUME_DIR2 "dir2" + +#define BTRFS_SUBVOLUME_MNT "mnt_subvolume1" + +#define BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3 "subvol1/subvol3" +#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2 "subvol1/dir1/dir2" +#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4 "subvol1/dir1/dir2/subvol4" + +/* + * We create the following mount layout to test lookup: + * + * |-/mnt/test /dev/loop0 btrfs rw,relatime,space_cache,subvolid=5,subvol=/ + * | |-/mnt/test/mnt1 /dev/loop1[/subvol1] btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=268,subvol=/subvol1 + * '-/mnt/scratch /dev/loop1 btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=5,subvol=/ + */ +static int btrfs_subvolume_lookup_user(const struct vfstest_info *info) +{ + int fret = -1, i; + int dir1_fd = -EBADF, dir2_fd = -EBADF, mnt_fd = -EBADF, + open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF; + int subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID + 1]; + uint64_t subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID + 1]; + uint64_t subvolid = -EINVAL; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + struct btrfs_iter *iter; + + if (!caps_supported()) + return 0; + + for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++) + subvolume_fds[i] = -EBADF; + + for (i = 0; i < ARRAY_SIZE(subvolume_ids); i++) + subvolume_ids[i] = -EINVAL; + + if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL1)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL2)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] = openat(info->t_mnt_scratch_fd, + BTRFS_SUBVOLUME_SUBVOL1, + O_CLOEXEC | O_DIRECTORY); + if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* create subvolume */ + if (btrfs_create_subvolume(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_SUBVOL3)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (mkdirat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir1_fd = openat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1, + O_CLOEXEC | O_DIRECTORY); + if (dir1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (mkdirat(dir1_fd, BTRFS_SUBVOLUME_DIR2, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir2_fd = openat(dir1_fd, BTRFS_SUBVOLUME_DIR2, O_CLOEXEC | O_DIRECTORY); + if (dir2_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (btrfs_create_subvolume(dir2_fd, BTRFS_SUBVOLUME_SUBVOL4)) { + log_stderr("failure: btrfs_create_subvolume"); + goto out; + } + + if (mkdirat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT); + if (sys_mount(info->t_device_scratch, t_buf, "btrfs", 0, + "subvol=" BTRFS_SUBVOLUME_SUBVOL1)) { + log_stderr("failure: mount"); + goto out; + } + + mnt_fd = openat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, O_CLOEXEC | O_DIRECTORY); + if (mnt_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (chown_r(info->t_mnt_scratch_fd, ".", 1000, 1000)) { + log_stderr("failure: chown_r"); + goto out; + } + + subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] = openat(info->t_mnt_scratch_fd, + BTRFS_SUBVOLUME_SUBVOL2, + O_CLOEXEC | O_DIRECTORY); + if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], + &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL1_ID])) { + log_stderr("failure: btrfs_get_subvolume_id"); + goto out; + } + + if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID], + &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL2_ID])) { + log_stderr("failure: btrfs_get_subvolume_id"); + goto out; + } + + subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] = openat(info->t_mnt_scratch_fd, + BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3, + O_CLOEXEC | O_DIRECTORY); + if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID], + &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])) { + log_stderr("failure: btrfs_get_subvolume_id"); + goto out; + } + + subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] = openat(info->t_mnt_scratch_fd, + BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4, + O_CLOEXEC | O_DIRECTORY); + if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID], + &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])) { + log_stderr("failure: btrfs_get_subvolume_id"); + goto out; + } + + + if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(mnt_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* + * The open_tree() syscall returns an O_PATH file descriptor which we + * can't use with ioctl(). So let's reopen it as a proper file + * descriptor. + */ + tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (tree_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + bool subvolume3_found = false, subvolume4_found = false; + + if (!switch_fsids(11000, 11000)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: lower caps"); + + if (btrfs_iterator_start(tree_fd, 0, &iter)) + die("failure: btrfs_iterator_start"); + + for (;;) { + char *subvol_path = NULL; + int ret; + + ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); + if (ret == 1) + break; + else if (ret) + die("failure: btrfs_iterator_next"); + + if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] && + subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) + die("failure: subvolume id %llu->%s", + (long long unsigned)subvolid, subvol_path); + + if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID]) + subvolume3_found = true; + + if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) + subvolume4_found = true; + + free(subvol_path); + } + btrfs_iterator_end(iter); + + if (!subvolume3_found || !subvolume4_found) + die("failure: subvolume id"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + bool subvolume3_found = false, subvolume4_found = false; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (btrfs_iterator_start(tree_fd, 0, &iter)) + die("failure: btrfs_iterator_start"); + + for (;;) { + char *subvol_path = NULL; + int ret; + + ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); + if (ret == 1) + break; + else if (ret) + die("failure: btrfs_iterator_next"); + + if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] && + subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) + die("failure: subvolume id %llu->%s", + (long long unsigned)subvolid, subvol_path); + + if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID]) + subvolume3_found = true; + + if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) + subvolume4_found = true; + + free(subvol_path); + } + btrfs_iterator_end(iter); + + if (!subvolume3_found || !subvolume4_found) + die("failure: subvolume id"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + bool subvolume_found = false; + + if (!switch_fsids(0, 0)) + die("failure: switch fsids"); + + if (!caps_down()) + die("failure: lower caps"); + + if (btrfs_iterator_start(tree_fd, 0, &iter)) + die("failure: btrfs_iterator_start"); + + for (;;) { + char *subvol_path = NULL; + int ret; + + ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); + if (ret == 1) + break; + else if (ret) + die("failure: btrfs_iterator_next"); + + free(subvol_path); + + subvolume_found = true; + break; + } + btrfs_iterator_end(iter); + + if (subvolume_found) + die("failure: subvolume id"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + userns_fd = get_userns_fd(0, 30000, 10000); + if (userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + bool subvolume_found = false; + + if (!switch_userns(userns_fd, 0, 0, true)) + die("failure: switch_userns"); + + if (btrfs_iterator_start(tree_fd, 0, &iter)) + die("failure: btrfs_iterator_start"); + + for (;;) { + char *subvol_path = NULL; + int ret; + + ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); + if (ret == 1) + break; + else if (ret) + die("failure: btrfs_iterator_next"); + + free(subvol_path); + + subvolume_found = true; + break; + } + btrfs_iterator_end(iter); + + if (subvolume_found) + die("failure: subvolume id"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(dir1_fd); + safe_close(dir2_fd); + safe_close(open_tree_fd); + safe_close(tree_fd); + safe_close(userns_fd); + for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++) + safe_close(subvolume_fds[i]); + snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT); + sys_umount2(t_buf, MNT_DETACH); + unlinkat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, AT_REMOVEDIR); + + return fret; +} + +static const struct test_struct t_btrfs[] = { + { btrfs_subvolumes_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids", }, + { btrfs_subvolumes_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids inside user namespace", }, + { btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option", }, + { btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option inside user namespace", }, + { btrfs_subvolumes_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids", }, + { btrfs_subvolumes_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids inside user namespace", }, + { btrfs_snapshots_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids", }, + { btrfs_snapshots_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids inside user namespace", }, + { btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option", }, + { btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option inside user namespace", }, + { btrfs_snapshots_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids", }, + { btrfs_snapshots_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids inside user namespace", }, + { btrfs_delete_by_spec_id, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion by spec id", }, + { btrfs_subvolumes_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids", }, + { btrfs_subvolumes_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids inside user namespace", }, + { btrfs_subvolumes_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids", }, + { btrfs_subvolumes_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids inside user namespace", }, + { btrfs_snapshots_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids", }, + { btrfs_snapshots_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids inside user namespace", }, + { btrfs_snapshots_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids", }, + { btrfs_snapshots_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids inside user namespace", }, + { btrfs_subvolume_lookup_user, T_REQUIRE_IDMAPPED_MOUNTS, "test unprivileged subvolume lookup", }, +}; + +const struct test_suite s_btrfs_idmapped_mounts = { + .tests = t_btrfs, + .nr_tests = ARRAY_SIZE(t_btrfs), +}; diff --git a/src/vfs/btrfs-idmapped-mounts.h b/src/vfs/btrfs-idmapped-mounts.h new file mode 100644 index 00000000..ed714640 --- /dev/null +++ b/src/vfs/btrfs-idmapped-mounts.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __BTRFS_IDMAPPED_MOUNTS_H +#define __BTRFS_IDMAPPED_MOUNTS_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "utils.h" + +extern const struct test_suite s_btrfs_idmapped_mounts; + +#endif /* __BTRFS_IDMAPPED_MOUNTS_H */ + diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c index d90eaa7a..f9399b26 100644 --- a/src/vfs/vfstest.c +++ b/src/vfs/vfstest.c @@ -23,18 +23,7 @@ #include #include -#ifdef HAVE_LINUX_BTRFS_H -# ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID -# define btrfs_ioctl_vol_args_v2 override_btrfs_ioctl_vol_args_v2 -# endif -#include -# undef btrfs_ioctl_vol_args_v2 -#endif - -#ifdef HAVE_LINUX_BTRFS_TREE_H -#include -#endif - +#include "btrfs-idmapped-mounts.h" #include "idmapped-mounts.h" #include "missing.h" #include "utils.h" @@ -2646,4025 +2635,242 @@ out: return fret; } -#ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS - -#ifndef BTRFS_PATH_NAME_MAX -#define BTRFS_PATH_NAME_MAX 4087 -#endif +#define USER1 "fsgqa" +#define USER2 "fsgqa2" -struct btrfs_ioctl_vol_args { - __s64 fd; - char name[BTRFS_PATH_NAME_MAX + 1]; -}; -#endif +/** + * lookup_ids - lookup uid and gid for a username + * @name: [in] name of the user + * @uid: [out] pointer to the user-ID + * @gid: [out] pointer to the group-ID + * + * Lookup the uid and gid of a user. + * + * Return: On success, true is returned. + * On error, false is returned. + */ +static bool lookup_ids(const char *name, uid_t *uid, gid_t *gid) +{ + bool bret = false; + struct passwd *pwentp = NULL; + struct passwd pwent; + char *buf; + ssize_t bufsize; + int ret; -#ifndef HAVE_STRUCT_BTRFS_QGROUP_LIMIT -struct btrfs_qgroup_limit { - __u64 flags; - __u64 max_rfer; - __u64 max_excl; - __u64 rsv_rfer; - __u64 rsv_excl; -}; -#endif + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize < 0) + bufsize = 1024; -#ifndef HAVE_STRUCT_BTRFS_QGROUP_INHERIT -struct btrfs_qgroup_inherit { - __u64 flags; - __u64 num_qgroups; - __u64 num_ref_copies; - __u64 num_excl_copies; - struct btrfs_qgroup_limit lim; - __u64 qgroups[0]; -}; -#endif + buf = malloc(bufsize); + if (!buf) + return bret; -#if !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2) || !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID) + ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp); + if (!ret && pwentp) { + *uid = pwent.pw_uid; + *gid = pwent.pw_gid; + bret = true; + } -#ifndef BTRFS_SUBVOL_NAME_MAX -#define BTRFS_SUBVOL_NAME_MAX 4039 -#endif + free(buf); + return bret; +} -struct btrfs_ioctl_vol_args_v2 { - __s64 fd; - __u64 transid; - __u64 flags; - union { - struct { - __u64 size; - struct btrfs_qgroup_inherit *qgroup_inherit; - }; - __u64 unused[4]; - }; - union { - char name[BTRFS_SUBVOL_NAME_MAX + 1]; - __u64 devid; - __u64 subvolid; +/** + * setattr_fix_968219708108 - test for commit 968219708108 ("fs: handle circular mappings correctly") + * + * Test that ->setattr() works correctly for idmapped mounts with circular + * idmappings such as: + * + * b:1000:1001:1 + * b:1001:1000:1 + * + * Assume a directory /source with two files: + * + * /source/file1 | 1000:1000 + * /source/file2 | 1001:1001 + * + * and we create an idmapped mount of /source at /target with an idmapped of: + * + * mnt_userns: 1000:1001:1 + * 1001:1000:1 + * + * In the idmapped mount file1 will be owned by uid 1001 and file2 by uid 1000: + * + * /target/file1 | 1001:1001 + * /target/file2 | 1000:1000 + * + * Because in essence the idmapped mount switches ownership for {g,u}id 1000 + * and {g,u}id 1001. + * + * 1. A user with fs{g,u}id 1000 must be allowed to setattr /target/file2 from + * {g,u}id 1000 in the idmapped mount to {g,u}id 1000. + * 2. A user with fs{g,u}id 1001 must be allowed to setattr /target/file1 from + * {g,u}id 1001 in the idmapped mount to {g,u}id 1001. + * 3. A user with fs{g,u}id 1000 must fail to setattr /target/file1 from + * {g,u}id 1001 in the idmapped mount to {g,u}id 1000. + * This must fail with EPERM. The caller's fs{g,u}id doesn't match the + * {g,u}id of the file. + * 4. A user with fs{g,u}id 1001 must fail to setattr /target/file2 from + * {g,u}id 1000 in the idmapped mount to {g,u}id 1000. + * This must fail with EPERM. The caller's fs{g,u}id doesn't match the + * {g,u}id of the file. + * 5. Both, a user with fs{g,u}id 1000 and a user with fs{g,u}id 1001, must + * fail to setattr /target/file1 owned by {g,u}id 1001 in the idmapped mount + * and /target/file2 owned by {g,u}id 1000 in the idmapped mount to any + * {g,u}id apart from {g,u}id 1000 or 1001 with EINVAL. + * Only {g,u}id 1000 and 1001 have a mapping in the idmapped mount. Other + * {g,u}id are unmapped. + */ +static int setattr_fix_968219708108(const struct vfstest_info *info) +{ + int fret = -1; + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = -EBADF, }; -}; -#endif - -#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_ARGS - -#ifndef BTRFS_INO_LOOKUP_PATH_MAX -#define BTRFS_INO_LOOKUP_PATH_MAX 4080 -#endif -struct btrfs_ioctl_ino_lookup_args { - __u64 treeid; - __u64 objectid; - char name[BTRFS_INO_LOOKUP_PATH_MAX]; -}; -#endif - -#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_USER_ARGS + int ret; + uid_t user1_uid, user2_uid; + gid_t user1_gid, user2_gid; + pid_t pid; + struct list idmap; + struct list *it_cur, *it_next; -#ifndef BTRFS_VOL_NAME_MAX -#define BTRFS_VOL_NAME_MAX 255 -#endif + if (!caps_supported()) + return 0; -#ifndef BTRFS_INO_LOOKUP_USER_PATH_MAX -#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080 - BTRFS_VOL_NAME_MAX - 1) -#endif + list_init(&idmap); -struct btrfs_ioctl_ino_lookup_user_args { - __u64 dirid; - __u64 treeid; - char name[BTRFS_VOL_NAME_MAX + 1]; - char path[BTRFS_INO_LOOKUP_USER_PATH_MAX]; -}; -#endif + if (!lookup_ids(USER1, &user1_uid, &user1_gid)) { + log_stderr("failure: lookup_user"); + goto out; + } -#ifndef HAVE_STRUCT_BTRFS_IOCTL_GET_SUBVOL_ROOTREF_ARGS + if (!lookup_ids(USER2, &user2_uid, &user2_gid)) { + log_stderr("failure: lookup_user"); + goto out; + } -#ifndef BTRFS_MAX_ROOTREF_BUFFER_NUM -#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255 -#endif + log_debug("Found " USER1 " with uid(%d) and gid(%d) and " USER2 " with uid(%d) and gid(%d)", + user1_uid, user1_gid, user2_uid, user2_gid); -struct btrfs_ioctl_get_subvol_rootref_args { - __u64 min_treeid; - struct { - __u64 treeid; - __u64 dirid; - } rootref[BTRFS_MAX_ROOTREF_BUFFER_NUM]; - __u8 num_items; - __u8 align[7]; -}; -#endif + if (mkdirat(info->t_dir1_fd, DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } -#ifndef BTRFS_IOCTL_MAGIC -#define BTRFS_IOCTL_MAGIC 0x94 -#endif + if (mknodat(info->t_dir1_fd, DIR1 "/" FILE1, S_IFREG | 0644, 0)) { + log_stderr("failure: mknodat"); + goto out; + } -#ifndef BTRFS_IOC_SNAP_DESTROY -#define BTRFS_IOC_SNAP_DESTROY \ - _IOW(BTRFS_IOCTL_MAGIC, 15, struct btrfs_ioctl_vol_args) -#endif + if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) { + log_stderr("failure: chown_r"); + goto out; + } -#ifndef BTRFS_IOC_SNAP_DESTROY_V2 -#define BTRFS_IOC_SNAP_DESTROY_V2 \ - _IOW(BTRFS_IOCTL_MAGIC, 63, struct btrfs_ioctl_vol_args_v2) -#endif + if (mknodat(info->t_dir1_fd, DIR1 "/" FILE2, S_IFREG | 0644, 0)) { + log_stderr("failure: mknodat"); + goto out; + } -#ifndef BTRFS_IOC_SNAP_CREATE_V2 -#define BTRFS_IOC_SNAP_CREATE_V2 \ - _IOW(BTRFS_IOCTL_MAGIC, 23, struct btrfs_ioctl_vol_args_v2) -#endif + if (fchownat(info->t_dir1_fd, DIR1 "/" FILE2, user2_uid, user2_gid, AT_SYMLINK_NOFOLLOW)) { + log_stderr("failure: fchownat"); + goto out; + } -#ifndef BTRFS_IOC_SUBVOL_CREATE_V2 -#define BTRFS_IOC_SUBVOL_CREATE_V2 \ - _IOW(BTRFS_IOCTL_MAGIC, 24, struct btrfs_ioctl_vol_args_v2) -#endif + print_r(info->t_mnt_fd, T_DIR1); -#ifndef BTRFS_IOC_SUBVOL_GETFLAGS -#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) -#endif + /* u:1000:1001:1 */ + ret = add_map_entry(&idmap, user1_uid, user2_uid, 1, ID_TYPE_UID); + if (ret) { + log_stderr("failure: add_map_entry"); + goto out; + } -#ifndef BTRFS_IOC_SUBVOL_SETFLAGS -#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) -#endif + /* u:1001:1000:1 */ + ret = add_map_entry(&idmap, user2_uid, user1_uid, 1, ID_TYPE_UID); + if (ret) { + log_stderr("failure: add_map_entry"); + goto out; + } -#ifndef BTRFS_IOC_INO_LOOKUP -#define BTRFS_IOC_INO_LOOKUP \ - _IOWR(BTRFS_IOCTL_MAGIC, 18, struct btrfs_ioctl_ino_lookup_args) -#endif + /* g:1000:1001:1 */ + ret = add_map_entry(&idmap, user1_gid, user2_gid, 1, ID_TYPE_GID); + if (ret) { + log_stderr("failure: add_map_entry"); + goto out; + } -#ifndef BTRFS_IOC_INO_LOOKUP_USER -#define BTRFS_IOC_INO_LOOKUP_USER \ - _IOWR(BTRFS_IOCTL_MAGIC, 62, struct btrfs_ioctl_ino_lookup_user_args) -#endif + /* g:1001:1000:1 */ + ret = add_map_entry(&idmap, user2_gid, user1_gid, 1, ID_TYPE_GID); + if (ret) { + log_stderr("failure: add_map_entry"); + goto out; + } -#ifndef BTRFS_IOC_GET_SUBVOL_ROOTREF -#define BTRFS_IOC_GET_SUBVOL_ROOTREF \ - _IOWR(BTRFS_IOCTL_MAGIC, 61, struct btrfs_ioctl_get_subvol_rootref_args) -#endif + attr.userns_fd = get_userns_fd_from_idmap(&idmap); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } -#ifndef BTRFS_SUBVOL_RDONLY -#define BTRFS_SUBVOL_RDONLY (1ULL << 1) -#endif + open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE | + AT_RECURSIVE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } -#ifndef BTRFS_SUBVOL_SPEC_BY_ID -#define BTRFS_SUBVOL_SPEC_BY_ID (1ULL << 4) -#endif + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } -#ifndef BTRFS_FIRST_FREE_OBJECTID -#define BTRFS_FIRST_FREE_OBJECTID 256ULL -#endif + print_r(open_tree_fd, ""); -static int btrfs_delete_subvolume(int parent_fd, const char *name) -{ - struct btrfs_ioctl_vol_args args = {}; - size_t len; - int ret; + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + /* switch to {g,u}id 1001 */ + if (!switch_resids(user2_uid, user2_gid)) + die("failure: switch_resids"); - len = strlen(name); - if (len >= sizeof(args.name)) - return -ENAMETOOLONG; + /* drop all capabilities */ + if (!caps_down()) + die("failure: caps_down"); - memcpy(args.name, name, len); - args.name[len] = '\0'; + /* + * The {g,u}id 0 is not mapped in this idmapped mount so this + * needs to fail with EINVAL. + */ + if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW)) + die("failure: change ownership"); + if (errno != EINVAL) + die("failure: errno"); - ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY, &args); - if (ret < 0) - return -1; + /* + * A user with fs{g,u}id 1001 must be allowed to change + * ownership of /target/file1 owned by {g,u}id 1001 in this + * idmapped mount to {g,u}id 1001. + */ + if (fchownat(open_tree_fd, FILE1, user2_uid, user2_gid, + AT_SYMLINK_NOFOLLOW)) + die("failure: change ownership"); - return 0; -} - -static int btrfs_delete_subvolume_id(int parent_fd, uint64_t subvolid) -{ - struct btrfs_ioctl_vol_args_v2 args = {}; - int ret; - - args.flags = BTRFS_SUBVOL_SPEC_BY_ID; - args.subvolid = subvolid; - - ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY_V2, &args); - if (ret < 0) - return -1; - - return 0; -} - -static int btrfs_create_subvolume(int parent_fd, const char *name) -{ - struct btrfs_ioctl_vol_args_v2 args = {}; - size_t len; - int ret; - - len = strlen(name); - if (len >= sizeof(args.name)) - return -ENAMETOOLONG; - - memcpy(args.name, name, len); - args.name[len] = '\0'; - - ret = ioctl(parent_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args); - if (ret < 0) - return -1; - - return 0; -} - -static int btrfs_create_snapshot(int fd, int parent_fd, const char *name, - int flags) -{ - struct btrfs_ioctl_vol_args_v2 args = { - .fd = fd, - }; - size_t len; - int ret; - - if (flags & ~BTRFS_SUBVOL_RDONLY) - return -EINVAL; - - len = strlen(name); - if (len >= sizeof(args.name)) - return -ENAMETOOLONG; - memcpy(args.name, name, len); - args.name[len] = '\0'; - - if (flags & BTRFS_SUBVOL_RDONLY) - args.flags |= BTRFS_SUBVOL_RDONLY; - ret = ioctl(parent_fd, BTRFS_IOC_SNAP_CREATE_V2, &args); - if (ret < 0) - return -1; - - return 0; -} - -static int btrfs_get_subvolume_ro(int fd, bool *read_only_ret) -{ - uint64_t flags; - int ret; - - ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); - if (ret < 0) - return -1; - - *read_only_ret = flags & BTRFS_SUBVOL_RDONLY; - return 0; -} - -static int btrfs_set_subvolume_ro(int fd, bool read_only) -{ - uint64_t flags; - int ret; - - ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); - if (ret < 0) - return -1; - - if (read_only) - flags |= BTRFS_SUBVOL_RDONLY; - else - flags &= ~BTRFS_SUBVOL_RDONLY; - - ret = ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags); - if (ret < 0) - return -1; - - return 0; -} - -static int btrfs_get_subvolume_id(int fd, uint64_t *id_ret) -{ - struct btrfs_ioctl_ino_lookup_args args = { - .treeid = 0, - .objectid = BTRFS_FIRST_FREE_OBJECTID, - }; - int ret; - - ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args); - if (ret < 0) - return -1; - - *id_ret = args.treeid; - - return 0; -} - -/* - * The following helpers are adapted from the btrfsutils library. We can't use - * the library directly since we need full control over how the subvolume - * iteration happens. We need to be able to check whether unprivileged - * subvolume iteration is possible, i.e. whether BTRFS_IOC_INO_LOOKUP_USER is - * available and also ensure that it is actually used when looking up paths. - */ -struct btrfs_stack { - uint64_t tree_id; - struct btrfs_ioctl_get_subvol_rootref_args rootref_args; - size_t items_pos; - size_t path_len; -}; - -struct btrfs_iter { - int fd; - int cur_fd; - - struct btrfs_stack *search_stack; - size_t stack_len; - size_t stack_capacity; - - char *cur_path; - size_t cur_path_capacity; -}; - -static struct btrfs_stack *top_stack_entry(struct btrfs_iter *iter) -{ - return &iter->search_stack[iter->stack_len - 1]; -} - -static int pop_stack(struct btrfs_iter *iter) -{ - struct btrfs_stack *top, *parent; - int fd, parent_fd; - size_t i; - - if (iter->stack_len == 1) { - iter->stack_len--; - return 0; - } - - top = top_stack_entry(iter); - iter->stack_len--; - parent = top_stack_entry(iter); - - fd = iter->cur_fd; - for (i = parent->path_len; i < top->path_len; i++) { - if (i == 0 || iter->cur_path[i] == '/') { - parent_fd = openat(fd, "..", O_RDONLY); - if (fd != iter->cur_fd) - close(fd); - if (parent_fd == -1) - return -1; - fd = parent_fd; - } - } - if (iter->cur_fd != iter->fd) - close(iter->cur_fd); - iter->cur_fd = fd; - - return 0; -} - -static int append_stack(struct btrfs_iter *iter, uint64_t tree_id, size_t path_len) -{ - struct btrfs_stack *entry; - - if (iter->stack_len >= iter->stack_capacity) { - size_t new_capacity = iter->stack_capacity * 2; - struct btrfs_stack *new_search_stack; -#ifdef HAVE_REALLOCARRAY - new_search_stack = reallocarray(iter->search_stack, new_capacity, - sizeof(*iter->search_stack)); -#else - new_search_stack = realloc(iter->search_stack, new_capacity * sizeof(*iter->search_stack)); -#endif - if (!new_search_stack) - return -ENOMEM; - - iter->stack_capacity = new_capacity; - iter->search_stack = new_search_stack; - } - - entry = &iter->search_stack[iter->stack_len]; - - memset(entry, 0, sizeof(*entry)); - entry->path_len = path_len; - entry->tree_id = tree_id; - - if (iter->stack_len) { - struct btrfs_stack *top; - char *path; - int fd; - - top = top_stack_entry(iter); - path = &iter->cur_path[top->path_len]; - if (*path == '/') - path++; - fd = openat(iter->cur_fd, path, O_RDONLY); - if (fd == -1) - return -errno; - - close(iter->cur_fd); - iter->cur_fd = fd; - } - - iter->stack_len++; - - return 0; -} - -static int btrfs_iterator_start(int fd, uint64_t top, struct btrfs_iter **ret) -{ - struct btrfs_iter *iter; - int err; - - iter = malloc(sizeof(*iter)); - if (!iter) - return -ENOMEM; - - iter->fd = fd; - iter->cur_fd = fd; - - iter->stack_len = 0; - iter->stack_capacity = 4; - iter->search_stack = malloc(sizeof(*iter->search_stack) * - iter->stack_capacity); - if (!iter->search_stack) { - err = -ENOMEM; - goto out_iter; - } - - iter->cur_path_capacity = 256; - iter->cur_path = malloc(iter->cur_path_capacity); - if (!iter->cur_path) { - err = -ENOMEM; - goto out_search_stack; - } - - err = append_stack(iter, top, 0); - if (err) - goto out_cur_path; - - *ret = iter; - - return 0; - -out_cur_path: - free(iter->cur_path); -out_search_stack: - free(iter->search_stack); -out_iter: - free(iter); - return err; -} - -static void btrfs_iterator_end(struct btrfs_iter *iter) -{ - if (iter) { - free(iter->cur_path); - free(iter->search_stack); - if (iter->cur_fd != iter->fd) - close(iter->cur_fd); - close(iter->fd); - free(iter); - } -} - -static int __append_path(struct btrfs_iter *iter, const char *name, - size_t name_len, const char *dir, size_t dir_len, - size_t *path_len_ret) -{ - struct btrfs_stack *top = top_stack_entry(iter); - size_t path_len; - char *p; - - path_len = top->path_len; - /* - * We need a joining slash if we have a current path and a subdirectory. - */ - if (top->path_len && dir_len) - path_len++; - path_len += dir_len; - /* - * We need another joining slash if we have a current path and a name, - * but not if we have a subdirectory, because the lookup ioctl includes - * a trailing slash. - */ - if (top->path_len && !dir_len && name_len) - path_len++; - path_len += name_len; - - /* We need one extra character for the NUL terminator. */ - if (path_len + 1 > iter->cur_path_capacity) { - char *tmp = realloc(iter->cur_path, path_len + 1); - - if (!tmp) - return -ENOMEM; - iter->cur_path = tmp; - iter->cur_path_capacity = path_len + 1; - } - - p = iter->cur_path + top->path_len; - if (top->path_len && dir_len) - *p++ = '/'; - memcpy(p, dir, dir_len); - p += dir_len; - if (top->path_len && !dir_len && name_len) - *p++ = '/'; - memcpy(p, name, name_len); - p += name_len; - *p = '\0'; - - *path_len_ret = path_len; - - return 0; -} - -static int get_subvolume_path(struct btrfs_iter *iter, uint64_t treeid, - uint64_t dirid, size_t *path_len_ret) -{ - struct btrfs_ioctl_ino_lookup_user_args args = { - .treeid = treeid, - .dirid = dirid, - }; - int ret; - - ret = ioctl(iter->cur_fd, BTRFS_IOC_INO_LOOKUP_USER, &args); - if (ret == -1) - return -1; - - return __append_path(iter, args.name, strlen(args.name), args.path, - strlen(args.path), path_len_ret); -} - -static int btrfs_iterator_next(struct btrfs_iter *iter, char **path_ret, - uint64_t *id_ret) -{ - struct btrfs_stack *top; - uint64_t treeid, dirid; - size_t path_len; - int ret, err; - - for (;;) { - for (;;) { - if (iter->stack_len == 0) - return 1; - - top = top_stack_entry(iter); - if (top->items_pos < top->rootref_args.num_items) { - break; - } else { - ret = ioctl(iter->cur_fd, - BTRFS_IOC_GET_SUBVOL_ROOTREF, - &top->rootref_args); - if (ret == -1 && errno != EOVERFLOW) - return -1; - top->items_pos = 0; - - if (top->rootref_args.num_items == 0) { - err = pop_stack(iter); - if (err) - return err; - } - } - } - - treeid = top->rootref_args.rootref[top->items_pos].treeid; - dirid = top->rootref_args.rootref[top->items_pos].dirid; - top->items_pos++; - err = get_subvolume_path(iter, treeid, dirid, &path_len); - if (err) { - /* Skip the subvolume if we can't access it. */ - if (errno == EACCES) - continue; - return err; - } - - err = append_stack(iter, treeid, path_len); - if (err) { - /* - * Skip the subvolume if it does not exist (which can - * happen if there is another filesystem mounted over a - * parent directory) or we don't have permission to - * access it. - */ - if (errno == ENOENT || errno == EACCES) - continue; - return err; - } - - top = top_stack_entry(iter); - goto out; - } - -out: - if (path_ret) { - *path_ret = malloc(top->path_len + 1); - if (!*path_ret) - return -ENOMEM; - memcpy(*path_ret, iter->cur_path, top->path_len); - (*path_ret)[top->path_len] = '\0'; - } - if (id_ret) - *id_ret = top->tree_id; - return 0; -} - -#define BTRFS_SUBVOLUME1 "subvol1" -#define BTRFS_SUBVOLUME1_SNAPSHOT1 "subvol1_snapshot1" -#define BTRFS_SUBVOLUME1_SNAPSHOT1_RO "subvol1_snapshot1_ro" -#define BTRFS_SUBVOLUME1_RENAME "subvol1_rename" -#define BTRFS_SUBVOLUME2 "subvol2" - -static int btrfs_subvolumes_fsids_mapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_up()) - die("failure: raise caps"); - - /* - * The caller's fsids now have mappings in the idmapped mount so - * any file creation must succeed. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: check ownership"); - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!caps_down()) - die("failure: lower caps"); - - /* - * The filesystem is not mounted with user_subvol_rm_allowed so - * subvolume deletion must fail. - */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - if (errno != EPERM) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: check ownership"); - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_fsids_mapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* The caller's fsids now have mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) - die("failure: check ownership"); - - /* remove subvolume */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_fsids_unmapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - /* create directory for rename test */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - if (!switch_fsids(0, 0)) { - log_stderr("failure: switch_fsids"); - goto out; - } - - /* - * The caller's fsids don't have a mappings in the idmapped mount so - * any file creation must fail. - */ - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* try to rename a subvolume */ - if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, - BTRFS_SUBVOLUME1_RENAME)) { - log_stderr("failure: renameat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* The caller is privileged over the inode so file deletion must work. */ - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_fsids_unmapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - /* create directory for rename test */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - userns_fd = get_userns_fd(0, 30000, 10000); - if (userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - /* - * The caller's fsids don't have a mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create subvolume */ - if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) - die("failure: btrfs_create_subvolume"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* try to rename a subvolume */ - if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, - BTRFS_SUBVOLUME1_RENAME)) - die("failure: renameat"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* - * The caller is not privileged over the inode so subvolume - * deletion must fail. - */ - - /* remove subvolume */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - safe_close(userns_fd); - - return fret; -} - -static int btrfs_snapshots_fsids_mapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_up()) - die("failure: raise caps"); - - /* The caller's fsids now have mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - /* create read-only snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - safe_close(subvolume_fd); - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - /* remove read-write snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) - die("failure: btrfs_delete_subvolume"); - - /* remove read-only snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) - die("failure: btrfs_delete_subvolume"); - - /* create directory */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - /* create read-only snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - /* remove read-write snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - /* remove read-only snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_fsids_mapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - /* create read-only snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0)) - die("failure: expected_uid_gid"); - - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - /* remove read-write snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - /* remove read-only snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_fsids_unmapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create directory for rename test */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, - sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - - if (!switch_fsids(0, 0)) { - log_stderr("failure: switch_fsids"); - goto out; - } - - /* - * The caller's fsids don't have a mappings in the idmapped - * mount so any file creation must fail. - */ - - /* - * The open_tree() syscall returns an O_PATH file descriptor - * which we can't use with ioctl(). So let's reopen it as a - * proper file descriptor. - */ - tree_fd = openat(open_tree_fd, ".", - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) - die("failure: openat"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create directory */ - if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) - die("failure: btrfs_create_subvolume"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* create read-write snapshot */ - if (!btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* create read-only snapshot */ - if (!btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* try to rename a directory */ - if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, - BTRFS_SUBVOLUME1_RENAME)) - die("failure: renameat"); - if (errno != EOVERFLOW) - die("failure: errno"); - - if (!caps_down()) - die("failure: caps_down"); - - /* create read-write snapshot */ - if (!btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - if (errno != EPERM) - die("failure: errno"); - - /* create read-only snapshot */ - if (!btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - if (errno != EPERM) - die("failure: errno"); - - /* - * The caller is not privileged over the inode so subvolume - * deletion must fail. - */ - - /* remove directory */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - if (errno != EPERM) - die("failure: errno"); - - if (!caps_up()) - die("failure: caps_down"); - - /* - * The caller is privileged over the inode so subvolume - * deletion must work. - */ - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_fsids_unmapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF, - userns_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create directory for rename test */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - userns_fd = get_userns_fd(0, 30000, 10000); - if (userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, - sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor - * which we can't use with ioctl(). So let's reopen it as a - * proper file descriptor. - */ - tree_fd = openat(open_tree_fd, ".", - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - /* - * The caller's fsids don't have a mappings in the idmapped - * mount so any file creation must fail. - */ - - /* create directory */ - if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) - die("failure: btrfs_create_subvolume"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* create read-write snapshot */ - if (!btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - if (errno != EPERM) - die("failure: errno"); - - /* create read-only snapshot */ - if (!btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - if (errno != EPERM) - die("failure: errno"); - - /* try to rename a directory */ - if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd, - BTRFS_SUBVOLUME1_RENAME)) - die("failure: renameat"); - if (errno != EOVERFLOW) - die("failure: errno"); - - /* - * The caller is not privileged over the inode so subvolume - * deletion must fail. - */ - - /* remove directory */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - if (errno != EPERM) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(subvolume_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: raise caps"); - - /* - * The caller's fsids now have mappings in the idmapped mount so - * any file creation must succedd. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: check ownership"); - - /* - * The scratch device is mounted with user_subvol_rm_allowed so - * subvolume deletion must succeed. - */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* The caller's fsids now have mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) - die("failure: check ownership"); - - /* - * The scratch device is mounted with user_subvol_rm_allowed so - * subvolume deletion must succeed. - */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: raise caps"); - - /* - * The caller's fsids now have mappings in the idmapped mount so - * any file creation must succeed. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - /* create read-only snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - safe_close(subvolume_fd); - - /* remove subvolume */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - /* remove read-write snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) - die("failure: btrfs_delete_subvolume"); - - /* remove read-only snapshot */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) - die("failure: btrfs_delete_subvolume"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - if (btrfs_set_subvolume_ro(subvolume_fd, false)) - die("failure: btrfs_set_subvolume_ro"); - - safe_close(subvolume_fd); - - /* remove read-only snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - /* create read-only snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - BTRFS_SUBVOL_RDONLY)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0)) - die("failure: expected_uid_gid"); - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_delete_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - /* remove read-write snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) - die("failure: btrfs_delete_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0)) - die("failure: expected_uid_gid"); - - /* remove read-only snapshot */ - if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) - die("failure: btrfs_delete_subvolume"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - if (btrfs_set_subvolume_ro(subvolume_fd, false)) - die("failure: btrfs_set_subvolume_ro"); - - safe_close(subvolume_fd); - - /* remove read-only snapshot */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) - die("failure: btrfs_delete_subvolume"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_delete_by_spec_id(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF; - uint64_t subvolume_id1 = -EINVAL, subvolume_id2 = -EINVAL; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "A")) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "B")) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - subvolume_fd = openat(info->t_mnt_scratch_fd, "B", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(subvolume_fd, "C")) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - safe_close(subvolume_fd); - - subvolume_fd = openat(info->t_mnt_scratch_fd, "A", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id1)) { - log_stderr("failure: btrfs_get_subvolume_id"); - goto out; - } - - subvolume_fd = openat(info->t_mnt_scratch_fd, "B/C", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id2)) { - log_stderr("failure: btrfs_get_subvolume_id"); - goto out; - } - - if (sys_mount(info->t_device_scratch, info->t_mountpoint, "btrfs", 0, "subvol=B/C")) { - log_stderr("failure: mount"); - goto out; - } - - open_tree_fd = sys_open_tree(-EBADF, info->t_mountpoint, - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - /* - * The subvolume isn't exposed in the idmapped mount so - * delation via spec id must fail. - */ - if (!btrfs_delete_subvolume_id(tree_fd, subvolume_id1)) - die("failure: btrfs_delete_subvolume_id"); - if (errno != EOPNOTSUPP) - die("failure: errno"); - - if (btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id1)) - die("failure: btrfs_delete_subvolume_id"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - sys_umount2(info->t_mountpoint, MNT_DETACH); - btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id2); - btrfs_delete_subvolume(info->t_mnt_scratch_fd, "B"); - - return fret; -} - -static int btrfs_subvolumes_setflags_fsids_mapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - bool read_only = false; - - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: raise caps"); - - /* The caller's fsids now have mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (btrfs_set_subvolume_ro(subvolume_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (!read_only) - die("failure: not read_only"); - - if (btrfs_set_subvolume_ro(subvolume_fd, false)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_setflags_fsids_mapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - bool read_only = false; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* The caller's fsids now have mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (btrfs_set_subvolume_ro(subvolume_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (!read_only) - die("failure: not read_only"); - - if (btrfs_set_subvolume_ro(subvolume_fd, false)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_setflags_fsids_unmapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - bool read_only = false; - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - if (!switch_fsids(0, 0)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: raise caps"); - - /* - * The caller's fsids don't have mappings in the idmapped mount - * so any file creation must fail. - */ - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (!btrfs_set_subvolume_ro(subvolume_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - if (errno != EPERM) - die("failure: errno"); - - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_subvolumes_setflags_fsids_unmapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - userns_fd = get_userns_fd(0, 30000, 10000); - if (userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int subvolume_fd = -EBADF; - bool read_only = false; - - /* - * The caller's fsids don't have mappings in the idmapped mount - * so any file creation must fail. - */ - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (btrfs_get_subvolume_ro(subvolume_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (!btrfs_set_subvolume_ro(subvolume_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - if (errno != EPERM) - die("failure: errno"); - - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - safe_close(userns_fd); - - return fret; -} - -static int btrfs_snapshots_setflags_fsids_mapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int snapshot_fd = -EBADF, subvolume_fd = -EBADF; - bool read_only = false; - - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: raise caps"); - - /* - * The caller's fsids now have mappings in the idmapped mount - * so any file creation must succeed. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) - die("failure: expected_uid_gid"); - - snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (snapshot_fd < 0) - die("failure: openat"); - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (btrfs_set_subvolume_ro(snapshot_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (!read_only) - die("failure: not read_only"); - - if (btrfs_set_subvolume_ro(snapshot_fd, false)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - safe_close(snapshot_fd); - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_setflags_fsids_mapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int snapshot_fd = -EBADF, subvolume_fd = -EBADF; - bool read_only = false; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* - * The caller's fsids now have mappings in the idmapped mount so - * any file creation must succeed. - */ - - /* create subvolume */ - if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1)) - die("failure: btrfs_create_subvolume"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) - die("failure: openat"); - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, tree_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) - die("failure: btrfs_create_snapshot"); - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (snapshot_fd < 0) - die("failure: openat"); - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (btrfs_set_subvolume_ro(snapshot_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (!read_only) - die("failure: not read_only"); - - if (btrfs_set_subvolume_ro(snapshot_fd, false)) - die("failure: btrfs_set_subvolume_ro"); - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - safe_close(snapshot_fd); - safe_close(subvolume_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_setflags_fsids_unmapped(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) { - log_stderr("failure: btrfs_create_snapshot"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int snapshot_fd = -EBADF; - bool read_only = false; - - snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (snapshot_fd < 0) - die("failure: openat"); - - if (!switch_fsids(0, 0)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: raise caps"); - - /* - * The caller's fsids don't have mappings in the idmapped mount - * so any file creation must fail. - */ - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (!btrfs_set_subvolume_ro(snapshot_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - if (errno != EPERM) - die("failure: errno"); - - safe_close(snapshot_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(subvolume_fd); - safe_close(tree_fd); - - return fret; -} - -static int btrfs_snapshots_setflags_fsids_unmapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF, - userns_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - userns_fd = get_userns_fd(0, 30000, 10000); - if (userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (subvolume_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create read-write snapshot */ - if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd, - BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) { - log_stderr("failure: btrfs_create_snapshot"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int snapshot_fd = -EBADF; - bool read_only = false; - - /* - * The caller's fsids don't have mappings in the idmapped mount - * so any file creation must fail. - */ - - snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, - O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (snapshot_fd < 0) - die("failure: openat"); - - - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0, - info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - /* - * The caller's fsids don't have mappings in the idmapped mount - * so any file creation must fail. - */ - - if (btrfs_get_subvolume_ro(snapshot_fd, &read_only)) - die("failure: btrfs_get_subvolume_ro"); - - if (read_only) - die("failure: read_only"); - - if (!btrfs_set_subvolume_ro(snapshot_fd, true)) - die("failure: btrfs_set_subvolume_ro"); - if (errno != EPERM) - die("failure: errno"); - - safe_close(snapshot_fd); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - /* remove directory */ - if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) { - log_stderr("failure: btrfs_delete_subvolume"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - safe_close(subvolume_fd); - safe_close(tree_fd); - safe_close(userns_fd); - - return fret; -} - -#define BTRFS_SUBVOLUME_SUBVOL1 "subvol1" -#define BTRFS_SUBVOLUME_SUBVOL2 "subvol2" -#define BTRFS_SUBVOLUME_SUBVOL3 "subvol3" -#define BTRFS_SUBVOLUME_SUBVOL4 "subvol4" - -#define BTRFS_SUBVOLUME_SUBVOL1_ID 0 -#define BTRFS_SUBVOLUME_SUBVOL2_ID 1 -#define BTRFS_SUBVOLUME_SUBVOL3_ID 2 -#define BTRFS_SUBVOLUME_SUBVOL4_ID 3 - -#define BTRFS_SUBVOLUME_DIR1 "dir1" -#define BTRFS_SUBVOLUME_DIR2 "dir2" - -#define BTRFS_SUBVOLUME_MNT "mnt_subvolume1" - -#define BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3 "subvol1/subvol3" -#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2 "subvol1/dir1/dir2" -#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4 "subvol1/dir1/dir2/subvol4" - -/* - * We create the following mount layout to test lookup: - * - * |-/mnt/test /dev/loop0 btrfs rw,relatime,space_cache,subvolid=5,subvol=/ - * | |-/mnt/test/mnt1 /dev/loop1[/subvol1] btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=268,subvol=/subvol1 - * '-/mnt/scratch /dev/loop1 btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=5,subvol=/ - */ -static int btrfs_subvolume_lookup_user(const struct vfstest_info *info) -{ - int fret = -1, i; - int dir1_fd = -EBADF, dir2_fd = -EBADF, mnt_fd = -EBADF, - open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF; - int subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID + 1]; - uint64_t subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID + 1]; - uint64_t subvolid = -EINVAL; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - struct btrfs_iter *iter; - - if (!caps_supported()) - return 0; - - for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++) - subvolume_fds[i] = -EBADF; - - for (i = 0; i < ARRAY_SIZE(subvolume_ids); i++) - subvolume_ids[i] = -EINVAL; - - if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL1)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL2)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] = openat(info->t_mnt_scratch_fd, - BTRFS_SUBVOLUME_SUBVOL1, - O_CLOEXEC | O_DIRECTORY); - if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create subvolume */ - if (btrfs_create_subvolume(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_SUBVOL3)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (mkdirat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir1_fd = openat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1, - O_CLOEXEC | O_DIRECTORY); - if (dir1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (mkdirat(dir1_fd, BTRFS_SUBVOLUME_DIR2, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir2_fd = openat(dir1_fd, BTRFS_SUBVOLUME_DIR2, O_CLOEXEC | O_DIRECTORY); - if (dir2_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (btrfs_create_subvolume(dir2_fd, BTRFS_SUBVOLUME_SUBVOL4)) { - log_stderr("failure: btrfs_create_subvolume"); - goto out; - } - - if (mkdirat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT); - if (sys_mount(info->t_device_scratch, t_buf, "btrfs", 0, - "subvol=" BTRFS_SUBVOLUME_SUBVOL1)) { - log_stderr("failure: mount"); - goto out; - } - - mnt_fd = openat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, O_CLOEXEC | O_DIRECTORY); - if (mnt_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (chown_r(info->t_mnt_scratch_fd, ".", 1000, 1000)) { - log_stderr("failure: chown_r"); - goto out; - } - - subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] = openat(info->t_mnt_scratch_fd, - BTRFS_SUBVOLUME_SUBVOL2, - O_CLOEXEC | O_DIRECTORY); - if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], - &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL1_ID])) { - log_stderr("failure: btrfs_get_subvolume_id"); - goto out; - } - - if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID], - &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL2_ID])) { - log_stderr("failure: btrfs_get_subvolume_id"); - goto out; - } - - subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] = openat(info->t_mnt_scratch_fd, - BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3, - O_CLOEXEC | O_DIRECTORY); - if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID], - &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])) { - log_stderr("failure: btrfs_get_subvolume_id"); - goto out; - } - - subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] = openat(info->t_mnt_scratch_fd, - BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4, - O_CLOEXEC | O_DIRECTORY); - if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID], - &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])) { - log_stderr("failure: btrfs_get_subvolume_id"); - goto out; - } - - - if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(mnt_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* - * The open_tree() syscall returns an O_PATH file descriptor which we - * can't use with ioctl(). So let's reopen it as a proper file - * descriptor. - */ - tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY); - if (tree_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - bool subvolume3_found = false, subvolume4_found = false; - - if (!switch_fsids(11000, 11000)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: lower caps"); - - if (btrfs_iterator_start(tree_fd, 0, &iter)) - die("failure: btrfs_iterator_start"); - - for (;;) { - char *subvol_path = NULL; - int ret; - - ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); - if (ret == 1) - break; - else if (ret) - die("failure: btrfs_iterator_next"); - - if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] && - subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) - die("failure: subvolume id %llu->%s", - (long long unsigned)subvolid, subvol_path); - - if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID]) - subvolume3_found = true; - - if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) - subvolume4_found = true; - - free(subvol_path); - } - btrfs_iterator_end(iter); - - if (!subvolume3_found || !subvolume4_found) - die("failure: subvolume id"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - bool subvolume3_found = false, subvolume4_found = false; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (btrfs_iterator_start(tree_fd, 0, &iter)) - die("failure: btrfs_iterator_start"); - - for (;;) { - char *subvol_path = NULL; - int ret; - - ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); - if (ret == 1) - break; - else if (ret) - die("failure: btrfs_iterator_next"); - - if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] && - subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) - die("failure: subvolume id %llu->%s", - (long long unsigned)subvolid, subvol_path); - - if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID]) - subvolume3_found = true; - - if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID]) - subvolume4_found = true; - - free(subvol_path); - } - btrfs_iterator_end(iter); - - if (!subvolume3_found || !subvolume4_found) - die("failure: subvolume id"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - bool subvolume_found = false; - - if (!switch_fsids(0, 0)) - die("failure: switch fsids"); - - if (!caps_down()) - die("failure: lower caps"); - - if (btrfs_iterator_start(tree_fd, 0, &iter)) - die("failure: btrfs_iterator_start"); - - for (;;) { - char *subvol_path = NULL; - int ret; - - ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); - if (ret == 1) - break; - else if (ret) - die("failure: btrfs_iterator_next"); - - free(subvol_path); - - subvolume_found = true; - break; - } - btrfs_iterator_end(iter); - - if (subvolume_found) - die("failure: subvolume id"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - userns_fd = get_userns_fd(0, 30000, 10000); - if (userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - bool subvolume_found = false; - - if (!switch_userns(userns_fd, 0, 0, true)) - die("failure: switch_userns"); - - if (btrfs_iterator_start(tree_fd, 0, &iter)) - die("failure: btrfs_iterator_start"); - - for (;;) { - char *subvol_path = NULL; - int ret; - - ret = btrfs_iterator_next(iter, &subvol_path, &subvolid); - if (ret == 1) - break; - else if (ret) - die("failure: btrfs_iterator_next"); - - free(subvol_path); - - subvolume_found = true; - break; - } - btrfs_iterator_end(iter); - - if (subvolume_found) - die("failure: subvolume id"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(dir1_fd); - safe_close(dir2_fd); - safe_close(open_tree_fd); - safe_close(tree_fd); - safe_close(userns_fd); - for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++) - safe_close(subvolume_fds[i]); - snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT); - sys_umount2(t_buf, MNT_DETACH); - unlinkat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, AT_REMOVEDIR); - - return fret; -} - -#define USER1 "fsgqa" -#define USER2 "fsgqa2" - -/** - * lookup_ids - lookup uid and gid for a username - * @name: [in] name of the user - * @uid: [out] pointer to the user-ID - * @gid: [out] pointer to the group-ID - * - * Lookup the uid and gid of a user. - * - * Return: On success, true is returned. - * On error, false is returned. - */ -static bool lookup_ids(const char *name, uid_t *uid, gid_t *gid) -{ - bool bret = false; - struct passwd *pwentp = NULL; - struct passwd pwent; - char *buf; - ssize_t bufsize; - int ret; - - bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (bufsize < 0) - bufsize = 1024; - - buf = malloc(bufsize); - if (!buf) - return bret; - - ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp); - if (!ret && pwentp) { - *uid = pwent.pw_uid; - *gid = pwent.pw_gid; - bret = true; - } - - free(buf); - return bret; -} - -/** - * setattr_fix_968219708108 - test for commit 968219708108 ("fs: handle circular mappings correctly") - * - * Test that ->setattr() works correctly for idmapped mounts with circular - * idmappings such as: - * - * b:1000:1001:1 - * b:1001:1000:1 - * - * Assume a directory /source with two files: - * - * /source/file1 | 1000:1000 - * /source/file2 | 1001:1001 - * - * and we create an idmapped mount of /source at /target with an idmapped of: - * - * mnt_userns: 1000:1001:1 - * 1001:1000:1 - * - * In the idmapped mount file1 will be owned by uid 1001 and file2 by uid 1000: - * - * /target/file1 | 1001:1001 - * /target/file2 | 1000:1000 - * - * Because in essence the idmapped mount switches ownership for {g,u}id 1000 - * and {g,u}id 1001. - * - * 1. A user with fs{g,u}id 1000 must be allowed to setattr /target/file2 from - * {g,u}id 1000 in the idmapped mount to {g,u}id 1000. - * 2. A user with fs{g,u}id 1001 must be allowed to setattr /target/file1 from - * {g,u}id 1001 in the idmapped mount to {g,u}id 1001. - * 3. A user with fs{g,u}id 1000 must fail to setattr /target/file1 from - * {g,u}id 1001 in the idmapped mount to {g,u}id 1000. - * This must fail with EPERM. The caller's fs{g,u}id doesn't match the - * {g,u}id of the file. - * 4. A user with fs{g,u}id 1001 must fail to setattr /target/file2 from - * {g,u}id 1000 in the idmapped mount to {g,u}id 1000. - * This must fail with EPERM. The caller's fs{g,u}id doesn't match the - * {g,u}id of the file. - * 5. Both, a user with fs{g,u}id 1000 and a user with fs{g,u}id 1001, must - * fail to setattr /target/file1 owned by {g,u}id 1001 in the idmapped mount - * and /target/file2 owned by {g,u}id 1000 in the idmapped mount to any - * {g,u}id apart from {g,u}id 1000 or 1001 with EINVAL. - * Only {g,u}id 1000 and 1001 have a mapping in the idmapped mount. Other - * {g,u}id are unmapped. - */ -static int setattr_fix_968219708108(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - .userns_fd = -EBADF, - }; - int ret; - uid_t user1_uid, user2_uid; - gid_t user1_gid, user2_gid; - pid_t pid; - struct list idmap; - struct list *it_cur, *it_next; - - if (!caps_supported()) - return 0; - - list_init(&idmap); - - if (!lookup_ids(USER1, &user1_uid, &user1_gid)) { - log_stderr("failure: lookup_user"); - goto out; - } - - if (!lookup_ids(USER2, &user2_uid, &user2_gid)) { - log_stderr("failure: lookup_user"); - goto out; - } - - log_debug("Found " USER1 " with uid(%d) and gid(%d) and " USER2 " with uid(%d) and gid(%d)", - user1_uid, user1_gid, user2_uid, user2_gid); - - if (mkdirat(info->t_dir1_fd, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - if (mknodat(info->t_dir1_fd, DIR1 "/" FILE1, S_IFREG | 0644, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - - if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) { - log_stderr("failure: chown_r"); - goto out; - } - - if (mknodat(info->t_dir1_fd, DIR1 "/" FILE2, S_IFREG | 0644, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - - if (fchownat(info->t_dir1_fd, DIR1 "/" FILE2, user2_uid, user2_gid, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - - print_r(info->t_mnt_fd, T_DIR1); - - /* u:1000:1001:1 */ - ret = add_map_entry(&idmap, user1_uid, user2_uid, 1, ID_TYPE_UID); - if (ret) { - log_stderr("failure: add_map_entry"); - goto out; - } - - /* u:1001:1000:1 */ - ret = add_map_entry(&idmap, user2_uid, user1_uid, 1, ID_TYPE_UID); - if (ret) { - log_stderr("failure: add_map_entry"); - goto out; - } - - /* g:1000:1001:1 */ - ret = add_map_entry(&idmap, user1_gid, user2_gid, 1, ID_TYPE_GID); - if (ret) { - log_stderr("failure: add_map_entry"); - goto out; - } - - /* g:1001:1000:1 */ - ret = add_map_entry(&idmap, user2_gid, user1_gid, 1, ID_TYPE_GID); - if (ret) { - log_stderr("failure: add_map_entry"); - goto out; - } - - attr.userns_fd = get_userns_fd_from_idmap(&idmap); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1, - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE | - AT_RECURSIVE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - print_r(open_tree_fd, ""); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - /* switch to {g,u}id 1001 */ - if (!switch_resids(user2_uid, user2_gid)) - die("failure: switch_resids"); - - /* drop all capabilities */ - if (!caps_down()) - die("failure: caps_down"); - - /* - * The {g,u}id 0 is not mapped in this idmapped mount so this - * needs to fail with EINVAL. - */ - if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW)) - die("failure: change ownership"); - if (errno != EINVAL) - die("failure: errno"); - - /* - * A user with fs{g,u}id 1001 must be allowed to change - * ownership of /target/file1 owned by {g,u}id 1001 in this - * idmapped mount to {g,u}id 1001. - */ - if (fchownat(open_tree_fd, FILE1, user2_uid, user2_gid, - AT_SYMLINK_NOFOLLOW)) - die("failure: change ownership"); - - /* Verify that the ownership is still {g,u}id 1001. */ - if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW, - user2_uid, user2_gid)) - die("failure: check ownership"); + /* Verify that the ownership is still {g,u}id 1001. */ + if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW, + user2_uid, user2_gid)) + die("failure: check ownership"); /* * A user with fs{g,u}id 1001 must not be allowed to change @@ -7017,36 +3223,6 @@ static const struct test_suite s_nested_userns = { .nr_tests = ARRAY_SIZE(t_nested_userns), }; -static const struct test_struct t_btrfs[] = { - { btrfs_subvolumes_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids", }, - { btrfs_subvolumes_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids inside user namespace", }, - { btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option", }, - { btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option inside user namespace", }, - { btrfs_subvolumes_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids", }, - { btrfs_subvolumes_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids inside user namespace", }, - { btrfs_snapshots_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids", }, - { btrfs_snapshots_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids inside user namespace", }, - { btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option", }, - { btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option inside user namespace", }, - { btrfs_snapshots_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids", }, - { btrfs_snapshots_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids inside user namespace", }, - { btrfs_delete_by_spec_id, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion by spec id", }, - { btrfs_subvolumes_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids", }, - { btrfs_subvolumes_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids inside user namespace", }, - { btrfs_subvolumes_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids", }, - { btrfs_subvolumes_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids inside user namespace", }, - { btrfs_snapshots_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids", }, - { btrfs_snapshots_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids inside user namespace", }, - { btrfs_snapshots_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids", }, - { btrfs_snapshots_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids inside user namespace", }, - { btrfs_subvolume_lookup_user, T_REQUIRE_IDMAPPED_MOUNTS, "test unprivileged subvolume lookup", }, -}; - -static const struct test_suite s_btrfs = { - .tests = t_btrfs, - .nr_tests = ARRAY_SIZE(t_btrfs), -}; - /* Test for commit 968219708108 ("fs: handle circular mappings correctly"). */ static const struct test_struct t_setattr_fix_968219708108[] = { { setattr_fix_968219708108, T_REQUIRE_IDMAPPED_MOUNTS, "test that setattr works correctly", }, @@ -7272,7 +3448,7 @@ int main(int argc, char *argv[]) if (test_nested_userns && !run_suite(&info, &s_nested_userns)) goto out; - if (test_btrfs && !run_suite(&info, &s_btrfs)) + if (test_btrfs && !run_suite(&info, &s_btrfs_idmapped_mounts)) goto out; if (test_setattr_fix_968219708108 &&