| 1 | /* |
| 2 | * 9p utilities |
| 3 | * |
| 4 | * Copyright IBM, Corp. 2017 |
| 5 | * |
| 6 | * Authors: |
| 7 | * Greg Kurz <groug@kaod.org> |
| 8 | * |
| 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 10 | * See the COPYING file in the top-level directory. |
| 11 | */ |
| 12 | |
| 13 | #ifndef QEMU_9P_UTIL_H |
| 14 | #define QEMU_9P_UTIL_H |
| 15 | |
| 16 | #include "qemu/error-report.h" |
| 17 | |
| 18 | #ifdef O_PATH |
| 19 | #define O_PATH_9P_UTIL O_PATH |
| 20 | #else |
| 21 | #define O_PATH_9P_UTIL 0 |
| 22 | #endif |
| 23 | |
| 24 | #ifdef CONFIG_FREEBSD |
| 25 | /* |
| 26 | * FreeBSD does not have these flags, so we can only emulate their intended |
| 27 | * behaviour (racily). |
| 28 | */ |
| 29 | #define XATTR_CREATE 0x1 |
| 30 | #define XATTR_REPLACE 0x2 |
| 31 | #endif |
| 32 | |
| 33 | #if !defined(CONFIG_LINUX) |
| 34 | |
| 35 | /* |
| 36 | * Generates a Linux device number (a.k.a. dev_t) for given device major |
| 37 | * and minor numbers. |
| 38 | * |
| 39 | * To be more precise: it generates a device number in glibc's format |
| 40 | * (MMMM_Mmmm_mmmM_MMmm, 64 bits) actually, which is compatible with |
| 41 | * Linux's format (mmmM_MMmm, 32 bits), as described in <bits/sysmacros.h>. |
| 42 | */ |
| 43 | static inline uint64_t makedev_dotl(uint32_t dev_major, uint32_t dev_minor) |
| 44 | { |
| 45 | uint64_t dev; |
| 46 | |
| 47 | // from glibc sysmacros.h: |
| 48 | dev = (((uint64_t) (dev_major & 0x00000fffu)) << 8); |
| 49 | dev |= (((uint64_t) (dev_major & 0xfffff000u)) << 32); |
| 50 | dev |= (((uint64_t) (dev_minor & 0x000000ffu)) << 0); |
| 51 | dev |= (((uint64_t) (dev_minor & 0xffffff00u)) << 12); |
| 52 | return dev; |
| 53 | } |
| 54 | |
| 55 | #endif |
| 56 | |
| 57 | /* |
| 58 | * Converts given device number from host's device number format to Linux |
| 59 | * device number format. As both the size of type dev_t and encoding of |
| 60 | * dev_t is system dependent, we have to convert them for Linux guests if |
| 61 | * host is not running Linux. |
| 62 | */ |
| 63 | static inline uint64_t host_dev_to_dotl_dev(dev_t dev) |
| 64 | { |
| 65 | #ifdef CONFIG_LINUX |
| 66 | return dev; |
| 67 | #else |
| 68 | return makedev_dotl(major(dev), minor(dev)); |
| 69 | #endif |
| 70 | } |
| 71 | |
| 72 | /* Translates errno from host -> Linux if needed */ |
| 73 | static inline int errno_to_dotl(int err) { |
| 74 | #if defined(CONFIG_LINUX) |
| 75 | /* nothing to translate (Linux -> Linux) */ |
| 76 | #elif defined(CONFIG_DARWIN) || defined(CONFIG_FREEBSD) |
| 77 | /* |
| 78 | * translation mandatory for non-Linux hosts |
| 79 | * |
| 80 | * FIXME: Only most important errnos translated here yet, this should be |
| 81 | * extended to as many errnos being translated as possible in future. |
| 82 | */ |
| 83 | if (err == ENAMETOOLONG) { |
| 84 | err = 36; /* ==ENAMETOOLONG on Linux */ |
| 85 | } else if (err == ENOTEMPTY) { |
| 86 | err = 39; /* ==ENOTEMPTY on Linux */ |
| 87 | } else if (err == ELOOP) { |
| 88 | err = 40; /* ==ELOOP on Linux */ |
| 89 | } else if (err == ENOATTR) { |
| 90 | err = 61; /* ==ENODATA on Linux */ |
| 91 | } else if (err == ENOTSUP) { |
| 92 | err = 95; /* ==EOPNOTSUPP on Linux */ |
| 93 | } else if (err == EOPNOTSUPP) { |
| 94 | err = 95; /* ==EOPNOTSUPP on Linux */ |
| 95 | } |
| 96 | #else |
| 97 | #error Missing errno translation to Linux for this host system |
| 98 | #endif |
| 99 | return err; |
| 100 | } |
| 101 | |
| 102 | #ifdef CONFIG_DARWIN |
| 103 | #define qemu_fgetxattr(...) fgetxattr(__VA_ARGS__, 0, 0) |
| 104 | #else |
| 105 | #define qemu_fgetxattr fgetxattr |
| 106 | #endif |
| 107 | |
| 108 | #define qemu_openat openat |
| 109 | #define qemu_fstat fstat |
| 110 | #define qemu_fstatat fstatat |
| 111 | #define qemu_mkdirat mkdirat |
| 112 | #define qemu_renameat renameat |
| 113 | #define qemu_utimensat utimensat |
| 114 | #define qemu_unlinkat unlinkat |
| 115 | #define qemu_futimens futimens |
| 116 | |
| 117 | static inline void close_preserve_errno(int fd) |
| 118 | { |
| 119 | int serrno = errno; |
| 120 | close(fd); |
| 121 | errno = serrno; |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * close_if_special_file() - Close @fd if neither regular file nor directory. |
| 126 | * |
| 127 | * @fd: file descriptor of open file |
| 128 | * Return: 0 on regular file or directory, -1 otherwise |
| 129 | * |
| 130 | * CVE-2023-2861: Prohibit opening any special file directly on host |
| 131 | * (especially device files), as a compromised client could potentially gain |
| 132 | * access outside exported tree under certain, unsafe setups. We expect |
| 133 | * client to handle I/O on special files exclusively on guest side. |
| 134 | */ |
| 135 | static inline int close_if_special_file(int fd) |
| 136 | { |
| 137 | struct stat stbuf; |
| 138 | |
| 139 | if (qemu_fstat(fd, &stbuf) < 0) { |
| 140 | close_preserve_errno(fd); |
| 141 | return -1; |
| 142 | } |
| 143 | if (!S_ISREG(stbuf.st_mode) && !S_ISDIR(stbuf.st_mode)) { |
| 144 | error_report_once( |
| 145 | "9p: broken or compromised client detected; attempt to open " |
| 146 | "special file (i.e. neither regular file, nor directory)" |
| 147 | ); |
| 148 | close(fd); |
| 149 | errno = ENXIO; |
| 150 | return -1; |
| 151 | } |
| 152 | |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | static inline int openat_dir(int dirfd, const char *name) |
| 157 | { |
| 158 | return qemu_openat(dirfd, name, |
| 159 | O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_PATH_9P_UTIL); |
| 160 | } |
| 161 | |
| 162 | static inline int openat_file(int dirfd, const char *name, int flags, |
| 163 | mode_t mode) |
| 164 | { |
| 165 | int fd, serrno, ret; |
| 166 | |
| 167 | #if !defined(CONFIG_DARWIN) && !defined(CONFIG_FREEBSD) |
| 168 | again: |
| 169 | #endif |
| 170 | fd = qemu_openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK, |
| 171 | mode); |
| 172 | if (fd == -1) { |
| 173 | #if !defined(CONFIG_DARWIN) && !defined(CONFIG_FREEBSD) |
| 174 | if (errno == EPERM && (flags & O_NOATIME)) { |
| 175 | /* |
| 176 | * The client passed O_NOATIME but we lack permissions to honor it. |
| 177 | * Rather than failing the open, fall back without O_NOATIME. This |
| 178 | * doesn't break the semantics on the client side, as the Linux |
| 179 | * open(2) man page notes that O_NOATIME "may not be effective on |
| 180 | * all filesystems". In particular, NFS and other network |
| 181 | * filesystems ignore it entirely. |
| 182 | */ |
| 183 | flags &= ~O_NOATIME; |
| 184 | goto again; |
| 185 | } |
| 186 | #endif |
| 187 | return -1; |
| 188 | } |
| 189 | |
| 190 | /* Only if O_PATH is not set ... */ |
| 191 | if (!(flags & O_PATH_9P_UTIL)) { |
| 192 | /* |
| 193 | * Prevent I/O on special files (device files, etc.) on host side, |
| 194 | * however it is safe and required to allow opening them with O_PATH, |
| 195 | * as this is limited to (required) path based operations only. |
| 196 | */ |
| 197 | if (close_if_special_file(fd) < 0) { |
| 198 | return -1; |
| 199 | } |
| 200 | |
| 201 | serrno = errno; |
| 202 | /* |
| 203 | * O_NONBLOCK was only needed to open the file. Let's drop it. We don't |
| 204 | * do that with O_PATH since fcntl(F_SETFL) isn't supported, and |
| 205 | * openat() ignored it anyway. |
| 206 | */ |
| 207 | ret = fcntl(fd, F_SETFL, flags); |
| 208 | assert(!ret); |
| 209 | errno = serrno; |
| 210 | } |
| 211 | return fd; |
| 212 | } |
| 213 | |
| 214 | #ifdef CONFIG_FREEBSD |
| 215 | ssize_t fgetxattr(int dirfd, const char *name, void *value, size_t size); |
| 216 | #endif |
| 217 | ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name, |
| 218 | void *value, size_t size); |
| 219 | int fsetxattrat_nofollow(int dirfd, const char *path, const char *name, |
| 220 | void *value, size_t size, int flags); |
| 221 | ssize_t flistxattrat_nofollow(int dirfd, const char *filename, |
| 222 | char *list, size_t size); |
| 223 | ssize_t fremovexattrat_nofollow(int dirfd, const char *filename, |
| 224 | const char *name); |
| 225 | |
| 226 | /* |
| 227 | * Darwin has d_seekoff, which appears to function similarly to d_off. |
| 228 | * However, it does not appear to be supported on all file systems, |
| 229 | * so ensure it is manually injected earlier and call here when |
| 230 | * needed. |
| 231 | */ |
| 232 | static inline off_t qemu_dirent_off(struct dirent *dent) |
| 233 | { |
| 234 | #ifdef CONFIG_DARWIN |
| 235 | return dent->d_seekoff; |
| 236 | #else |
| 237 | return dent->d_off; |
| 238 | #endif |
| 239 | } |
| 240 | |
| 241 | /** |
| 242 | * qemu_dirent_dup() - Duplicate directory entry @dent. |
| 243 | * |
| 244 | * @dent: original directory entry to be duplicated |
| 245 | * Return: duplicated directory entry which should be freed with g_free() |
| 246 | * |
| 247 | * It is highly recommended to use this function instead of open coding |
| 248 | * duplication of dirent objects, because the actual struct dirent |
| 249 | * size may be bigger or shorter than sizeof(struct dirent) and correct |
| 250 | * handling is platform specific (see gitlab issue #841). |
| 251 | */ |
| 252 | static inline struct dirent *qemu_dirent_dup(struct dirent *dent) |
| 253 | { |
| 254 | size_t sz = 0; |
| 255 | #if defined _DIRENT_HAVE_D_RECLEN |
| 256 | /* Avoid use of strlen() if platform supports d_reclen. */ |
| 257 | sz = dent->d_reclen; |
| 258 | #endif |
| 259 | /* |
| 260 | * Test sz for zero even if d_reclen is available |
| 261 | * because some drivers may set d_reclen to zero. |
| 262 | */ |
| 263 | if (sz == 0) { |
| 264 | /* Fallback to the most portable way. */ |
| 265 | sz = offsetof(struct dirent, d_name) + |
| 266 | strlen(dent->d_name) + 1; |
| 267 | } |
| 268 | return g_memdup(dent, sz); |
| 269 | } |
| 270 | |
| 271 | /* |
| 272 | * As long as mknodat is not available on macOS, this workaround |
| 273 | * using pthread_fchdir_np is needed. qemu_mknodat is defined in |
| 274 | * os-posix.c. pthread_fchdir_np is weakly linked here as a guard |
| 275 | * in case it disappears in future macOS versions, because it is |
| 276 | * is a private API. |
| 277 | */ |
| 278 | #if defined CONFIG_DARWIN && defined CONFIG_PTHREAD_FCHDIR_NP |
| 279 | int pthread_fchdir_np(int fd) __attribute__((weak_import)); |
| 280 | #endif |
| 281 | int qemu_mknodat(int dirfd, const char *filename, mode_t mode, dev_t dev); |
| 282 | |
| 283 | /* |
| 284 | * Returns a newly allocated string presentation of open() flags, intended |
| 285 | * for debugging (tracing) purposes only. |
| 286 | */ |
| 287 | char *qemu_open_flags_tostr(int flags); |
| 288 | |
| 289 | #endif |