blob: 78b7cc094772a5d337c9364d676cb241c114e6a5 [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 <sched.h>
#include <stdbool.h>
#include <sys/fsuid.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
#ifdef HAVE_LINUX_BTRFS_H
#include <linux/btrfs.h>
#endif
#ifdef HAVE_LINUX_BTRFS_TREE_H
#include <linux/btrfs_tree.h>
#endif
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/capability.h>
#endif
#ifdef HAVE_LIBURING_H
#include <liburing.h>
#endif
#include "missing.h"
#include "utils.h"
#define T_DIR1 "idmapped_mounts_1"
#define FILE1 "file1"
#define FILE1_RENAME "file1_rename"
#define FILE2 "file2"
#define FILE2_RENAME "file2_rename"
#define DIR1 "dir1"
#define DIR2 "dir2"
#define DIR3 "dir3"
#define DIR1_RENAME "dir1_rename"
#define HARDLINK1 "hardlink1"
#define SYMLINK1 "symlink1"
#define SYMLINK_USER1 "symlink_user1"
#define SYMLINK_USER2 "symlink_user2"
#define SYMLINK_USER3 "symlink_user3"
#define CHRDEV1 "chrdev1"
#define log_stderr(format, ...) \
fprintf(stderr, "%s: %d: %s - %m - " format "\n", __FILE__, __LINE__, __func__, \
##__VA_ARGS__)
#ifdef DEBUG_TRACE
#define log_debug(format, ...) \
fprintf(stderr, "%s: %d: %s - " format "\n", __FILE__, __LINE__, \
__func__, ##__VA_ARGS__)
#else
#define log_debug(format, ...)
#endif
#define log_error_errno(__ret__, __errno__, format, ...) \
({ \
typeof(__ret__) __internal_ret__ = (__ret__); \
errno = (__errno__); \
log_stderr(format, ##__VA_ARGS__); \
__internal_ret__; \
})
#define log_errno(__ret__, format, ...) log_error_errno(__ret__, errno, format, ##__VA_ARGS__)
#define die_errno(__errno__, format, ...) \
({ \
errno = (__errno__); \
log_stderr(format, ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
})
#define die(format, ...) die_errno(errno, format, ##__VA_ARGS__)
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
uid_t t_overflowuid = 65534;
gid_t t_overflowgid = 65534;
/* path of the test device */
const char *t_fstype;
/* path of the test device */
const char *t_device;
/* path of the test scratch device */
const char *t_device_scratch;
/* mountpoint of the test device */
const char *t_mountpoint;
/* mountpoint of the test device */
const char *t_mountpoint_scratch;
/* fd for @t_mountpoint */
int t_mnt_fd;
/* fd for @t_mountpoint_scratch */
int t_mnt_scratch_fd;
/* fd for @T_DIR1 */
int t_dir1_fd;
/* temporary buffer */
char t_buf[PATH_MAX];
static void stash_overflowuid(void)
{
int fd;
ssize_t ret;
char buf[256];
fd = open("/proc/sys/fs/overflowuid", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return;
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret < 0)
return;
t_overflowuid = atoi(buf);
}
static void stash_overflowgid(void)
{
int fd;
ssize_t ret;
char buf[256];
fd = open("/proc/sys/fs/overflowgid", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return;
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret < 0)
return;
t_overflowgid = atoi(buf);
}
static bool is_xfs(void)
{
static int enabled = -1;
if (enabled == -1)
enabled = !strcmp(t_fstype, "xfs");
return enabled;
}
static bool protected_symlinks_enabled(void)
{
static int enabled = -1;
if (enabled == -1) {
int fd;
ssize_t ret;
char buf[256];
enabled = 0;
fd = open("/proc/sys/fs/protected_symlinks", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return false;
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret < 0)
return false;
if (atoi(buf) >= 1)
enabled = 1;
}
return enabled == 1;
}
static bool xfs_irix_sgid_inherit_enabled(void)
{
static int enabled = -1;
if (enabled == -1) {
int fd;
ssize_t ret;
char buf[256];
enabled = 0;
if (is_xfs()) {
fd = open("/proc/sys/fs/xfs/irix_sgid_inherit", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return false;
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret < 0)
return false;
if (atoi(buf) >= 1)
enabled = 1;
}
}
return enabled == 1;
}
static inline bool caps_supported(void)
{
bool ret = false;
#ifdef HAVE_SYS_CAPABILITY_H
ret = true;
#endif
return ret;
}
/* caps_down - lower all effective caps */
static int caps_down(void)
{
bool fret = false;
#ifdef HAVE_SYS_CAPABILITY_H
cap_t caps = NULL;
int ret = -1;
caps = cap_get_proc();
if (!caps)
goto out;
ret = cap_clear_flag(caps, CAP_EFFECTIVE);
if (ret)
goto out;
ret = cap_set_proc(caps);
if (ret)
goto out;
fret = true;
out:
cap_free(caps);
#endif
return fret;
}
/* caps_up - raise all permitted caps */
static int caps_up(void)
{
bool fret = false;
#ifdef HAVE_SYS_CAPABILITY_H
cap_t caps = NULL;
cap_value_t cap;
int ret = -1;
caps = cap_get_proc();
if (!caps)
goto out;
for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
cap_flag_value_t flag;
ret = cap_get_flag(caps, cap, CAP_PERMITTED, &flag);
if (ret) {
if (errno == EINVAL)
break;
else
goto out;
}
ret = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, flag);
if (ret)
goto out;
}
ret = cap_set_proc(caps);
if (ret)
goto out;
fret = true;
out:
cap_free(caps);
#endif
return fret;
}
/* __expected_uid_gid - check whether file is owned by the provided uid and gid */
static bool __expected_uid_gid(int dfd, const char *path, int flags,
uid_t expected_uid, gid_t expected_gid, bool log)
{
int ret;
struct stat st;
ret = fstatat(dfd, path, &st, flags);
if (ret < 0)
return log_errno(false, "failure: fstatat");
if (log && st.st_uid != expected_uid)
log_stderr("failure: uid(%d) != expected_uid(%d)", st.st_uid, expected_uid);
if (log && st.st_gid != expected_gid)
log_stderr("failure: gid(%d) != expected_gid(%d)", st.st_gid, expected_gid);
errno = 0; /* Don't report misleading errno. */
return st.st_uid == expected_uid && st.st_gid == expected_gid;
}
static bool expected_uid_gid(int dfd, const char *path, int flags,
uid_t expected_uid, gid_t expected_gid)
{
return __expected_uid_gid(dfd, path, flags,
expected_uid, expected_gid, true);
}
static bool expected_file_size(int dfd, const char *path,
int flags, off_t expected_size)
{
int ret;
struct stat st;
ret = fstatat(dfd, path, &st, flags);
if (ret < 0)
return log_errno(false, "failure: fstatat");
if (st.st_size != expected_size)
return log_errno(false, "failure: st_size(%zu) != expected_size(%zu)",
(size_t)st.st_size, (size_t)expected_size);
return true;
}
/* is_setid - check whether file is S_ISUID and S_ISGID */
static bool is_setid(int dfd, const char *path, int flags)
{
int ret;
struct stat st;
ret = fstatat(dfd, path, &st, flags);
if (ret < 0)
return false;
errno = 0; /* Don't report misleading errno. */
return (st.st_mode & S_ISUID) || (st.st_mode & S_ISGID);
}
/* is_setgid - check whether file or directory is S_ISGID */
static bool is_setgid(int dfd, const char *path, int flags)
{
int ret;
struct stat st;
ret = fstatat(dfd, path, &st, flags);
if (ret < 0)
return false;
errno = 0; /* Don't report misleading errno. */
return (st.st_mode & S_ISGID);
}
/* is_sticky - check whether file is S_ISVTX */
static bool is_sticky(int dfd, const char *path, int flags)
{
int ret;
struct stat st;
ret = fstatat(dfd, path, &st, flags);
if (ret < 0)
return false;
errno = 0; /* Don't report misleading errno. */
return (st.st_mode & S_ISVTX) > 0;
}
static inline bool switch_fsids(uid_t fsuid, gid_t fsgid)
{
if (setfsgid(fsgid))
return log_errno(false, "failure: setfsgid");
if (setfsgid(-1) != fsgid)
return log_errno(false, "failure: setfsgid(-1)");
if (setfsuid(fsuid))
return log_errno(false, "failure: setfsuid");
if (setfsuid(-1) != fsuid)
return log_errno(false, "failure: setfsuid(-1)");
return true;
}
static inline bool switch_userns(int fd, uid_t uid, gid_t gid, bool drop_caps)
{
if (setns(fd, CLONE_NEWUSER))
return log_errno(false, "failure: setns");
if (!switch_ids(uid, gid))
return log_errno(false, "failure: switch_ids");
if (drop_caps && !caps_down())
return log_errno(false, "failure: caps_down");
return true;
}
/* rm_r - recursively remove all files */
static int rm_r(int fd, const char *path)
{
int dfd, ret;
DIR *dir;
struct dirent *direntp;
if (!path || strcmp(path, "") == 0)
return -1;
dfd = openat(fd, path, O_CLOEXEC | O_DIRECTORY);
if (dfd < 0)
return -1;
dir = fdopendir(dfd);
if (!dir) {
close(dfd);
return -1;
}
while ((direntp = readdir(dir))) {
struct stat st;
if (!strcmp(direntp->d_name, ".") ||
!strcmp(direntp->d_name, ".."))
continue;
ret = fstatat(dfd, direntp->d_name, &st, AT_SYMLINK_NOFOLLOW);
if (ret < 0 && errno != ENOENT)
break;
if (S_ISDIR(st.st_mode))
ret = rm_r(dfd, direntp->d_name);
else
ret = unlinkat(dfd, direntp->d_name, 0);
if (ret < 0 && errno != ENOENT)
break;
}
ret = unlinkat(fd, path, AT_REMOVEDIR);
closedir(dir);
return ret;
}
/* chown_r - recursively change ownership of all files */
static int chown_r(int fd, const char *path, uid_t uid, gid_t gid)
{
int dfd, ret;
DIR *dir;
struct dirent *direntp;
dfd = openat(fd, path, O_CLOEXEC | O_DIRECTORY);
if (dfd < 0)
return -1;
dir = fdopendir(dfd);
if (!dir) {
close(dfd);
return -1;
}
while ((direntp = readdir(dir))) {
struct stat st;
if (!strcmp(direntp->d_name, ".") ||
!strcmp(direntp->d_name, ".."))
continue;
ret = fstatat(dfd, direntp->d_name, &st, AT_SYMLINK_NOFOLLOW);
if (ret < 0 && errno != ENOENT)
break;
if (S_ISDIR(st.st_mode))
ret = chown_r(dfd, direntp->d_name, uid, gid);
else
ret = fchownat(dfd, direntp->d_name, uid, gid, AT_SYMLINK_NOFOLLOW);
if (ret < 0 && errno != ENOENT)
break;
}
ret = fchownat(fd, path, uid, gid, AT_SYMLINK_NOFOLLOW);
closedir(dir);
return ret;
}
/*
* There'll be scenarios where you'll want to see the attributes associated with
* a directory tree during debugging or just to make sure things look correct.
* Simply uncomment and place the print_r() helper where you need it.
*/
#ifdef DEBUG_TRACE
static int fd_cloexec(int fd, bool cloexec)
{
int oflags, nflags;
oflags = fcntl(fd, F_GETFD, 0);
if (oflags < 0)
return -errno;
if (cloexec)
nflags = oflags | FD_CLOEXEC;
else
nflags = oflags & ~FD_CLOEXEC;
if (nflags == oflags)
return 0;
if (fcntl(fd, F_SETFD, nflags) < 0)
return -errno;
return 0;
}
static inline int dup_cloexec(int fd)
{
int fd_dup;
fd_dup = dup(fd);
if (fd_dup < 0)
return -errno;
if (fd_cloexec(fd_dup, true)) {
close(fd_dup);
return -errno;
}
return fd_dup;
}
__attribute__((unused)) static int print_r(int fd, const char *path)
{
int ret = 0;
int dfd, dfd_dup;
DIR *dir;
struct dirent *direntp;
struct stat st;
if (!path || *path == '\0') {
char buf[sizeof("/proc/self/fd/") + 30];
ret = snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
if (ret < 0 || (size_t)ret >= sizeof(buf))
return -1;
/*
* O_PATH file descriptors can't be used so we need to re-open
* just in case.
*/
dfd = openat(-EBADF, buf, O_CLOEXEC | O_DIRECTORY, 0);
} else {
dfd = openat(fd, path, O_CLOEXEC | O_DIRECTORY, 0);
}
if (dfd < 0)
return -1;
/*
* When fdopendir() below succeeds it assumes ownership of the fd so we
* to make sure we always have an fd that fdopendir() can own which is
* why we dup() in the case where the caller wants us to operate on the
* fd directly.
*/
dfd_dup = dup_cloexec(dfd);
if (dfd_dup < 0) {
close(dfd);
return -1;
}
dir = fdopendir(dfd);
if (!dir) {
close(dfd);
close(dfd_dup);
return -1;
}
/* Transfer ownership to fdopendir(). */
dfd = -EBADF;
while ((direntp = readdir(dir))) {
if (!strcmp(direntp->d_name, ".") ||
!strcmp(direntp->d_name, ".."))
continue;
ret = fstatat(dfd_dup, direntp->d_name, &st, AT_SYMLINK_NOFOLLOW);
if (ret < 0 && errno != ENOENT)
break;
ret = 0;
if (S_ISDIR(st.st_mode))
ret = print_r(dfd_dup, direntp->d_name);
else
fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %d/%s\n",
(st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
dfd_dup, direntp->d_name);
if (ret < 0 && errno != ENOENT)
break;
}
if (!path || *path == '\0')
ret = fstatat(fd, "", &st,
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_EMPTY_PATH);
else
ret = fstatat(fd, path, &st,
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
if (!ret)
fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %s\n",
(st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
(path && *path) ? path : "(null)");
close(dfd_dup);
closedir(dir);
return ret;
}
#endif
/* fd_to_fd - transfer data from one fd to another */
static int fd_to_fd(int from, int to)
{
for (;;) {
uint8_t buf[PATH_MAX];
uint8_t *p = buf;
ssize_t bytes_to_write;
ssize_t bytes_read;
bytes_read = read_nointr(from, buf, sizeof buf);
if (bytes_read < 0)
return -1;
if (bytes_read == 0)
break;
bytes_to_write = (size_t)bytes_read;
do {
ssize_t bytes_written;
bytes_written = write_nointr(to, p, bytes_to_write);
if (bytes_written < 0)
return -1;
bytes_to_write -= bytes_written;
p += bytes_written;
} while (bytes_to_write > 0);
}
return 0;
}
static int sys_execveat(int fd, const char *path, char **argv, char **envp,
int flags)
{
#ifdef __NR_execveat
return syscall(__NR_execveat, fd, path, argv, envp, flags);
#else
errno = ENOSYS;
return -1;
#endif
}
#ifndef CAP_NET_RAW
#define CAP_NET_RAW 13
#endif
#ifndef VFS_CAP_FLAGS_EFFECTIVE
#define VFS_CAP_FLAGS_EFFECTIVE 0x000001
#endif
#ifndef VFS_CAP_U32_3
#define VFS_CAP_U32_3 2
#endif
#ifndef VFS_CAP_U32
#define VFS_CAP_U32 VFS_CAP_U32_3
#endif
#ifndef VFS_CAP_REVISION_1
#define VFS_CAP_REVISION_1 0x01000000
#endif
#ifndef VFS_CAP_REVISION_2
#define VFS_CAP_REVISION_2 0x02000000
#endif
#ifndef VFS_CAP_REVISION_3
#define VFS_CAP_REVISION_3 0x03000000
struct vfs_ns_cap_data {
__le32 magic_etc;
struct {
__le32 permitted;
__le32 inheritable;
} data[VFS_CAP_U32];
__le32 rootid;
};
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
#define cpu_to_le16(w16) le16_to_cpu(w16)
#define le16_to_cpu(w16) ((u_int16_t)((u_int16_t)(w16) >> 8) | (u_int16_t)((u_int16_t)(w16) << 8))
#define cpu_to_le32(w32) le32_to_cpu(w32)
#define le32_to_cpu(w32) \
((u_int32_t)((u_int32_t)(w32) >> 24) | (u_int32_t)(((u_int32_t)(w32) >> 8) & 0xFF00) | \
(u_int32_t)(((u_int32_t)(w32) << 8) & 0xFF0000) | (u_int32_t)((u_int32_t)(w32) << 24))
#elif __BYTE_ORDER == __LITTLE_ENDIAN
#define cpu_to_le16(w16) ((u_int16_t)(w16))
#define le16_to_cpu(w16) ((u_int16_t)(w16))
#define cpu_to_le32(w32) ((u_int32_t)(w32))
#define le32_to_cpu(w32) ((u_int32_t)(w32))
#else
#error Expected endianess macro to be set
#endif
/* expected_dummy_vfs_caps_uid - check vfs caps are stored with the provided uid */
static bool expected_dummy_vfs_caps_uid(int fd, uid_t expected_uid)
{
#define __cap_raised_permitted(x, ns_cap_data) \
((ns_cap_data.data[(x) >> 5].permitted) & (1 << ((x)&31)))
struct vfs_ns_cap_data ns_xattr = {};
ssize_t ret;
ret = fgetxattr(fd, "security.capability", &ns_xattr, sizeof(ns_xattr));
if (ret < 0 || ret == 0)
return false;
if (ns_xattr.magic_etc & VFS_CAP_REVISION_3) {
if (le32_to_cpu(ns_xattr.rootid) != expected_uid) {
errno = EINVAL;
log_stderr("failure: rootid(%d) != expected_rootid(%d)", le32_to_cpu(ns_xattr.rootid), expected_uid);
}
return (le32_to_cpu(ns_xattr.rootid) == expected_uid) &&
(__cap_raised_permitted(CAP_NET_RAW, ns_xattr) > 0);
} else {
log_stderr("failure: fscaps version");
}
return false;
}
/* set_dummy_vfs_caps - set dummy vfs caps for the provided uid */
static int set_dummy_vfs_caps(int fd, int flags, int rootuid)
{
#define __raise_cap_permitted(x, ns_cap_data) \
ns_cap_data.data[(x) >> 5].permitted |= (1 << ((x)&31))
struct vfs_ns_cap_data ns_xattr;
memset(&ns_xattr, 0, sizeof(ns_xattr));
__raise_cap_permitted(CAP_NET_RAW, ns_xattr);
ns_xattr.magic_etc |= VFS_CAP_REVISION_3 | VFS_CAP_FLAGS_EFFECTIVE;
ns_xattr.rootid = cpu_to_le32(rootuid);
return fsetxattr(fd, "security.capability",
&ns_xattr, sizeof(ns_xattr), flags);
}
#define safe_close(fd) \
if (fd >= 0) { \
int _e_ = errno; \
close(fd); \
errno = _e_; \
fd = -EBADF; \
}
static void test_setup(void)
{
if (mkdirat(t_mnt_fd, T_DIR1, 0777))
die("failure: mkdirat");
t_dir1_fd = openat(t_mnt_fd, T_DIR1, O_CLOEXEC | O_DIRECTORY);
if (t_dir1_fd < 0)
die("failure: openat");
if (fchmod(t_dir1_fd, 0777))
die("failure: fchmod");
}
static void test_cleanup(void)
{
safe_close(t_dir1_fd);
if (rm_r(t_mnt_fd, T_DIR1))
die("failure: rm_r");
}
/* Validate that basic file operations on idmapped mounts. */
static int fsids_unmapped(void)
{
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(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(t_dir1_fd, DIR1, 0700)) {
log_stderr("failure: mkdirat");
goto out;
}
/* change ownership of all files to uid 0 */
if (chown_r(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(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(void)
{
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(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(t_dir1_fd, DIR1, 0700)) {
log_stderr("failure: mkdirat");
goto out;
}
/* change ownership of all files to uid 0 */
if (chown_r(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(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(void)
{
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(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(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(void)
{
int fret = -1;
int file1_fd = -EBADF, open_tree_fd = -EBADF;
if (chown_r(t_mnt_fd, T_DIR1, 10000, 10000)) {
log_stderr("failure: chown_r");
goto out;
}
open_tree_fd = sys_open_tree(t_dir1_fd, "",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
if (open_tree_fd < 0) {
log_stderr("failure: sys_open_tree");
goto out;
}
file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (file1_fd < 0) {
log_stderr("failure: openat");
goto out;
}
if (mkdirat(open_tree_fd, DIR1, 0777)) {
log_stderr("failure: mkdirat");
goto out;
}
/* We're crossing a mountpoint so this must fail.
*
* Note that this must also fail for non-idmapped mounts but here we're
* interested in making sure we're not introducing an accidental way to
* violate that restriction or that suddenly this becomes possible.
*/
if (!linkat(open_tree_fd, FILE1, t_dir1_fd, HARDLINK1, 0)) {
log_stderr("failure: linkat");
goto out;
}
if (errno != EXDEV) {
log_stderr("failure: errno");
goto out;
}
fret = 0;
log_debug("Ran test");
out:
safe_close(file1_fd);
safe_close(open_tree_fd);
return fret;
}
static int hardlink_crossing_idmapped_mounts(void)
{
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(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(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(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(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(void)
{
int fret = -1;
int file1_fd = -EBADF, open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (chown_r(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(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(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(void)
{
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(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(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(void)
{
int fret = -1;
int file1_fd = -EBADF, open_tree_fd = -EBADF;
if (chown_r(t_mnt_fd, T_DIR1, 10000, 10000)) {
log_stderr("failure: chown_r");
goto out;
}
open_tree_fd = sys_open_tree(t_dir1_fd, "",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
if (open_tree_fd < 0) {
log_stderr("failure: sys_open_tree");
goto out;
}
file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (file1_fd < 0) {
log_stderr("failure: openat");
goto out;
}
if (mkdirat(open_tree_fd, DIR1, 0777)) {
log_stderr("failure: mkdirat");
goto out;
}
/* We're crossing a mountpoint so this must fail.
*
* Note that this must also fail for non-idmapped mounts but here we're
* interested in making sure we're not introducing an accidental way to
* violate that restriction or that suddenly this becomes possible.
*/
if (!renameat(open_tree_fd, FILE1, t_dir1_fd, FILE1_RENAME)) {
log_stderr("failure: renameat");
goto out;
}
if (errno != EXDEV) {
log_stderr("failure: errno");
goto out;
}
fret = 0;
log_debug("Ran test");
out:
safe_close(file1_fd);
safe_close(open_tree_fd);
return fret;
}
static int rename_crossing_idmapped_mounts(void)
{
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(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(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(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(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(void)
{
int fret = -1;
int file1_fd = -EBADF, open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (chown_r(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(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(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(void)
{
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(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(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(void)
{
int fret = -1;
int file1_fd = -EBADF, open_tree_fd = -EBADF;
struct stat st;
file1_fd = openat(t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (file1_fd < 0) {
log_stderr("failure: openat");
goto out;
}
if (chown_r(t_mnt_fd, T_DIR1, 10000, 10000)) {
log_stderr("failure: chown_r");
goto out;
}
open_tree_fd = sys_open_tree(t_dir1_fd, "",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
if (open_tree_fd < 0) {
log_stderr("failure: sys_open_tree");
goto out;
}
if (symlinkat(FILE1, open_tree_fd, FILE2)) {
log_stderr("failure: symlinkat");
goto out;
}
if (fchownat(open_tree_fd, FILE2, 15000, 15000, AT_SYMLINK_NOFOLLOW)) {
log_stderr("failure: fchownat");
goto out;
}
if (fstatat(open_tree_fd, FILE2, &st, AT_SYMLINK_NOFOLLOW)) {
log_stderr("failure: fstatat");
goto out;
}
if (st.st_uid != 15000 || st.st_gid != 15000) {
log_stderr("failure: compare ids");
goto out;
}
if (fstatat(open_tree_fd, FILE1, &st, 0)) {
log_stderr("failure: fstatat");
goto out;
}
if (st.st_uid != 10000 || st.st_gid != 10000) {
log_stderr("failure: compare ids");
goto out;
}
fret = 0;
log_debug("Ran test");
out:
safe_close(file1_fd);
safe_close(open_tree_fd);
return fret;
}
static int symlink_idmapped_mounts(void)
{
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(t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (file1_fd < 0) {
log_stderr("failure: openat");
goto out;
}
if (chown_r(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(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(void)
{
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(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(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(t_dir1_fd, FILE2, AT_SYMLINK_NOFOLLOW, 5000, 5000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(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(void)
{
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(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(void)
{
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(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(t_dir1_fd, FILE2, S_IFREG | 0000, 0)) {
log_stderr("failure: mknodat");
goto out;
}
/* create character device */
if (mknodat(t_dir1_fd, CHRDEV1, S_IFCHR | 0644, makedev(5, 1))) {
log_stderr("failure: mknodat");
goto out;
}
/* create hardlink */
if (linkat(t_dir1_fd, FILE1, t_dir1_fd, HARDLINK1, 0)) {
log_stderr("failure: linkat");
goto out;
}
/* create symlink */
if (symlinkat(FILE2, t_dir1_fd, SYMLINK1)) {
log_stderr("failure: symlinkat");
goto out;
}
/* create directory */
if (mkdirat(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(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(t_dir1_fd, FILE1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, FILE2, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, HARDLINK1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, CHRDEV1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(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(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(t_dir1_fd, FILE1, 2000, 2000, 0)) {
log_stderr("failure: fchownat");
goto out;
}
if (fchownat(t_dir1_fd, FILE2, 2000, 2000, 0)) {
log_stderr("failure: fchownat");
goto out;
}
if (fchownat(t_dir1_fd, HARDLINK1, 2000, 2000, 0)) {
log_stderr("failure: fchownat");
goto out;
}
if (fchownat(t_dir1_fd, CHRDEV1, 2000, 2000, 0)) {
log_stderr("failure: fchownat");
goto out;
}
if (fchownat(t_dir1_fd, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) {
log_stderr("failure: fchownat");
goto out;
}
if (fchownat(t_dir1_fd, SYMLINK1, 2000, 2000, AT_EMPTY_PATH)) {
log_stderr("failure: fchownat");
goto out;
}
if (fchownat(t_dir1_fd, DIR1, 2000, 2000, AT_EMPTY_PATH)) {
log_stderr("failure: fchownat");
goto out;
}
/* Check ownership through original mount. */
if (!expected_uid_gid(t_dir1_fd, FILE1, 0, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, FILE2, 0, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, HARDLINK1, 0, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, CHRDEV1, 0, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 3000, 3000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, 0, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(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, t_overflowuid, 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(t_dir1_fd, FILE1, 1000, 1000, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, FILE2, 1000, 1000, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, HARDLINK1, 1000, 1000, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, CHRDEV1, 1000, 1000, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, SYMLINK1, 2000, 2000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, SYMLINK1, 1000, 1000, AT_EMPTY_PATH))
die("failure: fchownat");
if (!fchownat(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(t_dir1_fd, FILE1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, FILE2, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, HARDLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, CHRDEV1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, DIR1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, FILE1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, FILE2, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, HARDLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, CHRDEV1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, SYMLINK1, AT_SYMLINK_NOFOLLOW, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, SYMLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd2, DIR1, 0, t_overflowuid, 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(t_dir1_fd, FILE1, 0, 1000, 1000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, FILE2, 0, 1000, 1000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, HARDLINK1, 0, 1000, 1000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, CHRDEV1, 0, 1000, 1000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, 0, 1000, 1000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(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(t_dir1_fd, FILE1, 0, 0, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, FILE2, 0, 0, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, HARDLINK1, 0, 0, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, CHRDEV1, 0, 0, 0))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, SYMLINK1, 3000, 3000, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW))
die("failure: fchownat");
if (!fchownat(t_dir1_fd, SYMLINK1, 0, 0, AT_EMPTY_PATH))
die("failure: fchownat");
if (!fchownat(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(t_dir1_fd, FILE1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, FILE2, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, HARDLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, CHRDEV1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(t_dir1_fd, DIR1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, FILE1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, FILE2, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, HARDLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, CHRDEV1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, SYMLINK1, AT_SYMLINK_NOFOLLOW, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, SYMLINK1, 0, t_overflowuid, t_overflowgid))
die("failure: expected_uid_gid");
if (!expected_uid_gid(open_tree_fd1, DIR1, 0, t_overflowuid, 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(t_dir1_fd, FILE1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, FILE2, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, HARDLINK1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, CHRDEV1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, AT_SYMLINK_NOFOLLOW, 2000, 2000)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(t_dir1_fd, SYMLINK1, 0, 0, 0)) {
log_stderr("failure: expected_uid_gid");
goto out;
}
if (!expected_uid_gid(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(void)
{
int fret = -1;
int file1_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
pid_t pid;
file1_fd = openat(t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (file1_fd < 0) {
log_stderr("failure: openat");
goto out;
}
/* Skip if vfs caps are unsupported. */
if (set_dummy_vfs_caps(file1_fd, 0, 1000))
return 0;
/* Changing mount properties on a detached mount. */
attr.userns_fd = get_userns_fd(0, 10000, 10000);
if (attr.userns_fd < 0) {
log_stderr("failure: get_userns_fd");
goto out;
}
if (!expected_dummy_vfs_caps_uid(file1_fd, 1000)) {
log_stderr("failure: expected_dummy_vfs_caps_uid");
goto out;
}
pid = fork();
if (pid < 0) {
log_stderr("failure: fork");
goto out;
}
if (pid == 0) {
if (!switch_userns(attr.userns_fd, 0, 0, false))
die("failure: switch_userns");
/*
* On kernels before 5.12 this would succeed and return the
* unconverted caps. Then - for whatever reason - this behavior
* got changed and since 5.12 EOVERFLOW is returned when the
* rootid stored alongside the vfs caps does not map to uid 0 in
* the caller's user namespace.
*/
if (!expected_dummy_vfs_caps_uid(file1_fd, 1000) && errno != EOVERFLOW)
die("failure: expected_dummy_vfs_caps_uid");
exit(EXIT_SUCCESS);
}
if (wait_for_pid(pid))
goto out;
if (fremovexattr(file1_fd, "security.capability")) {
log_stderr("failure: fremovexattr");
goto out;
}
if (expected_dummy_vfs_caps_uid(file1_fd, -1)) {
log_stderr("failure: expected_dummy_vfs_caps_uid");
goto out;
}
if (errno != ENODATA) {
log_stderr("failure: errno");
goto out;
}
if (set_dummy_vfs_caps(file1_fd, 0, 10000)) {
log_stderr("failure: set_dummy_vfs_caps");
goto out;
}
if (!expected_dummy_vfs_caps_uid(file1_fd, 10000)) {
log_stderr("failure: expected_dummy_vfs_caps_uid");
goto out;
}
pid = fork();
if (pid < 0) {
log_stderr("failure: fork");
goto out;
}
if (pid == 0) {
if (!switch_userns(attr.userns_fd, 0, 0, false))
die("failure: switch_userns");
if (!expected_dummy_vfs_caps_uid(file1_fd, 0))
die("failure: expected_dummy_vfs_caps_uid");
exit(EXIT_SUCCESS);
}
if (wait_for_pid(pid))
goto out;
if (fremovexattr(file1_fd, "security.capability")) {
log_stderr("failure: fremovexattr");
goto out;
}
if (expected_dummy_vfs_caps_uid(file1_fd, -1)) {
log_stderr("failure: expected_dummy_vfs_caps_uid");
goto out;
}
if (errno != ENODATA) {
log_stderr("failure: errno");
goto out;
}
fret = 0;
log_debug("Ran test");
out:
safe_close(attr.userns_fd);
safe_close(file1_fd);
return fret;
}
static int fscaps_idmapped_mounts(void)
{
int fret = -1;