blob: 01a5fbd4903273d7a6d8439d9c24937c6f32f1dc [file] [log] [blame]
// 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;