2017-02-27 06:42:03 +08:00
|
|
|
/*
|
|
|
|
* 9p utilities
|
|
|
|
*
|
|
|
|
* Copyright IBM, Corp. 2017
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Greg Kurz <groug@kaod.org>
|
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
|
|
* See the COPYING file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef QEMU_9P_UTIL_H
|
|
|
|
#define QEMU_9P_UTIL_H
|
|
|
|
|
2023-06-08 00:29:33 +08:00
|
|
|
#include "qemu/error-report.h"
|
|
|
|
|
2017-08-10 20:21:04 +08:00
|
|
|
#ifdef O_PATH
|
|
|
|
#define O_PATH_9P_UTIL O_PATH
|
|
|
|
#else
|
|
|
|
#define O_PATH_9P_UTIL 0
|
|
|
|
#endif
|
|
|
|
|
2022-04-29 18:25:15 +08:00
|
|
|
#if !defined(CONFIG_LINUX)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generates a Linux device number (a.k.a. dev_t) for given device major
|
|
|
|
* and minor numbers.
|
|
|
|
*
|
|
|
|
* To be more precise: it generates a device number in glibc's format
|
|
|
|
* (MMMM_Mmmm_mmmM_MMmm, 64 bits) actually, which is compatible with
|
|
|
|
* Linux's format (mmmM_MMmm, 32 bits), as described in <bits/sysmacros.h>.
|
|
|
|
*/
|
|
|
|
static inline uint64_t makedev_dotl(uint32_t dev_major, uint32_t dev_minor)
|
|
|
|
{
|
|
|
|
uint64_t dev;
|
|
|
|
|
|
|
|
// from glibc sysmacros.h:
|
|
|
|
dev = (((uint64_t) (dev_major & 0x00000fffu)) << 8);
|
|
|
|
dev |= (((uint64_t) (dev_major & 0xfffff000u)) << 32);
|
|
|
|
dev |= (((uint64_t) (dev_minor & 0x000000ffu)) << 0);
|
|
|
|
dev |= (((uint64_t) (dev_minor & 0xffffff00u)) << 12);
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Converts given device number from host's device number format to Linux
|
|
|
|
* device number format. As both the size of type dev_t and encoding of
|
|
|
|
* dev_t is system dependant, we have to convert them for Linux guests if
|
|
|
|
* host is not running Linux.
|
|
|
|
*/
|
|
|
|
static inline uint64_t host_dev_to_dotl_dev(dev_t dev)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_LINUX
|
|
|
|
return dev;
|
|
|
|
#else
|
|
|
|
return makedev_dotl(major(dev), minor(dev));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-04-29 18:25:18 +08:00
|
|
|
/* Translates errno from host -> Linux if needed */
|
|
|
|
static inline int errno_to_dotl(int err) {
|
|
|
|
#if defined(CONFIG_LINUX)
|
|
|
|
/* nothing to translate (Linux -> Linux) */
|
|
|
|
#elif defined(CONFIG_DARWIN)
|
|
|
|
/*
|
|
|
|
* translation mandatory for macOS hosts
|
|
|
|
*
|
|
|
|
* FIXME: Only most important errnos translated here yet, this should be
|
|
|
|
* extended to as many errnos being translated as possible in future.
|
|
|
|
*/
|
|
|
|
if (err == ENAMETOOLONG) {
|
|
|
|
err = 36; /* ==ENAMETOOLONG on Linux */
|
|
|
|
} else if (err == ENOTEMPTY) {
|
|
|
|
err = 39; /* ==ENOTEMPTY on Linux */
|
|
|
|
} else if (err == ELOOP) {
|
|
|
|
err = 40; /* ==ELOOP on Linux */
|
|
|
|
} else if (err == ENOATTR) {
|
|
|
|
err = 61; /* ==ENODATA on Linux */
|
|
|
|
} else if (err == ENOTSUP) {
|
|
|
|
err = 95; /* ==EOPNOTSUPP on Linux */
|
|
|
|
} else if (err == EOPNOTSUPP) {
|
|
|
|
err = 95; /* ==EOPNOTSUPP on Linux */
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#error Missing errno translation to Linux for this host system
|
|
|
|
#endif
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2022-02-28 06:35:19 +08:00
|
|
|
#ifdef CONFIG_DARWIN
|
|
|
|
#define qemu_fgetxattr(...) fgetxattr(__VA_ARGS__, 0, 0)
|
|
|
|
#else
|
|
|
|
#define qemu_fgetxattr fgetxattr
|
|
|
|
#endif
|
|
|
|
|
2022-12-19 18:20:07 +08:00
|
|
|
#define qemu_openat openat
|
2023-06-08 00:29:33 +08:00
|
|
|
#define qemu_fstat fstat
|
2022-12-19 18:20:07 +08:00
|
|
|
#define qemu_fstatat fstatat
|
|
|
|
#define qemu_mkdirat mkdirat
|
|
|
|
#define qemu_renameat renameat
|
|
|
|
#define qemu_utimensat utimensat
|
|
|
|
#define qemu_unlinkat unlinkat
|
|
|
|
|
2017-02-27 06:42:03 +08:00
|
|
|
static inline void close_preserve_errno(int fd)
|
|
|
|
{
|
|
|
|
int serrno = errno;
|
|
|
|
close(fd);
|
|
|
|
errno = serrno;
|
|
|
|
}
|
|
|
|
|
2023-06-08 00:29:33 +08:00
|
|
|
/**
|
|
|
|
* close_if_special_file() - Close @fd if neither regular file nor directory.
|
|
|
|
*
|
|
|
|
* @fd: file descriptor of open file
|
|
|
|
* Return: 0 on regular file or directory, -1 otherwise
|
|
|
|
*
|
|
|
|
* CVE-2023-2861: Prohibit opening any special file directly on host
|
|
|
|
* (especially device files), as a compromised client could potentially gain
|
|
|
|
* access outside exported tree under certain, unsafe setups. We expect
|
|
|
|
* client to handle I/O on special files exclusively on guest side.
|
|
|
|
*/
|
|
|
|
static inline int close_if_special_file(int fd)
|
|
|
|
{
|
|
|
|
struct stat stbuf;
|
|
|
|
|
|
|
|
if (qemu_fstat(fd, &stbuf) < 0) {
|
|
|
|
close_preserve_errno(fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!S_ISREG(stbuf.st_mode) && !S_ISDIR(stbuf.st_mode)) {
|
|
|
|
error_report_once(
|
|
|
|
"9p: broken or compromised client detected; attempt to open "
|
|
|
|
"special file (i.e. neither regular file, nor directory)"
|
|
|
|
);
|
|
|
|
close(fd);
|
|
|
|
errno = ENXIO;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-02-27 06:42:03 +08:00
|
|
|
static inline int openat_dir(int dirfd, const char *name)
|
|
|
|
{
|
2022-12-19 18:20:07 +08:00
|
|
|
return qemu_openat(dirfd, name,
|
|
|
|
O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_PATH_9P_UTIL);
|
2017-02-27 06:42:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int openat_file(int dirfd, const char *name, int flags,
|
|
|
|
mode_t mode)
|
|
|
|
{
|
|
|
|
int fd, serrno, ret;
|
|
|
|
|
2022-02-28 06:35:16 +08:00
|
|
|
#ifndef CONFIG_DARWIN
|
9pfs: local: ignore O_NOATIME if we don't have permissions
QEMU's local 9pfs server passes through O_NOATIME from the client. If
the QEMU process doesn't have permissions to use O_NOATIME (namely, it
does not own the file nor have the CAP_FOWNER capability), the open will
fail. This causes issues when from the client's point of view, it
believes it has permissions to use O_NOATIME (e.g., a process running as
root in the virtual machine). Additionally, overlayfs on Linux opens
files on the lower layer using O_NOATIME, so in this case a 9pfs mount
can't be used as a lower layer for overlayfs (cf.
https://github.com/osandov/drgn/blob/dabfe1971951701da13863dbe6d8a1d172ad9650/vmtest/onoatimehack.c
and https://github.com/NixOS/nixpkgs/issues/54509).
Luckily, O_NOATIME is effectively a hint, and is often ignored by, e.g.,
network filesystems. open(2) notes that O_NOATIME "may not be effective
on all filesystems. One example is NFS, where the server maintains the
access time." This means that we can honor it when possible but fall
back to ignoring it.
Acked-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
Signed-off-by: Omar Sandoval <osandov@fb.com>
Message-Id: <e9bee604e8df528584693a4ec474ded6295ce8ad.1587149256.git.osandov@fb.com>
Signed-off-by: Greg Kurz <groug@kaod.org>
2020-05-14 14:06:43 +08:00
|
|
|
again:
|
2022-02-28 06:35:16 +08:00
|
|
|
#endif
|
2022-12-19 18:20:07 +08:00
|
|
|
fd = qemu_openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK,
|
|
|
|
mode);
|
2017-02-27 06:42:03 +08:00
|
|
|
if (fd == -1) {
|
2022-02-28 06:35:16 +08:00
|
|
|
#ifndef CONFIG_DARWIN
|
9pfs: local: ignore O_NOATIME if we don't have permissions
QEMU's local 9pfs server passes through O_NOATIME from the client. If
the QEMU process doesn't have permissions to use O_NOATIME (namely, it
does not own the file nor have the CAP_FOWNER capability), the open will
fail. This causes issues when from the client's point of view, it
believes it has permissions to use O_NOATIME (e.g., a process running as
root in the virtual machine). Additionally, overlayfs on Linux opens
files on the lower layer using O_NOATIME, so in this case a 9pfs mount
can't be used as a lower layer for overlayfs (cf.
https://github.com/osandov/drgn/blob/dabfe1971951701da13863dbe6d8a1d172ad9650/vmtest/onoatimehack.c
and https://github.com/NixOS/nixpkgs/issues/54509).
Luckily, O_NOATIME is effectively a hint, and is often ignored by, e.g.,
network filesystems. open(2) notes that O_NOATIME "may not be effective
on all filesystems. One example is NFS, where the server maintains the
access time." This means that we can honor it when possible but fall
back to ignoring it.
Acked-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
Signed-off-by: Omar Sandoval <osandov@fb.com>
Message-Id: <e9bee604e8df528584693a4ec474ded6295ce8ad.1587149256.git.osandov@fb.com>
Signed-off-by: Greg Kurz <groug@kaod.org>
2020-05-14 14:06:43 +08:00
|
|
|
if (errno == EPERM && (flags & O_NOATIME)) {
|
|
|
|
/*
|
|
|
|
* The client passed O_NOATIME but we lack permissions to honor it.
|
|
|
|
* Rather than failing the open, fall back without O_NOATIME. This
|
|
|
|
* doesn't break the semantics on the client side, as the Linux
|
|
|
|
* open(2) man page notes that O_NOATIME "may not be effective on
|
|
|
|
* all filesystems". In particular, NFS and other network
|
|
|
|
* filesystems ignore it entirely.
|
|
|
|
*/
|
|
|
|
flags &= ~O_NOATIME;
|
|
|
|
goto again;
|
|
|
|
}
|
2022-02-28 06:35:16 +08:00
|
|
|
#endif
|
2017-02-27 06:42:03 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-06-08 00:29:33 +08:00
|
|
|
if (close_if_special_file(fd) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-02-27 06:42:03 +08:00
|
|
|
serrno = errno;
|
2017-08-10 20:21:04 +08:00
|
|
|
/* O_NONBLOCK was only needed to open the file. Let's drop it. We don't
|
|
|
|
* do that with O_PATH since fcntl(F_SETFL) isn't supported, and openat()
|
|
|
|
* ignored it anyway.
|
|
|
|
*/
|
|
|
|
if (!(flags & O_PATH_9P_UTIL)) {
|
|
|
|
ret = fcntl(fd, F_SETFL, flags);
|
|
|
|
assert(!ret);
|
|
|
|
}
|
2017-02-27 06:42:03 +08:00
|
|
|
errno = serrno;
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
2017-02-27 06:42:26 +08:00
|
|
|
ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name,
|
|
|
|
void *value, size_t size);
|
2017-02-27 06:42:43 +08:00
|
|
|
int fsetxattrat_nofollow(int dirfd, const char *path, const char *name,
|
|
|
|
void *value, size_t size, int flags);
|
2018-06-07 18:17:22 +08:00
|
|
|
ssize_t flistxattrat_nofollow(int dirfd, const char *filename,
|
|
|
|
char *list, size_t size);
|
|
|
|
ssize_t fremovexattrat_nofollow(int dirfd, const char *filename,
|
|
|
|
const char *name);
|
2017-02-27 06:42:03 +08:00
|
|
|
|
2022-03-03 21:20:29 +08:00
|
|
|
/*
|
2022-02-28 06:35:15 +08:00
|
|
|
* Darwin has d_seekoff, which appears to function similarly to d_off.
|
|
|
|
* However, it does not appear to be supported on all file systems,
|
|
|
|
* so ensure it is manually injected earlier and call here when
|
|
|
|
* needed.
|
|
|
|
*/
|
|
|
|
static inline off_t qemu_dirent_off(struct dirent *dent)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_DARWIN
|
|
|
|
return dent->d_seekoff;
|
|
|
|
#else
|
|
|
|
return dent->d_off;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-03-02 04:33:49 +08:00
|
|
|
/**
|
2022-03-03 21:20:29 +08:00
|
|
|
* qemu_dirent_dup() - Duplicate directory entry @dent.
|
|
|
|
*
|
|
|
|
* @dent: original directory entry to be duplicated
|
|
|
|
* Return: duplicated directory entry which should be freed with g_free()
|
2022-03-02 04:33:49 +08:00
|
|
|
*
|
|
|
|
* It is highly recommended to use this function instead of open coding
|
2022-03-03 02:30:39 +08:00
|
|
|
* duplication of dirent objects, because the actual struct dirent
|
|
|
|
* size may be bigger or shorter than sizeof(struct dirent) and correct
|
2022-03-02 04:33:49 +08:00
|
|
|
* handling is platform specific (see gitlab issue #841).
|
|
|
|
*/
|
|
|
|
static inline struct dirent *qemu_dirent_dup(struct dirent *dent)
|
|
|
|
{
|
|
|
|
size_t sz = 0;
|
|
|
|
#if defined _DIRENT_HAVE_D_RECLEN
|
|
|
|
/* Avoid use of strlen() if platform supports d_reclen. */
|
|
|
|
sz = dent->d_reclen;
|
|
|
|
#endif
|
|
|
|
/*
|
|
|
|
* Test sz for zero even if d_reclen is available
|
|
|
|
* because some drivers may set d_reclen to zero.
|
|
|
|
*/
|
|
|
|
if (sz == 0) {
|
|
|
|
/* Fallback to the most portable way. */
|
|
|
|
sz = offsetof(struct dirent, d_name) +
|
|
|
|
strlen(dent->d_name) + 1;
|
|
|
|
}
|
|
|
|
return g_memdup(dent, sz);
|
|
|
|
}
|
|
|
|
|
2022-02-28 06:35:20 +08:00
|
|
|
/*
|
|
|
|
* As long as mknodat is not available on macOS, this workaround
|
|
|
|
* using pthread_fchdir_np is needed. qemu_mknodat is defined in
|
|
|
|
* os-posix.c. pthread_fchdir_np is weakly linked here as a guard
|
|
|
|
* in case it disappears in future macOS versions, because it is
|
|
|
|
* is a private API.
|
|
|
|
*/
|
|
|
|
#if defined CONFIG_DARWIN && defined CONFIG_PTHREAD_FCHDIR_NP
|
|
|
|
int pthread_fchdir_np(int fd) __attribute__((weak_import));
|
|
|
|
#endif
|
|
|
|
int qemu_mknodat(int dirfd, const char *filename, mode_t mode, dev_t dev);
|
2022-02-28 06:35:15 +08:00
|
|
|
|
2017-02-27 06:42:03 +08:00
|
|
|
#endif
|