| // 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> |
| |
| #include "missing.h" |
| #include "utils.h" |
| |
| static char t_buf[PATH_MAX]; |
| |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| /* 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; |
| } |
| |
| 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. */ |
| 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; |
| } |
| |
| /* 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_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; |
| } |
| |
| 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; |
| } |
| |
| |
| #ifdef HAVE_LIBURING_H |
| 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 */ |
| |
| /* 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; |
|