diff options
author | Greg Kurz <groug@kaod.org> | 2017-02-26 23:43:40 +0100 |
---|---|---|
committer | Greg Kurz <groug@kaod.org> | 2017-02-28 11:21:15 +0100 |
commit | bec1e9546e03b9e7f5152cf3e8c95cf8acff5e12 (patch) | |
tree | d16f6162f599a7cdfa35e06f0175867a3065e6a4 | |
parent | ac125d993b461d4dee4d6df4d93ac3f2eb959d1d (diff) | |
download | qemu-bec1e9546e03b9e7f5152cf3e8c95cf8acff5e12.zip qemu-bec1e9546e03b9e7f5152cf3e8c95cf8acff5e12.tar.gz qemu-bec1e9546e03b9e7f5152cf3e8c95cf8acff5e12.tar.bz2 |
9pfs: local: readlink: don't follow symlinks
The local_readlink() callback is vulnerable to symlink attacks because it
calls:
(1) open(O_NOFOLLOW) which follows symbolic links for all path elements but
the rightmost one
(2) readlink() which follows symbolic links for all path elements but the
rightmost one
This patch converts local_readlink() to rely on open_nofollow() to fix (1)
and opendir_nofollow(), readlinkat() to fix (2).
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
-rw-r--r-- | hw/9pfs/9p-local.c | 26 |
1 files changed, 17 insertions, 9 deletions
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c index 1a3dfd7..e373cca 100644 --- a/hw/9pfs/9p-local.c +++ b/hw/9pfs/9p-local.c @@ -340,27 +340,35 @@ static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path, char *buf, size_t bufsz) { ssize_t tsize = -1; - char *buffer; - char *path = fs_path->data; if ((fs_ctx->export_flags & V9FS_SM_MAPPED) || (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) { int fd; - buffer = rpath(fs_ctx, path); - fd = open(buffer, O_RDONLY | O_NOFOLLOW); - g_free(buffer); + + fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0); if (fd == -1) { return -1; } do { tsize = read(fd, (void *)buf, bufsz); } while (tsize == -1 && errno == EINTR); - close(fd); + close_preserve_errno(fd); } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) || (fs_ctx->export_flags & V9FS_SM_NONE)) { - buffer = rpath(fs_ctx, path); - tsize = readlink(buffer, buf, bufsz); - g_free(buffer); + char *dirpath = g_path_get_dirname(fs_path->data); + char *name = g_path_get_basename(fs_path->data); + int dirfd; + + dirfd = local_opendir_nofollow(fs_ctx, dirpath); + if (dirfd == -1) { + goto out; + } + + tsize = readlinkat(dirfd, name, buf, bufsz); + close_preserve_errno(dirfd); + out: + g_free(name); + g_free(dirpath); } return tsize; } |