Blame 0005-9pfs-local-forbid-client-access-to-metadata-CVE-2017.patch

335584
From: Greg Kurz <groug@kaod.org>
335584
Date: Fri, 5 May 2017 14:48:08 +0200
335584
Subject: [PATCH] 9pfs: local: forbid client access to metadata (CVE-2017-7493)
335584
335584
When using the mapped-file security mode, we shouldn't let the client mess
335584
with the metadata. The current code already tries to hide the metadata dir
335584
from the client by skipping it in local_readdir(). But the client can still
335584
access or modify it through several other operations. This can be used to
335584
escalate privileges in the guest.
335584
335584
Affected backend operations are:
335584
- local_mknod()
335584
- local_mkdir()
335584
- local_open2()
335584
- local_symlink()
335584
- local_link()
335584
- local_unlinkat()
335584
- local_renameat()
335584
- local_rename()
335584
- local_name_to_path()
335584
335584
Other operations are safe because they are only passed a fid path, which
335584
is computed internally in local_name_to_path().
335584
335584
This patch converts all the functions listed above to fail and return
335584
EINVAL when being passed the name of the metadata dir. This may look
335584
like a poor choice for errno, but there's no such thing as an illegal
335584
path name on Linux and I could not think of anything better.
335584
335584
This fixes CVE-2017-7493.
335584
335584
Reported-by: Leo Gaspard <leo@gaspard.io>
335584
Signed-off-by: Greg Kurz <groug@kaod.org>
335584
Reviewed-by: Eric Blake <eblake@redhat.com>
335584
(cherry picked from commit 7a95434e0ca8a037fd8aa1a2e2461f92585eb77b)
335584
---
335584
 hw/9pfs/9p-local.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
335584
 1 file changed, 56 insertions(+), 2 deletions(-)
335584
335584
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
335584
index f3ebca4f7a..a2486566af 100644
335584
--- a/hw/9pfs/9p-local.c
335584
+++ b/hw/9pfs/9p-local.c
335584
@@ -452,6 +452,11 @@ static off_t local_telldir(FsContext *ctx, V9fsFidOpenState *fs)
335584
     return telldir(fs->dir.stream);
335584
 }
335584
 
335584
+static bool local_is_mapped_file_metadata(FsContext *fs_ctx, const char *name)
335584
+{
335584
+    return !strcmp(name, VIRTFS_META_DIR);
335584
+}
335584
+
335584
 static struct dirent *local_readdir(FsContext *ctx, V9fsFidOpenState *fs)
335584
 {
335584
     struct dirent *entry;
335584
@@ -465,8 +470,8 @@ again:
335584
     if (ctx->export_flags & V9FS_SM_MAPPED) {
335584
         entry->d_type = DT_UNKNOWN;
335584
     } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
335584
-        if (!strcmp(entry->d_name, VIRTFS_META_DIR)) {
335584
-            /* skp the meta data directory */
335584
+        if (local_is_mapped_file_metadata(ctx, entry->d_name)) {
335584
+            /* skip the meta data directory */
335584
             goto again;
335584
         }
335584
         entry->d_type = DT_UNKNOWN;
335584
@@ -559,6 +564,12 @@ static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
335584
     int err = -1;
335584
     int dirfd;
335584
 
335584
+    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(fs_ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
335584
     if (dirfd == -1) {
335584
         return -1;
335584
@@ -605,6 +616,12 @@ static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
335584
     int err = -1;
335584
     int dirfd;
335584
 
335584
+    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(fs_ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
335584
     if (dirfd == -1) {
335584
         return -1;
335584
@@ -694,6 +711,12 @@ static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
335584
     int err = -1;
335584
     int dirfd;
335584
 
335584
+    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(fs_ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     /*
335584
      * Mark all the open to not follow symlinks
335584
      */
335584
@@ -752,6 +775,12 @@ static int local_symlink(FsContext *fs_ctx, const char *oldpath,
335584
     int err = -1;
335584
     int dirfd;
335584
 
335584
+    if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(fs_ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
335584
     if (dirfd == -1) {
335584
         return -1;
335584
@@ -826,6 +855,12 @@ static int local_link(FsContext *ctx, V9fsPath *oldpath,
335584
     int ret = -1;
335584
     int odirfd, ndirfd;
335584
 
335584
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     odirfd = local_opendir_nofollow(ctx, odirpath);
335584
     if (odirfd == -1) {
335584
         goto out;
335584
@@ -1096,6 +1131,12 @@ static int local_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
335584
 static int local_name_to_path(FsContext *ctx, V9fsPath *dir_path,
335584
                               const char *name, V9fsPath *target)
335584
 {
335584
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     if (dir_path) {
335584
         v9fs_path_sprintf(target, "%s/%s", dir_path->data, name);
335584
     } else if (strcmp(name, "/")) {
335584
@@ -1116,6 +1157,13 @@ static int local_renameat(FsContext *ctx, V9fsPath *olddir,
335584
     int ret;
335584
     int odirfd, ndirfd;
335584
 
335584
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        (local_is_mapped_file_metadata(ctx, old_name) ||
335584
+         local_is_mapped_file_metadata(ctx, new_name))) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     odirfd = local_opendir_nofollow(ctx, olddir->data);
335584
     if (odirfd == -1) {
335584
         return -1;
335584
@@ -1206,6 +1254,12 @@ static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
335584
     int ret;
335584
     int dirfd;
335584
 
335584
+    if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
335584
+        local_is_mapped_file_metadata(ctx, name)) {
335584
+        errno = EINVAL;
335584
+        return -1;
335584
+    }
335584
+
335584
     dirfd = local_opendir_nofollow(ctx, dir->data);
335584
     if (dirfd == -1) {
335584
         return -1;