// SPDX-License-Identifier: GPL-2.0 #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "../global.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "btrfs-idmapped-mounts.h" #include "tmpfs-idmapped-mounts.h" #include "idmapped-mounts.h" #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); } 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"); } 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"); } 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 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 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 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; } /* 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; } 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_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; } /* 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; } #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; } #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; } /* The current_umask() is stripped from the mode directly in the vfs if the * filesystem either doesn't support acls or the filesystem has been * mounted without posic acl support. * * If the filesystem does support acls then current_umask() stripping is * deferred to posix_acl_create(). So when the filesystem calls * posix_acl_create() and there are no acls set or not supported then * current_umask() will be stripped. * * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly. * * test for commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile") * and 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers"). */ static int setgid_create_umask(const struct vfstest_info *info) { int fret = -1; int file1_fd = -EBADF; int tmpfile_fd = -EBADF; pid_t pid; bool supported = false; mode_t mode; 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) { umask(S_IXGRP); mode = umask(S_IXGRP); if (!(mode & S_IXGRP)) die("failure: umask"); 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"); if (is_ixgrp(info->t_dir1_fd, FILE1, 0)) die("failure: is_ixgrp"); 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"); } if (is_ixgrp(info->t_dir1_fd, DIR1, 0)) die("failure: is_ixgrp"); /* 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"); if (is_ixgrp(info->t_dir1_fd, FILE2, 0)) die("failure: is_ixgrp"); /* 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 (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0)) die("failure: is_ixgrp"); /* * 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 (is_ixgrp(info->t_dir1_fd, FILE3, 0)) die("failure: is_ixgrp"); 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; } /* * If the parent directory has a default acl then permissions are based off * of that and current_umask() is ignored. Specifically, if the ACL has an * ACL_MASK entry, the group permissions correspond to the permissions of * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the * group permissions correspond to the permissions of the ACL_GROUP_OBJ * entry. * * Use setfacl to check whether inode strip S_ISGID works correctly under * the above two situations. * * Test for commit * 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers"). */ static int setgid_create_acl(const struct vfstest_info *info) { int fret = -1; int file1_fd = -EBADF; int tmpfile_fd = -EBADF; pid_t pid; bool supported = false; mode_t mode; 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) { umask(S_IXGRP); mode = umask(S_IXGRP); if (!(mode & S_IXGRP)) die("failure: umask"); /* The group permissions correspond to the permissions of the * ACL_MASK entry. Use setfacl to set ACL mask(m) as rw, so now * the group permissions is rw. Also, umask doesn't affect * group permissions because umask will be ignored if having * acl. */ snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1); if (system(t_buf)) die("failure: system"); 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"); if (is_ixgrp(info->t_dir1_fd, FILE1, 0)) die("failure: is_ixgrp"); /* 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"); } if (is_ixgrp(info->t_dir1_fd, DIR1, 0)) die("failure: is_ixgrp"); /* 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"); if (is_ixgrp(info->t_dir1_fd, FILE2, 0)) die("failure: is_ixgrp"); /* 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 (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0)) die("failure: is_ixgrp"); /* * 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 (is_ixgrp(info->t_dir1_fd, FILE3, 0)) die("failure: is_ixgrp"); 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) { umask(S_IXGRP); mode = umask(S_IXGRP); if (!(mode & S_IXGRP)) die("failure: umask"); /* The group permissions correspond to the permissions of the * ACL_GROUP_OBJ entry. Don't use setfacl to set ACL_MASK, so * the group permissions is equal to ACL_GROUP_OBJ(g) * entry(rwx). Also, umask doesn't affect group permissions * because umask will be ignored if having acl. */ snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1); if (system(t_buf)) die("failure: system"); 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"); if (!is_ixgrp(info->t_dir1_fd, FILE1, 0)) die("failure: is_ixgrp"); /* 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"); } if (is_ixgrp(info->t_dir1_fd, DIR1, 0)) die("failure: is_ixgrp"); /* 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"); if (!is_ixgrp(info->t_dir1_fd, FILE2, 0)) die("failure: is_ixgrp"); /* 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 (!is_ixgrp(info->t_dir1_fd, CHRDEV1, 0)) die("failure: is_ixgrp"); /* * 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 (!is_ixgrp(info->t_dir1_fd, FILE3, 0)) die("failure: is_ixgrp"); 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 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 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-tmpfs Run tmpfs 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"); fprintf(stderr, "--test-setgid-create-umask Run setgid with umask tests\n"); fprintf(stderr, "--test-setgid-create-acl Run setgid with acl 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'}, {"test-setgid-create-umask", no_argument, 0, 'u'}, {"test-setgid-create-acl", no_argument, 0, 'l'}, {"test-tmpfs", no_argument, 0, 't'}, {NULL, 0, 0, 0}, }; static const struct test_struct t_basic[] = { { fscaps, T_REQUIRE_USERNS, "fscaps on regular mounts", }, { hardlink_crossing_mounts, 0, "cross mount hardlink", }, #ifdef HAVE_LIBURING_H { io_uring, 0, "io_uring", }, { io_uring_userns, T_REQUIRE_USERNS, "io_uring in user namespace", }, #endif { protected_symlinks, 0, "following protected symlinks on regular mounts", }, { rename_crossing_mounts, 0, "cross mount rename", }, { setattr_truncate, 0, "setattr truncate", }, { setgid_create, 0, "create operations in directories with setgid bit set", }, { setid_binaries, 0, "setid binaries on regular mounts", }, { sticky_bit_unlink, 0, "sticky bit unlink operations on regular mounts", }, { sticky_bit_rename, 0, "sticky bit rename operations on regular mounts", }, { symlink_regular_mounts, 0, "symlink from regular mounts", }, }; static const struct test_suite s_basic = { .tests = t_basic, .nr_tests = ARRAY_SIZE(t_basic), }; static const struct test_struct t_setgid_create_umask[] = { { setgid_create_umask, 0, "create operations in directories with setgid bit set under umask", }, }; static const struct test_suite s_setgid_create_umask = { .tests = t_setgid_create_umask, .nr_tests = ARRAY_SIZE(t_setgid_create_umask), }; static const struct test_struct t_setgid_create_acl[] = { { setgid_create_acl, 0, "create operations in directories with setgid bit set under posix acl", }, }; static const struct test_suite s_setgid_create_acl = { .tests = t_setgid_create_acl, .nr_tests = ARRAY_SIZE(t_setgid_create_acl), }; 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 inline bool run_suite(struct vfstest_info *info, const struct test_suite *suite) { return run_test(info, suite->tests, suite->nr_tests); } 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, test_tmpfs = false, test_setgid_create_umask = false, test_setgid_create_acl = 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 'u': test_setgid_create_umask = true; break; case 'l': test_setgid_create_acl = true; break; case 't': test_tmpfs = 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) { if (!run_suite(&info, &s_basic)) goto out; if (!run_suite(&info, &s_idmapped_mounts)) goto out; } if (test_fscaps_regression && !run_suite(&info, &s_fscaps_in_ancestor_userns)) goto out; if (test_nested_userns && !run_suite(&info, &s_nested_userns)) goto out; if (test_btrfs && !run_suite(&info, &s_btrfs_idmapped_mounts)) goto out; if (test_setattr_fix_968219708108 && !run_suite(&info, &s_setattr_fix_968219708108)) goto out; if (test_setxattr_fix_705191b03d50 && !run_suite(&info, &s_setxattr_fix_705191b03d50)) goto out; if (test_setgid_create_umask) { if (!run_suite(&info, &s_setgid_create_umask)) goto out; if (!run_suite(&info, &s_setgid_create_umask_idmapped_mounts)) goto out; } if (test_setgid_create_acl) { if (!run_suite(&info, &s_setgid_create_acl)) goto out; if (!run_suite(&info, &s_setgid_create_acl_idmapped_mounts)) goto out; } if (test_tmpfs) { if (!run_suite(&info, &s_tmpfs_idmapped_mounts)) goto out; } fret = EXIT_SUCCESS; out: exit(fret); }