From: Miklos Szeredi Date: Wed, 11 Mar 2026 21:05:17 +0000 (+0100) Subject: fuse: add refcount to fuse_dev X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e9bf38500ed9aec7cfdf9219c75d353645b41168;p=thirdparty%2Flinux.git fuse: add refcount to fuse_dev 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 --- diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index dfcb98a654d83..174333633471b 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -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; diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 0c03bbb21c128..3d96e7a16103c 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -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); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 522b2012cd1f5..910f883cd090f 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -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; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 94b49384a2f76..339e57a901592 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -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); /** diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index d34a7fbf849ca..fe72ef2a416c7 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -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) diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index f685916754ad4..12300651a0f1a 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -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; } }