Blame SOURCES/kvm-virtiofsd-prevent-opening-of-special-files-CVE-2020-.patch

c687bc
From cc9a776fba8ec62c862db55753107f19459dafa8 Mon Sep 17 00:00:00 2001
c687bc
From: Jon Maloy <jmaloy@redhat.com>
c687bc
Date: Tue, 9 Feb 2021 23:14:56 -0500
c687bc
Subject: [PATCH 3/3] virtiofsd: prevent opening of special files
c687bc
 (CVE-2020-35517)
c687bc
c687bc
RH-Author: Jon Maloy <jmaloy@redhat.com>
c687bc
Message-id: <20210209231456.1555472-4-jmaloy@redhat.com>
c687bc
Patchwork-id: 101023
c687bc
O-Subject: [RHEL-8.4.0 qemu-kvm PATCH 3/3] virtiofsd: prevent opening of special files (CVE-2020-35517)
c687bc
Bugzilla: 1919111
c687bc
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
c687bc
RH-Acked-by: Greg Kurz <gkurz@redhat.com>
c687bc
RH-Acked-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
c687bc
c687bc
From: Stefan Hajnoczi <stefanha@redhat.com>
c687bc
c687bc
A well-behaved FUSE client does not attempt to open special files with
c687bc
FUSE_OPEN because they are handled on the client side (e.g. device nodes
c687bc
are handled by client-side device drivers).
c687bc
c687bc
The check to prevent virtiofsd from opening special files is missing in
c687bc
a few cases, most notably FUSE_OPEN. A malicious client can cause
c687bc
virtiofsd to open a device node, potentially allowing the guest to
c687bc
escape. This can be exploited by a modified guest device driver. It is
c687bc
not exploitable from guest userspace since the guest kernel will handle
c687bc
special files inside the guest instead of sending FUSE requests.
c687bc
c687bc
This patch fixes this issue by introducing the lo_inode_open() function
c687bc
to check the file type before opening it. This is a short-term solution
c687bc
because it does not prevent a compromised virtiofsd process from opening
c687bc
device nodes on the host.
c687bc
c687bc
Restructure lo_create() to try O_CREAT | O_EXCL first. Note that O_CREAT
c687bc
| O_EXCL does not follow symlinks, so O_NOFOLLOW masking is not
c687bc
necessary here. If the file exists and the user did not specify O_EXCL,
c687bc
open it via lo_do_open().
c687bc
c687bc
Reported-by: Alex Xu <alex@alxu.ca>
c687bc
Fixes: CVE-2020-35517
c687bc
Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
c687bc
Reviewed-by: Vivek Goyal <vgoyal@redhat.com>
c687bc
Reviewed-by: Greg Kurz <groug@kaod.org>
c687bc
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
c687bc
Message-Id: <20210204150208.367837-4-stefanha@redhat.com>
c687bc
Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
c687bc
c687bc
(cherry picked from commit a3fdbbc7f271bff7d53d0501b29d910ece0b3789)
c687bc
Signed-off-by: Jon Maloy <jmaloy@redhat.com>
c687bc
Signed-off-by: Jon Maloy <jmaloy.redhat.com>
c687bc
---
c687bc
 tools/virtiofsd/passthrough_ll.c | 144 ++++++++++++++++++++-----------
c687bc
 1 file changed, 92 insertions(+), 52 deletions(-)
c687bc
c687bc
diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
c687bc
index e5bd3d73e4..cb0992f2db 100644
c687bc
--- a/tools/virtiofsd/passthrough_ll.c
c687bc
+++ b/tools/virtiofsd/passthrough_ll.c
c687bc
@@ -535,6 +535,38 @@ static int lo_fd(fuse_req_t req, fuse_ino_t ino)
c687bc
     return fd;
c687bc
 }
c687bc
 
