Blame 0098-virtiofsd-prevent-races-with-lo_dirp_put.patch

1d442b
From: Stefan Hajnoczi <stefanha@redhat.com>
1d442b
Date: Mon, 27 Jan 2020 19:02:07 +0000
1d442b
Subject: [PATCH] virtiofsd: prevent races with lo_dirp_put()
1d442b
MIME-Version: 1.0
1d442b
Content-Type: text/plain; charset=UTF-8
1d442b
Content-Transfer-Encoding: 8bit
1d442b
1d442b
Introduce lo_dirp_put() so that FUSE_RELEASEDIR does not cause
1d442b
use-after-free races with other threads that are accessing lo_dirp.
1d442b
1d442b
Also make lo_releasedir() atomic to prevent FUSE_RELEASEDIR racing with
1d442b
itself.  This prevents double-frees.
1d442b
1d442b
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
1d442b
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
1d442b
Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
1d442b
(cherry picked from commit acefdde73b403576a241ebd8dbe8431ddc0d9442)
1d442b
---
1d442b
 tools/virtiofsd/passthrough_ll.c | 41 +++++++++++++++++++++++++++-----
1d442b
 1 file changed, 35 insertions(+), 6 deletions(-)
1d442b
1d442b
diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
1d442b
index 690edbc4c5..2d703b57e5 100644
1d442b
--- a/tools/virtiofsd/passthrough_ll.c
1d442b
+++ b/tools/virtiofsd/passthrough_ll.c
1d442b
@@ -1284,11 +1284,28 @@ static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
1d442b
 }
1d442b
 
1d442b
 struct lo_dirp {
1d442b
+    gint refcount;
1d442b
     DIR *dp;
1d442b
     struct dirent *entry;
1d442b
     off_t offset;
1d442b
 };
1d442b
 
1d442b
+static void lo_dirp_put(struct lo_dirp **dp)
1d442b
+{
1d442b
+    struct lo_dirp *d = *dp;
1d442b
+
1d442b
+    if (!d) {
1d442b
+        return;
1d442b
+    }
1d442b
+    *dp = NULL;
1d442b
+
1d442b
+    if (g_atomic_int_dec_and_test(&d->refcount)) {
1d442b
+        closedir(d->dp);
1d442b
+        free(d);
1d442b
+    }
1d442b
+}
1d442b
+
1d442b
+/* Call lo_dirp_put() on the return value when no longer needed */
1d442b
 static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
1d442b
 {
1d442b
     struct lo_data *lo = lo_data(req);
1d442b
@@ -1296,6 +1313,9 @@ static struct lo_dirp *lo_dirp(fuse_req_t req, struct fuse_file_info *fi)
1d442b
 
1d442b
     pthread_mutex_lock(&lo->mutex);
1d442b
     elem = lo_map_get(&lo->dirp_map, fi->fh);
1d442b
+    if (elem) {
1d442b
+        g_atomic_int_inc(&elem->dirp->refcount);
1d442b
+    }
1d442b
     pthread_mutex_unlock(&lo->mutex);
1d442b
     if (!elem) {
1d442b
         return NULL;
1d442b
@@ -1331,6 +1351,7 @@ static void lo_opendir(fuse_req_t req, fuse_ino_t ino,
1d442b
     d->offset = 0;
1d442b
     d->entry = NULL;
1d442b
 
1d442b
+    g_atomic_int_set(&d->refcount, 1); /* paired with lo_releasedir() */
1d442b
     pthread_mutex_lock(&lo->mutex);
1d442b
     fh = lo_add_dirp_mapping(req, d);
1d442b
     pthread_mutex_unlock(&lo->mutex);
1d442b
@@ -1364,7 +1385,7 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
1d442b
                           off_t offset, struct fuse_file_info *fi, int plus)
1d442b
 {
1d442b
     struct lo_data *lo = lo_data(req);
1d442b
-    struct lo_dirp *d;
1d442b
+    struct lo_dirp *d = NULL;
1d442b
     struct lo_inode *dinode;
1d442b
     char *buf = NULL;
1d442b
     char *p;
1d442b
@@ -1454,6 +1475,8 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
1d442b
 
1d442b
     err = 0;
1d442b
 error:
1d442b
+    lo_dirp_put(&d);
1d442b
+
1d442b
     /*
1d442b
      * If there's an error, we can only signal it if we haven't stored
1d442b
      * any entries yet - otherwise we'd end up with wrong lookup
1d442b
@@ -1484,22 +1507,25 @@ static void lo_releasedir(fuse_req_t req, fuse_ino_t ino,
1d442b
                           struct fuse_file_info *fi)
1d442b
 {
1d442b
     struct lo_data *lo = lo_data(req);
1d442b
+    struct lo_map_elem *elem;
1d442b
     struct lo_dirp *d;
1d442b
 
1d442b
     (void)ino;
1d442b
 
1d442b
-    d = lo_dirp(req, fi);
1d442b
-    if (!d) {
1d442b
+    pthread_mutex_lock(&lo->mutex);
1d442b
+    elem = lo_map_get(&lo->dirp_map, fi->fh);
1d442b
+    if (!elem) {
1d442b
+        pthread_mutex_unlock(&lo->mutex);
1d442b
         fuse_reply_err(req, EBADF);
1d442b
         return;
1d442b
     }
1d442b
 
1d442b
-    pthread_mutex_lock(&lo->mutex);
1d442b
+    d = elem->dirp;
1d442b
     lo_map_remove(&lo->dirp_map, fi->fh);
1d442b
     pthread_mutex_unlock(&lo->mutex);
1d442b
 
1d442b
-    closedir(d->dp);
1d442b
-    free(d);
1d442b
+    lo_dirp_put(&d); /* paired with lo_opendir() */
1d442b
+
1d442b
     fuse_reply_err(req, 0);
1d442b
 }
1d442b
 
1d442b
@@ -1710,6 +1736,9 @@ static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
1d442b
     } else {
1d442b
         res = fsync(fd);
1d442b
     }
1d442b
+
1d442b
+    lo_dirp_put(&d);
1d442b
+
1d442b
     fuse_reply_err(req, res == -1 ? errno : 0);
1d442b
 }
1d442b