The 9p protocol does not specifically define how server shall behave when
client tries to open a special file, however from security POV it does
make sense for 9p server to prohibit opening any special file on host side
in general. A sane Linux 9p client for instance would never attempt to
open a special file on host side, it would always handle those exclusively
on its guest side. A malicious client however could potentially escape
from the exported 9p tree by creating and opening a device file on host
side.
With QEMU this could only be exploited in the following unsafe setups:
- Running QEMU binary as root AND 9p 'local' fs driver AND 'passthrough'
security model.
or
- Using 9p 'proxy' fs driver (which is running its helper daemon as
root).
These setups were already discouraged for safety reasons before,
however for obvious reasons we are now tightening behaviour on this.
Fixes: CVE-2023-2861
Reported-by: Yanwu Shen <ywsPlz@gmail.com>
Reported-by: Jietao Xiao <shawtao1125@gmail.com>
Reported-by: Jinku Li <jkli@xidian.edu.cn>
Reported-by: Wenbo Shen <shenwenbo@zju.edu.cn>
Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
Reviewed-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Michael Tokarev <mjt@tls.msk.ru>
Message-Id: <E1q6w7r-0000Q0-NM@lizzy.crudebyte.com>
(cherry picked from commit f6b0de53fb)
Resolves: bsc#1212968
Signed-off-by: Dario Faggioli <dfaggioli@suse.com>
120 lines
3.4 KiB
C
120 lines
3.4 KiB
C
/*
|
|
* 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
|
|
|
|
#include "qemu/error-report.h"
|
|
|
|
#ifdef O_PATH
|
|
#define O_PATH_9P_UTIL O_PATH
|
|
#else
|
|
#define O_PATH_9P_UTIL 0
|
|
#endif
|
|
|
|
static inline void close_preserve_errno(int fd)
|
|
{
|
|
int serrno = errno;
|
|
close(fd);
|
|
errno = serrno;
|
|
}
|
|
|
|
/**
|
|
* 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 (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;
|
|
}
|
|
|
|
static inline int openat_dir(int dirfd, const char *name)
|
|
{
|
|
return openat(dirfd, name,
|
|
O_DIRECTORY | O_RDONLY | O_NOFOLLOW | O_PATH_9P_UTIL);
|
|
}
|
|
|
|
static inline int openat_file(int dirfd, const char *name, int flags,
|
|
mode_t mode)
|
|
{
|
|
int fd, serrno, ret;
|
|
|
|
again:
|
|
fd = openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK,
|
|
mode);
|
|
if (fd == -1) {
|
|
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;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (close_if_special_file(fd) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
serrno = errno;
|
|
/* 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);
|
|
}
|
|
errno = serrno;
|
|
return fd;
|
|
}
|
|
|
|
ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name,
|
|
void *value, size_t size);
|
|
int fsetxattrat_nofollow(int dirfd, const char *path, const char *name,
|
|
void *value, size_t size, int flags);
|
|
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);
|
|
|
|
#endif
|