]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fuse: add refcount to fuse_dev
authorMiklos Szeredi <mszeredi@redhat.com>
Wed, 11 Mar 2026 21:05:17 +0000 (22:05 +0100)
committerMiklos Szeredi <mszeredi@redhat.com>
Thu, 2 Apr 2026 18:43:24 +0000 (20:43 +0200)
This will make it possible to grab the fuse_dev and subsequently release
the file that it came from.

In the above case, fud->fc will be set to FUSE_DEV_FC_DISCONNECTED to
indicate that this is no longer a functional device.

When trying to assign an fc to such a disconnected fuse_dev, the fc is set
to the disconnected state.

Use atomic operations xchg() and cmpxchg() to prevent races.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/cuse.c
fs/fuse/dev.c
fs/fuse/fuse_dev_i.h
fs/fuse/fuse_i.h
fs/fuse/inode.c
fs/fuse/virtio_fs.c

index dfcb98a654d83bb34460a6e8c9e4b196dfdfdfbe..174333633471b2f05677d385d8e5d52c2cd30aba 100644 (file)
@@ -527,7 +527,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
        cc->fc.initialized = 1;
        rc = cuse_send_init(cc);
        if (rc) {
-               fuse_dev_free(fud);
+               fuse_dev_put(fud);
                return rc;
        }
        file->private_data = fud;
index 0c03bbb21c1282b5ca38b70aaac29239daf9fc5f..3d96e7a16103cd7072a1e9ce502f3a047fdf7e81 100644 (file)
@@ -2540,7 +2540,8 @@ void fuse_wait_aborted(struct fuse_conn *fc)
 int fuse_dev_release(struct inode *inode, struct file *file)
 {
        struct fuse_dev *fud = fuse_file_to_fud(file);
-       struct fuse_conn *fc = fuse_dev_fc_get(fud);
+       /* Pairs with cmpxchg() in fuse_dev_install() */
+       struct fuse_conn *fc = xchg(&fud->fc, FUSE_DEV_FC_DISCONNECTED);
 
        if (fc) {
                struct fuse_pqueue *fpq = &fud->pq;
@@ -2560,8 +2561,12 @@ int fuse_dev_release(struct inode *inode, struct file *file)
                        WARN_ON(fc->iq.fasync != NULL);
                        fuse_abort_conn(fc);
                }
+               spin_lock(&fc->lock);
+               list_del(&fud->entry);
+               spin_unlock(&fc->lock);
+               fuse_conn_put(fc);
        }
-       fuse_dev_free(fud);
+       fuse_dev_put(fud);
        return 0;
 }
 EXPORT_SYMBOL_GPL(fuse_dev_release);
index 522b2012cd1f5ca20fdb0bb58d3b1b0cb80bc57f..910f883cd090fdd306fd3e8d59e4f4e8bd7feef5 100644 (file)
@@ -39,22 +39,23 @@ struct fuse_copy_state {
        } ring;
 };
 
+/* fud->fc gets assigned to this value when /dev/fuse is closed */
+#define FUSE_DEV_FC_DISCONNECTED ((struct fuse_conn *) 1)
+
 /*
  * Lockless access is OK, because fud->fc is set once during mount and is valid
  * until the file is released.
+ *
+ * fud->fc is set to FUSE_DEV_FC_DISCONNECTED only after the containing file is
+ * released, so result is safe to dereference in most cases.  Exceptions are:
+ * fuse_dev_put() and fuse_fill_super_common().
  */
 static inline struct fuse_conn *fuse_dev_fc_get(struct fuse_dev *fud)
 {
-       /* Pairs with smp_store_release() in fuse_dev_fc_set() */
+       /* Pairs with xchg() in fuse_dev_install() */
        return smp_load_acquire(&fud->fc);
 }
 
