diff options
author | Christian Brauner <brauner@kernel.org> | 2022-05-12 18:52:47 +0200 |
---|---|---|
committer | Zorro Lang <zlang@kernel.org> | 2022-05-15 09:04:35 +0800 |
commit | 48fdc9dd7dac26505153e7590db5fd97b2bf610b (patch) | |
tree | f2a028ac89d75b514835876e229020f32fb598ef | |
parent | 0a26b226d83827f8f20368661760a7cb7fe487dc (diff) |
vfstest: split idmapped mount tests into separate suite
Now that we finished restructuring split the core idmapped mounts test
suite into a separate source file.
Acked-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org>
Signed-off-by: Zorro Lang <zlang@kernel.org>
-rw-r--r-- | src/vfs/Makefile | 4 | ||||
-rw-r--r-- | src/vfs/idmapped-mounts.c | 6733 | ||||
-rw-r--r-- | src/vfs/idmapped-mounts.h | 15 | ||||
-rw-r--r-- | src/vfs/vfstest.c | 6729 |
4 files changed, 6771 insertions, 6710 deletions
diff --git a/src/vfs/Makefile b/src/vfs/Makefile index adef9ff3..fa0f1621 100644 --- a/src/vfs/Makefile +++ b/src/vfs/Makefile @@ -4,10 +4,10 @@ TOPDIR = ../.. include $(TOPDIR)/include/builddefs TARGETS = vfstest mount-idmapped -CFILES_VFSTEST = vfstest.c utils.c +CFILES_VFSTEST = vfstest.c idmapped-mounts.c utils.c CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c -HFILES = missing.h utils.h +HFILES = missing.h utils.h idmapped-mounts.h LLDLIBS += -pthread LDIRT = $(TARGETS) diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c new file mode 100644 index 00000000..e746ae89 --- /dev/null +++ b/src/vfs/idmapped-mounts.c @@ -0,0 +1,6733 @@ +// 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; + } + + /* validate file can be directly read */ + fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) { + log_stderr("failure: openat"); + goto out; + } + safe_close(fd); + + /* validate file can be read through own symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) { + log_stderr("failure: openat"); + goto out; + } + safe_close(fd); + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + /* validate file can be directly read */ + fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through own symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through root symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can't be read through other users symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) + die("failure: openat"); + if (errno != EACCES) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(2000, 2000)) + die("failure: switch_ids"); + + /* validate file can be directly read */ + fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through own symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through root symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can't be read through other users symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) + die("failure: openat"); + if (errno != EACCES) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(fd); + safe_close(dir_fd); + safe_close(open_tree_fd); + + return fret; +} + +/* Validate that protected symlinks work correctly on idmapped mounts inside a + * user namespace. + */ +static int protected_symlinks_idmapped_mounts_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int dir_fd = -EBADF, fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!protected_symlinks_enabled()) + return 0; + + if (!caps_supported()) + return 0; + + /* create directory */ + if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); + if (dir_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + if (fchown(dir_fd, 0, 0)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create symlinks */ + if (symlinkat(FILE1, dir_fd, SYMLINK_USER1)) { + log_stderr("failure: symlinkat"); + goto out; + } + if (fchownat(dir_fd, SYMLINK_USER1, 0, 0, AT_SYMLINK_NOFOLLOW)) { + log_stderr("failure: fchownat"); + goto out; + } + if (!expected_uid_gid(dir_fd, SYMLINK_USER1, AT_SYMLINK_NOFOLLOW, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (symlinkat(FILE1, dir_fd, SYMLINK_USER2)) { + log_stderr("failure: symlinkat"); + goto out; + } + if (fchownat(dir_fd, SYMLINK_USER2, 1000, 1000, AT_SYMLINK_NOFOLLOW)) { + log_stderr("failure: fchownat"); + goto out; + } + if (!expected_uid_gid(dir_fd, SYMLINK_USER2, AT_SYMLINK_NOFOLLOW, 1000, 1000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (symlinkat(FILE1, dir_fd, SYMLINK_USER3)) { + log_stderr("failure: symlinkat"); + goto out; + } + if (fchownat(dir_fd, SYMLINK_USER3, 2000, 2000, AT_SYMLINK_NOFOLLOW)) { + log_stderr("failure: fchownat"); + goto out; + } + if (!expected_uid_gid(dir_fd, SYMLINK_USER3, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* validate file can be directly read */ + fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) { + log_stderr("failure: openat"); + goto out; + } + safe_close(fd); + + /* validate file can be read through own symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) { + log_stderr("failure: openat"); + goto out; + } + safe_close(fd); + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + /* validate file can be directly read */ + fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through own symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through root symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can't be read through other users symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) + die("failure: openat"); + if (errno != EACCES) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 2000, 2000, true)) + die("failure: switch_userns"); + + /* validate file can be directly read */ + fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through own symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can be read through root symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) + die("failure: openat"); + safe_close(fd); + + /* validate file can't be read through other users symlink */ + fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) + die("failure: openat"); + if (errno != EACCES) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(dir_fd); + safe_close(open_tree_fd); + safe_close(attr.userns_fd); + + return fret; +} + +static int rename_crossing_idmapped_mounts(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { + log_stderr("failure: chown_r"); + goto out; + } + + attr.userns_fd = get_userns_fd(10000, 0, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd1 = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd1 < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + file1_fd = openat(open_tree_fd1, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (mkdirat(open_tree_fd1, DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + open_tree_fd2 = sys_open_tree(info->t_dir1_fd, DIR1, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE | + AT_RECURSIVE); + if (open_tree_fd2 < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* We're crossing a mountpoint so this must fail. + * + * Note that this must also fail for non-idmapped mounts but here we're + * interested in making sure we're not introducing an accidental way to + * violate that restriction or that suddenly this becomes possible. + */ + if (!renameat(open_tree_fd1, FILE1, open_tree_fd2, FILE1_RENAME)) { + log_stderr("failure: renameat"); + goto out; + } + if (errno != EXDEV) { + log_stderr("failure: errno"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd1); + safe_close(open_tree_fd2); + + return fret; +} + +static int rename_from_idmapped_mount(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { + log_stderr("failure: chown_r"); + goto out; + } + + attr.userns_fd = get_userns_fd(10000, 0, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + /* We're not crossing a mountpoint so this must succeed. */ + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) { + log_stderr("failure: renameat"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int rename_from_idmapped_mount_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + pid_t pid; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { + log_stderr("failure: chown_r"); + goto out; + } + + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file1_fd < 0) + die("failure: create"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + /* We're not crossing a mountpoint so this must succeed. */ + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: create"); + + if (!expected_uid_gid(open_tree_fd, FILE1_RENAME, 0, 0, 0)) + die("failure: check ownership"); + + exit(EXIT_SUCCESS); + } + + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int setattr_truncate_idmapped(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + pid_t pid; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(10000, 10000)) + die("failure: switch_ids"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + if (ftruncate(file1_fd, 10000)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) + die("failure: expected_file_size"); + + if (ftruncate(file1_fd, 0)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) + die("failure: expected_file_size"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int file1_fd2 = -EBADF; + + /* create regular file via open() */ + file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd2 < 0) + die("failure: create"); + + if (ftruncate(file1_fd2, 10000)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) + die("failure: expected_file_size"); + + if (ftruncate(file1_fd2, 0)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) + die("failure: expected_file_size"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int setattr_truncate_idmapped_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + if (ftruncate(file1_fd, 10000)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) + die("failure: expected_file_size"); + + if (ftruncate(file1_fd, 0)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) + die("failure: expected_file_size"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 0, 0, true)) + die("failure: switch_userns"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + if (ftruncate(file1_fd, 10000)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) + die("failure: expected_file_size"); + + if (ftruncate(file1_fd, 0)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) + die("failure: expected_file_size"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 0, 1000, true)) + die("failure: switch_userns"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + if (ftruncate(file1_fd, 10000)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) + die("failure: expected_file_size"); + + if (ftruncate(file1_fd, 0)) + die("failure: ftruncate"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) + die("failure: check ownership"); + + if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) + die("failure: expected_file_size"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int setgid_create_idmapped(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + int tmpfile_fd = -EBADF; + bool supported = false; + char path[PATH_MAX]; + + if (!caps_supported()) + return 0; + + if (fchmod(info->t_dir1_fd, S_IRUSR | + S_IWUSR | + S_IRGRP | + S_IWGRP | + S_IROTH | + S_IWOTH | + S_IXUSR | + S_IXGRP | + S_IXOTH | + S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { + log_stderr("failure: is_setgid"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + supported = openat_tmpfile_supported(open_tree_fd); + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(10000, 11000)) + die("failure: switch fsids"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid + * bit needs to be stripped. + */ + if (is_setgid(open_tree_fd, FILE1, 0)) + die("failure: is_setgid"); + + /* create directory */ + if (mkdirat(open_tree_fd, DIR1, 0000)) + die("failure: create"); + + if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { + /* We're not in_group_p(). */ + if (is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + } else { + /* Directories always inherit the setgid bit. */ + if (!is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + } + + /* create a special file via mknodat() vfs_create */ + if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (is_setgid(open_tree_fd, FILE2, 0)) + die("failure: is_setgid"); + + /* create a whiteout device via mknodat() vfs_mknod */ + if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (is_setgid(open_tree_fd, CHRDEV1, 0)) + die("failure: is_setgid"); + + /* + * In setgid directories newly created files always inherit the + * gid from the parent directory. Verify that the file is owned + * by gid 10000, not by gid 11000. + */ + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) + die("failure: check ownership"); + + /* + * In setgid directories newly created directories always + * inherit the gid from the parent directory. Verify that the + * directory is owned by gid 10000, not by gid 11000. + */ + if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000)) + die("failure: check ownership"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, CHRDEV1, 0)) + die("failure: delete"); + + /* create tmpfile via filesystem tmpfile api */ + if (supported) { + tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); + if (tmpfile_fd < 0) + die("failure: create"); + /* link the temporary file into the filesystem, making it permanent */ + snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); + if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) + die("failure: linkat"); + if (close(tmpfile_fd)) + die("failure: close"); + if (is_setgid(open_tree_fd, FILE3, 0)) + die("failure: is_setgid"); + if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000)) + die("failure: check ownership"); + if (unlinkat(open_tree_fd, FILE3, 0)) + die("failure: delete"); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int setgid_create_idmapped_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + int tmpfile_fd = -EBADF; + bool supported = false; + char path[PATH_MAX]; + + if (!caps_supported()) + return 0; + + if (fchmod(info->t_dir1_fd, S_IRUSR | + S_IWUSR | + S_IRGRP | + S_IWGRP | + S_IROTH | + S_IWOTH | + S_IXUSR | + S_IXGRP | + S_IXOTH | + S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { + log_stderr("failure: is_setgid"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + supported = openat_tmpfile_supported(open_tree_fd); + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + /* We're in_group_p() and capable_wrt_inode_uidgid() so setgid + * bit needs to be set. + */ + if (!is_setgid(open_tree_fd, FILE1, 0)) + die("failure: is_setgid"); + + /* create directory */ + if (mkdirat(open_tree_fd, DIR1, 0000)) + die("failure: create"); + + /* Directories always inherit the setgid bit. */ + if (!is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + + /* create a special file via mknodat() vfs_create */ + if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (!is_setgid(open_tree_fd, FILE2, 0)) + die("failure: is_setgid"); + + /* create a whiteout device via mknodat() vfs_mknod */ + if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (!is_setgid(open_tree_fd, CHRDEV1, 0)) + die("failure: is_setgid"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 0)) + die("failure: check ownership"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, CHRDEV1, 0)) + die("failure: delete"); + + /* create tmpfile via filesystem tmpfile api */ + if (supported) { + tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); + if (tmpfile_fd < 0) + die("failure: create"); + /* link the temporary file into the filesystem, making it permanent */ + snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); + if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) + die("failure: linkat"); + if (close(tmpfile_fd)) + die("failure: close"); + if (!is_setgid(open_tree_fd, FILE3, 0)) + die("failure: is_setgid"); + if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 0)) + die("failure: check ownership"); + if (unlinkat(open_tree_fd, FILE3, 0)) + die("failure: delete"); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* + * Below we verify that setgid inheritance for a newly created file or + * directory works correctly. As part of this we need to verify that + * newly created files or directories inherit their gid from their + * parent directory. So we change the parent directorie's gid to 1000 + * and create a file with fs{g,u}id 0 and verify that the newly created + * file and directory inherit gid 1000, not 0. + */ + if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!caps_down_fsetid()) + die("failure: caps_down_fsetid"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid + * bit needs to be stripped. + */ + if (is_setgid(open_tree_fd, FILE1, 0)) + die("failure: is_setgid"); + + /* create directory */ + if (mkdirat(open_tree_fd, DIR1, 0000)) + die("failure: create"); + + if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { + /* We're not in_group_p(). */ + if (is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + } else { + /* Directories always inherit the setgid bit. */ + if (!is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + } + + /* create a special file via mknodat() vfs_create */ + if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (is_setgid(open_tree_fd, FILE2, 0)) + die("failure: is_setgid"); + + /* create a whiteout device via mknodat() vfs_mknod */ + if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (is_setgid(open_tree_fd, CHRDEV1, 0)) + die("failure: is_setgid"); + + /* + * In setgid directories newly created files always inherit the + * gid from the parent directory. Verify that the file is owned + * by gid 1000, not by gid 0. + */ + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) + die("failure: check ownership"); + + /* + * In setgid directories newly created directories always + * inherit the gid from the parent directory. Verify that the + * directory is owned by gid 1000, not by gid 0. + */ + if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000)) + die("failure: check ownership"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, CHRDEV1, 0)) + die("failure: delete"); + + /* create tmpfile via filesystem tmpfile api */ + if (supported) { + tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); + if (tmpfile_fd < 0) + die("failure: create"); + /* link the temporary file into the filesystem, making it permanent */ + snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); + if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) + die("failure: linkat"); + if (close(tmpfile_fd)) + die("failure: close"); + if (is_setgid(open_tree_fd, FILE3, 0)) + die("failure: is_setgid"); + if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000)) + die("failure: check ownership"); + if (unlinkat(open_tree_fd, FILE3, 0)) + die("failure: delete"); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { + log_stderr("failure: fchownat"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 0, 1000, false)) + die("failure: switch_userns"); + + if (!caps_down_fsetid()) + die("failure: caps_down_fsetid"); + + /* create regular file via open() */ + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); + if (file1_fd < 0) + die("failure: create"); + + /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid + * bit needs to be stripped. + */ + if (is_setgid(open_tree_fd, FILE1, 0)) + die("failure: is_setgid"); + + /* create directory */ + if (mkdirat(open_tree_fd, DIR1, 0000)) + die("failure: create"); + + /* Directories always inherit the setgid bit. */ + if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { + /* We're not in_group_p(). */ + if (is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + } else { + /* Directories always inherit the setgid bit. */ + if (!is_setgid(open_tree_fd, DIR1, 0)) + die("failure: is_setgid"); + } + + /* create a special file via mknodat() vfs_create */ + if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (is_setgid(open_tree_fd, FILE2, 0)) + die("failure: is_setgid"); + + /* create a whiteout device via mknodat() vfs_mknod */ + if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) + die("failure: mknodat"); + + if (is_setgid(open_tree_fd, CHRDEV1, 0)) + die("failure: is_setgid"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 0)) + die("failure: check ownership"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: delete"); + + if (unlinkat(open_tree_fd, CHRDEV1, 0)) + die("failure: delete"); + + /* create tmpfile via filesystem tmpfile api */ + if (supported) { + tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); + if (tmpfile_fd < 0) + die("failure: create"); + /* link the temporary file into the filesystem, making it permanent */ + snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); + if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) + die("failure: linkat"); + if (close(tmpfile_fd)) + die("failure: close"); + if (is_setgid(open_tree_fd, FILE3, 0)) + die("failure: is_setgid"); + if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 0)) + die("failure: check ownership"); + if (unlinkat(open_tree_fd, FILE3, 0)) + die("failure: delete"); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +/* Validate that setid transitions are handled correctly on idmapped mounts. */ +static int setid_binaries_idmapped_mounts(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + /* create a file to be used as setuid binary */ + file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* open our own executable */ + exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); + if (exec_fd < 0) { + log_stderr("failure:openat "); + goto out; + } + + /* copy our own executable into the file we created */ + if (fd_to_fd(exec_fd, file1_fd)) { + log_stderr("failure: fd_to_fd"); + goto out; + } + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 5000, 5000)) { + log_stderr("failure: fchown"); + goto out; + } + + /* set the setid bits and grant execute permissions to the group */ + if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(exec_fd); + safe_close(file1_fd); + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* A detached mount will have an anonymous mount namespace attached to + * it. This means that we can't execute setid binaries on a detached + * mount because the mnt_may_suid() helper will fail the check_mount() + * part of its check which compares the caller's mount namespace to the + * detached mount's mount namespace. Since by definition an anonymous + * mount namespace is not equale to any mount namespace currently in + * use this can't work. So attach the mount to the filesystem first + * before performing this check. + */ + if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { + log_stderr("failure: sys_move_mount"); + goto out; + } + + /* Verify we run setid binary as uid and gid 10000 from idmapped mount mount. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + static char *envp[] = { + "IDMAP_MOUNT_TEST_RUN_SETID=1", + "EXPECTED_EUID=15000", + "EXPECTED_EGID=15000", + NULL, + }; + static char *argv[] = { + NULL, + }; + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 15000, 15000)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(exec_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); + sys_umount2(t_buf, MNT_DETACH); + rm_r(info->t_mnt_fd, DIR1); + + return fret; +} + +/* Validate that setid transitions are handled correctly on idmapped mounts + * running in a user namespace where the uid and gid of the setid binary have no + * mapping. + */ +static int setid_binaries_idmapped_mounts_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { + log_stderr("failure: "); + goto out; + } + + /* create a file to be used as setuid binary */ + file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* open our own executable */ + exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); + if (exec_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* copy our own executable into the file we created */ + if (fd_to_fd(exec_fd, file1_fd)) { + log_stderr("failure: fd_to_fd"); + goto out; + } + + safe_close(exec_fd); + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 5000, 5000)) { + log_stderr("failure: fchown"); + goto out; + } + + /* set the setid bits and grant execute permissions to the group */ + if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(file1_fd); + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* A detached mount will have an anonymous mount namespace attached to + * it. This means that we can't execute setid binaries on a detached + * mount because the mnt_may_suid() helper will fail the check_mount() + * part of its check which compares the caller's mount namespace to the + * detached mount's mount namespace. Since by definition an anonymous + * mount namespace is not equale to any mount namespace currently in + * use this can't work. So attach the mount to the filesystem first + * before performing this check. + */ + if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { + log_stderr("failure: sys_move_mount"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + static char *envp[] = { + "IDMAP_MOUNT_TEST_RUN_SETID=1", + "EXPECTED_EUID=5000", + "EXPECTED_EGID=5000", + NULL, + }; + static char *argv[] = { + NULL, + }; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 5000, 5000)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 0, 0)) { + log_stderr("failure: fchown"); + goto out; + } + + /* set the setid bits and grant execute permissions to the group */ + if (fchmod(file1_fd, S_IXOTH | S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(file1_fd); + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + static char *envp[] = { + "IDMAP_MOUNT_TEST_RUN_SETID=1", + "EXPECTED_EUID=0", + "EXPECTED_EGID=0", + NULL, + }; + static char *argv[] = { + NULL, + }; + + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 5000, 5000, true)) + die("failure: switch_userns"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 30000, 30000)) { + log_stderr("failure: fchown"); + goto out; + } + + if (fchmod(file1_fd, S_IXOTH | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(file1_fd); + + /* Verify that we can't assume a uid and gid of a setid binary for which + * we have no mapping in our user namespace. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + char expected_euid[100]; + char expected_egid[100]; + static char *envp[4] = { + NULL, + NULL, + NULL, + NULL, + }; + static char *argv[] = { + NULL, + }; + + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + envp[0] = "IDMAP_MOUNT_TEST_RUN_SETID=0"; + snprintf(expected_euid, sizeof(expected_euid), "EXPECTED_EUID=%d", geteuid()); + envp[1] = expected_euid; + snprintf(expected_egid, sizeof(expected_egid), "EXPECTED_EGID=%d", getegid()); + envp[2] = expected_egid; + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(exec_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); + sys_umount2(t_buf, MNT_DETACH); + rm_r(info->t_mnt_fd, DIR1); + + return fret; +} + +/* Validate that setid transitions are handled correctly on idmapped mounts + * running in a user namespace where the uid and gid of the setid binary have no + * mapping. + */ +static int setid_binaries_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + /* create a file to be used as setuid binary */ + file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* open our own executable */ + exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); + if (exec_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* copy our own executable into the file we created */ + if (fd_to_fd(exec_fd, file1_fd)) { + log_stderr("failure: fd_to_fd"); + goto out; + } + + safe_close(exec_fd); + + /* change ownership of all files to uid 0 */ + if (chown_r(info->t_mnt_fd, T_DIR1, 20000, 20000)) { + log_stderr("failure: chown_r"); + goto out; + } + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 25000, 25000)) { + log_stderr("failure: fchown"); + goto out; + } + + /* set the setid bits and grant execute permissions to the group */ + if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(file1_fd); + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(20000, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* A detached mount will have an anonymous mount namespace attached to + * it. This means that we can't execute setid binaries on a detached + * mount because the mnt_may_suid() helper will fail the check_mount() + * part of its check which compares the caller's mount namespace to the + * detached mount's mount namespace. Since by definition an anonymous + * mount namespace is not equale to any mount namespace currently in + * use this can't work. So attach the mount to the filesystem first + * before performing this check. + */ + if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { + log_stderr("failure: sys_move_mount"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int userns_fd; + static char *envp[] = { + "IDMAP_MOUNT_TEST_RUN_SETID=1", + "EXPECTED_EUID=5000", + "EXPECTED_EGID=5000", + NULL, + }; + static char *argv[] = { + NULL, + }; + + userns_fd = get_userns_fd(0, 10000, 10000); + if (userns_fd < 0) + die("failure: get_userns_fd"); + + if (!switch_userns(userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 5000, 5000)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 20000, 20000)) { + log_stderr("failure: fchown"); + goto out; + } + + /* set the setid bits and grant execute permissions to the group */ + if (fchmod(file1_fd, S_IXOTH | S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(file1_fd); + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int userns_fd; + static char *envp[] = { + "IDMAP_MOUNT_TEST_RUN_SETID=1", + "EXPECTED_EUID=0", + "EXPECTED_EGID=0", + NULL, + }; + static char *argv[] = { + NULL, + }; + + userns_fd = get_userns_fd(0, 10000, 10000); + if (userns_fd < 0) + die("failure: get_userns_fd"); + + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + /* chown the file to the uid and gid we want to assume */ + if (fchown(file1_fd, 0, 0)) { + log_stderr("failure: fchown"); + goto out; + } + + if (fchmod(file1_fd, S_IXOTH | S_IEXEC | S_ISUID | S_ISGID), 0) { + log_stderr("failure: fchmod"); + goto out; + } + + /* Verify that the sid bits got raised. */ + if (!is_setid(info->t_dir1_fd, FILE1, 0)) { + log_stderr("failure: is_setid"); + goto out; + } + + safe_close(file1_fd); + + /* Verify that we can't assume a uid and gid of a setid binary for + * which we have no mapping in our user namespace. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int userns_fd; + char expected_euid[100]; + char expected_egid[100]; + static char *envp[4] = { + NULL, + NULL, + NULL, + NULL, + }; + static char *argv[] = { + NULL, + }; + + userns_fd = get_userns_fd(0, 10000, 10000); + if (userns_fd < 0) + die("failure: get_userns_fd"); + + if (!switch_userns(userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + envp[0] = "IDMAP_MOUNT_TEST_RUN_SETID=0"; + snprintf(expected_euid, sizeof(expected_euid), "EXPECTED_EUID=%d", geteuid()); + envp[1] = expected_euid; + snprintf(expected_egid, sizeof(expected_egid), "EXPECTED_EGID=%d", getegid()); + envp[2] = expected_egid; + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) + die("failure: expected_uid_gid"); + + sys_execveat(open_tree_fd, FILE1, argv, envp, 0); + die("failure: sys_execveat"); + + exit(EXIT_FAILURE); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(exec_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); + sys_umount2(t_buf, MNT_DETACH); + rm_r(info->t_mnt_fd, DIR1); + + return fret; +} + +static int sticky_bit_unlink_idmapped_mounts(const struct vfstest_info *info) +{ + int fret = -1; + int dir_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* create directory */ + if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); + if (dir_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + if (fchown(dir_fd, 10000, 10000)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(10000, 0, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(dir_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* The sticky bit is not set so we must be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* set sticky bit */ + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* The sticky bit is set so we must not be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (!unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (!unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* The sticky bit is set and we own the files so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + /* change ownership */ + if (fchownat(dir_fd, FILE1, 11000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE1, 0, 11000, 10000)) + die("failure: expected_uid_gid"); + if (fchownat(dir_fd, FILE2, 11000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE2, 0, 11000, 12000)) + die("failure: expected_uid_gid"); + + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* change uid to unprivileged user */ + if (fchown(dir_fd, 11000, -1)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* The sticky bit is set and we own the directory so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(dir_fd); + safe_close(open_tree_fd); + + return fret; +} + +/* Validate that the sticky bit behaves correctly on idmapped mounts for unlink + * operations in a user namespace. + */ +static int sticky_bit_unlink_idmapped_mounts_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int dir_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* create directory */ + if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); + if (dir_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + if (fchown(dir_fd, 0, 0)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(dir_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* The sticky bit is not set so we must be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (unlinkat(dir_fd, FILE1, 0)) + die("failure: unlinkat"); + + if (unlinkat(dir_fd, FILE2, 0)) + die("failure: unlinkat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* set sticky bit */ + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* The sticky bit is set so we must not be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (!unlinkat(dir_fd, FILE1, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (!unlinkat(dir_fd, FILE2, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (!unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (!unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* The sticky bit is set and we own the files so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + /* change ownership */ + if (fchownat(dir_fd, FILE1, 1000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) + die("failure: expected_uid_gid"); + if (fchownat(dir_fd, FILE2, 1000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) + die("failure: expected_uid_gid"); + + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (!unlinkat(dir_fd, FILE1, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (!unlinkat(dir_fd, FILE2, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* change uid to unprivileged user */ + if (fchown(dir_fd, 1000, -1)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* The sticky bit is set and we own the directory so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + /* we don't own the directory from the original mount */ + if (!unlinkat(dir_fd, FILE1, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + if (!unlinkat(dir_fd, FILE2, 0)) + die("failure: unlinkat"); + if (errno != EPERM) + die("failure: errno"); + + /* we own the file from the idmapped mount */ + if (unlinkat(open_tree_fd, FILE1, 0)) + die("failure: unlinkat"); + if (unlinkat(open_tree_fd, FILE2, 0)) + die("failure: unlinkat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(dir_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int sticky_bit_rename_idmapped_mounts(const struct vfstest_info *info) +{ + int fret = -1; + int dir_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* create directory */ + if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); + if (dir_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (fchown(dir_fd, 10000, 10000)) { + log_stderr("failure: fchown"); + goto out; + } + + if (fchmod(dir_fd, 0777)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(10000, 0, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(dir_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* The sticky bit is not set so we must be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) + die("failure: renameat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* set sticky bit */ + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* The sticky bit is set so we must not be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (!renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* The sticky bit is set and we own the files so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + /* change ownership */ + if (fchownat(dir_fd, FILE1, 11000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE1, 0, 11000, 10000)) + die("failure: expected_uid_gid"); + if (fchownat(dir_fd, FILE2, 11000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE2, 0, 11000, 12000)) + die("failure: expected_uid_gid"); + + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) + die("failure: renameat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* change uid to unprivileged user */ + if (fchown(dir_fd, 11000, -1)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* The sticky bit is set and we own the directory so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_ids(1000, 1000)) + die("failure: switch_ids"); + + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) + die("failure: renameat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(dir_fd); + safe_close(open_tree_fd); + + return fret; +} + +/* Validate that the sticky bit behaves correctly on idmapped mounts for unlink + * operations in a user namespace. + */ +static int sticky_bit_rename_idmapped_mounts_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int dir_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + /* create directory */ + if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); + if (dir_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + if (fchown(dir_fd, 0, 0)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE1, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* create regular file via mknod */ + if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { + log_stderr("failure: mknodat"); + goto out; + } + if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { + log_stderr("failure: fchownat"); + goto out; + } + if (fchmodat(dir_fd, FILE2, 0644, 0)) { + log_stderr("failure: fchmodat"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(dir_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* The sticky bit is not set so we must be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) + die("failure: renameat"); + + if (renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) + die("failure: renameat"); + + if (renameat(dir_fd, FILE1_RENAME, dir_fd, FILE1)) + die("failure: renameat"); + + if (renameat(dir_fd, FILE2_RENAME, dir_fd, FILE2)) + die("failure: renameat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* set sticky bit */ + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* The sticky bit is set so we must not be able to delete files not + * owned by us. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (!renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* The sticky bit is set and we own the files so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + /* change ownership */ + if (fchownat(dir_fd, FILE1, 1000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) + die("failure: expected_uid_gid"); + if (fchownat(dir_fd, FILE2, 1000, -1, 0)) + die("failure: fchownat"); + if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) + die("failure: expected_uid_gid"); + + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) + die("failure: renameat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + /* change uid to unprivileged user */ + if (fchown(dir_fd, 1000, -1)) { + log_stderr("failure: fchown"); + goto out; + } + if (fchmod(dir_fd, 0777 | S_ISVTX)) { + log_stderr("failure: fchmod"); + goto out; + } + /* validate sticky bit is set */ + if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { + log_stderr("failure: is_sticky"); + goto out; + } + + /* The sticky bit is set and we own the directory so we must be able to + * delete the files now. + */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!caps_supported()) { + log_debug("skip: capability library not installed"); + exit(EXIT_SUCCESS); + } + + if (!switch_userns(attr.userns_fd, 1000, 1000, true)) + die("failure: switch_userns"); + + /* we don't own the directory from the original mount */ + if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) + die("failure: renameat"); + if (errno != EPERM) + die("failure: errno"); + + /* we own the file from the idmapped mount */ + if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) + die("failure: renameat"); + + if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) + die("failure: renameat"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) { + log_stderr("failure: wait_for_pid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(open_tree_fd); + safe_close(attr.userns_fd); + safe_close(dir_fd); + + return fret; +} + +static int symlink_idmapped_mounts(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (!caps_supported()) + return 0; + + file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { + log_stderr("failure: chown_r"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_fsids(10000, 10000)) + die("failure: switch fsids"); + + if (!caps_up()) + die("failure: raise caps"); + + if (symlinkat(FILE1, open_tree_fd, FILE2)) + die("failure: create"); + + if (fchownat(open_tree_fd, FILE2, 15000, 15000, AT_SYMLINK_NOFOLLOW)) + die("failure: change ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, 15000, 15000)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) + die("failure: check ownership"); + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +static int symlink_idmapped_mounts_in_userns(const struct vfstest_info *info) +{ + int fret = -1; + int file1_fd = -EBADF, open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + pid_t pid; + + if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { + log_stderr("failure: chown_r"); + goto out; + } + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file1_fd < 0) + die("failure: create"); + safe_close(file1_fd); + + if (symlinkat(FILE1, open_tree_fd, FILE2)) + die("failure: create"); + + if (fchownat(open_tree_fd, FILE2, 5000, 5000, AT_SYMLINK_NOFOLLOW)) + die("failure: change ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) + die("failure: check ownership"); + + if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) + die("failure: check ownership"); + + exit(EXIT_SUCCESS); + } + + if (wait_for_pid(pid)) + goto out; + + if (!expected_uid_gid(info->t_dir1_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + fret = 0; + log_debug("Ran test"); +out: + safe_close(attr.userns_fd); + safe_close(file1_fd); + safe_close(open_tree_fd); + + return fret; +} + +#define PTR_TO_INT(p) ((int)((intptr_t)(p))) +#define INT_TO_PTR(u) ((void *)((intptr_t)(u))) + +struct threaded_args { + const struct vfstest_info *info; + int open_tree_fd; +}; + +static void *idmapped_mount_create_cb(void *data) +{ + int fret = EXIT_FAILURE; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + struct threaded_args *args = data; + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + if (attr.userns_fd < 0) { + log_stderr("failure: get_userns_fd"); + goto out; + } + + if (sys_mount_setattr(args->open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + fret = EXIT_SUCCESS; + +out: + safe_close(attr.userns_fd); + pthread_exit(INT_TO_PTR(fret)); +} + +/* This tries to verify that we never see an inconistent ownership on-disk and + * can't write invalid ids to disk. To do this we create a race between + * idmapping a mount and creating files on it. + * Note, while it is perfectly fine to see overflowuid and overflowgid as owner + * if we create files through the open_tree_fd before the mount is idmapped but + * look at the files after the mount has been idmapped in this test it can never + * be the case that we see overflowuid and overflowgid when we access the file + * through a non-idmapped mount (in the initial user namespace). + */ +static void *idmapped_mount_operations_cb(void *data) +{ + int file1_fd = -EBADF, file2_fd = -EBADF, dir1_fd = -EBADF, + dir1_fd2 = -EBADF, fret = EXIT_FAILURE; + struct threaded_args *args = data; + const struct vfstest_info *info = args->info; + + if (!switch_fsids(10000, 10000)) { + log_stderr("failure: switch fsids"); + goto out; + } + + file1_fd = openat(args->open_tree_fd, FILE1, + O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + file2_fd = openat(args->open_tree_fd, FILE2, + O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (file2_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (mkdirat(args->open_tree_fd, DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + dir1_fd = openat(args->open_tree_fd, DIR1, + O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dir1_fd < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (!__expected_uid_gid(args->open_tree_fd, FILE1, 0, 0, 0, false) && + !__expected_uid_gid(args->open_tree_fd, FILE1, 0, 10000, 10000, false) && + !__expected_uid_gid(args->open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(args->open_tree_fd, FILE2, 0, 0, 0, false) && + !__expected_uid_gid(args->open_tree_fd, FILE2, 0, 10000, 10000, false) && + !__expected_uid_gid(args->open_tree_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(args->open_tree_fd, DIR1, 0, 0, 0, false) && + !__expected_uid_gid(args->open_tree_fd, DIR1, 0, 10000, 10000, false) && + !__expected_uid_gid(args->open_tree_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, 0, 0, false) && + !__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, 10000, 10000, false) && + !__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, info->t_overflowuid, info->t_overflowgid, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + dir1_fd2 = openat(info->t_dir1_fd, DIR1, + O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dir1_fd2 < 0) { + log_stderr("failure: openat"); + goto out; + } + + if (!__expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0, false) && + !__expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0, false) && + !__expected_uid_gid(info->t_dir1_fd, FILE2, 0, 10000, 10000, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0, false) && + !__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 10000, 10000, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0, false) && + !__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 10000, 10000, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + if (!__expected_uid_gid(dir1_fd2, "", AT_EMPTY_PATH, 0, 0, false) && + !__expected_uid_gid(dir1_fd2, "", AT_EMPTY_PATH, 10000, 10000, false)) { + log_stderr("failure: expected_uid_gid"); + goto out; + } + + fret = EXIT_SUCCESS; + +out: + safe_close(file1_fd); + safe_close(file2_fd); + safe_close(dir1_fd); + safe_close(dir1_fd2); + + pthread_exit(INT_TO_PTR(fret)); +} + +static int threaded_idmapped_mount_interactions(const struct vfstest_info *info) +{ + int i; + int fret = -1; + pid_t pid; + pthread_attr_t thread_attr; + pthread_t threads[2]; + + pthread_attr_init(&thread_attr); + + for (i = 0; i < 1000; i++) { + int ret1 = 0, ret2 = 0, tret1 = 0, tret2 = 0; + + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + int open_tree_fd = -EBADF; + struct threaded_args args = { + .info = info, + .open_tree_fd = -EBADF, + }; + + open_tree_fd = sys_open_tree(info->t_dir1_fd, "", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (open_tree_fd < 0) + die("failure: sys_open_tree"); + + args.open_tree_fd = open_tree_fd; + + if (pthread_create(&threads[0], &thread_attr, + idmapped_mount_create_cb, + &args)) + die("failure: pthread_create"); + + if (pthread_create(&threads[1], &thread_attr, + idmapped_mount_operations_cb, + &args)) + die("failure: pthread_create"); + + ret1 = pthread_join(threads[0], INT_TO_PTR(tret1)); + ret2 = pthread_join(threads[1], INT_TO_PTR(tret2)); + + if (ret1) { + errno = ret1; + die("failure: pthread_join"); + } + + if (ret2) { + errno = ret2; + die("failure: pthread_join"); + } + + if (tret1 || tret2) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + + } + + if (wait_for_pid(pid)) { + log_stderr("failure: iteration %d", i); + goto out; + } + + rm_r(info->t_dir1_fd, "."); + + } + + fret = 0; + log_debug("Ran test"); + +out: + return fret; +} + +static const struct test_struct t_idmapped_mounts[] = { + { acls, true, "posix acls on regular mounts", }, + { create_in_userns, true, "create operations in user namespace", }, + { device_node_in_userns, true, "device node in user namespace", }, + { expected_uid_gid_idmapped_mounts, true, "expected ownership on idmapped mounts", }, + { fscaps_idmapped_mounts, true, "fscaps on idmapped mounts", }, + { fscaps_idmapped_mounts_in_userns, true, "fscaps on idmapped mounts in user namespace", }, + { fscaps_idmapped_mounts_in_userns_separate_userns, true, "fscaps on idmapped mounts in user namespace with different id mappings", }, + { fsids_mapped, true, "mapped fsids", }, + { fsids_unmapped, true, "unmapped fsids", }, + { hardlink_crossing_idmapped_mounts, true, "cross idmapped mount hardlink", }, + { hardlink_from_idmapped_mount, true, "hardlinks from idmapped mounts", }, + { hardlink_from_idmapped_mount_in_userns, true, "hardlinks from idmapped mounts in user namespace", }, +#ifdef HAVE_LIBURING_H + { io_uring_idmapped, true, "io_uring from idmapped mounts", }, + { io_uring_idmapped_userns, true, "io_uring from idmapped mounts in user namespace", }, + { io_uring_idmapped_unmapped, true, "io_uring from idmapped mounts with unmapped ids", }, + { io_uring_idmapped_unmapped_userns, true, "io_uring from idmapped mounts with unmapped ids in user namespace", }, +#endif + { protected_symlinks_idmapped_mounts, true, "following protected symlinks on idmapped mounts", }, + { protected_symlinks_idmapped_mounts_in_userns, true, "following protected symlinks on idmapped mounts in user namespace", }, + { rename_crossing_idmapped_mounts, true, "cross idmapped mount rename", }, + { rename_from_idmapped_mount, true, "rename from idmapped mounts", }, + { rename_from_idmapped_mount_in_userns, true, "rename from idmapped mounts in user namespace", }, + { setattr_truncate_idmapped, true, "setattr truncate on idmapped mounts", }, + { setattr_truncate_idmapped_in_userns, true, "setattr truncate on idmapped mounts in user namespace", }, + { setgid_create_idmapped, true, "create operations in directories with setgid bit set on idmapped mounts", }, + { setgid_create_idmapped_in_userns, true, "create operations in directories with setgid bit set on idmapped mounts in user namespace", }, + { setid_binaries_idmapped_mounts, true, "setid binaries on idmapped mounts", }, + { setid_binaries_idmapped_mounts_in_userns, true, "setid binaries on idmapped mounts in user namespace", }, + { setid_binaries_idmapped_mounts_in_userns_separate_userns, true, "setid binaries on idmapped mounts in user namespace with different id mappings", }, + { sticky_bit_unlink_idmapped_mounts, true, "sticky bit unlink operations on idmapped mounts", }, + { sticky_bit_unlink_idmapped_mounts_in_userns, true, "sticky bit unlink operations on idmapped mounts in user namespace", }, + { sticky_bit_rename_idmapped_mounts, true, "sticky bit rename operations on idmapped mounts", }, + { sticky_bit_rename_idmapped_mounts_in_userns, true, "sticky bit rename operations on idmapped mounts in user namespace", }, + { symlink_idmapped_mounts, true, "symlink from idmapped mounts", }, + { symlink_idmapped_mounts_in_userns, true, "symlink from idmapped mounts in user namespace", }, + { threaded_idmapped_mount_interactions, true, "threaded operations on idmapped mounts", }, +}; + +const struct test_suite s_idmapped_mounts = { + .tests = t_idmapped_mounts, + .nr_tests = ARRAY_SIZE(t_idmapped_mounts), +}; + +static const struct test_struct t_fscaps_in_ancestor_userns[] = { + { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, true, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", }, +}; + +const struct test_suite s_fscaps_in_ancestor_userns = { + .tests = t_fscaps_in_ancestor_userns, + .nr_tests = ARRAY_SIZE(t_fscaps_in_ancestor_userns), +}; diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h new file mode 100644 index 00000000..37c8886d --- /dev/null +++ b/src/vfs/idmapped-mounts.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __IDMAPPED_MOUNTS_H +#define __IDMAPPED_MOUNTS_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "utils.h" + +extern const struct test_suite s_idmapped_mounts; +extern const struct test_suite s_fscaps_in_ancestor_userns; + +#endif /* __IDMAPPED_MOUNTS_H */ diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c index 6e14cff9..d90eaa7a 100644 --- a/src/vfs/vfstest.c +++ b/src/vfs/vfstest.c @@ -35,6 +35,7 @@ #include <linux/btrfs_tree.h> #endif +#include "idmapped-mounts.h" #include "missing.h" #include "utils.h" @@ -246,423 +247,6 @@ static void test_cleanup(struct vfstest_info *info) die("failure: rm_r"); } -/* Validate that basic file operations on idmapped mounts. */ -static int fsids_unmapped(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, hardlink_target_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - /* create hardlink target */ - hardlink_target_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (hardlink_target_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create directory for rename test */ - if (mkdirat(info->t_dir1_fd, DIR1, 0700)) { - log_stderr("failure: mkdirat"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - if (!switch_fsids(0, 0)) { - log_stderr("failure: switch_fsids"); - goto out; - } - - /* The caller's fsids don't have a mappings in the idmapped mount so any - * file creation must fail. - */ - - /* create hardlink */ - if (!linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) { - log_stderr("failure: linkat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* try to rename a file */ - if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) { - log_stderr("failure: renameat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* try to rename a directory */ - if (!renameat(open_tree_fd, DIR1, open_tree_fd, DIR1_RENAME)) { - log_stderr("failure: renameat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* The caller is privileged over the inode so file deletion must work. */ - - /* remove file */ - if (unlinkat(open_tree_fd, FILE1, 0)) { - log_stderr("failure: unlinkat"); - goto out; - } - - /* remove directory */ - if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) { - log_stderr("failure: unlinkat"); - goto out; - } - - /* The caller's fsids don't have a mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd >= 0) { - log_stderr("failure: create"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* create regular file via mknod */ - if (!mknodat(open_tree_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* create character device */ - if (!mknodat(open_tree_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) { - log_stderr("failure: mknodat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* create symlink */ - if (!symlinkat(FILE2, open_tree_fd, SYMLINK1)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - /* create directory */ - if (!mkdirat(open_tree_fd, DIR1, 0700)) { - log_stderr("failure: mkdirat"); - goto out; - } - if (errno != EOVERFLOW) { - log_stderr("failure: errno"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(hardlink_target_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int fsids_mapped(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, hardlink_target_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create hardlink target */ - hardlink_target_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (hardlink_target_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create directory for rename test */ - if (mkdirat(info->t_dir1_fd, DIR1, 0700)) { - log_stderr("failure: mkdirat"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_up()) - die("failure: raise caps"); - - /* The caller's fsids now have mappings in the idmapped mount so - * any file creation must fail. - */ - - /* create hardlink */ - if (linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) - die("failure: create hardlink"); - - /* try to rename a file */ - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: rename"); - - /* try to rename a directory */ - if (renameat(open_tree_fd, DIR1, open_tree_fd, DIR1_RENAME)) - die("failure: rename"); - - /* remove file */ - if (unlinkat(open_tree_fd, FILE1_RENAME, 0)) - die("failure: delete"); - - /* remove directory */ - if (unlinkat(open_tree_fd, DIR1_RENAME, AT_REMOVEDIR)) - die("failure: delete"); - - /* The caller's fsids have mappings in the idmapped mount so any - * file creation must fail. - */ - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) - die("failure: create"); - - /* create regular file via mknod */ - if (mknodat(open_tree_fd, FILE2, S_IFREG | 0000, 0)) - die("failure: create"); - - /* create character device */ - if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) - die("failure: create"); - - /* create symlink */ - if (symlinkat(FILE2, open_tree_fd, SYMLINK1)) - die("failure: create"); - - /* create directory */ - if (mkdirat(open_tree_fd, DIR1, 0700)) - die("failure: create"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(hardlink_target_fd); - safe_close(open_tree_fd); - - return fret; -} - -/* Validate that basic file operations on idmapped mounts from a user namespace. */ -static int create_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - /* change ownership of all files to uid 0 */ - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) - die("failure: open file"); - safe_close(file1_fd); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - /* create regular file via mknod */ - if (mknodat(open_tree_fd, FILE2, S_IFREG | 0000, 0)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) - die("failure: check ownership"); - - /* create symlink */ - if (symlinkat(FILE2, open_tree_fd, SYMLINK1)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 0, 0)) - die("failure: check ownership"); - - /* create directory */ - if (mkdirat(open_tree_fd, DIR1, 0700)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) - die("failure: check ownership"); - - /* try to rename a file */ - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, FILE1_RENAME, 0, 0, 0)) - die("failure: check ownership"); - - /* try to rename a file */ - if (renameat(open_tree_fd, DIR1, open_tree_fd, DIR1_RENAME)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, DIR1_RENAME, 0, 0, 0)) - die("failure: check ownership"); - - /* remove file */ - if (unlinkat(open_tree_fd, FILE1_RENAME, 0)) - die("failure: remove"); - - /* remove directory */ - if (unlinkat(open_tree_fd, DIR1_RENAME, AT_REMOVEDIR)) - die("failure: remove"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - static int hardlink_crossing_mounts(const struct vfstest_info *info) { int fret = -1; @@ -719,250 +303,6 @@ out: return fret; } -static int hardlink_crossing_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { - log_stderr("failure: chown_r"); - goto out; - } - - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd1 = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd1 < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd = openat(open_tree_fd1, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - safe_close(file1_fd); - - if (mkdirat(open_tree_fd1, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - open_tree_fd2 = sys_open_tree(info->t_dir1_fd, DIR1, - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE | - AT_RECURSIVE); - if (open_tree_fd2 < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* We're crossing a mountpoint so this must fail. - * - * Note that this must also fail for non-idmapped mounts but here we're - * interested in making sure we're not introducing an accidental way to - * violate that restriction or that suddenly this becomes possible. - */ - if (!linkat(open_tree_fd1, FILE1, open_tree_fd2, HARDLINK1, 0)) { - log_stderr("failure: linkat"); - goto out; - } - if (errno != EXDEV) { - log_stderr("failure: errno"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd1); - safe_close(open_tree_fd2); - - return fret; -} - -static int hardlink_from_idmapped_mount(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { - log_stderr("failure: chown_r"); - goto out; - } - - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - safe_close(file1_fd); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* We're not crossing a mountpoint so this must succeed. */ - if (linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) { - log_stderr("failure: linkat"); - goto out; - } - - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int hardlink_from_idmapped_mount_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - /* We're not crossing a mountpoint so this must succeed. */ - if (linkat(open_tree_fd, FILE1, open_tree_fd, HARDLINK1, 0)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, HARDLINK1, 0, 0, 0)) - die("failure: check ownership"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - static int rename_crossing_mounts(const struct vfstest_info *info) { int fret = -1; @@ -1019,246 +359,6 @@ out: return fret; } -static int rename_crossing_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { - log_stderr("failure: chown_r"); - goto out; - } - - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd1 = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd1 < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd = openat(open_tree_fd1, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (mkdirat(open_tree_fd1, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - open_tree_fd2 = sys_open_tree(info->t_dir1_fd, DIR1, - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE | - AT_RECURSIVE); - if (open_tree_fd2 < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* We're crossing a mountpoint so this must fail. - * - * Note that this must also fail for non-idmapped mounts but here we're - * interested in making sure we're not introducing an accidental way to - * violate that restriction or that suddenly this becomes possible. - */ - if (!renameat(open_tree_fd1, FILE1, open_tree_fd2, FILE1_RENAME)) { - log_stderr("failure: renameat"); - goto out; - } - if (errno != EXDEV) { - log_stderr("failure: errno"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd1); - safe_close(open_tree_fd2); - - return fret; -} - -static int rename_from_idmapped_mount(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - if (chown_r(info->t_mnt_fd, T_DIR1, 10000, 10000)) { - log_stderr("failure: chown_r"); - goto out; - } - - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* We're not crossing a mountpoint so this must succeed. */ - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) { - log_stderr("failure: renameat"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int rename_from_idmapped_mount_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - pid_t pid; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - /* We're not crossing a mountpoint so this must succeed. */ - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: create"); - - if (!expected_uid_gid(open_tree_fd, FILE1_RENAME, 0, 0, 0)) - die("failure: check ownership"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - static int symlink_regular_mounts(const struct vfstest_info *info) { int fret = -1; @@ -1326,962 +426,6 @@ out: return fret; } -static int symlink_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_fsids(10000, 10000)) - die("failure: switch fsids"); - - if (!caps_up()) - die("failure: raise caps"); - - if (symlinkat(FILE1, open_tree_fd, FILE2)) - die("failure: create"); - - if (fchownat(open_tree_fd, FILE2, 15000, 15000, AT_SYMLINK_NOFOLLOW)) - die("failure: change ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, 15000, 15000)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) - die("failure: check ownership"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int symlink_idmapped_mounts_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (chown_r(info->t_mnt_fd, T_DIR1, 0, 0)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) - die("failure: create"); - safe_close(file1_fd); - - if (symlinkat(FILE1, open_tree_fd, FILE2)) - die("failure: create"); - - if (fchownat(open_tree_fd, FILE2, 5000, 5000, AT_SYMLINK_NOFOLLOW)) - die("failure: change ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - if (!expected_uid_gid(info->t_dir1_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -/* Validate that a caller whose fsids map into the idmapped mount within it's - * user namespace cannot create any device nodes. - */ -static int device_node_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* create character device */ - if (!mknodat(open_tree_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) - die("failure: create"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(open_tree_fd); - - return fret; -} - - -/* Validate that changing file ownership works correctly on idmapped mounts. */ -static int expected_uid_gid_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd1 = -EBADF, open_tree_fd2 = -EBADF; - struct mount_attr attr1 = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - struct mount_attr attr2 = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!switch_fsids(0, 0)) { - log_stderr("failure: switch_fsids"); - goto out; - } - - /* create regular file via open() */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - - /* create character device */ - if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) { - log_stderr("failure: mknodat"); - goto out; - } - - /* create hardlink */ - if (linkat(info->t_dir1_fd, FILE1, info->t_dir1_fd, HARDLINK1, 0)) { - log_stderr("failure: linkat"); - goto out; - } - - /* create symlink */ - if (symlinkat(FILE2, info->t_dir1_fd, SYMLINK1)) { - log_stderr("failure: symlinkat"); - goto out; - } - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0700)) { - log_stderr("failure: mkdirat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr1.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr1.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd1 = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd1 < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd1, "", AT_EMPTY_PATH, &attr1, sizeof(attr1))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* Validate that all files created through the image mountpoint are - * owned by the callers fsuid and fsgid. - */ - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Validate that all files are owned by the uid and gid specified in - * the idmapping of the mount they are accessed from. - */ - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr2.userns_fd = get_userns_fd(0, 30000, 2001); - if (attr2.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd2 = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd2 < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd2, "", AT_EMPTY_PATH, &attr2, sizeof(attr2))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* Validate that all files are owned by the uid and gid specified in - * the idmapping of the mount they are accessed from. - */ - if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Change ownership throught original image mountpoint. */ - if (fchownat(info->t_dir1_fd, FILE1, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchownat(info->t_dir1_fd, FILE2, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchownat(info->t_dir1_fd, HARDLINK1, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchownat(info->t_dir1_fd, CHRDEV1, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchownat(info->t_dir1_fd, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchownat(info->t_dir1_fd, SYMLINK1, 2000, 2000, AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchownat(info->t_dir1_fd, DIR1, 2000, 2000, AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - /* Check ownership through original mount. */ - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 3000, 3000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Check ownership through first idmapped mount. */ - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 12000, 12000)) { - log_stderr("failure:expected_uid_gid "); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 13000, 13000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 12000, 12000)) { - log_stderr("failure:expected_uid_gid "); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Check ownership through second idmapped mount. */ - if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr1.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!fchownat(info->t_dir1_fd, FILE1, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, FILE2, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, HARDLINK1, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, CHRDEV1, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, SYMLINK1, 1000, 1000, AT_EMPTY_PATH)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, DIR1, 1000, 1000, AT_EMPTY_PATH)) - die("failure: fchownat"); - - if (!fchownat(open_tree_fd2, FILE1, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, FILE2, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, HARDLINK1, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, CHRDEV1, 1000, 1000, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, SYMLINK1, 1000, 1000, AT_EMPTY_PATH)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, DIR1, 1000, 1000, AT_EMPTY_PATH)) - die("failure: fchownat"); - - if (fchownat(open_tree_fd1, FILE1, 1000, 1000, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd1, FILE2, 1000, 1000, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd1, HARDLINK1, 1000, 1000, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd1, CHRDEV1, 1000, 1000, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd1, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) - die("failure: fchownat"); - if (fchownat(open_tree_fd1, SYMLINK1, 1000, 1000, AT_EMPTY_PATH)) - die("failure: fchownat"); - if (fchownat(open_tree_fd1, DIR1, 1000, 1000, AT_EMPTY_PATH)) - die("failure: fchownat"); - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd2, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 1000, 1000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 1000, 1000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 1000, 1000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 1000, 1000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 1000, 1000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 1000, 1000)) - die("failure: expected_uid_gid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - /* Check ownership through original mount. */ - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Check ownership through first idmapped mount. */ - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Check ownership through second idmapped mount. */ - if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 31000, 31000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 31000, 31000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 31000, 31000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 31000, 31000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 31000, 31000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 31000, 31000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr2.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!fchownat(info->t_dir1_fd, FILE1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, FILE2, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, HARDLINK1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, SYMLINK1, 0, 0, AT_EMPTY_PATH)) - die("failure: fchownat"); - if (!fchownat(info->t_dir1_fd, DIR1, 0, 0, AT_EMPTY_PATH)) - die("failure: fchownat"); - - if (!fchownat(open_tree_fd1, FILE1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd1, FILE2, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd1, HARDLINK1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd1, CHRDEV1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd1, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd1, SYMLINK1, 0, 0, AT_EMPTY_PATH)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd1, DIR1, 0, 0, AT_EMPTY_PATH)) - die("failure: fchownat"); - - if (fchownat(open_tree_fd2, FILE1, 0, 0, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd2, FILE2, 0, 0, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd2, HARDLINK1, 0, 0, 0)) - die("failure: fchownat"); - if (fchownat(open_tree_fd2, CHRDEV1, 0, 0, 0)) - die("failure: fchownat"); - if (!fchownat(open_tree_fd2, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) - die("failure: fchownat"); - if (fchownat(open_tree_fd2, SYMLINK1, 0, 0, AT_EMPTY_PATH)) - die("failure: fchownat"); - if (fchownat(open_tree_fd2, DIR1, 0, 0, AT_EMPTY_PATH)) - die("failure: fchownat"); - - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, FILE2, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd1, DIR1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 0, 0)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 0, 0)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 0, 0)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 0, 0)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 0, 0)) - die("failure: expected_uid_gid"); - if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - /* Check ownership through original mount. */ - if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, HARDLINK1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, SYMLINK1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Check ownership through first idmapped mount. */ - if (!expected_uid_gid(open_tree_fd1, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, FILE2, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd1, DIR1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Check ownership through second idmapped mount. */ - if (!expected_uid_gid(open_tree_fd2, FILE1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, FILE2, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, 32000, 32000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(open_tree_fd2, DIR1, 0, 30000, 30000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr1.userns_fd); - safe_close(attr2.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd1); - safe_close(open_tree_fd2); - - return fret; -} - static int fscaps(const struct vfstest_info *info) { int fret = -1; @@ -2398,516 +542,6 @@ out: return fret; } -static int fscaps_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* Skip if vfs caps are unsupported. */ - if (set_dummy_vfs_caps(file1_fd, 0, 1000)) - return 0; - - if (fremovexattr(file1_fd, "security.capability")) { - log_stderr("failure: fremovexattr"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); - if (file1_fd2 < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (!set_dummy_vfs_caps(file1_fd2, 0, 1000)) { - log_stderr("failure: set_dummy_vfs_caps"); - goto out; - } - - if (set_dummy_vfs_caps(file1_fd2, 0, 10000)) { - log_stderr("failure: set_dummy_vfs_caps"); - goto out; - } - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 10000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 0)) - die("failure: expected_dummy_vfs_caps_uid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - if (fremovexattr(file1_fd2, "security.capability")) { - log_stderr("failure: fremovexattr"); - goto out; - } - if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - if (errno != ENODATA) { - log_stderr("failure: errno"); - goto out; - } - - if (set_dummy_vfs_caps(file1_fd2, 0, 12000)) { - log_stderr("failure: set_dummy_vfs_caps"); - goto out; - } - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 12000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - if (!expected_dummy_vfs_caps_uid(file1_fd, 2000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 2000)) - die("failure: expected_dummy_vfs_caps_uid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(file1_fd2); - safe_close(open_tree_fd); - - return fret; -} - -static int fscaps_idmapped_mounts_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* Skip if vfs caps are unsupported. */ - if (set_dummy_vfs_caps(file1_fd, 0, 1000)) - return 0; - - if (fremovexattr(file1_fd, "security.capability")) { - log_stderr("failure: fremovexattr"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); - if (file1_fd2 < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) - die("failure: expected_dummy_vfs_caps_uid"); - if (errno != ENODATA) - die("failure: errno"); - - if (set_dummy_vfs_caps(file1_fd2, 0, 1000)) - die("failure: set_dummy_vfs_caps"); - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 1000)) - die("failure: expected_dummy_vfs_caps_uid"); - - if (!expected_dummy_vfs_caps_uid(file1_fd, 1000) && errno != EOVERFLOW) - die("failure: expected_dummy_vfs_caps_uid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - if (!expected_dummy_vfs_caps_uid(file1_fd, 1000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(file1_fd2); - safe_close(open_tree_fd); - - return fret; -} - -static int fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* Skip if vfs caps are unsupported. */ - if (set_dummy_vfs_caps(file1_fd, 0, 1000)) - return 0; - - if (fremovexattr(file1_fd, "security.capability")) { - log_stderr("failure: fremovexattr"); - goto out; - } - if (expected_dummy_vfs_caps_uid(file1_fd, -1)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - if (errno != ENODATA) { - log_stderr("failure: errno"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); - if (file1_fd2 < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* - * Verify we can set an v3 fscap for real root this was regressed at - * some point. Make sure this doesn't happen again! - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) - die("failure: expected_dummy_vfs_caps_uid"); - if (errno != ENODATA) - die("failure: errno"); - - if (set_dummy_vfs_caps(file1_fd2, 0, 0)) - die("failure: set_dummy_vfs_caps"); - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 0)) - die("failure: expected_dummy_vfs_caps_uid"); - - if (!expected_dummy_vfs_caps_uid(file1_fd, 0) && errno != EOVERFLOW) - die("failure: expected_dummy_vfs_caps_uid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 10000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(file1_fd2); - safe_close(open_tree_fd); - - return fret; -} - -static int fscaps_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* Skip if vfs caps are unsupported. */ - if (set_dummy_vfs_caps(file1_fd, 0, 1000)) { - log_stderr("failure: set_dummy_vfs_caps"); - goto out; - } - - if (fremovexattr(file1_fd, "security.capability")) { - log_stderr("failure: fremovexattr"); - goto out; - } - - /* change ownership of all files to uid 0 */ - if (chown_r(info->t_mnt_fd, T_DIR1, 20000, 20000)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(20000, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0); - if (file1_fd2 < 0) { - log_stderr("failure: openat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int userns_fd; - - userns_fd = get_userns_fd(0, 10000, 10000); - if (userns_fd < 0) - die("failure: get_userns_fd"); - - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (set_dummy_vfs_caps(file1_fd2, 0, 0)) - die("failure: set fscaps"); - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 0)) - die("failure: expected_dummy_vfs_caps_uid"); - - if (!expected_dummy_vfs_caps_uid(file1_fd, 20000) && errno != EOVERFLOW) - die("failure: expected_dummy_vfs_caps_uid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - if (!expected_dummy_vfs_caps_uid(file1_fd, 20000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int userns_fd; - - userns_fd = get_userns_fd(0, 10000, 10000); - if (userns_fd < 0) - die("failure: get_userns_fd"); - - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (fremovexattr(file1_fd2, "security.capability")) - die("failure: fremovexattr"); - if (expected_dummy_vfs_caps_uid(file1_fd2, -1)) - die("failure: expected_dummy_vfs_caps_uid"); - if (errno != ENODATA) - die("failure: errno"); - - if (set_dummy_vfs_caps(file1_fd2, 0, 1000)) - die("failure: set_dummy_vfs_caps"); - - if (!expected_dummy_vfs_caps_uid(file1_fd2, 1000)) - die("failure: expected_dummy_vfs_caps_uid"); - - if (!expected_dummy_vfs_caps_uid(file1_fd, 21000) && errno != EOVERFLOW) - die("failure: expected_dummy_vfs_caps_uid"); - - exit(EXIT_SUCCESS); - } - - if (wait_for_pid(pid)) - goto out; - - if (!expected_dummy_vfs_caps_uid(file1_fd, 21000)) { - log_stderr("failure: expected_dummy_vfs_caps_uid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(file1_fd2); - safe_close(open_tree_fd); - - return fret; -} - /* Validate that when the IDMAP_MOUNT_TEST_RUN_SETID environment variable is set * to 1 that we are executed with setid privileges and if set to 0 we are not. * If the env variable isn't set the tests are not run. @@ -3034,717 +668,6 @@ out: return fret; } -/* Validate that setid transitions are handled correctly on idmapped mounts. */ -static int setid_binaries_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - /* create a file to be used as setuid binary */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* open our own executable */ - exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); - if (exec_fd < 0) { - log_stderr("failure:openat "); - goto out; - } - - /* copy our own executable into the file we created */ - if (fd_to_fd(exec_fd, file1_fd)) { - log_stderr("failure: fd_to_fd"); - goto out; - } - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 5000, 5000)) { - log_stderr("failure: fchown"); - goto out; - } - - /* set the setid bits and grant execute permissions to the group */ - if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(exec_fd); - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* A detached mount will have an anonymous mount namespace attached to - * it. This means that we can't execute setid binaries on a detached - * mount because the mnt_may_suid() helper will fail the check_mount() - * part of its check which compares the caller's mount namespace to the - * detached mount's mount namespace. Since by definition an anonymous - * mount namespace is not equale to any mount namespace currently in - * use this can't work. So attach the mount to the filesystem first - * before performing this check. - */ - if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { - log_stderr("failure: sys_move_mount"); - goto out; - } - - /* Verify we run setid binary as uid and gid 10000 from idmapped mount mount. */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - static char *envp[] = { - "IDMAP_MOUNT_TEST_RUN_SETID=1", - "EXPECTED_EUID=15000", - "EXPECTED_EGID=15000", - NULL, - }; - static char *argv[] = { - NULL, - }; - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 15000, 15000)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(exec_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); - sys_umount2(t_buf, MNT_DETACH); - rm_r(info->t_mnt_fd, DIR1); - - return fret; -} - -/* Validate that setid transitions are handled correctly on idmapped mounts - * running in a user namespace where the uid and gid of the setid binary have no - * mapping. - */ -static int setid_binaries_idmapped_mounts_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { - log_stderr("failure: "); - goto out; - } - - /* create a file to be used as setuid binary */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* open our own executable */ - exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); - if (exec_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* copy our own executable into the file we created */ - if (fd_to_fd(exec_fd, file1_fd)) { - log_stderr("failure: fd_to_fd"); - goto out; - } - - safe_close(exec_fd); - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 5000, 5000)) { - log_stderr("failure: fchown"); - goto out; - } - - /* set the setid bits and grant execute permissions to the group */ - if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* A detached mount will have an anonymous mount namespace attached to - * it. This means that we can't execute setid binaries on a detached - * mount because the mnt_may_suid() helper will fail the check_mount() - * part of its check which compares the caller's mount namespace to the - * detached mount's mount namespace. Since by definition an anonymous - * mount namespace is not equale to any mount namespace currently in - * use this can't work. So attach the mount to the filesystem first - * before performing this check. - */ - if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { - log_stderr("failure: sys_move_mount"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - static char *envp[] = { - "IDMAP_MOUNT_TEST_RUN_SETID=1", - "EXPECTED_EUID=5000", - "EXPECTED_EGID=5000", - NULL, - }; - static char *argv[] = { - NULL, - }; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 5000, 5000)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - - /* set the setid bits and grant execute permissions to the group */ - if (fchmod(file1_fd, S_IXOTH | S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(file1_fd); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - static char *envp[] = { - "IDMAP_MOUNT_TEST_RUN_SETID=1", - "EXPECTED_EUID=0", - "EXPECTED_EGID=0", - NULL, - }; - static char *argv[] = { - NULL, - }; - - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 5000, 5000, true)) - die("failure: switch_userns"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 30000, 30000)) { - log_stderr("failure: fchown"); - goto out; - } - - if (fchmod(file1_fd, S_IXOTH | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(file1_fd); - - /* Verify that we can't assume a uid and gid of a setid binary for which - * we have no mapping in our user namespace. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - char expected_euid[100]; - char expected_egid[100]; - static char *envp[4] = { - NULL, - NULL, - NULL, - NULL, - }; - static char *argv[] = { - NULL, - }; - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - envp[0] = "IDMAP_MOUNT_TEST_RUN_SETID=0"; - snprintf(expected_euid, sizeof(expected_euid), "EXPECTED_EUID=%d", geteuid()); - envp[1] = expected_euid; - snprintf(expected_egid, sizeof(expected_egid), "EXPECTED_EGID=%d", getegid()); - envp[2] = expected_egid; - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(exec_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); - sys_umount2(t_buf, MNT_DETACH); - rm_r(info->t_mnt_fd, DIR1); - - return fret; -} - -/* Validate that setid transitions are handled correctly on idmapped mounts - * running in a user namespace where the uid and gid of the setid binary have no - * mapping. - */ -static int setid_binaries_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, exec_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (mkdirat(info->t_mnt_fd, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - /* create a file to be used as setuid binary */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* open our own executable */ - exec_fd = openat(-EBADF, "/proc/self/exe", O_RDONLY | O_CLOEXEC, 0000); - if (exec_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* copy our own executable into the file we created */ - if (fd_to_fd(exec_fd, file1_fd)) { - log_stderr("failure: fd_to_fd"); - goto out; - } - - safe_close(exec_fd); - - /* change ownership of all files to uid 0 */ - if (chown_r(info->t_mnt_fd, T_DIR1, 20000, 20000)) { - log_stderr("failure: chown_r"); - goto out; - } - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 25000, 25000)) { - log_stderr("failure: fchown"); - goto out; - } - - /* set the setid bits and grant execute permissions to the group */ - if (fchmod(file1_fd, S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(20000, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* A detached mount will have an anonymous mount namespace attached to - * it. This means that we can't execute setid binaries on a detached - * mount because the mnt_may_suid() helper will fail the check_mount() - * part of its check which compares the caller's mount namespace to the - * detached mount's mount namespace. Since by definition an anonymous - * mount namespace is not equale to any mount namespace currently in - * use this can't work. So attach the mount to the filesystem first - * before performing this check. - */ - if (sys_move_mount(open_tree_fd, "", info->t_mnt_fd, DIR1, MOVE_MOUNT_F_EMPTY_PATH)) { - log_stderr("failure: sys_move_mount"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int userns_fd; - static char *envp[] = { - "IDMAP_MOUNT_TEST_RUN_SETID=1", - "EXPECTED_EUID=5000", - "EXPECTED_EGID=5000", - NULL, - }; - static char *argv[] = { - NULL, - }; - - userns_fd = get_userns_fd(0, 10000, 10000); - if (userns_fd < 0) - die("failure: get_userns_fd"); - - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 5000, 5000)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 20000, 20000)) { - log_stderr("failure: fchown"); - goto out; - } - - /* set the setid bits and grant execute permissions to the group */ - if (fchmod(file1_fd, S_IXOTH | S_IXGRP | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(file1_fd); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int userns_fd; - static char *envp[] = { - "IDMAP_MOUNT_TEST_RUN_SETID=1", - "EXPECTED_EUID=0", - "EXPECTED_EGID=0", - NULL, - }; - static char *argv[] = { - NULL, - }; - - userns_fd = get_userns_fd(0, 10000, 10000); - if (userns_fd < 0) - die("failure: get_userns_fd"); - - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDWR | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - /* chown the file to the uid and gid we want to assume */ - if (fchown(file1_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - - if (fchmod(file1_fd, S_IXOTH | S_IEXEC | S_ISUID | S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setid(info->t_dir1_fd, FILE1, 0)) { - log_stderr("failure: is_setid"); - goto out; - } - - safe_close(file1_fd); - - /* Verify that we can't assume a uid and gid of a setid binary for - * which we have no mapping in our user namespace. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int userns_fd; - char expected_euid[100]; - char expected_egid[100]; - static char *envp[4] = { - NULL, - NULL, - NULL, - NULL, - }; - static char *argv[] = { - NULL, - }; - - userns_fd = get_userns_fd(0, 10000, 10000); - if (userns_fd < 0) - die("failure: get_userns_fd"); - - if (!switch_userns(userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - envp[0] = "IDMAP_MOUNT_TEST_RUN_SETID=0"; - snprintf(expected_euid, sizeof(expected_euid), "EXPECTED_EUID=%d", geteuid()); - envp[1] = expected_euid; - snprintf(expected_egid, sizeof(expected_egid), "EXPECTED_EGID=%d", getegid()); - envp[2] = expected_egid; - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid)) - die("failure: expected_uid_gid"); - - sys_execveat(open_tree_fd, FILE1, argv, envp, 0); - die("failure: sys_execveat"); - - exit(EXIT_FAILURE); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(exec_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - snprintf(t_buf, sizeof(t_buf), "%s/" DIR1, info->t_mountpoint); - sys_umount2(t_buf, MNT_DETACH); - rm_r(info->t_mnt_fd, DIR1); - - return fret; -} - static int sticky_bit_unlink(const struct vfstest_info *info) { int fret = -1; @@ -4009,640 +932,6 @@ out: return fret; } -static int sticky_bit_unlink_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int dir_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(dir_fd, 10000, 10000)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(dir_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* The sticky bit is not set so we must be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* set sticky bit */ - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* The sticky bit is set so we must not be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (!unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (!unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* The sticky bit is set and we own the files so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - /* change ownership */ - if (fchownat(dir_fd, FILE1, 11000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE1, 0, 11000, 10000)) - die("failure: expected_uid_gid"); - if (fchownat(dir_fd, FILE2, 11000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE2, 0, 11000, 12000)) - die("failure: expected_uid_gid"); - - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* change uid to unprivileged user */ - if (fchown(dir_fd, 11000, -1)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* The sticky bit is set and we own the directory so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(dir_fd); - safe_close(open_tree_fd); - - return fret; -} - -/* Validate that the sticky bit behaves correctly on idmapped mounts for unlink - * operations in a user namespace. - */ -static int sticky_bit_unlink_idmapped_mounts_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int dir_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(dir_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(dir_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* The sticky bit is not set so we must be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (unlinkat(dir_fd, FILE1, 0)) - die("failure: unlinkat"); - - if (unlinkat(dir_fd, FILE2, 0)) - die("failure: unlinkat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* set sticky bit */ - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* The sticky bit is set so we must not be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (!unlinkat(dir_fd, FILE1, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (!unlinkat(dir_fd, FILE2, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (!unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (!unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* The sticky bit is set and we own the files so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - /* change ownership */ - if (fchownat(dir_fd, FILE1, 1000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) - die("failure: expected_uid_gid"); - if (fchownat(dir_fd, FILE2, 1000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) - die("failure: expected_uid_gid"); - - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (!unlinkat(dir_fd, FILE1, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (!unlinkat(dir_fd, FILE2, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* change uid to unprivileged user */ - if (fchown(dir_fd, 1000, -1)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* The sticky bit is set and we own the directory so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - /* we don't own the directory from the original mount */ - if (!unlinkat(dir_fd, FILE1, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - if (!unlinkat(dir_fd, FILE2, 0)) - die("failure: unlinkat"); - if (errno != EPERM) - die("failure: errno"); - - /* we own the file from the idmapped mount */ - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: unlinkat"); - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: unlinkat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(dir_fd); - safe_close(open_tree_fd); - - return fret; -} - static int sticky_bit_rename(const struct vfstest_info *info) { int fret = -1; @@ -4868,567 +1157,6 @@ out: return fret; } -static int sticky_bit_rename_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int dir_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (fchown(dir_fd, 10000, 10000)) { - log_stderr("failure: fchown"); - goto out; - } - - if (fchmod(dir_fd, 0777)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 12000, 12000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(dir_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* The sticky bit is not set so we must be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) - die("failure: renameat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* set sticky bit */ - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* The sticky bit is set so we must not be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (!renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* The sticky bit is set and we own the files so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - /* change ownership */ - if (fchownat(dir_fd, FILE1, 11000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE1, 0, 11000, 10000)) - die("failure: expected_uid_gid"); - if (fchownat(dir_fd, FILE2, 11000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE2, 0, 11000, 12000)) - die("failure: expected_uid_gid"); - - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) - die("failure: renameat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* change uid to unprivileged user */ - if (fchown(dir_fd, 11000, -1)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* The sticky bit is set and we own the directory so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) - die("failure: renameat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(dir_fd); - safe_close(open_tree_fd); - - return fret; -} - -/* Validate that the sticky bit behaves correctly on idmapped mounts for unlink - * operations in a user namespace. - */ -static int sticky_bit_rename_idmapped_mounts_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int dir_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!caps_supported()) - return 0; - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(dir_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE2, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE2, 2000, 2000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE2, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(dir_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* The sticky bit is not set so we must be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) - die("failure: renameat"); - - if (renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) - die("failure: renameat"); - - if (renameat(dir_fd, FILE1_RENAME, dir_fd, FILE1)) - die("failure: renameat"); - - if (renameat(dir_fd, FILE2_RENAME, dir_fd, FILE2)) - die("failure: renameat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* set sticky bit */ - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* The sticky bit is set so we must not be able to delete files not - * owned by us. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (!renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (!renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* The sticky bit is set and we own the files so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - /* change ownership */ - if (fchownat(dir_fd, FILE1, 1000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE1, 0, 1000, 0)) - die("failure: expected_uid_gid"); - if (fchownat(dir_fd, FILE2, 1000, -1, 0)) - die("failure: fchownat"); - if (!expected_uid_gid(dir_fd, FILE2, 0, 1000, 2000)) - die("failure: expected_uid_gid"); - - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) - die("failure: renameat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* change uid to unprivileged user */ - if (fchown(dir_fd, 1000, -1)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* The sticky bit is set and we own the directory so we must be able to - * delete the files now. - */ - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - /* we don't own the directory from the original mount */ - if (!renameat(dir_fd, FILE1, dir_fd, FILE1_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - if (!renameat(dir_fd, FILE2, dir_fd, FILE2_RENAME)) - die("failure: renameat"); - if (errno != EPERM) - die("failure: errno"); - - /* we own the file from the idmapped mount */ - if (renameat(open_tree_fd, FILE1, open_tree_fd, FILE1_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2, open_tree_fd, FILE2_RENAME)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE1_RENAME, open_tree_fd, FILE1)) - die("failure: renameat"); - - if (renameat(open_tree_fd, FILE2_RENAME, open_tree_fd, FILE2)) - die("failure: renameat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(open_tree_fd); - safe_close(attr.userns_fd); - safe_close(dir_fd); - - return fret; -} - /* Validate that protected symlinks work correctly. */ static int protected_symlinks(const struct vfstest_info *info) { @@ -5640,720 +1368,6 @@ out: return fret; } -/* Validate that protected symlinks work correctly on idmapped mounts. */ -static int protected_symlinks_idmapped_mounts(const struct vfstest_info *info) -{ - int fret = -1; - int dir_fd = -EBADF, fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!protected_symlinks_enabled()) - return 0; - - if (!caps_supported()) - return 0; - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(dir_fd, 10000, 10000)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 10000, 10000, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create symlinks */ - if (symlinkat(FILE1, dir_fd, SYMLINK_USER1)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (fchownat(dir_fd, SYMLINK_USER1, 10000, 10000, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (!expected_uid_gid(dir_fd, SYMLINK_USER1, AT_SYMLINK_NOFOLLOW, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(dir_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (symlinkat(FILE1, dir_fd, SYMLINK_USER2)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (fchownat(dir_fd, SYMLINK_USER2, 11000, 11000, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (!expected_uid_gid(dir_fd, SYMLINK_USER2, AT_SYMLINK_NOFOLLOW, 11000, 11000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(dir_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (symlinkat(FILE1, dir_fd, SYMLINK_USER3)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (fchownat(dir_fd, SYMLINK_USER3, 12000, 12000, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (!expected_uid_gid(dir_fd, SYMLINK_USER3, AT_SYMLINK_NOFOLLOW, 12000, 12000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(dir_fd, FILE1, 0, 10000, 10000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(10000, 0, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: open_tree_fd"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* validate file can be directly read */ - fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) { - log_stderr("failure: openat"); - goto out; - } - safe_close(fd); - - /* validate file can be read through own symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) { - log_stderr("failure: openat"); - goto out; - } - safe_close(fd); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(1000, 1000)) - die("failure: switch_ids"); - - /* validate file can be directly read */ - fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through own symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through root symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can't be read through other users symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); - if (fd >= 0) - die("failure: openat"); - if (errno != EACCES) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(2000, 2000)) - die("failure: switch_ids"); - - /* validate file can be directly read */ - fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through own symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through root symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can't be read through other users symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); - if (fd >= 0) - die("failure: openat"); - if (errno != EACCES) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(fd); - safe_close(dir_fd); - safe_close(open_tree_fd); - - return fret; -} - -/* Validate that protected symlinks work correctly on idmapped mounts inside a - * user namespace. - */ -static int protected_symlinks_idmapped_mounts_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int dir_fd = -EBADF, fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (!protected_symlinks_enabled()) - return 0; - - if (!caps_supported()) - return 0; - - /* create directory */ - if (mkdirat(info->t_dir1_fd, DIR1, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(dir_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir_fd, 0777 | S_ISVTX)) { - log_stderr("failure: fchmod"); - goto out; - } - /* validate sticky bit is set */ - if (!is_sticky(info->t_dir1_fd, DIR1, 0)) { - log_stderr("failure: is_sticky"); - goto out; - } - - /* create regular file via mknod */ - if (mknodat(dir_fd, FILE1, S_IFREG | 0000, 0)) { - log_stderr("failure: mknodat"); - goto out; - } - if (fchownat(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: fchownat"); - goto out; - } - if (fchmodat(dir_fd, FILE1, 0644, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* create symlinks */ - if (symlinkat(FILE1, dir_fd, SYMLINK_USER1)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (fchownat(dir_fd, SYMLINK_USER1, 0, 0, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (!expected_uid_gid(dir_fd, SYMLINK_USER1, AT_SYMLINK_NOFOLLOW, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (symlinkat(FILE1, dir_fd, SYMLINK_USER2)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (fchownat(dir_fd, SYMLINK_USER2, 1000, 1000, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (!expected_uid_gid(dir_fd, SYMLINK_USER2, AT_SYMLINK_NOFOLLOW, 1000, 1000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (symlinkat(FILE1, dir_fd, SYMLINK_USER3)) { - log_stderr("failure: symlinkat"); - goto out; - } - if (fchownat(dir_fd, SYMLINK_USER3, 2000, 2000, AT_SYMLINK_NOFOLLOW)) { - log_stderr("failure: fchownat"); - goto out; - } - if (!expected_uid_gid(dir_fd, SYMLINK_USER3, AT_SYMLINK_NOFOLLOW, 2000, 2000)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - if (!expected_uid_gid(dir_fd, FILE1, 0, 0, 0)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - /* validate file can be directly read */ - fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) { - log_stderr("failure: openat"); - goto out; - } - safe_close(fd); - - /* validate file can be read through own symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) { - log_stderr("failure: openat"); - goto out; - } - safe_close(fd); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - /* validate file can be directly read */ - fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through own symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through root symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can't be read through other users symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); - if (fd >= 0) - die("failure: openat"); - if (errno != EACCES) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 2000, 2000, true)) - die("failure: switch_userns"); - - /* validate file can be directly read */ - fd = openat(open_tree_fd, DIR1 "/" FILE1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through own symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER3, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can be read through root symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER1, O_RDONLY | O_CLOEXEC, 0); - if (fd < 0) - die("failure: openat"); - safe_close(fd); - - /* validate file can't be read through other users symlink */ - fd = openat(open_tree_fd, DIR1 "/" SYMLINK_USER2, O_RDONLY | O_CLOEXEC, 0); - if (fd >= 0) - die("failure: openat"); - if (errno != EACCES) - die("failure: errno"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - safe_close(dir_fd); - safe_close(open_tree_fd); - safe_close(attr.userns_fd); - - return fret; -} - -static int acls(const struct vfstest_info *info) -{ - int fret = -1; - int dir1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - if (mkdirat(info->t_dir1_fd, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - if (fchmodat(info->t_dir1_fd, DIR1, 0777, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - if (mkdirat(info->t_dir1_fd, DIR2, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - if (fchmodat(info->t_dir1_fd, DIR2, 0777, 0)) { - log_stderr("failure: fchmodat"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(100010, 100020, 5); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1, - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - if (sys_move_mount(open_tree_fd, "", info->t_dir1_fd, DIR2, MOVE_MOUNT_F_EMPTY_PATH)) { - log_stderr("failure: sys_move_mount"); - goto out; - } - - dir1_fd = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); - if (dir1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (mkdirat(dir1_fd, DIR3, 0000)) { - log_stderr("failure: mkdirat"); - goto out; - } - if (fchown(dir1_fd, 100010, 100010)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(dir1_fd, 0777)) { - log_stderr("failure: fchmod"); - goto out; - } - - snprintf(t_buf, sizeof(t_buf), "setfacl -m u:100010:rwx %s/%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1, DIR3); - if (system(t_buf)) { - log_stderr("failure: system"); - goto out; - } - - snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:100010:rwx", info->t_mountpoint, T_DIR1, DIR1, DIR3); - if (system(t_buf)) { - log_stderr("failure: system"); - goto out; - } - - snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:100020:rwx", info->t_mountpoint, T_DIR1, DIR2, DIR3); - if (system(t_buf)) { - log_stderr("failure: system"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 100010, 100010, true)) - die("failure: switch_userns"); - - snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:%lu:rwx", - info->t_mountpoint, T_DIR1, DIR1, DIR3, 4294967295LU); - if (system(t_buf)) - die("failure: system"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 100010, 100010, true)) - die("failure: switch_userns"); - - snprintf(t_buf, sizeof(t_buf), "getfacl -p %s/%s/%s/%s | grep -q user:%lu:rwx", - info->t_mountpoint, T_DIR1, DIR2, DIR3, 100010LU); - if (system(t_buf)) - die("failure: system"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* Now, dir is owned by someone else in the user namespace, but we can - * still read it because of acls. - */ - if (fchown(dir1_fd, 100012, 100012)) { - log_stderr("failure: fchown"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int fd; - - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 100010, 100010, true)) - die("failure: switch_userns"); - - fd = openat(open_tree_fd, DIR3, O_CLOEXEC | O_DIRECTORY); - if (fd < 0) - die("failure: openat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - /* if we delete the acls, the ls should fail because it's 700. */ - snprintf(t_buf, sizeof(t_buf), "%s/%s/%s/%s", info->t_mountpoint, T_DIR1, DIR1, DIR3); - if (removexattr(t_buf, "system.posix_acl_access")) { - log_stderr("failure: removexattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int fd; - - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 100010, 100010, true)) - die("failure: switch_userns"); - - fd = openat(open_tree_fd, DIR3, O_CLOEXEC | O_DIRECTORY); - if (fd >= 0) - die("failure: openat"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - snprintf(t_buf, sizeof(t_buf), "%s/" T_DIR1 "/" DIR2, info->t_mountpoint); - sys_umount2(t_buf, MNT_DETACH); - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(dir1_fd); - safe_close(open_tree_fd); - - return fret; -} - #ifdef HAVE_LIBURING_H static int io_uring(const struct vfstest_info *info) { @@ -6627,543 +1641,6 @@ out_unmap: return fret; } - -static int io_uring_idmapped(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct io_uring *ring; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - int cred_id, ret; - pid_t pid; - - ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, 0, 0); - if (!ring) - return log_errno(-1, "failure: io_uring_queue_init"); - - ret = io_uring_queue_init(8, ring, 0); - if (ret) { - log_stderr("failure: io_uring_queue_init"); - goto out_unmap; - } - - ret = io_uring_register_personality(ring); - if (ret < 0) { - fret = 0; - goto out_unmap; /* personalities not supported */ - } - cred_id = ret; - - /* create file only owner can open */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(file1_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(file1_fd, 0600)) { - log_stderr("failure: fchmod"); - goto out; - } - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) - return log_errno(-1, "failure: create user namespace"); - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) - return log_errno(-1, "failure: create detached mount"); - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) - return log_errno(-1, "failure: set mount attributes"); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(10000, 10000)) - die("failure: switch_ids"); - - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - -1, false, NULL); - if (file1_fd < 0) - die("failure: io_uring_open_file"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(10001, 10001)) - die("failure: switch_ids"); - - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, false, NULL); - if (file1_fd < 0) - die("failure: io_uring_open_file"); - - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, true, NULL); - if (file1_fd < 0) - die("failure: io_uring_open_file"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - ret = io_uring_unregister_personality(ring, cred_id); - if (ret) - log_stderr("failure: io_uring_unregister_personality"); - -out_unmap: - munmap(ring, sizeof(struct io_uring)); - - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -/* - * Create an idmapped mount where the we leave the owner of the file unmapped. - * In no circumstances, even with recorded credentials can it be allowed to - * open the file. - */ -static int io_uring_idmapped_unmapped(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct io_uring *ring; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - int cred_id, ret, ret_cqe; - pid_t pid; - - ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, 0, 0); - if (!ring) - return log_errno(-1, "failure: io_uring_queue_init"); - - ret = io_uring_queue_init(8, ring, 0); - if (ret) { - log_stderr("failure: io_uring_queue_init"); - goto out_unmap; - } - - ret = io_uring_register_personality(ring); - if (ret < 0) { - fret = 0; - goto out_unmap; /* personalities not supported */ - } - cred_id = ret; - - /* create file only owner can open */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(file1_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(file1_fd, 0600)) { - log_stderr("failure: fchmod"); - goto out; - } - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(1, 10000, 10000); - if (attr.userns_fd < 0) - return log_errno(-1, "failure: create user namespace"); - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) - return log_errno(-1, "failure: create detached mount"); - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) - return log_errno(-1, "failure: set mount attributes"); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(10000, 10000)) - die("failure: switch_ids"); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, false, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, true, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - ret = io_uring_unregister_personality(ring, cred_id); - if (ret) - log_stderr("failure: io_uring_unregister_personality"); - -out_unmap: - munmap(ring, sizeof(struct io_uring)); - - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int io_uring_idmapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct io_uring *ring; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - int cred_id, ret, ret_cqe; - pid_t pid; - - ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, 0, 0); - if (!ring) - return log_errno(-1, "failure: io_uring_queue_init"); - - ret = io_uring_queue_init(8, ring, 0); - if (ret) { - log_stderr("failure: io_uring_queue_init"); - goto out_unmap; - } - - ret = io_uring_register_personality(ring); - if (ret < 0) { - fret = 0; - goto out_unmap; /* personalities not supported */ - } - cred_id = ret; - - /* create file only owner can open */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(file1_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(file1_fd, 0600)) { - log_stderr("failure: fchmod"); - goto out; - } - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) - return log_errno(-1, "failure: create user namespace"); - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) - return log_errno(-1, "failure: create detached mount"); - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) - return log_errno(-1, "failure: set mount attributes"); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - -1, false, NULL); - if (file1_fd < 0) - die("failure: io_uring_open_file"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 1000, 1000, true)) - die("failure: switch_userns"); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, - -1, false, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, info->t_dir1_fd, FILE1, - -1, true, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - -1, false, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - -1, true, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, false, NULL); - if (file1_fd < 0) - die("failure: io_uring_open_file"); - - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, true, NULL); - if (file1_fd < 0) - die("failure: io_uring_open_file"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - ret = io_uring_unregister_personality(ring, cred_id); - if (ret) - log_stderr("failure: io_uring_unregister_personality"); - -out_unmap: - munmap(ring, sizeof(struct io_uring)); - - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int io_uring_idmapped_unmapped_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct io_uring *ring; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - int cred_id, ret, ret_cqe; - pid_t pid; - - ring = mmap(0, sizeof(struct io_uring), PROT_READ|PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, 0, 0); - if (!ring) - return log_errno(-1, "failure: io_uring_queue_init"); - - ret = io_uring_queue_init(8, ring, 0); - if (ret) { - log_stderr("failure: io_uring_queue_init"); - goto out_unmap; - } - - ret = io_uring_register_personality(ring); - if (ret < 0) { - fret = 0; - goto out_unmap; /* personalities not supported */ - } - cred_id = ret; - - /* create file only owner can open */ - file1_fd = openat(info->t_dir1_fd, FILE1, O_RDONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - if (fchown(file1_fd, 0, 0)) { - log_stderr("failure: fchown"); - goto out; - } - if (fchmod(file1_fd, 0600)) { - log_stderr("failure: fchmod"); - goto out; - } - safe_close(file1_fd); - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(1, 10000, 10000); - if (attr.userns_fd < 0) - return log_errno(-1, "failure: create user namespace"); - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) - return log_errno(-1, "failure: create detached mount"); - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) - return log_errno(-1, "failure: set mount attributes"); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 10000, 10000, true)) - die("failure: switch_ids"); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, false, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - ret_cqe = 0; - file1_fd = io_uring_openat_with_creds(ring, open_tree_fd, FILE1, - cred_id, true, &ret_cqe); - if (file1_fd >= 0) - die("failure: io_uring_open_file"); - if (ret_cqe == 0) - die("failure: non-open() related io_uring_open_file failure"); - if (ret_cqe != -EACCES) - die("failure: errno(%d)", abs(ret_cqe)); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) { - log_stderr("failure: wait_for_pid"); - goto out; - } - - fret = 0; - log_debug("Ran test"); -out: - ret = io_uring_unregister_personality(ring, cred_id); - if (ret) - log_stderr("failure: io_uring_unregister_personality"); - -out_unmap: - munmap(ring, sizeof(struct io_uring)); - - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} #endif /* HAVE_LIBURING_H */ /* The following tests are concerned with setgid inheritance. These can be @@ -7405,824 +1882,6 @@ out: return fret; } -static int setgid_create_idmapped(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - int tmpfile_fd = -EBADF; - bool supported = false; - char path[PATH_MAX]; - - if (!caps_supported()) - return 0; - - if (fchmod(info->t_dir1_fd, S_IRUSR | - S_IWUSR | - S_IRGRP | - S_IWGRP | - S_IROTH | - S_IWOTH | - S_IXUSR | - S_IXGRP | - S_IXOTH | - S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { - log_stderr("failure: is_setgid"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - supported = openat_tmpfile_supported(open_tree_fd); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(10000, 11000)) - die("failure: switch fsids"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid - * bit needs to be stripped. - */ - if (is_setgid(open_tree_fd, FILE1, 0)) - die("failure: is_setgid"); - - /* create directory */ - if (mkdirat(open_tree_fd, DIR1, 0000)) - die("failure: create"); - - if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { - /* We're not in_group_p(). */ - if (is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - } else { - /* Directories always inherit the setgid bit. */ - if (!is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - } - - /* create a special file via mknodat() vfs_create */ - if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (is_setgid(open_tree_fd, FILE2, 0)) - die("failure: is_setgid"); - - /* create a whiteout device via mknodat() vfs_mknod */ - if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (is_setgid(open_tree_fd, CHRDEV1, 0)) - die("failure: is_setgid"); - - /* - * In setgid directories newly created files always inherit the - * gid from the parent directory. Verify that the file is owned - * by gid 10000, not by gid 11000. - */ - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) - die("failure: check ownership"); - - /* - * In setgid directories newly created directories always - * inherit the gid from the parent directory. Verify that the - * directory is owned by gid 10000, not by gid 11000. - */ - if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, CHRDEV1, 0)) - die("failure: delete"); - - /* create tmpfile via filesystem tmpfile api */ - if (supported) { - tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); - if (tmpfile_fd < 0) - die("failure: create"); - /* link the temporary file into the filesystem, making it permanent */ - snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); - if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) - die("failure: linkat"); - if (close(tmpfile_fd)) - die("failure: close"); - if (is_setgid(open_tree_fd, FILE3, 0)) - die("failure: is_setgid"); - if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000)) - die("failure: check ownership"); - if (unlinkat(open_tree_fd, FILE3, 0)) - die("failure: delete"); - } - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int setgid_create_idmapped_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - int tmpfile_fd = -EBADF; - bool supported = false; - char path[PATH_MAX]; - - if (!caps_supported()) - return 0; - - if (fchmod(info->t_dir1_fd, S_IRUSR | - S_IWUSR | - S_IRGRP | - S_IWGRP | - S_IROTH | - S_IWOTH | - S_IXUSR | - S_IXGRP | - S_IXOTH | - S_ISGID), 0) { - log_stderr("failure: fchmod"); - goto out; - } - - /* Verify that the sid bits got raised. */ - if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) { - log_stderr("failure: is_setgid"); - goto out; - } - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - supported = openat_tmpfile_supported(open_tree_fd); - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - /* We're in_group_p() and capable_wrt_inode_uidgid() so setgid - * bit needs to be set. - */ - if (!is_setgid(open_tree_fd, FILE1, 0)) - die("failure: is_setgid"); - - /* create directory */ - if (mkdirat(open_tree_fd, DIR1, 0000)) - die("failure: create"); - - /* Directories always inherit the setgid bit. */ - if (!is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - - /* create a special file via mknodat() vfs_create */ - if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (!is_setgid(open_tree_fd, FILE2, 0)) - die("failure: is_setgid"); - - /* create a whiteout device via mknodat() vfs_mknod */ - if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (!is_setgid(open_tree_fd, CHRDEV1, 0)) - die("failure: is_setgid"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 0)) - die("failure: check ownership"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, CHRDEV1, 0)) - die("failure: delete"); - - /* create tmpfile via filesystem tmpfile api */ - if (supported) { - tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); - if (tmpfile_fd < 0) - die("failure: create"); - /* link the temporary file into the filesystem, making it permanent */ - snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); - if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) - die("failure: linkat"); - if (close(tmpfile_fd)) - die("failure: close"); - if (!is_setgid(open_tree_fd, FILE3, 0)) - die("failure: is_setgid"); - if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 0)) - die("failure: check ownership"); - if (unlinkat(open_tree_fd, FILE3, 0)) - die("failure: delete"); - } - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - /* - * Below we verify that setgid inheritance for a newly created file or - * directory works correctly. As part of this we need to verify that - * newly created files or directories inherit their gid from their - * parent directory. So we change the parent directorie's gid to 1000 - * and create a file with fs{g,u}id 0 and verify that the newly created - * file and directory inherit gid 1000, not 0. - */ - if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - if (!caps_down_fsetid()) - die("failure: caps_down_fsetid"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid - * bit needs to be stripped. - */ - if (is_setgid(open_tree_fd, FILE1, 0)) - die("failure: is_setgid"); - - /* create directory */ - if (mkdirat(open_tree_fd, DIR1, 0000)) - die("failure: create"); - - if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { - /* We're not in_group_p(). */ - if (is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - } else { - /* Directories always inherit the setgid bit. */ - if (!is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - } - - /* create a special file via mknodat() vfs_create */ - if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (is_setgid(open_tree_fd, FILE2, 0)) - die("failure: is_setgid"); - - /* create a whiteout device via mknodat() vfs_mknod */ - if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (is_setgid(open_tree_fd, CHRDEV1, 0)) - die("failure: is_setgid"); - - /* - * In setgid directories newly created files always inherit the - * gid from the parent directory. Verify that the file is owned - * by gid 1000, not by gid 0. - */ - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) - die("failure: check ownership"); - - /* - * In setgid directories newly created directories always - * inherit the gid from the parent directory. Verify that the - * directory is owned by gid 1000, not by gid 0. - */ - if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000)) - die("failure: check ownership"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, CHRDEV1, 0)) - die("failure: delete"); - - /* create tmpfile via filesystem tmpfile api */ - if (supported) { - tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); - if (tmpfile_fd < 0) - die("failure: create"); - /* link the temporary file into the filesystem, making it permanent */ - snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); - if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) - die("failure: linkat"); - if (close(tmpfile_fd)) - die("failure: close"); - if (is_setgid(open_tree_fd, FILE3, 0)) - die("failure: is_setgid"); - if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000)) - die("failure: check ownership"); - if (unlinkat(open_tree_fd, FILE3, 0)) - die("failure: delete"); - } - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 0, 1000, false)) - die("failure: switch_userns"); - - if (!caps_down_fsetid()) - die("failure: caps_down_fsetid"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - /* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid - * bit needs to be stripped. - */ - if (is_setgid(open_tree_fd, FILE1, 0)) - die("failure: is_setgid"); - - /* create directory */ - if (mkdirat(open_tree_fd, DIR1, 0000)) - die("failure: create"); - - /* Directories always inherit the setgid bit. */ - if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) { - /* We're not in_group_p(). */ - if (is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - } else { - /* Directories always inherit the setgid bit. */ - if (!is_setgid(open_tree_fd, DIR1, 0)) - die("failure: is_setgid"); - } - - /* create a special file via mknodat() vfs_create */ - if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (is_setgid(open_tree_fd, FILE2, 0)) - die("failure: is_setgid"); - - /* create a whiteout device via mknodat() vfs_mknod */ - if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0)) - die("failure: mknodat"); - - if (is_setgid(open_tree_fd, CHRDEV1, 0)) - die("failure: is_setgid"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 0)) - die("failure: check ownership"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, FILE2, 0)) - die("failure: delete"); - - if (unlinkat(open_tree_fd, CHRDEV1, 0)) - die("failure: delete"); - - /* create tmpfile via filesystem tmpfile api */ - if (supported) { - tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); - if (tmpfile_fd < 0) - die("failure: create"); - /* link the temporary file into the filesystem, making it permanent */ - snprintf(path, PATH_MAX, "/proc/self/fd/%d", tmpfile_fd); - if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW)) - die("failure: linkat"); - if (close(tmpfile_fd)) - die("failure: close"); - if (is_setgid(open_tree_fd, FILE3, 0)) - die("failure: is_setgid"); - if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 0)) - die("failure: check ownership"); - if (unlinkat(open_tree_fd, FILE3, 0)) - die("failure: delete"); - } - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -#define PTR_TO_INT(p) ((int)((intptr_t)(p))) -#define INT_TO_PTR(u) ((void *)((intptr_t)(u))) - -struct threaded_args { - const struct vfstest_info *info; - int open_tree_fd; -}; - -static void *idmapped_mount_create_cb(void *data) -{ - int fret = EXIT_FAILURE; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - struct threaded_args *args = data; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - if (sys_mount_setattr(args->open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - fret = EXIT_SUCCESS; - -out: - safe_close(attr.userns_fd); - pthread_exit(INT_TO_PTR(fret)); -} - -/* This tries to verify that we never see an inconistent ownership on-disk and - * can't write invalid ids to disk. To do this we create a race between - * idmapping a mount and creating files on it. - * Note, while it is perfectly fine to see overflowuid and overflowgid as owner - * if we create files through the open_tree_fd before the mount is idmapped but - * look at the files after the mount has been idmapped in this test it can never - * be the case that we see overflowuid and overflowgid when we access the file - * through a non-idmapped mount (in the initial user namespace). - */ -static void *idmapped_mount_operations_cb(void *data) -{ - int file1_fd = -EBADF, file2_fd = -EBADF, dir1_fd = -EBADF, - dir1_fd2 = -EBADF, fret = EXIT_FAILURE; - struct threaded_args *args = data; - const struct vfstest_info *info = args->info; - - if (!switch_fsids(10000, 10000)) { - log_stderr("failure: switch fsids"); - goto out; - } - - file1_fd = openat(args->open_tree_fd, FILE1, - O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - file2_fd = openat(args->open_tree_fd, FILE2, - O_CREAT | O_EXCL | O_CLOEXEC, 0644); - if (file2_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (mkdirat(args->open_tree_fd, DIR1, 0777)) { - log_stderr("failure: mkdirat"); - goto out; - } - - dir1_fd = openat(args->open_tree_fd, DIR1, - O_RDONLY | O_DIRECTORY | O_CLOEXEC); - if (dir1_fd < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (!__expected_uid_gid(args->open_tree_fd, FILE1, 0, 0, 0, false) && - !__expected_uid_gid(args->open_tree_fd, FILE1, 0, 10000, 10000, false) && - !__expected_uid_gid(args->open_tree_fd, FILE1, 0, info->t_overflowuid, info->t_overflowgid, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(args->open_tree_fd, FILE2, 0, 0, 0, false) && - !__expected_uid_gid(args->open_tree_fd, FILE2, 0, 10000, 10000, false) && - !__expected_uid_gid(args->open_tree_fd, FILE2, 0, info->t_overflowuid, info->t_overflowgid, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(args->open_tree_fd, DIR1, 0, 0, 0, false) && - !__expected_uid_gid(args->open_tree_fd, DIR1, 0, 10000, 10000, false) && - !__expected_uid_gid(args->open_tree_fd, DIR1, 0, info->t_overflowuid, info->t_overflowgid, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, 0, 0, false) && - !__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, 10000, 10000, false) && - !__expected_uid_gid(dir1_fd, "", AT_EMPTY_PATH, info->t_overflowuid, info->t_overflowgid, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - dir1_fd2 = openat(info->t_dir1_fd, DIR1, - O_RDONLY | O_DIRECTORY | O_CLOEXEC); - if (dir1_fd2 < 0) { - log_stderr("failure: openat"); - goto out; - } - - if (!__expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0, false) && - !__expected_uid_gid(info->t_dir1_fd, FILE1, 0, 10000, 10000, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0, false) && - !__expected_uid_gid(info->t_dir1_fd, FILE2, 0, 10000, 10000, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0, false) && - !__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 10000, 10000, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0, false) && - !__expected_uid_gid(info->t_dir1_fd, DIR1, 0, 10000, 10000, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - if (!__expected_uid_gid(dir1_fd2, "", AT_EMPTY_PATH, 0, 0, false) && - !__expected_uid_gid(dir1_fd2, "", AT_EMPTY_PATH, 10000, 10000, false)) { - log_stderr("failure: expected_uid_gid"); - goto out; - } - - fret = EXIT_SUCCESS; - -out: - safe_close(file1_fd); - safe_close(file2_fd); - safe_close(dir1_fd); - safe_close(dir1_fd2); - - pthread_exit(INT_TO_PTR(fret)); -} - -static int threaded_idmapped_mount_interactions(const struct vfstest_info *info) -{ - int i; - int fret = -1; - pid_t pid; - pthread_attr_t thread_attr; - pthread_t threads[2]; - - pthread_attr_init(&thread_attr); - - for (i = 0; i < 1000; i++) { - int ret1 = 0, ret2 = 0, tret1 = 0, tret2 = 0; - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int open_tree_fd = -EBADF; - struct threaded_args args = { - .info = info, - .open_tree_fd = -EBADF, - }; - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) - die("failure: sys_open_tree"); - - args.open_tree_fd = open_tree_fd; - - if (pthread_create(&threads[0], &thread_attr, - idmapped_mount_create_cb, - &args)) - die("failure: pthread_create"); - - if (pthread_create(&threads[1], &thread_attr, - idmapped_mount_operations_cb, - &args)) - die("failure: pthread_create"); - - ret1 = pthread_join(threads[0], INT_TO_PTR(tret1)); - ret2 = pthread_join(threads[1], INT_TO_PTR(tret2)); - - if (ret1) { - errno = ret1; - die("failure: pthread_join"); - } - - if (ret2) { - errno = ret2; - die("failure: pthread_join"); - } - - if (tret1 || tret2) - exit(EXIT_FAILURE); - - exit(EXIT_SUCCESS); - - } - - if (wait_for_pid(pid)) { - log_stderr("failure: iteration %d", i); - goto out; - } - - rm_r(info->t_dir1_fd, "."); - - } - - fret = 0; - log_debug("Ran test"); - -out: - return fret; -} - static int setattr_truncate(const struct vfstest_info *info) { int fret = -1; @@ -8278,312 +1937,6 @@ out: return fret; } -static int setattr_truncate_idmapped(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - pid_t pid; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_ids(10000, 10000)) - die("failure: switch_ids"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - if (ftruncate(file1_fd, 10000)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) - die("failure: expected_file_size"); - - if (ftruncate(file1_fd, 0)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) - die("failure: expected_file_size"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - int file1_fd2 = -EBADF; - - /* create regular file via open() */ - file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd2 < 0) - die("failure: create"); - - if (ftruncate(file1_fd2, 10000)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) - die("failure: expected_file_size"); - - if (ftruncate(file1_fd2, 0)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) - die("failure: expected_file_size"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - -static int setattr_truncate_idmapped_in_userns(const struct vfstest_info *info) -{ - int fret = -1; - int file1_fd = -EBADF, open_tree_fd = -EBADF; - struct mount_attr attr = { - .attr_set = MOUNT_ATTR_IDMAP, - }; - pid_t pid; - - /* Changing mount properties on a detached mount. */ - attr.userns_fd = get_userns_fd(0, 10000, 10000); - if (attr.userns_fd < 0) { - log_stderr("failure: get_userns_fd"); - goto out; - } - - open_tree_fd = sys_open_tree(info->t_dir1_fd, "", - AT_EMPTY_PATH | - AT_NO_AUTOMOUNT | - AT_SYMLINK_NOFOLLOW | - OPEN_TREE_CLOEXEC | - OPEN_TREE_CLONE); - if (open_tree_fd < 0) { - log_stderr("failure: sys_open_tree"); - goto out; - } - - if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) { - log_stderr("failure: sys_mount_setattr"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!switch_userns(attr.userns_fd, 0, 0, false)) - die("failure: switch_userns"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - if (ftruncate(file1_fd, 10000)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) - die("failure: expected_file_size"); - - if (ftruncate(file1_fd, 0)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) - die("failure: expected_file_size"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 0, 0, true)) - die("failure: switch_userns"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - if (ftruncate(file1_fd, 10000)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) - die("failure: expected_file_size"); - - if (ftruncate(file1_fd, 0)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 0)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) - die("failure: expected_file_size"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - if (fchownat(info->t_dir1_fd, "", -1, 0, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) { - log_stderr("failure: fchownat"); - goto out; - } - - pid = fork(); - if (pid < 0) { - log_stderr("failure: fork"); - goto out; - } - if (pid == 0) { - if (!caps_supported()) { - log_debug("skip: capability library not installed"); - exit(EXIT_SUCCESS); - } - - if (!switch_userns(attr.userns_fd, 0, 1000, true)) - die("failure: switch_userns"); - - /* create regular file via open() */ - file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, S_IXGRP | S_ISGID); - if (file1_fd < 0) - die("failure: create"); - - if (ftruncate(file1_fd, 10000)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 10000)) - die("failure: expected_file_size"); - - if (ftruncate(file1_fd, 0)) - die("failure: ftruncate"); - - if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000)) - die("failure: check ownership"); - - if (!expected_file_size(open_tree_fd, FILE1, 0, 0)) - die("failure: expected_file_size"); - - if (unlinkat(open_tree_fd, FILE1, 0)) - die("failure: delete"); - - exit(EXIT_SUCCESS); - } - if (wait_for_pid(pid)) - goto out; - - fret = 0; - log_debug("Ran test"); -out: - safe_close(attr.userns_fd); - safe_close(file1_fd); - safe_close(open_tree_fd); - - return fret; -} - static int nested_userns(const struct vfstest_info *info) { int fret = -1; @@ -13634,55 +6987,20 @@ static const struct option longopts[] = { }; static const struct test_struct t_basic[] = { - { acls, T_REQUIRE_IDMAPPED_MOUNTS, "posix acls on regular mounts", }, - { create_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "create operations in user namespace", }, - { device_node_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "device node in user namespace", }, - { expected_uid_gid_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "expected ownership on idmapped mounts", }, - { fscaps, T_REQUIRE_USERNS, "fscaps on regular mounts", }, - { fscaps_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts", }, - { fscaps_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts in user namespace", }, - { fscaps_idmapped_mounts_in_userns_separate_userns, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts in user namespace with different id mappings", }, - { fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "mapped fsids", }, - { fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "unmapped fsids", }, - { hardlink_crossing_mounts, 0, "cross mount hardlink", }, - { hardlink_crossing_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "cross idmapped mount hardlink", }, - { hardlink_from_idmapped_mount, T_REQUIRE_IDMAPPED_MOUNTS, "hardlinks from idmapped mounts", }, - { hardlink_from_idmapped_mount_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "hardlinks from idmapped mounts in user namespace", }, + { 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", }, - { io_uring_idmapped, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts", }, - { io_uring_idmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts in user namespace", }, - { io_uring_idmapped_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts with unmapped ids", }, - { io_uring_idmapped_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "io_uring from idmapped mounts with unmapped ids in user namespace", }, + { 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", }, - { protected_symlinks_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "following protected symlinks on idmapped mounts", }, - { protected_symlinks_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "following protected symlinks on idmapped mounts in user namespace", }, - { rename_crossing_mounts, 0, "cross mount rename", }, - { rename_crossing_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "cross idmapped mount rename", }, - { rename_from_idmapped_mount, T_REQUIRE_IDMAPPED_MOUNTS, "rename from idmapped mounts", }, - { rename_from_idmapped_mount_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "rename from idmapped mounts in user namespace", }, - { setattr_truncate, 0, "setattr truncate", }, - { setattr_truncate_idmapped, T_REQUIRE_IDMAPPED_MOUNTS, "setattr truncate on idmapped mounts", }, - { setattr_truncate_idmapped_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "setattr truncate on idmapped mounts in user namespace", }, - { setgid_create, 0, "create operations in directories with setgid bit set", }, - { setgid_create_idmapped, T_REQUIRE_IDMAPPED_MOUNTS, "create operations in directories with setgid bit set on idmapped mounts", }, - { setgid_create_idmapped_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "create operations in directories with setgid bit set on idmapped mounts in user namespace", }, - { setid_binaries, 0, "setid binaries on regular mounts", }, - { setid_binaries_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "setid binaries on idmapped mounts", }, - { setid_binaries_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "setid binaries on idmapped mounts in user namespace", }, - { setid_binaries_idmapped_mounts_in_userns_separate_userns, T_REQUIRE_IDMAPPED_MOUNTS, "setid binaries on idmapped mounts in user namespace with different id mappings", }, - { sticky_bit_unlink, 0, "sticky bit unlink operations on regular mounts", }, - { sticky_bit_unlink_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit unlink operations on idmapped mounts", }, - { sticky_bit_unlink_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit unlink operations on idmapped mounts in user namespace", }, - { sticky_bit_rename, 0, "sticky bit rename operations on regular mounts", }, - { sticky_bit_rename_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit rename operations on idmapped mounts", }, - { sticky_bit_rename_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "sticky bit rename operations on idmapped mounts in user namespace", }, - { symlink_regular_mounts, 0, "symlink from regular mounts", }, - { symlink_idmapped_mounts, T_REQUIRE_IDMAPPED_MOUNTS, "symlink from idmapped mounts", }, - { symlink_idmapped_mounts_in_userns, T_REQUIRE_IDMAPPED_MOUNTS, "symlink from idmapped mounts in user namespace", }, - { threaded_idmapped_mount_interactions, T_REQUIRE_IDMAPPED_MOUNTS, "threaded operations on idmapped mounts", }, + { 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 = { @@ -13690,15 +7008,6 @@ static const struct test_suite s_basic = { .nr_tests = ARRAY_SIZE(t_basic), }; -static const struct test_struct t_fscaps_in_ancestor_userns[] = { - { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, T_REQUIRE_IDMAPPED_MOUNTS, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", }, -}; - -static const struct test_suite s_fscaps_in_ancestor_userns = { - .tests = t_fscaps_in_ancestor_userns, - .nr_tests = ARRAY_SIZE(t_fscaps_in_ancestor_userns), -}; - static const struct test_struct t_nested_userns[] = { { nested_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test that nested user namespaces behave correctly when attached to idmapped mounts", }, }; @@ -13949,11 +7258,15 @@ int main(int argc, char *argv[]) fret = EXIT_FAILURE; - if (test_core && !run_suite(&info, &s_basic)) - goto out; + 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)) + if (test_fscaps_regression && !run_suite(&info, &s_fscaps_in_ancestor_userns)) goto out; if (test_nested_userns && !run_suite(&info, &s_nested_userns)) |