| // SPDX-License-Identifier: GPL-2.0 |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include "../global.h" |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <grp.h> |
| #include <limits.h> |
| #include <linux/limits.h> |
| #include <linux/types.h> |
| #include <pthread.h> |
| #include <pwd.h> |
| #include <sched.h> |
| #include <stdbool.h> |
| #include <sys/fsuid.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/xattr.h> |
| #include <unistd.h> |
| |
| #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 <linux/btrfs.h> |
| # undef btrfs_ioctl_vol_args_v2 |
| #endif |
| |
| #ifdef HAVE_LINUX_BTRFS_TREE_H |
| #include <linux/btrfs_tree.h> |
| #endif |
| |
| #include "missing.h" |
| #include "utils.h" |
| |
| static char t_buf[PATH_MAX]; |
| |
| static void init_vfstest_info(struct vfstest_info *info) |
| { |
| info->t_overflowuid = 65534; |
| info->t_overflowgid = 65534; |
| info->t_fstype = NULL; |
| info->t_device = NULL; |
| info->t_device_scratch = NULL; |
| info->t_mountpoint = NULL; |
| info->t_mountpoint_scratch = NULL; |
| info->t_mnt_fd = -EBADF; |
| info->t_mnt_scratch_fd = -EBADF; |
| info->t_dir1_fd = -EBADF; |
| info->t_fs_allow_idmap = false; |
| } |
| |
| static void stash_overflowuid(struct vfstest_info *info) |
| { |
| int fd; |
| ssize_t ret; |
| char buf[256]; |
| |
| fd = open("/proc/sys/fs/overflowuid", O_RDONLY | O_CLOEXEC); |
| if (fd < 0) |
| return; |
| |
| ret = read(fd, buf, sizeof(buf)); |
| close(fd); |
| if (ret < 0) |
| return; |
| |
| info->t_overflowuid = atoi(buf); |
| } |
| |
| static void stash_overflowgid(struct vfstest_info *info) |
| { |
| int fd; |
| ssize_t ret; |
| char buf[256]; |
| |
| fd = open("/proc/sys/fs/overflowgid", O_RDONLY | O_CLOEXEC); |
| if (fd < 0) |
| return; |
| |
| ret = read(fd, buf, sizeof(buf)); |
| close(fd); |
| if (ret < 0) |
| return; |
| |
| info->t_overflowgid = atoi(buf); |
| } |
| |
| /* |
| * There'll be scenarios where you'll want to see the attributes associated with |
| * a directory tree during debugging or just to make sure things look correct. |
| * Simply uncomment and place the print_r() helper where you need it. |
| */ |
| #ifdef DEBUG_TRACE |
| static int fd_cloexec(int fd, bool cloexec) |
| { |
| int oflags, nflags; |
| |
| oflags = fcntl(fd, F_GETFD, 0); |
| if (oflags < 0) |
| return -errno; |
| |
| if (cloexec) |
| nflags = oflags | FD_CLOEXEC; |
| else |
| nflags = oflags & ~FD_CLOEXEC; |
| |
| if (nflags == oflags) |
| return 0; |
| |
| if (fcntl(fd, F_SETFD, nflags) < 0) |
| return -errno; |
| |
| return 0; |
| } |
| |
| static inline int dup_cloexec(int fd) |
| { |
| int fd_dup; |
| |
| fd_dup = dup(fd); |
| if (fd_dup < 0) |
| return -errno; |
| |
| if (fd_cloexec(fd_dup, true)) { |
| close(fd_dup); |
| return -errno; |
| } |
| |
| return fd_dup; |
| } |
| |
| __attribute__((unused)) static int print_r(int fd, const char *path) |
| { |
| int ret = 0; |
| int dfd, dfd_dup; |
| DIR *dir; |
| struct dirent *direntp; |
| struct stat st; |
| |
| if (!path || *path == '\0') { |
| char buf[sizeof("/proc/self/fd/") + 30]; |
| |
| ret = snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); |
| if (ret < 0 || (size_t)ret >= sizeof(buf)) |
| return -1; |
| |
| /* |
| * O_PATH file descriptors can't be used so we need to re-open |
| * just in case. |
| */ |
| dfd = openat(-EBADF, buf, O_CLOEXEC | O_DIRECTORY, 0); |
| } else { |
| dfd = openat(fd, path, O_CLOEXEC | O_DIRECTORY, 0); |
| } |
| if (dfd < 0) |
| return -1; |
| |
| /* |
| * When fdopendir() below succeeds it assumes ownership of the fd so we |
| * to make sure we always have an fd that fdopendir() can own which is |
| * why we dup() in the case where the caller wants us to operate on the |
| * fd directly. |
| */ |
| dfd_dup = dup_cloexec(dfd); |
| if (dfd_dup < 0) { |
| close(dfd); |
| return -1; |
| } |
| |
| dir = fdopendir(dfd); |
| if (!dir) { |
| close(dfd); |
| close(dfd_dup); |
| return -1; |
| } |
| /* Transfer ownership to fdopendir(). */ |
| dfd = -EBADF; |
| |
| while ((direntp = readdir(dir))) { |
| if (!strcmp(direntp->d_name, ".") || |
| !strcmp(direntp->d_name, "..")) |
| continue; |
| |
| ret = fstatat(dfd_dup, direntp->d_name, &st, AT_SYMLINK_NOFOLLOW); |
| if (ret < 0 && errno != ENOENT) |
| break; |
| |
| ret = 0; |
| if (S_ISDIR(st.st_mode)) |
| ret = print_r(dfd_dup, direntp->d_name); |
| else |
| fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %d/%s\n", |
| (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid, |
| dfd_dup, direntp->d_name); |
| if (ret < 0 && errno != ENOENT) |
| break; |
| } |
| |
| if (!path || *path == '\0') |
| ret = fstatat(fd, "", &st, |
| AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | |
| AT_EMPTY_PATH); |
| else |
| ret = fstatat(fd, path, &st, |
| AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW); |
| if (!ret) |
| fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %s\n", |
| (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid, |
| (path && *path) ? path : "(null)"); |
| |
| close(dfd_dup); |
| closedir(dir); |
| |
| return ret; |
| } |
| #else |
| __attribute__((unused)) static int print_r(int fd, const char *path) |
| { |
| return 0; |
| } |
| #endif |
| |
| static int sys_execveat(int fd, const char *path, char **argv, char **envp, |
| int flags) |
| { |
| #ifdef __NR_execveat |
| return syscall(__NR_execveat, fd, path, argv, envp, flags); |
| #else |
| errno = ENOSYS; |
| return -1; |
| #endif |
| } |
| |
| static void test_setup(struct vfstest_info *info) |
| { |
| if (mkdirat(info->t_mnt_fd, T_DIR1, 0777)) |
| die("failure: mkdirat"); |
| |
| info->t_dir1_fd = openat(info->t_mnt_fd, T_DIR1, O_CLOEXEC | O_DIRECTORY); |
| if (info->t_dir1_fd < 0) |
| die("failure: openat"); |
| |
| if (fchmod(info->t_dir1_fd, 0777)) |
| die("failure: fchmod"); |
| } |
| |
| static void test_cleanup(struct vfstest_info *info) |
| { |
| safe_close(info->t_dir1_fd); |
| if (rm_r(info->t_mnt_fd, T_DIR1)) |
| die("failure: rm_r"); |
| } |
| |
| /* Validate that basic file operations on idmapped mounts. */ |
| static int fsids_unmapped(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, hardlink_target_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| /* create hardlink target */ |
| hardlink_target_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (hardlink_target_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* create directory for rename test */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0700)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* change ownership of all files to uid 0 */ |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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. |
| */ |
| |
| /* create hardlink */ |
| if (!linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) { |
| log_stderr("failure: linkat"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| /* try to rename a file */ |
| if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) { |
| log_stderr("failure: renameat"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| /* try to rename a directory */ |
| if (!renameat(open_tree_fd, DIR1, open_tree_fd, DIR1_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 file */ |
| if (unlinkat(open_tree_fd, FILE1, 0)) { |
| log_stderr("failure: unlinkat"); |
| goto out; |
| } |
| |
| /* remove directory */ |
| if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) { |
| log_stderr("failure: unlinkat"); |
| goto out; |
| } |
| |
| /* The caller's fsids don't have a mappings in the idmapped mount so |
| * any file creation must fail. |
| */ |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd >= 0) { |
| log_stderr("failure: create"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (!mknodat(open_tree_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| /* create character device */ |
| if (!mknodat(open_tree_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| /* create symlink */ |
| if (!symlinkat(FILE2, open_tree_fd, SYMLINK1)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| /* create directory */ |
| if (!mkdirat(open_tree_fd, DIR1, 0700)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| if (errno != EOVERFLOW) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(hardlink_target_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int fsids_mapped(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, hardlink_target_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create hardlink target */ |
| hardlink_target_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (hardlink_target_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* create directory for rename test */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0700)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* change ownership of all files to uid 0 */ |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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) { |
| 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 hardlink */ |
| if (linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) |
| die("failure: create hardlink"); |
| |
| /* try to rename a file */ |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: rename"); |
| |
| /* try to rename a directory */ |
| if (renameat(open_tree_fd, DIR1, open_tree_fd, DIR1_RENAME)) |
| die("failure: rename"); |
| |
| /* remove file */ |
| if (unlinkat(open_tree_fd, FILE1_RENAME, 0)) |
| die("failure: delete"); |
| |
| /* remove directory */ |
| if (unlinkat(open_tree_fd, DIR1_RENAME, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| /* The caller's fsids have mappings in the idmapped mount so any |
| * file creation must fail. |
| */ |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* create regular file via mknod */ |
| if (mknodat(open_tree_fd, FILE2, S_IFREG | 0000, 0)) |
| die("failure: create"); |
| |
| /* create character device */ |
| if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) |
| die("failure: create"); |
| |
| /* create symlink */ |
| if (symlinkat(FILE2, open_tree_fd, SYMLINK1)) |
| die("failure: create"); |
| |
| /* create directory */ |
| if (mkdirat(open_tree_fd, DIR1, 0700)) |
| die("failure: create"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(hardlink_target_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that basic file operations on idmapped mounts from a user namespace. */ |
| static int create_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| /* change ownership of all files to uid 0 */ |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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) { |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) |
| die("failure: open file"); |
| safe_close(file1_fd); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* create regular file via mknod */ |
| if (mknodat(open_tree_fd, FILE2, S_IFREG | 0000, 0)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* create symlink */ |
| if (symlinkat(FILE2, open_tree_fd, SYMLINK1)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* create directory */ |
| if (mkdirat(open_tree_fd, DIR1, 0700)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* try to rename a file */ |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1_RENAME, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* try to rename a file */ |
| if (renameat(open_tree_fd, DIR1, open_tree_fd, DIR1_RENAME)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, DIR1_RENAME, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* remove file */ |
| if (unlinkat(open_tree_fd, FILE1_RENAME, 0)) |
| die("failure: remove"); |
| |
| /* remove directory */ |
| if (unlinkat(open_tree_fd, DIR1_RENAME, AT_REMOVEDIR)) |
| die("failure: remove"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int hardlink_crossing_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| 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; |
| } |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (mkdirat(open_tree_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* We're crossing a mountpoint so this must fail. |
| * |
| * Note that this must also fail for non-idmapped mounts but here we're |
| * interested in making sure we're not introducing an accidental way to |
| * violate that restriction or that suddenly this becomes possible. |
| */ |
| if (!linkat(open_tree_fd, FILE1, info->t_dir1_fd, HARDLINK1, 0)) { |
| log_stderr("failure: linkat"); |
| goto out; |
| } |
| if (errno != EXDEV) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int hardlink_crossing_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| attr.userns_fd = get_userns_fd(10000, 0, 10000); |
| if (attr.userns_fd < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| open_tree_fd1 = 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_fd1 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| file1_fd = openat(open_tree_fd1, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| if (mkdirat(open_tree_fd1, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| open_tree_fd2 = 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_fd2 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| /* We're crossing a mountpoint so this must fail. |
| * |
| * Note that this must also fail for non-idmapped mounts but here we're |
| * interested in making sure we're not introducing an accidental way to |
| * violate that restriction or that suddenly this becomes possible. |
| */ |
| if (!linkat(open_tree_fd1, FILE1, open_tree_fd2, HARDLINK1, 0)) { |
| log_stderr("failure: linkat"); |
| goto out; |
| } |
| if (errno != EXDEV) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd1); |
| safe_close(open_tree_fd2); |
| |
| return fret; |
| } |
| |
| static int hardlink_from_idmapped_mount(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| attr.userns_fd = get_userns_fd(10000, 0, 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; |
| } |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* We're not crossing a mountpoint so this must succeed. */ |
| if (linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) { |
| log_stderr("failure: linkat"); |
| goto out; |
| } |
| |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int hardlink_from_idmapped_mount_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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(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) { |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* We're not crossing a mountpoint so this must succeed. */ |
| if (linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, HARDLINK1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int rename_crossing_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| 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; |
| } |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (mkdirat(open_tree_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* We're crossing a mountpoint so this must fail. |
| * |
| * Note that this must also fail for non-idmapped mounts but here we're |
| * interested in making sure we're not introducing an accidental way to |
| * violate that restriction or that suddenly this becomes possible. |
| */ |
| if (!renameat(open_tree_fd, FILE1, info->t_dir1_fd, FILE1_RENAME)) { |
| log_stderr("failure: renameat"); |
| goto out; |
| } |
| if (errno != EXDEV) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int rename_crossing_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| attr.userns_fd = get_userns_fd(10000, 0, 10000); |
| if (attr.userns_fd < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| open_tree_fd1 = 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_fd1 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| file1_fd = openat(open_tree_fd1, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (mkdirat(open_tree_fd1, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| open_tree_fd2 = 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_fd2 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| /* We're crossing a mountpoint so this must fail. |
| * |
| * Note that this must also fail for non-idmapped mounts but here we're |
| * interested in making sure we're not introducing an accidental way to |
| * violate that restriction or that suddenly this becomes possible. |
| */ |
| if (!renameat(open_tree_fd1, FILE1, open_tree_fd2, FILE1_RENAME)) { |
| log_stderr("failure: renameat"); |
| goto out; |
| } |
| if (errno != EXDEV) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd1); |
| safe_close(open_tree_fd2); |
| |
| return fret; |
| } |
| |
| static int rename_from_idmapped_mount(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| attr.userns_fd = get_userns_fd(10000, 0, 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; |
| } |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* We're not crossing a mountpoint so this must succeed. */ |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) { |
| log_stderr("failure: renameat"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int rename_from_idmapped_mount_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| pid_t pid; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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(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) { |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* We're not crossing a mountpoint so this must succeed. */ |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: create"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1_RENAME, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int symlink_regular_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct stat st; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { |
| log_stderr("failure: chown_r"); |
| 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 (symlinkat(FILE1, open_tree_fd, FILE2)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| |
| if (fchownat(open_tree_fd, FILE2, 15000, 15000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| if (fstatat(open_tree_fd, FILE2, &st, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fstatat"); |
| goto out; |
| } |
| |
| if (st.st_uid != 15000 || st.st_gid != 15000) { |
| log_stderr("failure: compare ids"); |
| goto out; |
| } |
| |
| if (fstatat(open_tree_fd, FILE1, &st, 0)) { |
| log_stderr("failure: fstatat"); |
| goto out; |
| } |
| |
| if (st.st_uid != 10000 || st.st_gid != 10000) { |
| log_stderr("failure: compare ids"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int symlink_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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) { |
| if (!switch_fsids(10000, 10000)) |
| die("failure: switch fsids"); |
| |
| if (!caps_up()) |
| die("failure: raise caps"); |
| |
| if (symlinkat(FILE1, open_tree_fd, FILE2)) |
| die("failure: create"); |
| |
| if (fchownat(open_tree_fd, FILE2, 15000, 15000, AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, 15000, 15000)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int symlink_idmapped_mounts_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { |
| log_stderr("failure: chown_r"); |
| 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) { |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) |
| die("failure: create"); |
| safe_close(file1_fd); |
| |
| if (symlinkat(FILE1, open_tree_fd, FILE2)) |
| die("failure: create"); |
| |
| if (fchownat(open_tree_fd, FILE2, 5000, 5000, AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that a caller whose fsids map into the idmapped mount within it's |
| * user namespace cannot create any device nodes. |
| */ |
| static int device_node_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| 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) { |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| /* create character device */ |
| if (!mknodat(open_tree_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) |
| die("failure: create"); |
| |
| 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); |
| |
| return fret; |
| } |
| |
| |
| /* Validate that changing file ownership works correctly on idmapped mounts. */ |
| static int expected_uid_gid_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; |
| struct mount_attr attr1 = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| struct mount_attr attr2 = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!switch_fsids(0, 0)) { |
| log_stderr("failure: switch_fsids"); |
| goto out; |
| } |
| |
| /* create regular file via open() */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| |
| /* create character device */ |
| if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| |
| /* create hardlink */ |
| if (linkat(info->t_dir1_fd, FILE1, info->t_dir1_fd, HARDLINK1, 0)) { |
| log_stderr("failure: linkat"); |
| goto out; |
| } |
| |
| /* create symlink */ |
| if (symlinkat(FILE2, info->t_dir1_fd, SYMLINK1)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0700)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr1.userns_fd = get_userns_fd(0, 10000, 10000); |
| if (attr1.userns_fd < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| open_tree_fd1 = 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_fd1 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr1, sizeof(attr1))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| /* Validate that all files created through the image mountpoint are |
| * owned by the callers fsuid and fsgid. |
| */ |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Validate that all files are owned by the uid and gid specified in |
| * the idmapping of the mount they are accessed from. |
| */ |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr2.userns_fd = get_userns_fd(0, 30000, 2001); |
| if (attr2.userns_fd < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| open_tree_fd2 = 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_fd2 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr2, sizeof(attr2))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| /* Validate that all files are owned by the uid and gid specified in |
| * the idmapping of the mount they are accessed from. |
| */ |
| if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Change ownership throught original image mountpoint. */ |
| if (fchownat(info->t_dir1_fd, FILE1, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchownat(info->t_dir1_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchownat(info->t_dir1_fd, HARDLINK1, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchownat(info->t_dir1_fd, CHRDEV1, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchownat(info->t_dir1_fd, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchownat(info->t_dir1_fd, SYMLINK1, 2000, 2000, AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchownat(info->t_dir1_fd, DIR1, 2000, 2000, AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| /* Check ownership through original mount. */ |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 3000, 3000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Check ownership through first idmapped mount. */ |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 12000, 12000)) { |
| log_stderr("failure:expected_uid_gid "); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 13000, 13000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 12000, 12000)) { |
| log_stderr("failure:expected_uid_gid "); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Check ownership through second idmapped mount. */ |
| if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr1.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (!fchownat(info->t_dir1_fd, FILE1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, FILE2, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, HARDLINK1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, CHRDEV1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, SYMLINK1, 1000, 1000, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, DIR1, 1000, 1000, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| |
| if (!fchownat(open_tree_fd2, FILE1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, FILE2, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, HARDLINK1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, CHRDEV1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, SYMLINK1, 1000, 1000, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, DIR1, 1000, 1000, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| |
| if (fchownat(open_tree_fd1, FILE1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd1, FILE2, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd1, HARDLINK1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd1, CHRDEV1, 1000, 1000, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd1, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd1, SYMLINK1, 1000, 1000, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd1, DIR1, 1000, 1000, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| |
| if (!expected_uid_gid(open_tree_fd2, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 1000, 1000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 1000, 1000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 1000, 1000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 1000, 1000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 1000, 1000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 1000, 1000)) |
| die("failure: expected_uid_gid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Check ownership through original mount. */ |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Check ownership through first idmapped mount. */ |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Check ownership through second idmapped mount. */ |
| if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 31000, 31000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 31000, 31000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 31000, 31000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 31000, 31000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 31000, 31000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 31000, 31000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr2.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (!fchownat(info->t_dir1_fd, FILE1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, FILE2, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, HARDLINK1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, SYMLINK1, 0, 0, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| if (!fchownat(info->t_dir1_fd, DIR1, 0, 0, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| |
| if (!fchownat(open_tree_fd1, FILE1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd1, FILE2, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd1, HARDLINK1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd1, CHRDEV1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd1, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd1, SYMLINK1, 0, 0, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd1, DIR1, 0, 0, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| |
| if (fchownat(open_tree_fd2, FILE1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd2, FILE2, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd2, HARDLINK1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd2, CHRDEV1, 0, 0, 0)) |
| die("failure: fchownat"); |
| if (!fchownat(open_tree_fd2, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd2, SYMLINK1, 0, 0, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| if (fchownat(open_tree_fd2, DIR1, 0, 0, AT_EMPTY_PATH)) |
| die("failure: fchownat"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd1, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| |
| if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Check ownership through original mount. */ |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Check ownership through first idmapped mount. */ |
| if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Check ownership through second idmapped mount. */ |
| if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 32000, 32000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 30000, 30000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr1.userns_fd); |
| safe_close(attr2.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd1); |
| safe_close(open_tree_fd2); |
| |
| return fret; |
| } |
| |
| static int fscaps(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, fd_userns = -EBADF; |
| pid_t pid; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* Skip if vfs caps are unsupported. */ |
| if (set_dummy_vfs_caps(file1_fd, 0, 1000)) |
| return 0; |
| |
| /* Changing mount properties on a detached mount. */ |
| fd_userns = get_userns_fd(0, 10000, 10000); |
| if (fd_userns < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 1000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(fd_userns, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| /* |
| * On kernels before 5.12 this would succeed and return the |
| * unconverted caps. Then - for whatever reason - this behavior |
| * got changed and since 5.12 EOVERFLOW is returned when the |
| * rootid stored alongside the vfs caps does not map to uid 0 in |
| * the caller's user namespace. |
| */ |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 1000) && errno != EOVERFLOW) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (fremovexattr(file1_fd, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| goto out; |
| } |
| if (expected_dummy_vfs_caps_uid(file1_fd, -1)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| if (errno != ENODATA) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| if (set_dummy_vfs_caps(file1_fd, 0, 10000)) { |
| log_stderr("failure: set_dummy_vfs_caps"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 10000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(fd_userns, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (fremovexattr(file1_fd, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| goto out; |
| } |
| if (expected_dummy_vfs_caps_uid(file1_fd, -1)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| if (errno != ENODATA) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| safe_close(fd_userns); |
| |
| return fret; |
| } |
| |
| static int fscaps_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* Skip if vfs caps are unsupported. */ |
| if (set_dummy_vfs_caps(file1_fd, 0, 1000)) |
| return 0; |
| |
| if (fremovexattr(file1_fd, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| 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; |
| } |
| |
| file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); |
| if (file1_fd2 < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (!set_dummy_vfs_caps(file1_fd2, 0, 1000)) { |
| log_stderr("failure: set_dummy_vfs_caps"); |
| goto out; |
| } |
| |
| if (set_dummy_vfs_caps(file1_fd2, 0, 10000)) { |
| log_stderr("failure: set_dummy_vfs_caps"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 10000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| 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"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 0)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (fremovexattr(file1_fd2, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| goto out; |
| } |
| if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| if (errno != ENODATA) { |
| log_stderr("failure: errno"); |
| goto out; |
| } |
| |
| if (set_dummy_vfs_caps(file1_fd2, 0, 12000)) { |
| log_stderr("failure: set_dummy_vfs_caps"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 12000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 2000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| 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"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 2000)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(file1_fd2); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int fscaps_idmapped_mounts_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* Skip if vfs caps are unsupported. */ |
| if (set_dummy_vfs_caps(file1_fd, 0, 1000)) |
| return 0; |
| |
| if (fremovexattr(file1_fd, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| 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; |
| } |
| |
| file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); |
| if (file1_fd2 < 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"); |
| |
| if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| if (errno != ENODATA) |
| die("failure: errno"); |
| |
| if (set_dummy_vfs_caps(file1_fd2, 0, 1000)) |
| die("failure: set_dummy_vfs_caps"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 1000)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 1000) && errno != EOVERFLOW) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 1000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(file1_fd2); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* Skip if vfs caps are unsupported. */ |
| if (set_dummy_vfs_caps(file1_fd, 0, 1000)) |
| return 0; |
| |
| if (fremovexattr(file1_fd, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| goto out; |
| } |
| if (expected_dummy_vfs_caps_uid(file1_fd, -1)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| if (errno != ENODATA) { |
| log_stderr("failure: errno"); |
| 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; |
| } |
| |
| file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); |
| if (file1_fd2 < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* |
| * Verify we can set an v3 fscap for real root this was regressed at |
| * some point. Make sure this doesn't happen again! |
| */ |
| 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"); |
| |
| if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| if (errno != ENODATA) |
| die("failure: errno"); |
| |
| if (set_dummy_vfs_caps(file1_fd2, 0, 0)) |
| die("failure: set_dummy_vfs_caps"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 0)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 0) && errno != EOVERFLOW) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 10000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(file1_fd2); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int fscaps_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* Skip if vfs caps are unsupported. */ |
| if (set_dummy_vfs_caps(file1_fd, 0, 1000)) { |
| log_stderr("failure: set_dummy_vfs_caps"); |
| goto out; |
| } |
| |
| if (fremovexattr(file1_fd, "security.capability")) { |
| log_stderr("failure: fremovexattr"); |
| goto out; |
| } |
| |
| /* change ownership of all files to uid 0 */ |
| if (chown_r(info->t_mnt_fd, T_DIR1, 20000, 20000)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(20000, 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; |
| } |
| |
| file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); |
| if (file1_fd2 < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int userns_fd; |
| |
| userns_fd = get_userns_fd(0, 10000, 10000); |
| if (userns_fd < 0) |
| die("failure: get_userns_fd"); |
| |
| if (!switch_userns(userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (set_dummy_vfs_caps(file1_fd2, 0, 0)) |
| die("failure: set fscaps"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 0)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 20000) && errno != EOVERFLOW) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 20000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int userns_fd; |
| |
| userns_fd = get_userns_fd(0, 10000, 10000); |
| if (userns_fd < 0) |
| die("failure: get_userns_fd"); |
| |
| if (!switch_userns(userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (fremovexattr(file1_fd2, "security.capability")) |
| die("failure: fremovexattr"); |
| if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| if (errno != ENODATA) |
| die("failure: errno"); |
| |
| if (set_dummy_vfs_caps(file1_fd2, 0, 1000)) |
| die("failure: set_dummy_vfs_caps"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd2, 1000)) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 21000) && errno != EOVERFLOW) |
| die("failure: expected_dummy_vfs_caps_uid"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (!expected_dummy_vfs_caps_uid(file1_fd, 21000)) { |
| log_stderr("failure: expected_dummy_vfs_caps_uid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(file1_fd2); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that when the IDMAP_MOUNT_TEST_RUN_SETID environment variable is set |
| * to 1 that we are executed with setid privileges and if set to 0 we are not. |
| * If the env variable isn't set the tests are not run. |
| */ |
| static void __attribute__((constructor)) setuid_rexec(void) |
| { |
| const char *expected_euid_str, *expected_egid_str, *rexec; |
| |
| rexec = getenv("IDMAP_MOUNT_TEST_RUN_SETID"); |
| /* This is a regular test-suite run. */ |
| if (!rexec) |
| return; |
| |
| expected_euid_str = getenv("EXPECTED_EUID"); |
| expected_egid_str = getenv("EXPECTED_EGID"); |
| |
| if (expected_euid_str && expected_egid_str) { |
| uid_t expected_euid; |
| gid_t expected_egid; |
| |
| expected_euid = atoi(expected_euid_str); |
| expected_egid = atoi(expected_egid_str); |
| |
| if (strcmp(rexec, "1") == 0) { |
| /* we're expecting to run setid */ |
| if ((getuid() != geteuid()) && (expected_euid == geteuid()) && |
| (getgid() != getegid()) && (expected_egid == getegid())) |
| exit(EXIT_SUCCESS); |
| } else if (strcmp(rexec, "0") == 0) { |
| /* we're expecting to not run setid */ |
| if ((getuid() == geteuid()) && (expected_euid == geteuid()) && |
| (getgid() == getegid()) && (expected_egid == getegid())) |
| exit(EXIT_SUCCESS); |
| else |
| die("failure: non-setid"); |
| } |
| } |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| /* Validate that setid transitions are handled correctly. */ |
| static int setid_binaries(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, exec_fd = -EBADF; |
| pid_t pid; |
| |
| /* create a file to be used as setuid binary */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* open our own executable */ |
| exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); |
| if (exec_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* copy our own executable into the file we created */ |
| if (fd_to_fd(exec_fd, file1_fd)) { |
| log_stderr("failure: fd_to_fd"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 5000, 5000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| /* set the setid bits and grant execute permissions to the group */ |
| if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(exec_fd); |
| safe_close(file1_fd); |
| |
| /* Verify we run setid binary as uid and gid 5000 from the original |
| * mount. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| static char *envp[] = { |
| "IDMAP_MOUNT_TEST_RUN_SETID=1", |
| "EXPECTED_EUID=5000", |
| "EXPECTED_EGID=5000", |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 5000, 5000)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(info->t_dir1_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| |
| return fret; |
| } |
| |
| /* Validate that setid transitions are handled correctly on idmapped mounts. */ |
| static int setid_binaries_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* create a file to be used as setuid binary */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* open our own executable */ |
| exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); |
| if (exec_fd < 0) { |
| log_stderr("failure:openat "); |
| goto out; |
| } |
| |
| /* copy our own executable into the file we created */ |
| if (fd_to_fd(exec_fd, file1_fd)) { |
| log_stderr("failure: fd_to_fd"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 5000, 5000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| /* set the setid bits and grant execute permissions to the group */ |
| if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(exec_fd); |
| safe_close(file1_fd); |
| |
| /* 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; |
| } |
| |
| /* A detached mount will have an anonymous mount namespace attached to |
| * it. This means that we can't execute setid binaries on a detached |
| * mount because the mnt_may_suid() helper will fail the check_mount() |
| * part of its check which compares the caller's mount namespace to the |
| * detached mount's mount namespace. Since by definition an anonymous |
| * mount namespace is not equale to any mount namespace currently in |
| * use this can't work. So attach the mount to the filesystem first |
| * before performing this check. |
| */ |
| if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { |
| log_stderr("failure: sys_move_mount"); |
| goto out; |
| } |
| |
| /* Verify we run setid binary as uid and gid 10000 from idmapped mount mount. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| static char *envp[] = { |
| "IDMAP_MOUNT_TEST_RUN_SETID=1", |
| "EXPECTED_EUID=15000", |
| "EXPECTED_EGID=15000", |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 15000, 15000)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(exec_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); |
| sys_umount2(t_buf, MNT_DETACH); |
| rm_r(info->t_mnt_fd, DIR1); |
| |
| return fret; |
| } |
| |
| /* Validate that setid transitions are handled correctly on idmapped mounts |
| * running in a user namespace where the uid and gid of the setid binary have no |
| * mapping. |
| */ |
| static int setid_binaries_idmapped_mounts_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { |
| log_stderr("failure: "); |
| goto out; |
| } |
| |
| /* create a file to be used as setuid binary */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* open our own executable */ |
| exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); |
| if (exec_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* copy our own executable into the file we created */ |
| if (fd_to_fd(exec_fd, file1_fd)) { |
| log_stderr("failure: fd_to_fd"); |
| goto out; |
| } |
| |
| safe_close(exec_fd); |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 5000, 5000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| /* set the setid bits and grant execute permissions to the group */ |
| if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| /* 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; |
| } |
| |
| /* A detached mount will have an anonymous mount namespace attached to |
| * it. This means that we can't execute setid binaries on a detached |
| * mount because the mnt_may_suid() helper will fail the check_mount() |
| * part of its check which compares the caller's mount namespace to the |
| * detached mount's mount namespace. Since by definition an anonymous |
| * mount namespace is not equale to any mount namespace currently in |
| * use this can't work. So attach the mount to the filesystem first |
| * before performing this check. |
| */ |
| if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { |
| log_stderr("failure: sys_move_mount"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| static char *envp[] = { |
| "IDMAP_MOUNT_TEST_RUN_SETID=1", |
| "EXPECTED_EUID=5000", |
| "EXPECTED_EGID=5000", |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 5000, 5000)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| /* set the setid bits and grant execute permissions to the group */ |
| if (fchmod(file1_fd, S_IXOTH | S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| static char *envp[] = { |
| "IDMAP_MOUNT_TEST_RUN_SETID=1", |
| "EXPECTED_EUID=0", |
| "EXPECTED_EGID=0", |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 5000, 5000, true)) |
| die("failure: switch_userns"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 30000, 30000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| if (fchmod(file1_fd, S_IXOTH | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| /* Verify that we can't assume a uid and gid of a setid binary for which |
| * we have no mapping in our user namespace. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| char expected_euid[100]; |
| char expected_egid[100]; |
| static char *envp[4] = { |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| envp[0] = "IDMAP_MOUNT_TEST_RUN_SETID=0"; |
| snprintf(expected_euid, sizeof(expected_euid), "EXPECTED_EUID=%d", geteuid()); |
| envp[1] = expected_euid; |
| snprintf(expected_egid, sizeof(expected_egid), "EXPECTED_EGID=%d", getegid()); |
| envp[2] = expected_egid; |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(exec_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); |
| sys_umount2(t_buf, MNT_DETACH); |
| rm_r(info->t_mnt_fd, DIR1); |
| |
| return fret; |
| } |
| |
| /* Validate that setid transitions are handled correctly on idmapped mounts |
| * running in a user namespace where the uid and gid of the setid binary have no |
| * mapping. |
| */ |
| static int setid_binaries_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| /* create a file to be used as setuid binary */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* open our own executable */ |
| exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); |
| if (exec_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* copy our own executable into the file we created */ |
| if (fd_to_fd(exec_fd, file1_fd)) { |
| log_stderr("failure: fd_to_fd"); |
| goto out; |
| } |
| |
| safe_close(exec_fd); |
| |
| /* change ownership of all files to uid 0 */ |
| if (chown_r(info->t_mnt_fd, T_DIR1, 20000, 20000)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 25000, 25000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| /* set the setid bits and grant execute permissions to the group */ |
| if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(20000, 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; |
| } |
| |
| /* A detached mount will have an anonymous mount namespace attached to |
| * it. This means that we can't execute setid binaries on a detached |
| * mount because the mnt_may_suid() helper will fail the check_mount() |
| * part of its check which compares the caller's mount namespace to the |
| * detached mount's mount namespace. Since by definition an anonymous |
| * mount namespace is not equale to any mount namespace currently in |
| * use this can't work. So attach the mount to the filesystem first |
| * before performing this check. |
| */ |
| if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { |
| log_stderr("failure: sys_move_mount"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int userns_fd; |
| static char *envp[] = { |
| "IDMAP_MOUNT_TEST_RUN_SETID=1", |
| "EXPECTED_EUID=5000", |
| "EXPECTED_EGID=5000", |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| userns_fd = get_userns_fd(0, 10000, 10000); |
| if (userns_fd < 0) |
| die("failure: get_userns_fd"); |
| |
| if (!switch_userns(userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 5000, 5000)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 20000, 20000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| /* set the setid bits and grant execute permissions to the group */ |
| if (fchmod(file1_fd, S_IXOTH | S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int userns_fd; |
| static char *envp[] = { |
| "IDMAP_MOUNT_TEST_RUN_SETID=1", |
| "EXPECTED_EUID=0", |
| "EXPECTED_EGID=0", |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| userns_fd = get_userns_fd(0, 10000, 10000); |
| if (userns_fd < 0) |
| die("failure: get_userns_fd"); |
| |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| /* chown the file to the uid and gid we want to assume */ |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| if (fchmod(file1_fd, S_IXOTH | S_IEXEC | S_ISUID | S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setid(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: is_setid"); |
| goto out; |
| } |
| |
| safe_close(file1_fd); |
| |
| /* Verify that we can't assume a uid and gid of a setid binary for |
| * which we have no mapping in our user namespace. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int userns_fd; |
| char expected_euid[100]; |
| char expected_egid[100]; |
| static char *envp[4] = { |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| }; |
| static char *argv[] = { |
| NULL, |
| }; |
| |
| userns_fd = get_userns_fd(0, 10000, 10000); |
| if (userns_fd < 0) |
| die("failure: get_userns_fd"); |
| |
| if (!switch_userns(userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| envp[0] = "IDMAP_MOUNT_TEST_RUN_SETID=0"; |
| snprintf(expected_euid, sizeof(expected_euid), "EXPECTED_EUID=%d", geteuid()); |
| envp[1] = expected_euid; |
| snprintf(expected_egid, sizeof(expected_egid), "EXPECTED_EGID=%d", getegid()); |
| envp[2] = expected_egid; |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: expected_uid_gid"); |
| |
| sys_execveat(open_tree_fd, FILE1, argv, envp, 0); |
| die("failure: sys_execveat"); |
| |
| exit(EXIT_FAILURE); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(exec_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); |
| sys_umount2(t_buf, MNT_DETACH); |
| rm_r(info->t_mnt_fd, DIR1); |
| |
| return fret; |
| } |
| |
| static int sticky_bit_unlink(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (fchown(dir_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| if (fchmod(dir_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is not set so we must be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* set sticky bit */ |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is set so we must not be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (!unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the files so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* change ownership */ |
| if (fchownat(dir_fd, FILE1, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) |
| die("failure: expected_uid_gid"); |
| if (fchownat(dir_fd, FILE2, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) |
| die("failure: expected_uid_gid"); |
| |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* change uid to unprivileged user */ |
| if (fchown(dir_fd, 1000, -1)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the directory so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(dir_fd); |
| |
| return fret; |
| } |
| |
| static int sticky_bit_unlink_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 10000, 10000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(10000, 0, 10000); |
| if (attr.userns_fd < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| open_tree_fd = sys_open_tree(dir_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 sticky bit is not set so we must be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* set sticky bit */ |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is set so we must not be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (!unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the files so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* change ownership */ |
| if (fchownat(dir_fd, FILE1, 11000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 11000, 10000)) |
| die("failure: expected_uid_gid"); |
| if (fchownat(dir_fd, FILE2, 11000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE2, 0, 11000, 12000)) |
| die("failure: expected_uid_gid"); |
| |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* change uid to unprivileged user */ |
| if (fchown(dir_fd, 11000, -1)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the directory so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(dir_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that the sticky bit behaves correctly on idmapped mounts for unlink |
| * operations in a user namespace. |
| */ |
| static int sticky_bit_unlink_idmapped_mounts_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| 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(dir_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 sticky bit is not set so we must be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* set sticky bit */ |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is set so we must not be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (!unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the files so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* change ownership */ |
| if (fchownat(dir_fd, FILE1, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) |
| die("failure: expected_uid_gid"); |
| if (fchownat(dir_fd, FILE2, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) |
| die("failure: expected_uid_gid"); |
| |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (!unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* change uid to unprivileged user */ |
| if (fchown(dir_fd, 1000, -1)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the directory so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| /* we don't own the directory from the original mount */ |
| if (!unlinkat(dir_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!unlinkat(dir_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* we own the file from the idmapped mount */ |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: unlinkat"); |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: unlinkat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(dir_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int sticky_bit_rename(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* The sticky bit is not set so we must be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE1_RENAME, dir_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2_RENAME, dir_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* set sticky bit */ |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* The sticky bit is set so we must not be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the files so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* change ownership */ |
| if (fchownat(dir_fd, FILE1, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) |
| die("failure: expected_uid_gid"); |
| if (fchownat(dir_fd, FILE2, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) |
| die("failure: expected_uid_gid"); |
| |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE1_RENAME, dir_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2_RENAME, dir_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* change uid to unprivileged user */ |
| if (fchown(dir_fd, 1000, -1)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| |
| /* The sticky bit is set and we own the directory so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE1_RENAME, dir_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2_RENAME, dir_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(dir_fd); |
| |
| return fret; |
| } |
| |
| static int sticky_bit_rename_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (fchown(dir_fd, 10000, 10000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| if (fchmod(dir_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(10000, 0, 10000); |
| if (attr.userns_fd < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| open_tree_fd = sys_open_tree(dir_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 sticky bit is not set so we must be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* set sticky bit */ |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* The sticky bit is set so we must not be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the files so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* change ownership */ |
| if (fchownat(dir_fd, FILE1, 11000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 11000, 10000)) |
| die("failure: expected_uid_gid"); |
| if (fchownat(dir_fd, FILE2, 11000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE2, 0, 11000, 12000)) |
| die("failure: expected_uid_gid"); |
| |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* change uid to unprivileged user */ |
| if (fchown(dir_fd, 11000, -1)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the directory so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(dir_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that the sticky bit behaves correctly on idmapped mounts for unlink |
| * operations in a user namespace. |
| */ |
| static int sticky_bit_rename_idmapped_mounts_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE2, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| 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(dir_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 sticky bit is not set so we must be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE1_RENAME, dir_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(dir_fd, FILE2_RENAME, dir_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* set sticky bit */ |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* The sticky bit is set so we must not be able to delete files not |
| * owned by us. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the files so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* change ownership */ |
| if (fchownat(dir_fd, FILE1, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) |
| die("failure: expected_uid_gid"); |
| if (fchownat(dir_fd, FILE2, 1000, -1, 0)) |
| die("failure: fchownat"); |
| if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) |
| die("failure: expected_uid_gid"); |
| |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* change uid to unprivileged user */ |
| if (fchown(dir_fd, 1000, -1)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* The sticky bit is set and we own the directory so we must be able to |
| * delete the files now. |
| */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| /* we don't own the directory from the original mount */ |
| if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* we own the file from the idmapped mount */ |
| if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) |
| die("failure: renameat"); |
| |
| if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) |
| die("failure: renameat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(open_tree_fd); |
| safe_close(attr.userns_fd); |
| safe_close(dir_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that protected symlinks work correctly. */ |
| static int protected_symlinks(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, fd = -EBADF; |
| pid_t pid; |
| |
| if (!protected_symlinks_enabled()) |
| return 0; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create symlinks */ |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER1)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER1, 0, 0, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER1, AT_SYMLINK_NOFOLLOW, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER2)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER2, 1000, 1000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER2, AT_SYMLINK_NOFOLLOW, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER3)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER3, 2000, 2000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER3, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* validate file can be directly read */ |
| fd = openat(dir_fd, FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(dir_fd, SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| /* validate file can be directly read */ |
| fd = openat(dir_fd, FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(dir_fd, SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through root symlink */ |
| fd = openat(dir_fd, SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can't be read through other users symlink */ |
| fd = openat(dir_fd, SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); |
| if (fd >= 0) |
| die("failure: openat"); |
| if (errno != EACCES) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(2000, 2000)) |
| die("failure: switch_ids"); |
| |
| /* validate file can be directly read */ |
| fd = openat(dir_fd, FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(dir_fd, SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through root symlink */ |
| fd = openat(dir_fd, SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can't be read through other users symlink */ |
| fd = openat(dir_fd, SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); |
| if (fd >= 0) |
| die("failure: openat"); |
| if (errno != EACCES) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(fd); |
| safe_close(dir_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that protected symlinks work correctly on idmapped mounts. */ |
| static int protected_symlinks_idmapped_mounts(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!protected_symlinks_enabled()) |
| return 0; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 10000, 10000)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create symlinks */ |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER1)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER1, 10000, 10000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER1, AT_SYMLINK_NOFOLLOW, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER2)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER2, 11000, 11000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER2, AT_SYMLINK_NOFOLLOW, 11000, 11000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER3)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER3, 12000, 12000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER3, AT_SYMLINK_NOFOLLOW, 12000, 12000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 10000, 10000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(10000, 0, 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: open_tree_fd"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| /* validate file can be directly read */ |
| fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| /* validate file can be directly read */ |
| fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through root symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can't be read through other users symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); |
| if (fd >= 0) |
| die("failure: openat"); |
| if (errno != EACCES) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(2000, 2000)) |
| die("failure: switch_ids"); |
| |
| /* validate file can be directly read */ |
| fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through root symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can't be read through other users symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); |
| if (fd >= 0) |
| die("failure: openat"); |
| if (errno != EACCES) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(fd); |
| safe_close(dir_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* Validate that protected symlinks work correctly on idmapped mounts inside a |
| * user namespace. |
| */ |
| static int protected_symlinks_idmapped_mounts_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir_fd = -EBADF, fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (!protected_symlinks_enabled()) |
| return 0; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(dir_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir_fd, 0777 | S_ISVTX)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| /* validate sticky bit is set */ |
| if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { |
| log_stderr("failure: is_sticky"); |
| goto out; |
| } |
| |
| /* create regular file via mknod */ |
| if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { |
| log_stderr("failure: mknodat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (fchmodat(dir_fd, FILE1, 0644, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* create symlinks */ |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER1)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER1, 0, 0, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER1, AT_SYMLINK_NOFOLLOW, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER2)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER2, 1000, 1000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER2, AT_SYMLINK_NOFOLLOW, 1000, 1000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (symlinkat(FILE1, dir_fd, SYMLINK_USER3)) { |
| log_stderr("failure: symlinkat"); |
| goto out; |
| } |
| if (fchownat(dir_fd, SYMLINK_USER3, 2000, 2000, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, SYMLINK_USER3, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: expected_uid_gid"); |
| 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; |
| } |
| |
| /* validate file can be directly read */ |
| fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| safe_close(fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| /* validate file can be directly read */ |
| fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through root symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can't be read through other users symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); |
| if (fd >= 0) |
| die("failure: openat"); |
| if (errno != EACCES) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 2000, 2000, true)) |
| die("failure: switch_userns"); |
| |
| /* validate file can be directly read */ |
| fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through own symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can be read through root symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); |
| if (fd < 0) |
| die("failure: openat"); |
| safe_close(fd); |
| |
| /* validate file can't be read through other users symlink */ |
| fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); |
| if (fd >= 0) |
| die("failure: openat"); |
| if (errno != EACCES) |
| die("failure: errno"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(dir_fd); |
| safe_close(open_tree_fd); |
| safe_close(attr.userns_fd); |
| |
| return fret; |
| } |
| |
| static int acls(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int dir1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| |
| if (mkdirat(info->t_dir1_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| if (fchmodat(info->t_dir1_fd, DIR1, 0777, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| if (mkdirat(info->t_dir1_fd, DIR2, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| if (fchmodat(info->t_dir1_fd, DIR2, 0777, 0)) { |
| log_stderr("failure: fchmodat"); |
| goto out; |
| } |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(100010, 100020, 5); |
| 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); |
| 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 (sys_move_mount(open_tree_fd, "", info->t_dir1_fd, DIR2, MOVE_MOUNT_F_EMPTY_PATH)) { |
| log_stderr("failure: sys_move_mount"); |
| goto out; |
| } |
| |
| dir1_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (dir1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (mkdirat(dir1_fd, DIR3, 0000)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| if (fchown(dir1_fd, 100010, 100010)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(dir1_fd, 0777)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| snprintf(t_buf, sizeof(t_buf), "setfacl -m u:100010:rwx %s/%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1, DIR3); |
| if (system(t_buf)) { |
| log_stderr("failure: system"); |
| goto out; |
| } |
| |
| snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:100010:rwx", info->t_mountpoint, T_DIR1, DIR1, DIR3); |
| if (system(t_buf)) { |
| log_stderr("failure: system"); |
| goto out; |
| } |
| |
| snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:100020:rwx", info->t_mountpoint, T_DIR1, DIR2, DIR3); |
| if (system(t_buf)) { |
| log_stderr("failure: system"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 100010, 100010, true)) |
| die("failure: switch_userns"); |
| |
| snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:%lu:rwx", |
| info->t_mountpoint, T_DIR1, DIR1, DIR3, 4294967295LU); |
| if (system(t_buf)) |
| die("failure: system"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 100010, 100010, true)) |
| die("failure: switch_userns"); |
| |
| snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:%lu:rwx", |
| info->t_mountpoint, T_DIR1, DIR2, DIR3, 100010LU); |
| if (system(t_buf)) |
| die("failure: system"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* Now, dir is owned by someone else in the user namespace, but we can |
| * still read it because of acls. |
| */ |
| if (fchown(dir1_fd, 100012, 100012)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int fd; |
| |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 100010, 100010, true)) |
| die("failure: switch_userns"); |
| |
| fd = openat(open_tree_fd, DIR3, O_CLOEXEC | O_DIRECTORY); |
| if (fd < 0) |
| die("failure: openat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| /* if we delete the acls, the ls should fail because it's 700. */ |
| snprintf(t_buf, sizeof(t_buf), "%s/%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1, DIR3); |
| if (removexattr(t_buf, "system.posix_acl_access")) { |
| log_stderr("failure: removexattr"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int fd; |
| |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 100010, 100010, true)) |
| die("failure: switch_userns"); |
| |
| fd = openat(open_tree_fd, DIR3, O_CLOEXEC | O_DIRECTORY); |
| if (fd >= 0) |
| die("failure: openat"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| snprintf(t_buf, sizeof(t_buf), "%s/" T_DIR1 "/" DIR2, info->t_mountpoint); |
| sys_umount2(t_buf, MNT_DETACH); |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(dir1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| #ifdef HAVE_LIBURING_H |
| static int io_uring(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF; |
| struct io_uring *ring; |
| int cred_id, ret, ret_cqe; |
| pid_t pid; |
| |
| ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, 0, 0); |
| if (!ring) |
| return log_errno(-1, "failure: io_uring_queue_init"); |
| |
| ret = io_uring_queue_init(8, ring, 0); |
| if (ret) { |
| log_stderr("failure: io_uring_queue_init"); |
| goto out_unmap; |
| } |
| |
| ret = io_uring_register_personality(ring); |
| if (ret < 0) { |
| fret = 0; |
| goto out_unmap; /* personalities not supported */ |
| } |
| cred_id = ret; |
| |
| /* create file only owner can open */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(file1_fd, 0600)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* Verify we can open it with our current credentials. */ |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| -1, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| /* Verify we can't open it with our current credentials. */ |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| -1, false, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure %d", ret_cqe); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(1000, 1000)) |
| die("failure: switch_ids"); |
| |
| /* Verify we can open it with the registered credentials. */ |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| cred_id, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| /* Verify we can open it with the registered credentials and as |
| * a link. |
| */ |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| cred_id, true, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| ret = io_uring_unregister_personality(ring, cred_id); |
| if (ret) |
| log_stderr("failure: io_uring_unregister_personality"); |
| |
| out_unmap: |
| munmap(ring, sizeof(struct io_uring)); |
| |
| safe_close(file1_fd); |
| |
| return fret; |
| } |
| |
| static int io_uring_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, userns_fd = -EBADF; |
| struct io_uring *ring; |
| int cred_id, ret, ret_cqe; |
| pid_t pid; |
| |
| ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, 0, 0); |
| if (!ring) |
| return log_errno(-1, "failure: io_uring_queue_init"); |
| |
| ret = io_uring_queue_init(8, ring, 0); |
| if (ret) { |
| log_stderr("failure: io_uring_queue_init"); |
| goto out_unmap; |
| } |
| |
| ret = io_uring_register_personality(ring); |
| if (ret < 0) { |
| fret = 0; |
| goto out_unmap; /* personalities not supported */ |
| } |
| cred_id = ret; |
| |
| /* create file only owner can open */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(file1_fd, 0600)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| userns_fd = get_userns_fd(0, 10000, 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) { |
| /* Verify we can open it with our current credentials. */ |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| -1, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| 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"); |
| |
| /* Verify we can't open it with our current credentials. */ |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| -1, false, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| 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"); |
| |
| /* Verify we can open it with the registered credentials. */ |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| cred_id, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| /* Verify we can open it with the registered credentials and as |
| * a link. |
| */ |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| cred_id, true, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| ret = io_uring_unregister_personality(ring, cred_id); |
| if (ret) |
| log_stderr("failure: io_uring_unregister_personality"); |
| |
| out_unmap: |
| munmap(ring, sizeof(struct io_uring)); |
| |
| safe_close(file1_fd); |
| safe_close(userns_fd); |
| |
| return fret; |
| } |
| |
| static int io_uring_idmapped(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct io_uring *ring; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| int cred_id, ret; |
| pid_t pid; |
| |
| ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, 0, 0); |
| if (!ring) |
| return log_errno(-1, "failure: io_uring_queue_init"); |
| |
| ret = io_uring_queue_init(8, ring, 0); |
| if (ret) { |
| log_stderr("failure: io_uring_queue_init"); |
| goto out_unmap; |
| } |
| |
| ret = io_uring_register_personality(ring); |
| if (ret < 0) { |
| fret = 0; |
| goto out_unmap; /* personalities not supported */ |
| } |
| cred_id = ret; |
| |
| /* create file only owner can open */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(file1_fd, 0600)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| if (attr.userns_fd < 0) |
| return log_errno(-1, "failure: create user namespace"); |
| |
| 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) |
| return log_errno(-1, "failure: create detached mount"); |
| |
| if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) |
| return log_errno(-1, "failure: set mount attributes"); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(10000, 10000)) |
| die("failure: switch_ids"); |
| |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| -1, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(10001, 10001)) |
| die("failure: switch_ids"); |
| |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, true, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| ret = io_uring_unregister_personality(ring, cred_id); |
| if (ret) |
| log_stderr("failure: io_uring_unregister_personality"); |
| |
| out_unmap: |
| munmap(ring, sizeof(struct io_uring)); |
| |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| /* |
| * Create an idmapped mount where the we leave the owner of the file unmapped. |
| * In no circumstances, even with recorded credentials can it be allowed to |
| * open the file. |
| */ |
| static int io_uring_idmapped_unmapped(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct io_uring *ring; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| int cred_id, ret, ret_cqe; |
| pid_t pid; |
| |
| ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, 0, 0); |
| if (!ring) |
| return log_errno(-1, "failure: io_uring_queue_init"); |
| |
| ret = io_uring_queue_init(8, ring, 0); |
| if (ret) { |
| log_stderr("failure: io_uring_queue_init"); |
| goto out_unmap; |
| } |
| |
| ret = io_uring_register_personality(ring); |
| if (ret < 0) { |
| fret = 0; |
| goto out_unmap; /* personalities not supported */ |
| } |
| cred_id = ret; |
| |
| /* create file only owner can open */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(file1_fd, 0600)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(1, 10000, 10000); |
| if (attr.userns_fd < 0) |
| return log_errno(-1, "failure: create user namespace"); |
| |
| 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) |
| return log_errno(-1, "failure: create detached mount"); |
| |
| if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) |
| return log_errno(-1, "failure: set mount attributes"); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(10000, 10000)) |
| die("failure: switch_ids"); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, false, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, true, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| ret = io_uring_unregister_personality(ring, cred_id); |
| if (ret) |
| log_stderr("failure: io_uring_unregister_personality"); |
| |
| out_unmap: |
| munmap(ring, sizeof(struct io_uring)); |
| |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int io_uring_idmapped_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct io_uring *ring; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| int cred_id, ret, ret_cqe; |
| pid_t pid; |
| |
| ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, 0, 0); |
| if (!ring) |
| return log_errno(-1, "failure: io_uring_queue_init"); |
| |
| ret = io_uring_queue_init(8, ring, 0); |
| if (ret) { |
| log_stderr("failure: io_uring_queue_init"); |
| goto out_unmap; |
| } |
| |
| ret = io_uring_register_personality(ring); |
| if (ret < 0) { |
| fret = 0; |
| goto out_unmap; /* personalities not supported */ |
| } |
| cred_id = ret; |
| |
| /* create file only owner can open */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(file1_fd, 0600)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| if (attr.userns_fd < 0) |
| return log_errno(-1, "failure: create user namespace"); |
| |
| 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) |
| return log_errno(-1, "failure: create detached mount"); |
| |
| if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) |
| return log_errno(-1, "failure: set mount attributes"); |
| |
| 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"); |
| |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| -1, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 1000, 1000, true)) |
| die("failure: switch_userns"); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| -1, false, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, |
| -1, true, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| -1, false, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| -1, true, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, false, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, true, NULL); |
| if (file1_fd < 0) |
| die("failure: io_uring_open_file"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| ret = io_uring_unregister_personality(ring, cred_id); |
| if (ret) |
| log_stderr("failure: io_uring_unregister_personality"); |
| |
| out_unmap: |
| munmap(ring, sizeof(struct io_uring)); |
| |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int io_uring_idmapped_unmapped_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct io_uring *ring; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| int cred_id, ret, ret_cqe; |
| pid_t pid; |
| |
| ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, |
| MAP_SHARED | MAP_ANONYMOUS, 0, 0); |
| if (!ring) |
| return log_errno(-1, "failure: io_uring_queue_init"); |
| |
| ret = io_uring_queue_init(8, ring, 0); |
| if (ret) { |
| log_stderr("failure: io_uring_queue_init"); |
| goto out_unmap; |
| } |
| |
| ret = io_uring_register_personality(ring); |
| if (ret < 0) { |
| fret = 0; |
| goto out_unmap; /* personalities not supported */ |
| } |
| cred_id = ret; |
| |
| /* create file only owner can open */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| if (fchown(file1_fd, 0, 0)) { |
| log_stderr("failure: fchown"); |
| goto out; |
| } |
| if (fchmod(file1_fd, 0600)) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| safe_close(file1_fd); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(1, 10000, 10000); |
| if (attr.userns_fd < 0) |
| return log_errno(-1, "failure: create user namespace"); |
| |
| 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) |
| return log_errno(-1, "failure: create detached mount"); |
| |
| if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) |
| return log_errno(-1, "failure: set mount attributes"); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 10000, 10000, true)) |
| die("failure: switch_ids"); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, false, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| ret_cqe = 0; |
| file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, |
| cred_id, true, &ret_cqe); |
| if (file1_fd >= 0) |
| die("failure: io_uring_open_file"); |
| if (ret_cqe == 0) |
| die("failure: non-open() related io_uring_open_file failure"); |
| if (ret_cqe != -EACCES) |
| die("failure: errno(%d)", abs(ret_cqe)); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: wait_for_pid"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| ret = io_uring_unregister_personality(ring, cred_id); |
| if (ret) |
| log_stderr("failure: io_uring_unregister_personality"); |
| |
| out_unmap: |
| munmap(ring, sizeof(struct io_uring)); |
| |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| #endif /* HAVE_LIBURING_H */ |
| |
| /* The following tests are concerned with setgid inheritance. These can be |
| * filesystem type specific. For xfs, if a new file or directory or node is |
| * created within a setgid directory and irix_sgid_inhiert is set then inherit |
| * the setgid bit if the caller is in the group of the directory. |
| */ |
| static int setgid_create(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF; |
| int tmpfile_fd = -EBADF; |
| pid_t pid; |
| bool supported = false; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| if (fchmod(info->t_dir1_fd, S_IRUSR | |
| S_IWUSR | |
| S_IRGRP | |
| S_IWGRP | |
| S_IROTH | |
| S_IWOTH | |
| S_IXUSR | |
| S_IXGRP | |
| S_IXOTH | |
| S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the setgid bit got raised. */ |
| if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { |
| log_stderr("failure: is_setgid"); |
| goto out; |
| } |
| |
| supported = openat_tmpfile_supported(info->t_dir1_fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* create regular file via open() */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* We're capable_wrt_inode_uidgid() and also our fsgid matches |
| * the directories gid. |
| */ |
| if (!is_setgid(info->t_dir1_fd, FILE1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) |
| die("failure: create"); |
| |
| /* Directories always inherit the setgid bit. */ |
| if (!is_setgid(info->t_dir1_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a special file via mknodat() vfs_create */ |
| if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (!is_setgid(info->t_dir1_fd, FILE2, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a character device via mknodat() vfs_mknod */ |
| if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1))) |
| die("failure: mknodat"); |
| |
| if (!is_setgid(info->t_dir1_fd, CHRDEV1, 0)) |
| die("failure: is_setgid"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (unlinkat(info->t_dir1_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| if (unlinkat(info->t_dir1_fd, FILE2, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(info->t_dir1_fd, CHRDEV1, 0)) |
| die("failure: delete"); |
| |
| /* create tmpfile via filesystem tmpfile api */ |
| if (supported) { |
| tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); |
| if (tmpfile_fd < 0) |
| die("failure: create"); |
| /* link the temporary file into the filesystem, making it permanent */ |
| if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH)) |
| die("failure: linkat"); |
| if (close(tmpfile_fd)) |
| die("failure: close"); |
| if (!is_setgid(info->t_dir1_fd, FILE3, 0)) |
| die("failure: is_setgid"); |
| if (!expected_uid_gid(info->t_dir1_fd, FILE3, 0, 0, 0)) |
| die("failure: check ownership"); |
| if (unlinkat(info->t_dir1_fd, FILE3, 0)) |
| die("failure: delete"); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(0, 10000)) |
| die("failure: switch_ids"); |
| |
| if (!caps_down_fsetid()) |
| die("failure: caps_down_fsetid"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid |
| * bit needs to be stripped. |
| */ |
| if (is_setgid(info->t_dir1_fd, FILE1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create directory */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0000)) |
| die("failure: create"); |
| |
| if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { |
| /* We're not in_group_p(). */ |
| if (is_setgid(info->t_dir1_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } else { |
| /* Directories always inherit the setgid bit. */ |
| if (!is_setgid(info->t_dir1_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } |
| |
| /* create a special file via mknodat() vfs_create */ |
| if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(info->t_dir1_fd, FILE2, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a character device via mknodat() vfs_mknod */ |
| if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1))) |
| die("failure: mknodat"); |
| |
| if (is_setgid(info->t_dir1_fd, CHRDEV1, 0)) |
| die("failure: is_setgid"); |
| /* |
| * In setgid directories newly created files always inherit the |
| * gid from the parent directory. Verify that the file is owned |
| * by gid 0, not by gid 10000. |
| */ |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| /* |
| * In setgid directories newly created directories always |
| * inherit the gid from the parent directory. Verify that the |
| * directory is owned by gid 0, not by gid 10000. |
| */ |
| if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (unlinkat(info->t_dir1_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| if (unlinkat(info->t_dir1_fd, FILE2, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(info->t_dir1_fd, CHRDEV1, 0)) |
| die("failure: delete"); |
| |
| /* create tmpfile via filesystem tmpfile api */ |
| if (supported) { |
| tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); |
| if (tmpfile_fd < 0) |
| die("failure: create"); |
| /* link the temporary file into the filesystem, making it permanent */ |
| if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH)) |
| die("failure: linkat"); |
| if (close(tmpfile_fd)) |
| die("failure: close"); |
| if (is_setgid(info->t_dir1_fd, FILE3, 0)) |
| die("failure: is_setgid"); |
| if (!expected_uid_gid(info->t_dir1_fd, FILE3, 0, 0, 0)) |
| die("failure: check ownership"); |
| if (unlinkat(info->t_dir1_fd, FILE3, 0)) |
| die("failure: delete"); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| |
| return fret; |
| } |
| |
| static int setgid_create_idmapped(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| int tmpfile_fd = -EBADF; |
| bool supported = false; |
| char path[PATH_MAX]; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| if (fchmod(info->t_dir1_fd, S_IRUSR | |
| S_IWUSR | |
| S_IRGRP | |
| S_IWGRP | |
| S_IROTH | |
| S_IWOTH | |
| S_IXUSR | |
| S_IXGRP | |
| S_IXOTH | |
| S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { |
| log_stderr("failure: is_setgid"); |
| 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; |
| } |
| |
| supported = openat_tmpfile_supported(open_tree_fd); |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_ids(10000, 11000)) |
| die("failure: switch fsids"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid |
| * bit needs to be stripped. |
| */ |
| if (is_setgid(open_tree_fd, FILE1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create directory */ |
| if (mkdirat(open_tree_fd, DIR1, 0000)) |
| die("failure: create"); |
| |
| if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { |
| /* We're not in_group_p(). */ |
| if (is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } else { |
| /* Directories always inherit the setgid bit. */ |
| if (!is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } |
| |
| /* create a special file via mknodat() vfs_create */ |
| if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(open_tree_fd, FILE2, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a whiteout device via mknodat() vfs_mknod */ |
| if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(open_tree_fd, CHRDEV1, 0)) |
| die("failure: is_setgid"); |
| |
| /* |
| * In setgid directories newly created files always inherit the |
| * gid from the parent directory. Verify that the file is owned |
| * by gid 10000, not by gid 11000. |
| */ |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| /* |
| * In setgid directories newly created directories always |
| * inherit the gid from the parent directory. Verify that the |
| * directory is owned by gid 10000, not by gid 11000. |
| */ |
| if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, CHRDEV1, 0)) |
| die("failure: delete"); |
| |
| /* create tmpfile via filesystem tmpfile api */ |
| if (supported) { |
| tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); |
| if (tmpfile_fd < 0) |
| die("failure: create"); |
| /* link the temporary file into the filesystem, making it permanent */ |
| snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); |
| if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) |
| die("failure: linkat"); |
| if (close(tmpfile_fd)) |
| die("failure: close"); |
| if (is_setgid(open_tree_fd, FILE3, 0)) |
| die("failure: is_setgid"); |
| if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| if (unlinkat(open_tree_fd, FILE3, 0)) |
| die("failure: delete"); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int setgid_create_idmapped_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| pid_t pid; |
| int tmpfile_fd = -EBADF; |
| bool supported = false; |
| char path[PATH_MAX]; |
| |
| if (!caps_supported()) |
| return 0; |
| |
| if (fchmod(info->t_dir1_fd, S_IRUSR | |
| S_IWUSR | |
| S_IRGRP | |
| S_IWGRP | |
| S_IROTH | |
| S_IWOTH | |
| S_IXUSR | |
| S_IXGRP | |
| S_IXOTH | |
| S_ISGID), 0) { |
| log_stderr("failure: fchmod"); |
| goto out; |
| } |
| |
| /* Verify that the sid bits got raised. */ |
| if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { |
| log_stderr("failure: is_setgid"); |
| 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; |
| } |
| |
| supported = openat_tmpfile_supported(open_tree_fd); |
| |
| 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"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* We're in_group_p() and capable_wrt_inode_uidgid() so setgid |
| * bit needs to be set. |
| */ |
| if (!is_setgid(open_tree_fd, FILE1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create directory */ |
| if (mkdirat(open_tree_fd, DIR1, 0000)) |
| die("failure: create"); |
| |
| /* Directories always inherit the setgid bit. */ |
| if (!is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a special file via mknodat() vfs_create */ |
| if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (!is_setgid(open_tree_fd, FILE2, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a whiteout device via mknodat() vfs_mknod */ |
| if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (!is_setgid(open_tree_fd, CHRDEV1, 0)) |
| die("failure: is_setgid"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, CHRDEV1, 0)) |
| die("failure: delete"); |
| |
| /* create tmpfile via filesystem tmpfile api */ |
| if (supported) { |
| tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); |
| if (tmpfile_fd < 0) |
| die("failure: create"); |
| /* link the temporary file into the filesystem, making it permanent */ |
| snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); |
| if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) |
| die("failure: linkat"); |
| if (close(tmpfile_fd)) |
| die("failure: close"); |
| if (!is_setgid(open_tree_fd, FILE3, 0)) |
| die("failure: is_setgid"); |
| if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 0)) |
| die("failure: check ownership"); |
| if (unlinkat(open_tree_fd, FILE3, 0)) |
| die("failure: delete"); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* |
| * Below we verify that setgid inheritance for a newly created file or |
| * directory works correctly. As part of this we need to verify that |
| * newly created files or directories inherit their gid from their |
| * parent directory. So we change the parent directorie's gid to 1000 |
| * and create a file with fs{g,u}id 0 and verify that the newly created |
| * file and directory inherit gid 1000, not 0. |
| */ |
| if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| if (!caps_down_fsetid()) |
| die("failure: caps_down_fsetid"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid |
| * bit needs to be stripped. |
| */ |
| if (is_setgid(open_tree_fd, FILE1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create directory */ |
| if (mkdirat(open_tree_fd, DIR1, 0000)) |
| die("failure: create"); |
| |
| if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { |
| /* We're not in_group_p(). */ |
| if (is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } else { |
| /* Directories always inherit the setgid bit. */ |
| if (!is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } |
| |
| /* create a special file via mknodat() vfs_create */ |
| if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(open_tree_fd, FILE2, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a whiteout device via mknodat() vfs_mknod */ |
| if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(open_tree_fd, CHRDEV1, 0)) |
| die("failure: is_setgid"); |
| |
| /* |
| * In setgid directories newly created files always inherit the |
| * gid from the parent directory. Verify that the file is owned |
| * by gid 1000, not by gid 0. |
| */ |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) |
| die("failure: check ownership"); |
| |
| /* |
| * In setgid directories newly created directories always |
| * inherit the gid from the parent directory. Verify that the |
| * directory is owned by gid 1000, not by gid 0. |
| */ |
| if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000)) |
| die("failure: check ownership"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, CHRDEV1, 0)) |
| die("failure: delete"); |
| |
| /* create tmpfile via filesystem tmpfile api */ |
| if (supported) { |
| tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); |
| if (tmpfile_fd < 0) |
| die("failure: create"); |
| /* link the temporary file into the filesystem, making it permanent */ |
| snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); |
| if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) |
| die("failure: linkat"); |
| if (close(tmpfile_fd)) |
| die("failure: close"); |
| if (is_setgid(open_tree_fd, FILE3, 0)) |
| die("failure: is_setgid"); |
| if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000)) |
| die("failure: check ownership"); |
| if (unlinkat(open_tree_fd, FILE3, 0)) |
| die("failure: delete"); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 0, 1000, false)) |
| die("failure: switch_userns"); |
| |
| if (!caps_down_fsetid()) |
| die("failure: caps_down_fsetid"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid |
| * bit needs to be stripped. |
| */ |
| if (is_setgid(open_tree_fd, FILE1, 0)) |
| die("failure: is_setgid"); |
| |
| /* create directory */ |
| if (mkdirat(open_tree_fd, DIR1, 0000)) |
| die("failure: create"); |
| |
| /* Directories always inherit the setgid bit. */ |
| if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { |
| /* We're not in_group_p(). */ |
| if (is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } else { |
| /* Directories always inherit the setgid bit. */ |
| if (!is_setgid(open_tree_fd, DIR1, 0)) |
| die("failure: is_setgid"); |
| } |
| |
| /* create a special file via mknodat() vfs_create */ |
| if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(open_tree_fd, FILE2, 0)) |
| die("failure: is_setgid"); |
| |
| /* create a whiteout device via mknodat() vfs_mknod */ |
| if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) |
| die("failure: mknodat"); |
| |
| if (is_setgid(open_tree_fd, CHRDEV1, 0)) |
| die("failure: is_setgid"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, FILE2, 0)) |
| die("failure: delete"); |
| |
| if (unlinkat(open_tree_fd, CHRDEV1, 0)) |
| die("failure: delete"); |
| |
| /* create tmpfile via filesystem tmpfile api */ |
| if (supported) { |
| tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); |
| if (tmpfile_fd < 0) |
| die("failure: create"); |
| /* link the temporary file into the filesystem, making it permanent */ |
| snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); |
| if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) |
| die("failure: linkat"); |
| if (close(tmpfile_fd)) |
| die("failure: close"); |
| if (is_setgid(open_tree_fd, FILE3, 0)) |
| die("failure: is_setgid"); |
| if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 0)) |
| die("failure: check ownership"); |
| if (unlinkat(open_tree_fd, FILE3, 0)) |
| die("failure: delete"); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| #define PTR_TO_INT(p) ((int)((intptr_t)(p))) |
| #define INT_TO_PTR(u) ((void *)((intptr_t)(u))) |
| |
| struct threaded_args { |
| const struct vfstest_info *info; |
| int open_tree_fd; |
| }; |
| |
| static void *idmapped_mount_create_cb(void *data) |
| { |
| int fret = EXIT_FAILURE; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| struct threaded_args *args = data; |
| |
| /* 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; |
| } |
| |
| if (sys_mount_setattr(args->open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| fret = EXIT_SUCCESS; |
| |
| out: |
| safe_close(attr.userns_fd); |
| pthread_exit(INT_TO_PTR(fret)); |
| } |
| |
| /* This tries to verify that we never see an inconistent ownership on-disk and |
| * can't write invalid ids to disk. To do this we create a race between |
| * idmapping a mount and creating files on it. |
| * Note, while it is perfectly fine to see overflowuid and overflowgid as owner |
| * if we create files through the open_tree_fd before the mount is idmapped but |
| * look at the files after the mount has been idmapped in this test it can never |
| * be the case that we see overflowuid and overflowgid when we access the file |
| * through a non-idmapped mount (in the initial user namespace). |
| */ |
| static void *idmapped_mount_operations_cb(void *data) |
| { |
| int file1_fd = -EBADF, file2_fd = -EBADF, dir1_fd = -EBADF, |
| dir1_fd2 = -EBADF, fret = EXIT_FAILURE; |
| struct threaded_args *args = data; |
| const struct vfstest_info *info = args->info; |
| |
| if (!switch_fsids(10000, 10000)) { |
| log_stderr("failure: switch fsids"); |
| goto out; |
| } |
| |
| file1_fd = openat(args->open_tree_fd, FILE1, |
| O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| file2_fd = openat(args->open_tree_fd, FILE2, |
| O_CREAT | O_EXCL | O_CLOEXEC, 0644); |
| if (file2_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (mkdirat(args->open_tree_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| dir1_fd = openat(args->open_tree_fd, DIR1, |
| O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
| if (dir1_fd < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(args->open_tree_fd, FILE1, 0, 0, 0, false) && |
| !__expected_uid_gid(args->open_tree_fd, FILE1, 0, 10000, 10000, false) && |
| !__expected_uid_gid(args->open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(args->open_tree_fd, FILE2, 0, 0, 0, false) && |
| !__expected_uid_gid(args->open_tree_fd, FILE2, 0, 10000, 10000, false) && |
| !__expected_uid_gid(args->open_tree_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(args->open_tree_fd, DIR1, 0, 0, 0, false) && |
| !__expected_uid_gid(args->open_tree_fd, DIR1, 0, 10000, 10000, false) && |
| !__expected_uid_gid(args->open_tree_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, 0, 0, false) && |
| !__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, 10000, 10000, false) && |
| !__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, info->t_overflowuid, info->t_overflowgid, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| dir1_fd2 = openat(info->t_dir1_fd, DIR1, |
| O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
| if (dir1_fd2 < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0, false) && |
| !__expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0, false) && |
| !__expected_uid_gid(info->t_dir1_fd, FILE2, 0, 10000, 10000, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0, false) && |
| !__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 10000, 10000, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0, false) && |
| !__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 10000, 10000, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| if (!__expected_uid_gid(dir1_fd2, "", AT_EMPTY_PATH, 0, 0, false) && |
| !__expected_uid_gid(dir1_fd2, "", AT_EMPTY_PATH, 10000, 10000, false)) { |
| log_stderr("failure: expected_uid_gid"); |
| goto out; |
| } |
| |
| fret = EXIT_SUCCESS; |
| |
| out: |
| safe_close(file1_fd); |
| safe_close(file2_fd); |
| safe_close(dir1_fd); |
| safe_close(dir1_fd2); |
| |
| pthread_exit(INT_TO_PTR(fret)); |
| } |
| |
| static int threaded_idmapped_mount_interactions(const struct vfstest_info *info) |
| { |
| int i; |
| int fret = -1; |
| pid_t pid; |
| pthread_attr_t thread_attr; |
| pthread_t threads[2]; |
| |
| pthread_attr_init(&thread_attr); |
| |
| for (i = 0; i < 1000; i++) { |
| int ret1 = 0, ret2 = 0, tret1 = 0, tret2 = 0; |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int open_tree_fd = -EBADF; |
| struct threaded_args args = { |
| .info = info, |
| .open_tree_fd = -EBADF, |
| }; |
| |
| 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) |
| die("failure: sys_open_tree"); |
| |
| args.open_tree_fd = open_tree_fd; |
| |
| if (pthread_create(&threads[0], &thread_attr, |
| idmapped_mount_create_cb, |
| &args)) |
| die("failure: pthread_create"); |
| |
| if (pthread_create(&threads[1], &thread_attr, |
| idmapped_mount_operations_cb, |
| &args)) |
| die("failure: pthread_create"); |
| |
| ret1 = pthread_join(threads[0], INT_TO_PTR(tret1)); |
| ret2 = pthread_join(threads[1], INT_TO_PTR(tret2)); |
| |
| if (ret1) { |
| errno = ret1; |
| die("failure: pthread_join"); |
| } |
| |
| if (ret2) { |
| errno = ret2; |
| die("failure: pthread_join"); |
| } |
| |
| if (tret1 || tret2) |
| exit(EXIT_FAILURE); |
| |
| exit(EXIT_SUCCESS); |
| |
| } |
| |
| if (wait_for_pid(pid)) { |
| log_stderr("failure: iteration %d", i); |
| goto out; |
| } |
| |
| rm_r(info->t_dir1_fd, "."); |
| |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| |
| out: |
| return fret; |
| } |
| |
| static int setattr_truncate(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF; |
| |
| /* create regular file via open() */ |
| file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) { |
| log_stderr("failure: create"); |
| goto out; |
| } |
| |
| if (ftruncate(file1_fd, 10000)) { |
| log_stderr("failure: ftruncate"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: check ownership"); |
| goto out; |
| } |
| |
| if (!expected_file_size(file1_fd, "", AT_EMPTY_PATH, 10000)) { |
| log_stderr("failure: expected_file_size"); |
| goto out; |
| } |
| |
| if (ftruncate(file1_fd, 0)) { |
| log_stderr("failure: ftruncate"); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { |
| log_stderr("failure: check ownership"); |
| goto out; |
| } |
| |
| if (!expected_file_size(file1_fd, "", AT_EMPTY_PATH, 0)) { |
| log_stderr("failure: expected_file_size"); |
| goto out; |
| } |
| |
| if (unlinkat(info->t_dir1_fd, FILE1, 0)) { |
| log_stderr("failure: remove"); |
| goto out; |
| } |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| |
| return fret; |
| } |
| |
| static int setattr_truncate_idmapped(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| pid_t pid; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| /* 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) { |
| if (!switch_ids(10000, 10000)) |
| die("failure: switch_ids"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| if (ftruncate(file1_fd, 10000)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) |
| die("failure: expected_file_size"); |
| |
| if (ftruncate(file1_fd, 0)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) |
| die("failure: expected_file_size"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| int file1_fd2 = -EBADF; |
| |
| /* create regular file via open() */ |
| file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd2 < 0) |
| die("failure: create"); |
| |
| if (ftruncate(file1_fd2, 10000)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) |
| die("failure: expected_file_size"); |
| |
| if (ftruncate(file1_fd2, 0)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) |
| die("failure: expected_file_size"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int setattr_truncate_idmapped_in_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int file1_fd = -EBADF, open_tree_fd = -EBADF; |
| 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; |
| } |
| |
| 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) { |
| if (!switch_userns(attr.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| if (ftruncate(file1_fd, 10000)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) |
| die("failure: expected_file_size"); |
| |
| if (ftruncate(file1_fd, 0)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) |
| die("failure: expected_file_size"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 0, 0, true)) |
| die("failure: switch_userns"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| if (ftruncate(file1_fd, 10000)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) |
| die("failure: expected_file_size"); |
| |
| if (ftruncate(file1_fd, 0)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) |
| die("failure: expected_file_size"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { |
| log_stderr("failure: fchownat"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!caps_supported()) { |
| log_debug("skip: capability library not installed"); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!switch_userns(attr.userns_fd, 0, 1000, true)) |
| die("failure: switch_userns"); |
| |
| /* create regular file via open() */ |
| file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); |
| if (file1_fd < 0) |
| die("failure: create"); |
| |
| if (ftruncate(file1_fd, 10000)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) |
| die("failure: expected_file_size"); |
| |
| if (ftruncate(file1_fd, 0)) |
| die("failure: ftruncate"); |
| |
| if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) |
| die("failure: check ownership"); |
| |
| if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) |
| die("failure: expected_file_size"); |
| |
| if (unlinkat(open_tree_fd, FILE1, 0)) |
| die("failure: delete"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(attr.userns_fd); |
| safe_close(file1_fd); |
| safe_close(open_tree_fd); |
| |
| return fret; |
| } |
| |
| static int nested_userns(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int ret; |
| pid_t pid; |
| unsigned int id; |
| struct list *it, *next; |
| struct userns_hierarchy hierarchy[] = { |
| { .level = 1, .fd_userns = -EBADF, }, |
| { .level = 2, .fd_userns = -EBADF, }, |
| { .level = 3, .fd_userns = -EBADF, }, |
| { .level = 4, .fd_userns = -EBADF, }, |
| /* Dummy entry that marks the end. */ |
| { .level = MAX_USERNS_LEVEL, .fd_userns = -EBADF, }, |
| }; |
| struct mount_attr attr_level1 = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = -EBADF, |
| }; |
| struct mount_attr attr_level2 = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = -EBADF, |
| }; |
| struct mount_attr attr_level3 = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = -EBADF, |
| }; |
| struct mount_attr attr_level4 = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = -EBADF, |
| }; |
| int fd_dir1 = -EBADF, |
| fd_open_tree_level1 = -EBADF, |
| fd_open_tree_level2 = -EBADF, |
| fd_open_tree_level3 = -EBADF, |
| fd_open_tree_level4 = -EBADF; |
| const unsigned int id_file_range = 10000; |
| |
| list_init(&hierarchy[0].id_map); |
| list_init(&hierarchy[1].id_map); |
| list_init(&hierarchy[2].id_map); |
| list_init(&hierarchy[3].id_map); |
| |
| /* |
| * Give a large map to the outermost user namespace so we can create |
| * comfortable nested maps. |
| */ |
| ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: adding uidmap for userns at level 1"); |
| goto out; |
| } |
| |
| ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: adding gidmap for userns at level 1"); |
| goto out; |
| } |
| |
| /* This is uid:0->2000000:100000000 in init userns. */ |
| ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: adding uidmap for userns at level 2"); |
| goto out; |
| } |
| |
| /* This is gid:0->2000000:100000000 in init userns. */ |
| ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: adding gidmap for userns at level 2"); |
| goto out; |
| } |
| |
| /* This is uid:0->3000000:999 in init userns. */ |
| ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: adding uidmap for userns at level 3"); |
| goto out; |
| } |
| |
| /* This is gid:0->3000000:999 in the init userns. */ |
| ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: adding gidmap for userns at level 3"); |
| goto out; |
| } |
| |
| /* id 999 will remain unmapped. */ |
| |
| /* This is uid:1000->2001000:1 in init userns. */ |
| ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: adding uidmap for userns at level 3"); |
| goto out; |
| } |
| |
| /* This is gid:1000->2001000:1 in init userns. */ |
| ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: adding gidmap for userns at level 3"); |
| goto out; |
| } |
| |
| /* This is uid:1001->3001001:10000 in init userns. */ |
| ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: adding uidmap for userns at level 3"); |
| goto out; |
| } |
| |
| /* This is gid:1001->3001001:10000 in init userns. */ |
| ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: adding gidmap for userns at level 3"); |
| goto out; |
| } |
| |
| /* Don't write a mapping in the 4th userns. */ |
| list_empty(&hierarchy[4].id_map); |
| |
| /* Create the actual userns hierarchy. */ |
| ret = create_userns_hierarchy(hierarchy); |
| if (ret) { |
| log_stderr("failure: create userns hierarchy"); |
| goto out; |
| } |
| |
| attr_level1.userns_fd = hierarchy[0].fd_userns; |
| attr_level2.userns_fd = hierarchy[1].fd_userns; |
| attr_level3.userns_fd = hierarchy[2].fd_userns; |
| attr_level4.userns_fd = hierarchy[3].fd_userns; |
| |
| /* |
| * Create one directory where we create files for each uid/gid within |
| * the first userns. |
| */ |
| if (mkdirat(info->t_dir1_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| fd_dir1 = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); |
| if (fd_dir1 < 0) { |
| log_stderr("failure: openat"); |
| goto out; |
| } |
| |
| for (id = 0; id <= id_file_range; id++) { |
| char file[256]; |
| |
| snprintf(file, sizeof(file), DIR1 "/" FILE1 "_%u", id); |
| |
| if (mknodat(info->t_dir1_fd, file, S_IFREG | 0644, 0)) { |
| log_stderr("failure: create %s", file); |
| goto out; |
| } |
| |
| if (fchownat(info->t_dir1_fd, file, id, id, AT_SYMLINK_NOFOLLOW)) { |
| log_stderr("failure: fchownat %s", file); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(info->t_dir1_fd, file, 0, id, id)) { |
| log_stderr("failure: check ownership %s", file); |
| goto out; |
| } |
| } |
| |
| /* Create detached mounts for all the user namespaces. */ |
| fd_open_tree_level1 = sys_open_tree(info->t_dir1_fd, DIR1, |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| if (fd_open_tree_level1 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| fd_open_tree_level2 = sys_open_tree(info->t_dir1_fd, DIR1, |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| if (fd_open_tree_level2 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| fd_open_tree_level3 = sys_open_tree(info->t_dir1_fd, DIR1, |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| if (fd_open_tree_level3 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| fd_open_tree_level4 = sys_open_tree(info->t_dir1_fd, DIR1, |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| if (fd_open_tree_level4 < 0) { |
| log_stderr("failure: sys_open_tree"); |
| goto out; |
| } |
| |
| /* Turn detached mounts into detached idmapped mounts. */ |
| if (sys_mount_setattr(fd_open_tree_level1, "", AT_EMPTY_PATH, |
| &attr_level1, sizeof(attr_level1))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(fd_open_tree_level2, "", AT_EMPTY_PATH, |
| &attr_level2, sizeof(attr_level2))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(fd_open_tree_level3, "", AT_EMPTY_PATH, |
| &attr_level3, sizeof(attr_level3))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| if (sys_mount_setattr(fd_open_tree_level4, "", AT_EMPTY_PATH, |
| &attr_level4, sizeof(attr_level4))) { |
| log_stderr("failure: sys_mount_setattr"); |
| goto out; |
| } |
| |
| /* Verify that ownership looks correct for callers in the init userns. */ |
| for (id = 0; id <= id_file_range; id++) { |
| bool bret; |
| unsigned int id_level1, id_level2, id_level3; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| id_level1 = id + 1000000; |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) { |
| log_stderr("failure: check ownership %s", file); |
| goto out; |
| } |
| |
| id_level2 = id + 2000000; |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) { |
| log_stderr("failure: check ownership %s", file); |
| goto out; |
| } |
| |
| if (id == 999) { |
| /* This id is unmapped. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } else if (id == 1000) { |
| id_level3 = id + 2000000; /* We punched a hole in the map at 1000. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } else { |
| id_level3 = id + 3000000; /* Rest is business as usual. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } |
| if (!bret) { |
| log_stderr("failure: check ownership %s", file); |
| goto out; |
| } |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) { |
| log_stderr("failure: check ownership %s", file); |
| goto out; |
| } |
| } |
| |
| /* Verify that ownership looks correct for callers in the first userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr_level1.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| bool bret; |
| unsigned int id_level1, id_level2, id_level3; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| id_level1 = id; |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) |
| die("failure: check ownership %s", file); |
| |
| id_level2 = id + 1000000; |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) |
| die("failure: check ownership %s", file); |
| |
| if (id == 999) { |
| /* This id is unmapped. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } else if (id == 1000) { |
| id_level3 = id + 1000000; /* We punched a hole in the map at 1000. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } else { |
| id_level3 = id + 2000000; /* Rest is business as usual. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } |
| if (!bret) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that ownership looks correct for callers in the second userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr_level2.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| bool bret; |
| unsigned int id_level2, id_level3; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| id_level2 = id; |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) |
| die("failure: check ownership %s", file); |
| |
| if (id == 999) { |
| /* This id is unmapped. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } else if (id == 1000) { |
| id_level3 = id; /* We punched a hole in the map at 1000. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } else { |
| id_level3 = id + 1000000; /* Rest is business as usual. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } |
| if (!bret) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that ownership looks correct for callers in the third userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr_level3.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| bool bret; |
| unsigned int id_level2, id_level3; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (id == 1000) { |
| /* |
| * The idmapping of the third userns has a hole |
| * at uid/gid 1000. That means: |
| * - 1000->userns_0(2000000) // init userns |
| * - 1000->userns_1(2000000) // level 1 |
| * - 1000->userns_2(1000000) // level 2 |
| * - 1000->userns_3(1000) // level 3 (because level 3 has a hole) |
| */ |
| id_level2 = id; |
| bret = expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2); |
| } else { |
| bret = expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } |
| if (!bret) |
| die("failure: check ownership %s", file); |
| |
| |
| if (id == 999) { |
| /* This id is unmapped. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } else { |
| id_level3 = id; /* Rest is business as usual. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } |
| if (!bret) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that ownership looks correct for callers in the fourth userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (setns(attr_level4.userns_fd, CLONE_NEWUSER)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that chown works correctly for callers in the first userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr_level1.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| bool bret; |
| unsigned int id_level1, id_level2, id_level3, id_new; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| id_new = id + 1; |
| if (fchownat(fd_open_tree_level1, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| |
| id_level1 = id_new; |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) |
| die("failure: check ownership %s", file); |
| |
| id_level2 = id_new + 1000000; |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) |
| die("failure: check ownership %s", file); |
| |
| if (id_new == 999) { |
| /* This id is unmapped. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } else if (id_new == 1000) { |
| id_level3 = id_new + 1000000; /* We punched a hole in the map at 1000. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } else { |
| id_level3 = id_new + 2000000; /* Rest is business as usual. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } |
| if (!bret) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| /* Revert ownership. */ |
| if (fchownat(fd_open_tree_level1, file, id, id, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that chown works correctly for callers in the second userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr_level2.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| bool bret; |
| unsigned int id_level2, id_level3, id_new; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| id_new = id + 1; |
| if (fchownat(fd_open_tree_level2, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| id_level2 = id_new; |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) |
| die("failure: check ownership %s", file); |
| |
| if (id_new == 999) { |
| /* This id is unmapped. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid); |
| } else if (id_new == 1000) { |
| id_level3 = id_new; /* We punched a hole in the map at 1000. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } else { |
| id_level3 = id_new + 1000000; /* Rest is business as usual. */ |
| bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); |
| } |
| if (!bret) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| /* Revert ownership. */ |
| if (fchownat(fd_open_tree_level2, file, id, id, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that chown works correctly for callers in the third userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(attr_level3.userns_fd, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| unsigned int id_new; |
| char file[256]; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| id_new = id + 1; |
| if (id_new == 999 || id_new == 1000) { |
| /* |
| * We can't change ownership as we can't |
| * chown from or to an unmapped id. |
| */ |
| if (!fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| } else { |
| if (fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| } |
| |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| /* There's no id 1000 anymore as we changed ownership for id 1000 to 1001 above. */ |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (id_new == 999) { |
| /* |
| * We did not change ownership as we can't |
| * chown to an unmapped id. |
| */ |
| if (!expected_uid_gid(fd_open_tree_level3, file, 0, id, id)) |
| die("failure: check ownership %s", file); |
| } else if (id_new == 1000) { |
| /* |
| * We did not change ownership as we can't |
| * chown from an unmapped id. |
| */ |
| if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| } else { |
| if (!expected_uid_gid(fd_open_tree_level3, file, 0, id_new, id_new)) |
| die("failure: check ownership %s", file); |
| } |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| /* Revert ownership. */ |
| if (id_new != 999 && id_new != 1000) { |
| if (fchownat(fd_open_tree_level3, file, id, id, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| } |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| /* Verify that chown works correctly for callers in the fourth userns. */ |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (setns(attr_level4.userns_fd, CLONE_NEWUSER)) |
| die("failure: switch_userns"); |
| |
| for (id = 0; id <= id_file_range; id++) { |
| char file[256]; |
| unsigned long id_new; |
| |
| snprintf(file, sizeof(file), FILE1 "_%u", id); |
| |
| id_new = id + 1; |
| if (!fchownat(fd_open_tree_level4, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) |
| die("failure: fchownat %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level1, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->t_overflowgid)) |
| die("failure: check ownership %s", file); |
| |
| } |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| |
| out: |
| list_for_each_safe(it, &hierarchy[0].id_map, next) { |
| list_del(it); |
| free(it->elem); |
| free(it); |
| } |
| |
| list_for_each_safe(it, &hierarchy[1].id_map, next) { |
| list_del(it); |
| free(it->elem); |
| free(it); |
| } |
| |
| list_for_each_safe(it, &hierarchy[2].id_map, next) { |
| list_del(it); |
| free(it->elem); |
| free(it); |
| } |
| |
| safe_close(hierarchy[0].fd_userns); |
| safe_close(hierarchy[1].fd_userns); |
| safe_close(hierarchy[2].fd_userns); |
| safe_close(fd_dir1); |
| safe_close(fd_open_tree_level1); |
| safe_close(fd_open_tree_level2); |
| safe_close(fd_open_tree_level3); |
| safe_close(fd_open_tree_level4); |
| return fret; |
| } |
| |
| #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; |
| } |
| |
| #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"); |
| |
| /* |
| * A user with fs{g,u}id 1001 must not be allowed to change |
| * ownership of /target/file1 owned by {g,u}id 1001 in this |
| * idmapped mount to {g,u}id 1000. |
| */ |
| if (!fchownat(open_tree_fd, FILE1, user1_uid, user1_gid, |
| AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* 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 |
| * ownership of /target/file2 owned by {g,u}id 1000 in this |
| * idmapped mount to {g,u}id 1000. |
| */ |
| if (!fchownat(open_tree_fd, FILE2, user1_uid, user1_gid, |
| AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* Verify that the ownership is still {g,u}id 1000. */ |
| if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, |
| user1_uid, user1_gid)) |
| die("failure: check ownership"); |
| |
| /* |
| * A user with fs{g,u}id 1001 must not be allowed to change |
| * ownership of /target/file2 owned by {g,u}id 1000 in this |
| * idmapped mount to {g,u}id 1001. |
| */ |
| if (!fchownat(open_tree_fd, FILE2, user2_uid, user2_gid, |
| AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* Verify that the ownership is still {g,u}id 1000. */ |
| if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, |
| user1_uid, user1_gid)) |
| die("failure: check ownership"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| /* switch to {g,u}id 1000 */ |
| if (!switch_resids(user1_uid, user1_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 1000 must be allowed to change |
| * ownership of /target/file2 owned by {g,u}id 1000 in this |
| * idmapped mount to {g,u}id 1000. |
| */ |
| if (fchownat(open_tree_fd, FILE2, user1_uid, user1_gid, |
| AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| |
| /* Verify that the ownership is still {g,u}id 1000. */ |
| if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, |
| user1_uid, user1_gid)) |
| die("failure: check ownership"); |
| |
| /* |
| * A user with fs{g,u}id 1000 must not be allowed to change |
| * ownership of /target/file2 owned by {g,u}id 1000 in this |
| * idmapped mount to {g,u}id 1001. |
| */ |
| if (!fchownat(open_tree_fd, FILE2, user2_uid, user2_gid, |
| AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* Verify that the ownership is still {g,u}id 1000. */ |
| if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, |
| user1_uid, user1_gid)) |
| die("failure: check ownership"); |
| |
| /* |
| * A user with fs{g,u}id 1000 must not be allowed to change |
| * ownership of /target/file1 owned by {g,u}id 1001 in this |
| * idmapped mount to {g,u}id 1000. |
| */ |
| if (!fchownat(open_tree_fd, FILE1, user1_uid, user1_gid, |
| AT_SYMLINK_NOFOLLOW)) |
| die("failure: change ownership"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* 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 1000 must not 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"); |
| if (errno != EPERM) |
| die("failure: errno"); |
| |
| /* 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"); |
| |
| 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); |
| |
| list_for_each_safe(it_cur, &idmap, it_next) { |
| list_del(it_cur); |
| free(it_cur->elem); |
| free(it_cur); |
| } |
| |
| return fret; |
| } |
| |
| /** |
| * setxattr_fix_705191b03d50 - test for commit 705191b03d50 ("fs: fix acl translation"). |
| */ |
| static int setxattr_fix_705191b03d50(const struct vfstest_info *info) |
| { |
| int fret = -1; |
| int fd_userns = -EBADF; |
| int ret; |
| uid_t user1_uid; |
| gid_t user1_gid; |
| pid_t pid; |
| struct list idmap; |
| struct list *it_cur, *it_next; |
| |
| list_init(&idmap); |
| |
| if (!lookup_ids(USER1, &user1_uid, &user1_gid)) { |
| log_stderr("failure: lookup_user"); |
| goto out; |
| } |
| |
| log_debug("Found " USER1 " with uid(%d) and gid(%d)", user1_uid, user1_gid); |
| |
| if (mkdirat(info->t_dir1_fd, DIR1, 0777)) { |
| log_stderr("failure: mkdirat"); |
| goto out; |
| } |
| |
| if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) { |
| log_stderr("failure: chown_r"); |
| goto out; |
| } |
| |
| print_r(info->t_mnt_fd, T_DIR1); |
| |
| /* u:0:user1_uid:1 */ |
| ret = add_map_entry(&idmap, user1_uid, 0, 1, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: add_map_entry"); |
| goto out; |
| } |
| |
| /* g:0:user1_gid:1 */ |
| ret = add_map_entry(&idmap, user1_gid, 0, 1, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: add_map_entry"); |
| goto out; |
| } |
| |
| /* u:100:10000:100 */ |
| ret = add_map_entry(&idmap, 10000, 100, 100, ID_TYPE_UID); |
| if (ret) { |
| log_stderr("failure: add_map_entry"); |
| goto out; |
| } |
| |
| /* g:100:10000:100 */ |
| ret = add_map_entry(&idmap, 10000, 100, 100, ID_TYPE_GID); |
| if (ret) { |
| log_stderr("failure: add_map_entry"); |
| goto out; |
| } |
| |
| fd_userns = get_userns_fd_from_idmap(&idmap); |
| if (fd_userns < 0) { |
| log_stderr("failure: get_userns_fd"); |
| goto out; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| log_stderr("failure: fork"); |
| goto out; |
| } |
| if (pid == 0) { |
| if (!switch_userns(fd_userns, 0, 0, false)) |
| die("failure: switch_userns"); |
| |
| /* create separate mount namespace */ |
| if (unshare(CLONE_NEWNS)) |
| die("failure: create new mount namespace"); |
| |
| /* turn off mount propagation */ |
| if (sys_mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) |
| die("failure: turn mount propagation off"); |
| |
| snprintf(t_buf, sizeof(t_buf), "%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1); |
| |
| if (sys_mount("none", t_buf, "tmpfs", 0, "mode=0755")) |
| die("failure: mount"); |
| |
| snprintf(t_buf, sizeof(t_buf), "%s/%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1, DIR3); |
| if (mkdir(t_buf, 0700)) |
| die("failure: mkdir"); |
| |
| snprintf(t_buf, sizeof(t_buf), "setfacl -m u:100:rwx %s/%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1, DIR3); |
| if (system(t_buf)) |
| die("failure: system"); |
| |
| snprintf(t_buf, sizeof(t_buf), "getfacl -n -p %s/%s/%s/%s | grep -q user:100:rwx", info->t_mountpoint, T_DIR1, DIR1, DIR3); |
| if (system(t_buf)) |
| die("failure: system"); |
| |
| exit(EXIT_SUCCESS); |
| } |
| if (wait_for_pid(pid)) |
| goto out; |
| |
| fret = 0; |
| log_debug("Ran test"); |
| out: |
| safe_close(fd_userns); |
| |
| list_for_each_safe(it_cur, &idmap, it_next) { |
| list_del(it_cur); |
| free(it_cur->elem); |
| free(it_cur); |
| } |
| |
| return fret; |
| } |
| |
| static void usage(void) |
| { |
| fprintf(stderr, "Description:\n"); |
| fprintf(stderr, " Run idmapped mount tests\n\n"); |
| |
| fprintf(stderr, "Arguments:\n"); |
| fprintf(stderr, "--device Device used in the tests\n"); |
| fprintf(stderr, "--fstype Filesystem type used in the tests\n"); |
| fprintf(stderr, "--help Print help\n"); |
| fprintf(stderr, "--mountpoint Mountpoint of device\n"); |
| fprintf(stderr, "--idmapped-mounts-supported Test whether idmapped mounts are supported on this filesystem\n"); |
| fprintf(stderr, "--scratch-mountpoint Mountpoint of scratch device used in the tests\n"); |
| fprintf(stderr, "--scratch-device Scratch device used in the tests\n"); |
| fprintf(stderr, "--test-core Run core idmapped mount testsuite\n"); |
| fprintf(stderr, "--test-fscaps-regression Run fscap regression tests\n"); |
| fprintf(stderr, "--test-nested-userns Run nested userns idmapped mount testsuite\n"); |
| fprintf(stderr, "--test-btrfs Run btrfs specific idmapped mount testsuite\n"); |
| fprintf(stderr, "--test-setattr-fix-968219708108 Run setattr regression tests\n"); |
| fprintf(stderr, "--test-setxattr-fix-705191b03d50 Run setxattr regression tests\n"); |
| |
| _exit(EXIT_SUCCESS); |
| } |
| |
| static const struct option longopts[] = { |
| {"device", required_argument, 0, 'd'}, |
| {"fstype", required_argument, 0, 'f'}, |
| {"mountpoint", required_argument, 0, 'm'}, |
| {"scratch-mountpoint", required_argument, 0, 'a'}, |
| {"scratch-device", required_argument, 0, 'e'}, |
| {"idmapped-mounts-supported", no_argument, 0, 's'}, |
| {"help", no_argument, 0, 'h'}, |
| {"test-core", no_argument, 0, 'c'}, |
| {"test-fscaps-regression", no_argument, 0, 'g'}, |
| {"test-nested-userns", no_argument, 0, 'n'}, |
| {"test-btrfs", no_argument, 0, 'b'}, |
| {"test-setattr-fix-968219708108", no_argument, 0, 'i'}, |
| {"test-setxattr-fix-705191b03d50", no_argument, 0, 'j'}, |
| {NULL, 0, 0, 0}, |
| }; |
| |
| struct test_struct basic_suite[] = { |
| { acls, T_REQUIRE_IDMAPPED_MOUNTS, "posix acls on regular mounts", }, |
| { create_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "create operations in user namespace", }, |
| { device_node_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "device node in user namespace", }, |
| { expected_uid_gid_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "expected ownership on idmapped mounts", }, |
| { fscaps, T_REQUIRE_USERNS, "fscaps on regular mounts", }, |
| { fscaps_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts", }, |
| { fscaps_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts in user namespace", }, |
| { fscaps_idmapped_mounts_in_userns_separate_userns, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts in user namespace with different id mappings", }, |
| { fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "mapped fsids", }, |
| { fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "unmapped fsids", }, |
| { hardlink_crossing_mounts, 0, "cross mount hardlink", }, |
| { hardlink_crossing_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "cross idmapped mount hardlink", }, |
| { hardlink_from_idmapped_mount, T_REQUIRE_IDMAPPED_MOUNTS, "hardlinks from idmapped mounts", }, |
| { hardlink_from_idmapped_mount_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "hardlinks from idmapped mounts in user namespace", }, |
| #ifdef HAVE_LIBURING_H |
| { io_uring, 0, "io_uring", }, |
| { io_uring_userns, T_REQUIRE_USERNS, "io_uring in user namespace", }, |
| { io_uring_idmapped, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts", }, |
| { io_uring_idmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts in user namespace", }, |
| { io_uring_idmapped_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts with unmapped ids", }, |
| { io_uring_idmapped_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts with unmapped ids in user namespace", }, |
| #endif |
| { protected_symlinks, 0, "following protected symlinks on regular mounts", }, |
| { protected_symlinks_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "following protected symlinks on idmapped mounts", }, |
| { protected_symlinks_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "following protected symlinks on idmapped mounts in user namespace", }, |
| { rename_crossing_mounts, 0, "cross mount rename", }, |
| { rename_crossing_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "cross idmapped mount rename", }, |
| { rename_from_idmapped_mount, T_REQUIRE_IDMAPPED_MOUNTS, "rename from idmapped mounts", }, |
| { rename_from_idmapped_mount_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "rename from idmapped mounts in user namespace", }, |
| { setattr_truncate, 0, "setattr truncate", }, |
| { setattr_truncate_idmapped, T_REQUIRE_IDMAPPED_MOUNTS, "setattr truncate on idmapped mounts", }, |
| { setattr_truncate_idmapped_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "setattr truncate on idmapped mounts in user namespace", }, |
| { setgid_create, 0, "create operations in directories with setgid bit set", }, |
| { setgid_create_idmapped, T_REQUIRE_IDMAPPED_MOUNTS, "create operations in directories with setgid bit set on idmapped mounts", }, |
| { setgid_create_idmapped_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "create operations in directories with setgid bit set on idmapped mounts in user namespace", }, |
| { setid_binaries, 0, "setid binaries on regular mounts", }, |
| { setid_binaries_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "setid binaries on idmapped mounts", }, |
| { setid_binaries_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "setid binaries on idmapped mounts in user namespace", }, |
| { setid_binaries_idmapped_mounts_in_userns_separate_userns, T_REQUIRE_IDMAPPED_MOUNTS, "setid binaries on idmapped mounts in user namespace with different id mappings", }, |
| { sticky_bit_unlink, 0, "sticky bit unlink operations on regular mounts", }, |
| { sticky_bit_unlink_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit unlink operations on idmapped mounts", }, |
| { sticky_bit_unlink_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit unlink operations on idmapped mounts in user namespace", }, |
| { sticky_bit_rename, 0, "sticky bit rename operations on regular mounts", }, |
| { sticky_bit_rename_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit rename operations on idmapped mounts", }, |
| { sticky_bit_rename_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit rename operations on idmapped mounts in user namespace", }, |
| { symlink_regular_mounts, 0, "symlink from regular mounts", }, |
| { symlink_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "symlink from idmapped mounts", }, |
| { symlink_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "symlink from idmapped mounts in user namespace", }, |
| { threaded_idmapped_mount_interactions, T_REQUIRE_IDMAPPED_MOUNTS, "threaded operations on idmapped mounts", }, |
| }; |
| |
| struct test_struct fscaps_in_ancestor_userns[] = { |
| { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", }, |
| }; |
| |
| struct test_struct t_nested_userns[] = { |
| { nested_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test that nested user namespaces behave correctly when attached to idmapped mounts", }, |
| }; |
| |
| 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", }, |
| }; |
| |
| /* Test for commit 968219708108 ("fs: handle circular mappings correctly"). */ |
| struct test_struct t_setattr_fix_968219708108[] = { |
| { setattr_fix_968219708108, T_REQUIRE_IDMAPPED_MOUNTS, "test that setattr works correctly", }, |
| }; |
| |
| /* Test for commit 705191b03d50 ("fs: fix acl translation"). */ |
| struct test_struct t_setxattr_fix_705191b03d50[] = { |
| { setxattr_fix_705191b03d50, T_REQUIRE_USERNS, "test that setxattr works correctly for userns mountable filesystems", }, |
| }; |
| |
| static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size) |
| { |
| int i; |
| |
| for (i = 0; i < suite_size; i++) { |
| const struct test_struct *t = &suite[i]; |
| int ret; |
| pid_t pid; |
| |
| /* |
| * If the underlying filesystems does not support idmapped |
| * mounts only run vfs generic tests. |
| */ |
| if ((t->support_flags & T_REQUIRE_IDMAPPED_MOUNTS && |
| !info->t_fs_allow_idmap) || |
| (t->support_flags & T_REQUIRE_USERNS && !info->t_has_userns)) { |
| log_debug("Skipping test %s", t->description); |
| continue; |
| } |
| |
| test_setup(info); |
| |
| pid = fork(); |
| if (pid < 0) |
| return false; |
| |
| if (pid == 0) { |
| ret = t->test(info); |
| if (ret) |
| die("failure: %s", t->description); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| ret = wait_for_pid(pid); |
| test_cleanup(info); |
| |
| if (ret) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool fs_allow_idmap(const struct vfstest_info *info) |
| { |
| int ret; |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = -EBADF, |
| }; |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(0, 1000, 1); |
| if (attr.userns_fd < 0) |
| return false; |
| |
| open_tree_fd = sys_open_tree(info->t_mnt_fd, "", |
| AT_EMPTY_PATH | AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| if (open_tree_fd < 0) |
| ret = -1; |
| else |
| ret = sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, |
| sizeof(attr)); |
| close(open_tree_fd); |
| close(attr.userns_fd); |
| |
| return ret == 0; |
| } |
| |
| static bool sys_has_userns(void) |
| { |
| int fd = get_userns_fd(0, 1000, 1); |
| |
| if (fd < 0) |
| return false; |
| close(fd); |
| return true; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct vfstest_info info; |
| int fret, ret; |
| int index = 0; |
| bool idmapped_mounts_supported = false, test_btrfs = false, |
| test_core = false, test_fscaps_regression = false, |
| test_nested_userns = false, test_setattr_fix_968219708108 = false, |
| test_setxattr_fix_705191b03d50 = false; |
| |
| init_vfstest_info(&info); |
| |
| while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) { |
| switch (ret) { |
| case 'd': |
| info.t_device = optarg; |
| break; |
| case 'f': |
| info.t_fstype = optarg; |
| break; |
| case 'm': |
| info.t_mountpoint = optarg; |
| break; |
| case 's': |
| idmapped_mounts_supported = true; |
| break; |
| case 'c': |
| test_core = true; |
| break; |
| case 'g': |
| test_fscaps_regression = true; |
| break; |
| case 'n': |
| test_nested_userns = true; |
| break; |
| case 'b': |
| test_btrfs = true; |
| break; |
| case 'a': |
| info.t_mountpoint_scratch = optarg; |
| break; |
| case 'e': |
| info.t_device_scratch = optarg; |
| break; |
| case 'i': |
| test_setattr_fix_968219708108 = true; |
| break; |
| case 'j': |
| test_setxattr_fix_705191b03d50 = true; |
| break; |
| case 'h': |
| /* fallthrough */ |
| default: |
| usage(); |
| } |
| } |
| |
| if (!info.t_device) |
| die_errno(EINVAL, "test device missing"); |
| |
| if (!info.t_fstype) |
| die_errno(EINVAL, "test filesystem type missing"); |
| |
| if (!info.t_mountpoint) |
| die_errno(EINVAL, "mountpoint of test device missing"); |
| |
| /* create separate mount namespace */ |
| if (unshare(CLONE_NEWNS)) |
| die("failure: create new mount namespace"); |
| |
| /* turn off mount propagation */ |
| if (sys_mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) |
| die("failure: turn mount propagation off"); |
| |
| info.t_mnt_fd = openat(-EBADF, info.t_mountpoint, O_CLOEXEC | O_DIRECTORY); |
| if (info.t_mnt_fd < 0) |
| die("failed to open %s", info.t_mountpoint); |
| |
| info.t_mnt_scratch_fd = openat(-EBADF, info.t_mountpoint_scratch, O_CLOEXEC | O_DIRECTORY); |
| if (info.t_mnt_fd < 0) |
| die("failed to open %s", info.t_mountpoint_scratch); |
| |
| info.t_fs_allow_idmap = fs_allow_idmap(&info); |
| if (idmapped_mounts_supported) { |
| /* |
| * Caller just wants to know whether the filesystem we're on |
| * supports idmapped mounts. |
| */ |
| if (!info.t_fs_allow_idmap) |
| exit(EXIT_FAILURE); |
| |
| exit(EXIT_SUCCESS); |
| } |
| info.t_has_userns = sys_has_userns(); |
| /* don't copy ENOSYS errno to child process on older kernel */ |
| errno = 0; |
| |
| stash_overflowuid(&info); |
| stash_overflowgid(&info); |
| |
| fret = EXIT_FAILURE; |
| |
| if (test_core && !run_test(&info, basic_suite, ARRAY_SIZE(basic_suite))) |
| goto out; |
| |
| if (test_fscaps_regression && |
| !run_test(&info, fscaps_in_ancestor_userns, |
| ARRAY_SIZE(fscaps_in_ancestor_userns))) |
| goto out; |
| |
| if (test_nested_userns && |
| !run_test(&info, t_nested_userns, ARRAY_SIZE(t_nested_userns))) |
| goto out; |
| |
| if (test_btrfs && !run_test(&info, t_btrfs, ARRAY_SIZE(t_btrfs))) |
| goto out; |
| |
| if (test_setattr_fix_968219708108 && |
| !run_test(&info, t_setattr_fix_968219708108, |
| ARRAY_SIZE(t_setattr_fix_968219708108))) |
| goto out; |
| |
| if (test_setxattr_fix_705191b03d50 && |
| !run_test(&info, t_setxattr_fix_705191b03d50, |
| ARRAY_SIZE(t_setxattr_fix_705191b03d50))) |
| goto out; |
| |
| fret = EXIT_SUCCESS; |
| |
| out: |
| exit(fret); |
| } |