1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Author: Aleksa Sarai <cyphar@cyphar.com> 4 * Copyright (C) 2018-2019 SUSE LLC. 5 * Copyright (C) 2026 Amutable GmbH 6 */ 7 8 #ifndef __RESOLVEAT_H__ 9 #define __RESOLVEAT_H__ 10 11 #define _GNU_SOURCE 12 #include <stdint.h> 13 #include <stdbool.h> 14 #include <errno.h> 15 #include <limits.h> 16 #include <linux/types.h> 17 #include <linux/unistd.h> 18 #include "kselftest.h" 19 20 #define ARRAY_LEN(X) (sizeof (X) / sizeof (*(X))) 21 #define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); }))) 22 23 /* 24 * Arguments for how openat2(2) should open the target path. If @resolve is 25 * zero, then openat2(2) operates very similarly to openat(2). 26 * 27 * However, unlike openat(2), unknown bits in @flags result in -EINVAL rather 28 * than being silently ignored. @mode must be zero unless one of {O_CREAT, 29 * O_TMPFILE} are set. 30 * 31 * @flags: O_* flags. 32 * @mode: O_CREAT/O_TMPFILE file mode. 33 * @resolve: RESOLVE_* flags. 34 */ 35 struct open_how { 36 __u64 flags; 37 __u64 mode; 38 __u64 resolve; 39 }; 40 41 #define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */ 42 #define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0 43 44 #ifndef RESOLVE_IN_ROOT 45 /* how->resolve flags for openat2(2). */ 46 #define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings 47 (includes bind-mounts). */ 48 #define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style 49 "magic-links". */ 50 #define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks 51 (implies OEXT_NO_MAGICLINKS) */ 52 #define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like 53 "..", symlinks, and absolute 54 paths which escape the dirfd. */ 55 #define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".." 56 be scoped inside the dirfd 57 (similar to chroot(2)). */ 58 #endif /* RESOLVE_IN_ROOT */ 59 60 #define E_func(func, ...) \ 61 do { \ 62 errno = 0; \ 63 if (func(__VA_ARGS__) < 0) \ 64 ksft_exit_fail_msg("%s:%d %s failed - errno:%d\n", \ 65 __FILE__, __LINE__, #func, errno); \ 66 } while (0) 67 68 #define E_asprintf(...) E_func(asprintf, __VA_ARGS__) 69 #define E_chmod(...) E_func(chmod, __VA_ARGS__) 70 #define E_dup2(...) E_func(dup2, __VA_ARGS__) 71 #define E_fchdir(...) E_func(fchdir, __VA_ARGS__) 72 #define E_fstatat(...) E_func(fstatat, __VA_ARGS__) 73 #define E_kill(...) E_func(kill, __VA_ARGS__) 74 #define E_mkdirat(...) E_func(mkdirat, __VA_ARGS__) 75 #define E_mount(...) E_func(mount, __VA_ARGS__) 76 #define E_prctl(...) E_func(prctl, __VA_ARGS__) 77 #define E_readlink(...) E_func(readlink, __VA_ARGS__) 78 #define E_setresuid(...) E_func(setresuid, __VA_ARGS__) 79 #define E_symlinkat(...) E_func(symlinkat, __VA_ARGS__) 80 #define E_touchat(...) E_func(touchat, __VA_ARGS__) 81 #define E_unshare(...) E_func(unshare, __VA_ARGS__) 82 83 #define E_assert(expr, msg, ...) \ 84 do { \ 85 if (!(expr)) \ 86 ksft_exit_fail_msg("ASSERT(%s:%d) failed (%s): " msg "\n", \ 87 __FILE__, __LINE__, #expr, ##__VA_ARGS__); \ 88 } while (0) 89 90 __maybe_unused 91 static bool needs_openat2(const struct open_how *how) 92 { 93 return how->resolve != 0; 94 } 95 96 __maybe_unused 97 static int raw_openat2(int dfd, const char *path, void *how, size_t size) 98 { 99 int ret = syscall(__NR_openat2, dfd, path, how, size); 100 101 return ret >= 0 ? ret : -errno; 102 } 103 104 __maybe_unused 105 static int sys_openat2(int dfd, const char *path, struct open_how *how) 106 { 107 return raw_openat2(dfd, path, how, sizeof(*how)); 108 } 109 110 __maybe_unused 111 static int sys_openat(int dfd, const char *path, struct open_how *how) 112 { 113 int ret = openat(dfd, path, how->flags, how->mode); 114 115 return ret >= 0 ? ret : -errno; 116 } 117 118 __maybe_unused 119 static int sys_renameat2(int olddirfd, const char *oldpath, 120 int newdirfd, const char *newpath, unsigned int flags) 121 { 122 int ret = syscall(__NR_renameat2, olddirfd, oldpath, 123 newdirfd, newpath, flags); 124 125 return ret >= 0 ? ret : -errno; 126 } 127 128 __maybe_unused 129 static int touchat(int dfd, const char *path) 130 { 131 int fd = openat(dfd, path, O_CREAT, 0700); 132 133 if (fd >= 0) 134 close(fd); 135 return fd; 136 } 137 138 __maybe_unused 139 static char *fdreadlink(int fd) 140 { 141 char *target, *tmp; 142 143 E_asprintf(&tmp, "/proc/self/fd/%d", fd); 144 145 target = malloc(PATH_MAX); 146 if (!target) 147 ksft_exit_fail_msg("fdreadlink: malloc failed\n"); 148 memset(target, 0, PATH_MAX); 149 150 E_readlink(tmp, target, PATH_MAX); 151 free(tmp); 152 return target; 153 } 154 155 __maybe_unused 156 static bool fdequal(int fd, int dfd, const char *path) 157 { 158 char *fdpath, *dfdpath, *other; 159 bool cmp; 160 161 fdpath = fdreadlink(fd); 162 dfdpath = fdreadlink(dfd); 163 164 if (!path) 165 E_asprintf(&other, "%s", dfdpath); 166 else if (*path == '/') 167 E_asprintf(&other, "%s", path); 168 else 169 E_asprintf(&other, "%s/%s", dfdpath, path); 170 171 cmp = !strcmp(fdpath, other); 172 173 free(fdpath); 174 free(dfdpath); 175 free(other); 176 return cmp; 177 } 178 179 static bool openat2_supported = false; 180 181 __attribute__((constructor)) 182 static void __detect_openat2_supported(void) 183 { 184 struct open_how how = {}; 185 int fd; 186 187 BUILD_BUG_ON(sizeof(struct open_how) != OPEN_HOW_SIZE_VER0); 188 189 /* Check openat2(2) support. */ 190 fd = sys_openat2(AT_FDCWD, ".", &how); 191 openat2_supported = (fd >= 0); 192 193 if (fd >= 0) 194 close(fd); 195 } 196 197 #endif /* __RESOLVEAT_H__ */ 198