qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

9pfs: local: open/opendir: don't follow symlinks

The local_open() and local_opendir() callbacks are vulnerable to symlink
attacks because they call:

(1) open(O_NOFOLLOW) which follows symbolic links in all path elements but
the rightmost one
(2) opendir() which follows symbolic links in all path elements

This patch converts both callbacks to use new helpers based on
openat_nofollow() to only open files and directories if they are
below the virtfs shared folder

This partly fixes CVE-2016-9602.

Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

+47 -10
+27 -10
hw/9pfs/9p-local.c
··· 13 13 14 14 #include "qemu/osdep.h" 15 15 #include "9p.h" 16 + #include "9p-local.h" 16 17 #include "9p-xattr.h" 17 18 #include "9p-util.h" 18 19 #include "fsdev/qemu-fsdev.h" /* local_ops */ ··· 47 48 typedef struct { 48 49 int mountfd; 49 50 } LocalData; 51 + 52 + int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags, 53 + mode_t mode) 54 + { 55 + LocalData *data = fs_ctx->private; 56 + 57 + /* All paths are relative to the path data->mountfd points to */ 58 + while (*path == '/') { 59 + path++; 60 + } 61 + 62 + return relative_openat_nofollow(data->mountfd, path, flags, mode); 63 + } 64 + 65 + int local_opendir_nofollow(FsContext *fs_ctx, const char *path) 66 + { 67 + return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0); 68 + } 50 69 51 70 #define VIRTFS_META_DIR ".virtfs_metadata" 52 71 ··· 359 378 static int local_open(FsContext *ctx, V9fsPath *fs_path, 360 379 int flags, V9fsFidOpenState *fs) 361 380 { 362 - char *buffer; 363 - char *path = fs_path->data; 364 381 int fd; 365 382 366 - buffer = rpath(ctx, path); 367 - fd = open(buffer, flags | O_NOFOLLOW); 368 - g_free(buffer); 383 + fd = local_open_nofollow(ctx, fs_path->data, flags, 0); 369 384 if (fd == -1) { 370 385 return -1; 371 386 } ··· 376 391 static int local_opendir(FsContext *ctx, 377 392 V9fsPath *fs_path, V9fsFidOpenState *fs) 378 393 { 379 - char *buffer; 380 - char *path = fs_path->data; 394 + int dirfd; 381 395 DIR *stream; 382 396 383 - buffer = rpath(ctx, path); 384 - stream = opendir(buffer); 385 - g_free(buffer); 397 + dirfd = local_opendir_nofollow(ctx, fs_path->data); 398 + if (dirfd == -1) { 399 + return -1; 400 + } 401 + 402 + stream = fdopendir(dirfd); 386 403 if (!stream) { 387 404 return -1; 388 405 }
+20
hw/9pfs/9p-local.h
··· 1 + /* 2 + * 9p local backend 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_LOCAL_H 14 + #define QEMU_9P_LOCAL_H 15 + 16 + int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags, 17 + mode_t mode); 18 + int local_opendir_nofollow(FsContext *fs_ctx, const char *path); 19 + 20 + #endif