c687bc
+/*
c687bc
+ * Open a file descriptor for an inode. Returns -EBADF if the inode is not a
c687bc
+ * regular file or a directory.
c687bc
+ *
c687bc
+ * Use this helper function instead of raw openat(2) to prevent security issues
c687bc
+ * when a malicious client opens special files such as block device nodes.
c687bc
+ * Symlink inodes are also rejected since symlinks must already have been
c687bc
+ * traversed on the client side.
c687bc
+ */
c687bc
+static int lo_inode_open(struct lo_data *lo, struct lo_inode *inode,
c687bc
+                         int open_flags)
c687bc
+{
c687bc
+    g_autofree char *fd_str = g_strdup_printf("%d", inode->fd);
c687bc
+    int fd;
c687bc
+
c687bc
+    if (!S_ISREG(inode->filetype) && !S_ISDIR(inode->filetype)) {
c687bc
+        return -EBADF;
c687bc
+    }
c687bc
+
c687bc
+    /*
c687bc
+     * The file is a symlink so O_NOFOLLOW must be ignored. We checked earlier
c687bc
+     * that the inode is not a special file but if an external process races
c687bc
+     * with us then symlinks are traversed here. It is not possible to escape
c687bc
+     * the shared directory since it is mounted as "/" though.
c687bc
+     */
c687bc
+    fd = openat(lo->proc_self_fd, fd_str, open_flags & ~O_NOFOLLOW);
c687bc
+    if (fd < 0) {
c687bc
+        return -errno;
c687bc
+    }
c687bc
+    return fd;
c687bc
+}
c687bc
+
c687bc
 static void lo_init(void *userdata, struct fuse_conn_info *conn)
