diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/vfs/utils.c | 495 | ||||
-rw-r--r-- | src/vfs/utils.h | 196 | ||||
-rw-r--r-- | src/vfs/vfstest.c | 659 |
3 files changed, 691 insertions, 659 deletions
diff --git a/src/vfs/utils.c b/src/vfs/utils.c index faf06fcd..1634e5c8 100644 --- a/src/vfs/utils.c +++ b/src/vfs/utils.c @@ -9,12 +9,14 @@ #include <stdio.h> #include <stdlib.h> #include <sys/eventfd.h> +#include <sys/fsuid.h> #include <sys/mount.h> #include <sys/prctl.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> +#include <sys/xattr.h> #include "utils.h" @@ -423,3 +425,496 @@ int add_map_entry(struct list *head, list_add_tail(head, new_list); return 0; } + +/* __expected_uid_gid - check whether file is owned by the provided uid and gid */ +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; +} + +/* caps_down - lower all effective caps */ +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_down_fsetid - lower CAP_FSETID effective cap */ +int caps_down_fsetid(void) +{ + bool fret = false; +#ifdef HAVE_SYS_CAPABILITY_H + cap_t caps = NULL; + cap_value_t cap = CAP_FSETID; + int ret = -1; + + caps = cap_get_proc(); + if (!caps) + goto out; + + ret = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, 0); + if (ret) + goto out; + + ret = cap_set_proc(caps); + if (ret) + goto out; + + fret = true; + +out: + cap_free(caps); +#endif + return fret; +} + +#ifdef HAVE_LIBURING_H +int io_uring_openat_with_creds(struct io_uring *ring, int dfd, const char *path, + int cred_id, bool with_link, int *ret_cqe) +{ + struct io_uring_cqe *cqe; + struct io_uring_sqe *sqe; + int ret, i, to_submit = 1; + + if (with_link) { + sqe = io_uring_get_sqe(ring); + if (!sqe) + return log_error_errno(-EINVAL, EINVAL, "failure: io_uring_sqe"); + io_uring_prep_nop(sqe); + sqe->flags |= IOSQE_IO_LINK; + sqe->user_data = 1; + to_submit++; + } + + sqe = io_uring_get_sqe(ring); + if (!sqe) + return log_error_errno(-EINVAL, EINVAL, "failure: io_uring_sqe"); + io_uring_prep_openat(sqe, dfd, path, O_RDONLY | O_CLOEXEC, 0); + sqe->user_data = 2; + + if (cred_id != -1) + sqe->personality = cred_id; + + ret = io_uring_submit(ring); + if (ret != to_submit) { + log_stderr("failure: io_uring_submit"); + goto out; + } + + for (i = 0; i < to_submit; i++) { + ret = io_uring_wait_cqe(ring, &cqe); + if (ret < 0) { + log_stderr("failure: io_uring_wait_cqe"); + goto out; + } + + ret = cqe->res; + /* + * Make sure caller can identify that this is a proper io_uring + * failure and not some earlier error. + */ + if (ret_cqe) + *ret_cqe = ret; + io_uring_cqe_seen(ring, cqe); + } + log_debug("Ran test"); +out: + return ret; +} +#endif /* HAVE_LIBURING_H */ + +/* caps_up - raise all permitted caps */ +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; +} + +/* chown_r - recursively change ownership of all files */ +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; +} + +/* expected_dummy_vfs_caps_uid - check vfs caps are stored with the provided uid */ +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 */ +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); +} + +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 is_xfs(const char *fstype) +{ + static int enabled = -1; + + if (enabled == -1) + enabled = !strcmp(fstype, "xfs"); + + return enabled; +} + +bool xfs_irix_sgid_inherit_enabled(const char *fstype) +{ + static int enabled = -1; + + if (enabled == -1) { + int fd; + ssize_t ret; + char buf[256]; + + enabled = 0; + + if (is_xfs(fstype)) { + 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; +} + +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 */ +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 */ +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 */ +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; +} + +bool switch_resids(uid_t uid, gid_t gid) +{ + if (setresgid(gid, gid, gid)) + return log_errno(false, "failure: setregid"); + + if (setresuid(uid, uid, uid)) + return log_errno(false, "failure: setresuid"); + + if (setfsgid(-1) != gid) + return log_errno(false, "failure: setfsgid(-1)"); + + if (setfsuid(-1) != uid) + return log_errno(false, "failure: setfsuid(-1)"); + + return true; +} + +/* rm_r - recursively remove all files */ +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; +} + +/* fd_to_fd - transfer data from one fd to another */ +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; +} + +bool openat_tmpfile_supported(int dirfd) +{ + int fd = -1; + + fd = openat(dirfd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); + if (fd == -1) { + if (errno == ENOTSUP) + return false; + else + return log_errno(false, "failure: create"); + } + + if (close(fd)) + log_stderr("failure: close"); + + return true; +} diff --git a/src/vfs/utils.h b/src/vfs/utils.h index 54947ec7..d8ec3c5e 100644 --- a/src/vfs/utils.h +++ b/src/vfs/utils.h @@ -17,15 +17,41 @@ #include <stdlib.h> #include <string.h> #include <syscall.h> +#include <sys/fsuid.h> #include <sys/types.h> #include <unistd.h> +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif + +#ifdef HAVE_LIBURING_H +#include <liburing.h> +#endif + #include "missing.h" /* Flags for which functionality is required by the test */ #define T_REQUIRE_IDMAPPED_MOUNTS (1U << 0) #define T_REQUIRE_USERNS (1U << 1) +#define T_DIR1 "idmapped_mounts_1" +#define FILE1 "file1" +#define FILE1_RENAME "file1_rename" +#define FILE2 "file2" +#define FILE3 "file3" +#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" + /* Maximum number of nested user namespaces in the kernel. */ #define MAX_USERNS_LEVEL 32 @@ -41,6 +67,37 @@ ? 10 \ : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)]))) +#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 syserror(format, ...) \ ({ \ fprintf(stderr, "%m - " format "\n", ##__VA_ARGS__); \ @@ -55,6 +112,68 @@ __internal_ret__; \ }) +#define safe_close(fd) \ + if (fd >= 0) { \ + int _e_ = errno; \ + close(fd); \ + errno = _e_; \ + fd = -EBADF; \ + } + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +#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 + struct vfstest_info { uid_t t_overflowuid; gid_t t_overflowgid; @@ -159,9 +278,86 @@ extern int get_userns_fd_from_idmap(struct list *idmap); extern ssize_t read_nointr(int fd, void *buf, size_t count); extern int wait_for_pid(pid_t pid); extern ssize_t write_nointr(int fd, const void *buf, size_t count); + +extern int caps_down(void); +extern int caps_down_fsetid(void); +extern int caps_up(void); +static inline bool caps_supported(void) +{ + bool ret = false; + +#ifdef HAVE_SYS_CAPABILITY_H + ret = true; +#endif + + return ret; +} +extern bool expected_dummy_vfs_caps_uid(int fd, uid_t expected_uid); +extern int set_dummy_vfs_caps(int fd, int flags, int rootuid); + extern bool switch_ids(uid_t uid, gid_t gid); + extern int create_userns_hierarchy(struct userns_hierarchy *h); extern int add_map_entry(struct list *head, __u32 id_host, __u32 id_ns, __u32 range, idmap_type_t map_type); +extern bool __expected_uid_gid(int dfd, const char *path, int flags, + uid_t expected_uid, gid_t expected_gid, bool log); +static inline 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 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; +} + +extern bool switch_resids(uid_t uid, gid_t gid); + +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; +} + +#ifdef HAVE_LIBURING_H +extern int io_uring_openat_with_creds(struct io_uring *ring, int dfd, + const char *path, int cred_id, + bool with_link, int *ret_cqe); +#endif /* HAVE_LIBURING_H */ + +extern int chown_r(int fd, const char *path, uid_t uid, gid_t gid); +extern int rm_r(int fd, const char *path); +extern int fd_to_fd(int from, int to); +extern bool protected_symlinks_enabled(void); +extern bool xfs_irix_sgid_inherit_enabled(const char *fstype); +extern bool expected_file_size(int dfd, const char *path, int flags, + off_t expected_size); +extern bool is_setid(int dfd, const char *path, int flags); +extern bool is_setgid(int dfd, const char *path, int flags); +extern bool is_sticky(int dfd, const char *path, int flags); +extern bool openat_tmpfile_supported(int dirfd); + #endif /* __IDMAP_UTILS_H */ diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c index 86b79499..8a68565c 100644 --- a/src/vfs/vfstest.c +++ b/src/vfs/vfstest.c @@ -35,67 +35,9 @@ #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 FILE3 "file3" -#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])) - static char t_buf[PATH_MAX]; static void init_vfstest_info(struct vfstest_info *info) @@ -149,418 +91,6 @@ static void stash_overflowgid(struct vfstest_info *info) info->t_overflowgid = atoi(buf); } -static bool is_xfs(const char *fstype) -{ - static int enabled = -1; - - if (enabled == -1) - enabled = !strcmp(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(const char *fstype) -{ - static int enabled = -1; - - if (enabled == -1) { - int fd; - ssize_t ret; - char buf[256]; - - enabled = 0; - - if (is_xfs(fstype)) { - 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_fsetid - lower CAP_FSETID effective cap */ -static int caps_down_fsetid(void) -{ - bool fret = false; -#ifdef HAVE_SYS_CAPABILITY_H - cap_t caps = NULL; - cap_value_t cap = CAP_FSETID; - int ret = -1; - - caps = cap_get_proc(); - if (!caps) - goto out; - - ret = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, 0); - if (ret) - goto out; - - ret = cap_set_proc(caps); - if (ret) - goto out; - - fret = true; - -out: - cap_free(caps); -#endif - return fret; -} - -/* 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; -} - -static bool openat_tmpfile_supported(int dirfd) -{ - int fd = -1; - - fd = openat(dirfd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID); - if (fd == -1) { - if (errno == ENOTSUP) - return false; - else - return log_errno(false, "failure: create"); - } - - if (close(fd)) - log_stderr("failure: close"); - - return true; -} - -/* __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_resids(uid_t uid, gid_t gid) -{ - if (setresgid(gid, gid, gid)) - return log_errno(false, "failure: setregid"); - - if (setresuid(uid, uid, uid)) - return log_errno(false, "failure: setresuid"); - - if (setfsgid(-1) != gid) - return log_errno(false, "failure: setfsgid(-1)"); - - if (setfsuid(-1) != uid) - 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. @@ -696,37 +226,6 @@ __attribute__((unused)) static int print_r(int fd, const char *path) } #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) { @@ -738,111 +237,6 @@ static int sys_execveat(int fd, const char *path, char **argv, char **envp, #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(struct vfstest_info *info) { if (mkdirat(info->t_mnt_fd, T_DIR1, 0777)) @@ -6972,59 +6366,6 @@ out: } #ifdef HAVE_LIBURING_H -static int io_uring_openat_with_creds(struct io_uring *ring, int dfd, const char *path, int cred_id, - bool with_link, int *ret_cqe) -{ - struct io_uring_cqe *cqe; - struct io_uring_sqe *sqe; - int ret, i, to_submit = 1; - - if (with_link) { - sqe = io_uring_get_sqe(ring); - if (!sqe) - return log_error_errno(-EINVAL, EINVAL, "failure: io_uring_sqe"); - io_uring_prep_nop(sqe); - sqe->flags |= IOSQE_IO_LINK; - sqe->user_data = 1; - to_submit++; - } - - sqe = io_uring_get_sqe(ring); - if (!sqe) - return log_error_errno(-EINVAL, EINVAL, "failure: io_uring_sqe"); - io_uring_prep_openat(sqe, dfd, path, O_RDONLY | O_CLOEXEC, 0); - sqe->user_data = 2; - - if (cred_id != -1) - sqe->personality = cred_id; - - ret = io_uring_submit(ring); - if (ret != to_submit) { - log_stderr("failure: io_uring_submit"); - goto out; - } - - for (i = 0; i < to_submit; i++) { - ret = io_uring_wait_cqe(ring, &cqe); - if (ret < 0) { - log_stderr("failure: io_uring_wait_cqe"); - goto out; - } - - ret = cqe->res; - /* - * Make sure caller can identify that this is a proper io_uring - * failure and not some earlier error. - */ - if (ret_cqe) - *ret_cqe = ret; - io_uring_cqe_seen(ring, cqe); - } - log_debug("Ran test"); -out: - return ret; -} - static int io_uring(const struct vfstest_info *info) { int fret = -1; |