@@ -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;
+}
@@ -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 */
@@ -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;