-static inline void fuse_dev_fc_set(struct fuse_dev *fud, struct fuse_conn *fc)
-{
-       /* Pairs with smp_load_acquire() in fuse_dev_fc_get() */
-       smp_store_release(&fud->fc, fc);
-}
-
 static inline struct fuse_dev *fuse_file_to_fud(struct file *file)
 {
        return file->private_data;
index 94b49384a2f765061c50406879861a9042b6bfbe..339e57a901592fb172cce48c7b9e5bec9c9ecf1f 100644 (file)
@@ -577,6 +577,9 @@ struct fuse_pqueue {
  * Fuse device instance
  */
 struct fuse_dev {
+       /** Reference count of this object */
+       refcount_t ref;
+
        /** Issue FUSE_INIT synchronously */
        bool sync_init;
 
@@ -1344,7 +1347,7 @@ void fuse_conn_put(struct fuse_conn *fc);
 struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc);
 struct fuse_dev *fuse_dev_alloc(void);
 void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc);
-void fuse_dev_free(struct fuse_dev *fud);
+void fuse_dev_put(struct fuse_dev *fud);
 int fuse_send_init(struct fuse_mount *fm);
 
 /**
index d34a7fbf849ca2b4c55071b6937599c829683533..fe72ef2a416c7ee35fad5749a9a185919fd398fb 100644 (file)
@@ -1626,6 +1626,7 @@ struct fuse_dev *fuse_dev_alloc(void)
        if (!fud)
                return NULL;
 
+       refcount_set(&fud->ref, 1);
        pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE);
        if (!pq) {
                kfree(fud);
@@ -1641,9 +1642,26 @@ EXPORT_SYMBOL_GPL(fuse_dev_alloc);
 
 void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc)
 {
-       fuse_dev_fc_set(fud, fuse_conn_get(fc));
+       struct fuse_conn *old_fc;
+
        spin_lock(&fc->lock);
-       list_add_tail(&fud->entry, &fc->devices);
+       /*
+        * Pairs with:
+        *  - xchg() in fuse_dev_release()
+        *  - smp_load_acquire() in fuse_dev_fc_get()
+        */
+       old_fc = cmpxchg(&fud->fc, NULL, fc);
+       if (old_fc) {
+               /*
+                * failed to set fud->fc because
+                *  - it was already set to a different fc
+                *  - it was set to disconneted
+                */
+               fc->connected = 0;
+       } else {
+               list_add_tail(&fud->entry, &fc->devices);
+               fuse_conn_get(fc);
+       }
        spin_unlock(&fc->lock);
 }
 EXPORT_SYMBOL_GPL(fuse_dev_install);
@@ -1661,11 +1679,16 @@ struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc)
 }
 EXPORT_SYMBOL_GPL(fuse_dev_alloc_install);
 
-void fuse_dev_free(struct fuse_dev *fud)
+void fuse_dev_put(struct fuse_dev *fud)
 {
-       struct fuse_conn *fc = fuse_dev_fc_get(fud);
+       struct fuse_conn *fc;
+
+       if (!refcount_dec_and_test(&fud->ref))
+               return;
 
-       if (fc) {
+       fc = fuse_dev_fc_get(fud);
+       if (fc && fc != FUSE_DEV_FC_DISCONNECTED) {
+               /* This is the virtiofs case (fuse_dev_release() not called) */
                spin_lock(&fc->lock);
                list_del(&fud->entry);
                spin_unlock(&fc->lock);
@@ -1675,7 +1698,7 @@ void fuse_dev_free(struct fuse_dev *fud)
        kfree(fud->pq.processing);
        kfree(fud);
 }
-EXPORT_SYMBOL_GPL(fuse_dev_free);
+EXPORT_SYMBOL_GPL(fuse_dev_put);
 
 static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
                                      const struct fuse_inode *fi)
index f685916754ad4ded6efc0a40d9497198c494ee24..12300651a0f1ac81d631c7358fa5a39cc0db928f 100644 (file)
@@ -486,7 +486,7 @@ static void virtio_fs_free_devs(struct virtio_fs *fs)
                if (!fsvq->fud)
                        continue;
 
-               fuse_dev_free(fsvq->fud);
+               fuse_dev_put(fsvq->fud);
                fsvq->fud = NULL;
        }
 }