| // 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; |
| |