c687bc
 {
c687bc
     struct lo_data *lo = (struct lo_data *)userdata;
c687bc
@@ -788,9 +820,9 @@ static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
c687bc
         if (fi) {
c687bc
             truncfd = fd;
c687bc
         } else {
c687bc
-            sprintf(procname, "%i", ifd);
c687bc
-            truncfd = openat(lo->proc_self_fd, procname, O_RDWR);
c687bc
+            truncfd = lo_inode_open(lo, inode, O_RDWR);
c687bc
             if (truncfd < 0) {
c687bc
+                errno = -truncfd;
c687bc
                 goto out_err;
c687bc
             }
c687bc
         }
c687bc
@@ -894,7 +926,7 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
c687bc
     struct lo_inode *dir = lo_inode(req, parent);
c687bc
 
c687bc
     if (inodep) {
c687bc
-        *inodep = NULL;
c687bc
+        *inodep = NULL; /* in case there is an error */
c687bc
     }
c687bc
 
c687bc
     /*
c687bc
@@ -1725,19 +1757,26 @@ static void update_open_flags(int writeback, struct fuse_file_info *fi)
c687bc
     fi->flags &= ~O_DIRECT;
c687bc
 }
c687bc
 
c687bc
+/*
c687bc
+ * Open a regular file, set up an fd mapping, and fill out the struct
c687bc
+ * fuse_file_info for it. If existing_fd is not negative, use that fd instead
c687bc
+ * opening a new one. Takes ownership of existing_fd.
c687bc
+ *
c687bc
+ * Returns 0 on success or a positive errno.
c687bc
+ */
c687bc
 static int lo_do_open(struct lo_data *lo, struct lo_inode *inode,
c687bc
-                      struct fuse_file_info *fi)
c687bc
+                      int existing_fd, struct fuse_file_info *fi)
c687bc
 {
c687bc
-    char buf[64];
c687bc
     ssize_t fh;
c687bc
-    int fd;
c687bc
+    int fd = existing_fd;
c687bc
 
c687bc
     update_open_flags(lo->writeback, fi);
c687bc
 
c687bc
-    sprintf(buf, "%i", inode->fd);
c687bc
-    fd = openat(lo->proc_self_fd, buf, fi->flags & ~O_NOFOLLOW);
c687bc
-    if (fd == -1) {
c687bc
-        return errno;
c687bc
+    if (fd < 0) {
c687bc
+        fd = lo_inode_open(lo, inode, fi->flags);
c687bc
+        if (fd < 0) {
c687bc
+            return -fd;
c687bc
+        }
c687bc
     }
c687bc
 
c687bc
     pthread_mutex_lock(&lo->mutex);
c687bc
@@ -1760,9 +1799,10 @@ static int lo_do_open(struct lo_data *lo, struct lo_inode *inode,
c687bc
 static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
c687bc
                       mode_t mode, struct fuse_file_info *fi)
c687bc
 {
c687bc
-    int fd;
c687bc
+    int fd = -1;
c687bc
     struct lo_data *lo = lo_data(req);
c687bc
     struct lo_inode *parent_inode;
c687bc
+    struct lo_inode *inode = NULL;
c687bc
     struct fuse_entry_param e;
c687bc
     int err;
c687bc
     struct lo_cred old = {};
c687bc
@@ -1788,36 +1828,38 @@ static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
c687bc
 
c687bc
     update_open_flags(lo->writeback, fi);
c687bc
 
c687bc
-    fd = openat(parent_inode->fd, name, (fi->flags | O_CREAT) & ~O_NOFOLLOW,
c687bc
-                mode);
c687bc
+    /* Try to create a new file but don't open existing files */
c687bc
+    fd = openat(parent_inode->fd, name, fi->flags | O_CREAT | O_EXCL, mode);
c687bc
     err = fd == -1 ? errno : 0;
c687bc
-    lo_restore_cred(&old;;
c687bc
 
c687bc
-    if (!err) {
c687bc
-        ssize_t fh;
c687bc
+    lo_restore_cred(&old;;
c687bc
 
c687bc
-        pthread_mutex_lock(&lo->mutex);
c687bc
-        fh = lo_add_fd_mapping(lo, fd);
c687bc
-        pthread_mutex_unlock(&lo->mutex);
c687bc
-        if (fh == -1) {
c687bc
-            close(fd);
c687bc
-            err = ENOMEM;
c687bc
-            goto out;
c687bc
-        }
c687bc
+    /* Ignore the error if file exists and O_EXCL was not given */
c687bc
+    if (err && (err != EEXIST || (fi->flags & O_EXCL))) {
c687bc
+        goto out;
c687bc
+    }
c687bc
 
c687bc
-        fi->fh = fh;
c687bc
-        err = lo_do_lookup(req, parent, name, &e, NULL);
c687bc
+    err = lo_do_lookup(req, parent, name, &e, &inode;;
c687bc
+    if (err) {
c687bc
+        goto out;
c687bc
     }
c687bc
-    if (lo->cache == CACHE_NONE) {
c687bc
-        fi->direct_io = 1;
c687bc
-    } else if (lo->cache == CACHE_ALWAYS) {
c687bc
-        fi->keep_cache = 1;
c687bc
+
c687bc
+    err = lo_do_open(lo, inode, fd, fi);
c687bc
+    fd = -1; /* lo_do_open() takes ownership of fd */
c687bc
+    if (err) {
c687bc
+        /* Undo lo_do_lookup() nlookup ref */
c687bc
+        unref_inode_lolocked(lo, inode, 1);
c687bc
     }
c687bc
 
c687bc
 out:
c687bc
+    lo_inode_put(lo, &inode;;
c687bc
     lo_inode_put(lo, &parent_inode);
c687bc
 
c687bc
     if (err) {
c687bc
+        if (fd >= 0) {
c687bc
+            close(fd);
c687bc
+        }
c687bc
+
c687bc
         fuse_reply_err(req, err);
c687bc
     } else {
c687bc
         fuse_reply_create(req, &e, fi);
c687bc
@@ -1831,7 +1873,6 @@ static struct lo_inode_plock *lookup_create_plock_ctx(struct lo_data *lo,
c687bc
                                                       pid_t pid, int *err)
c687bc
 {
c687bc
     struct lo_inode_plock *plock;
c687bc
-    char procname[64];
c687bc
     int fd;
c687bc
 
c687bc
     plock =
c687bc
@@ -1848,12 +1889,10 @@ static struct lo_inode_plock *lookup_create_plock_ctx(struct lo_data *lo,
c687bc
     }
c687bc
 
c687bc
     /* Open another instance of file which can be used for ofd locks. */
c687bc
-    sprintf(procname, "%i", inode->fd);
c687bc
-
c687bc
     /* TODO: What if file is not writable? */
c687bc
-    fd = openat(lo->proc_self_fd, procname, O_RDWR);
c687bc
-    if (fd == -1) {
c687bc
-        *err = errno;
c687bc
+    fd = lo_inode_open(lo, inode, O_RDWR);
c687bc
+    if (fd < 0) {
c687bc
+        *err = -fd;
c687bc
         free(plock);
c687bc
         return NULL;
c687bc
     }
c687bc
@@ -2000,7 +2039,7 @@ static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
c687bc
         return;
c687bc
     }
c687bc
 
c687bc
-    err = lo_do_open(lo, inode, fi);
c687bc
+    err = lo_do_open(lo, inode, -1, fi);
c687bc
     lo_inode_put(lo, &inode;;
c687bc
     if (err) {
c687bc
         fuse_reply_err(req, err);
c687bc
@@ -2056,39 +2095,40 @@ static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
c687bc
 static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
c687bc
                      struct fuse_file_info *fi)
c687bc
 {
c687bc
+    struct lo_inode *inode = lo_inode(req, ino);
c687bc
+    struct lo_data *lo = lo_data(req);
c687bc
     int res;
c687bc
     int fd;
c687bc
-    char *buf;
c687bc
 
c687bc
     fuse_log(FUSE_LOG_DEBUG, "lo_fsync(ino=%" PRIu64 ", fi=0x%p)\n", ino,
c687bc
              (void *)fi);
c687bc
 
c687bc
-    if (!fi) {
c687bc
-        struct lo_data *lo = lo_data(req);
c687bc
-
c687bc
-        res = asprintf(&buf, "%i", lo_fd(req, ino));
c687bc
-        if (res == -1) {
c687bc
-            return (void)fuse_reply_err(req, errno);
c687bc
-        }
c687bc
+    if (!inode) {
c687bc
+        fuse_reply_err(req, EBADF);
c687bc
+        return;
c687bc
+    }
c687bc
 
c687bc
-        fd = openat(lo->proc_self_fd, buf, O_RDWR);
c687bc
-        free(buf);
c687bc
-        if (fd == -1) {
c687bc
-            return (void)fuse_reply_err(req, errno);
c687bc
+    if (!fi) {
c687bc
+        fd = lo_inode_open(lo, inode, O_RDWR);
c687bc
+        if (fd < 0) {
c687bc
+            res = -fd;
c687bc
+            goto out;
c687bc
         }
c687bc
     } else {
c687bc
         fd = lo_fi_fd(req, fi);
c687bc
     }
c687bc
 
c687bc
     if (datasync) {
c687bc
-        res = fdatasync(fd);
c687bc
+        res = fdatasync(fd) == -1 ? errno : 0;
c687bc
     } else {
c687bc
-        res = fsync(fd);
c687bc
+        res = fsync(fd) == -1 ? errno : 0;
c687bc
     }
c687bc
     if (!fi) {
c687bc
         close(fd);
c687bc
     }
c687bc
-    fuse_reply_err(req, res == -1 ? errno : 0);
c687bc
+out:
c687bc
+    lo_inode_put(lo, &inode;;
c687bc
+    fuse_reply_err(req, res);
c687bc
 }
c687bc
 
c687bc
 static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset,
c687bc
-- 
c687bc
2.18.2
c687bc