]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fuse: alloc pqueue before installing fch in fuse_dev
authorMiklos Szeredi <mszeredi@redhat.com>
Thu, 2 Apr 2026 12:49:09 +0000 (14:49 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Mon, 15 Jun 2026 12:06:18 +0000 (14:06 +0200)
Prior to this patchset, fuse_dev (containing fuse_pqueue) was allocated on
mount.  But now fuse_dev is allocated when opening /dev/fuse, even though
the queues are not needed at that time.

Delay allocation of the pqueue (4k worth of list_head) just before mounting
or cloning a device.

Various distributions (e.g. Debian/Fedora) configure /dev/fuse as world
writable, so the pqueue allocation should be deferred to a privileged
operation (mount) to prevent unprivileged userspace from consuming pinned
kernel memory.

[Li Wang: fix kernel NULL pointer dereference in fuse_uring_add_to_pq()]
[Fix race in fuse_dev_release()]

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

index 2403cd445e720643c9d11dac5b077a038db152e0..fd0b86ea5691083b78d269446562854bf1860726 100644 (file)
@@ -329,6 +329,7 @@ void fuse_chan_release(struct fuse_chan *fch)
 void fuse_chan_free(struct fuse_chan *fch)
 {
        WARN_ON(!list_empty(&fch->devices));
+       kfree(fch->pq_prealloc);
        kfree(fch);
 }
 EXPORT_SYMBOL_GPL(fuse_chan_free);
@@ -355,15 +356,30 @@ struct fuse_chan *fuse_chan_new(void)
 }
 EXPORT_SYMBOL_GPL(fuse_chan_new);
 
+struct list_head *fuse_pqueue_alloc(void)
+{
+       struct list_head *pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE);
+
+       if (pq) {
+               for (int i = 0; i < FUSE_PQ_HASH_SIZE; i++)
+                       INIT_LIST_HEAD(&pq[i]);
+       }
+       return pq;
+}
+
 struct fuse_chan *fuse_dev_chan_new(void)
 {
-       struct fuse_chan *fch = fuse_chan_new();
+       struct fuse_chan *fch __free(kfree) = fuse_chan_new();
        if (!fch)
                return NULL;
 
+       fch->pq_prealloc = fuse_pqueue_alloc();
+       if (!fch->pq_prealloc)
+               return NULL;
+
        fuse_iqueue_init(&fch->iq, &fuse_dev_fiq_ops, NULL);
 
-       return fch;
+       return no_free_ptr(fch);
 }
 EXPORT_SYMBOL_GPL(fuse_dev_chan_new);
 
@@ -404,39 +420,42 @@ void fuse_chan_io_uring_enable(struct fuse_chan *fch)
 
 void fuse_pqueue_init(struct fuse_pqueue *fpq)
 {
-       unsigned int i;
-
        spin_lock_init(&fpq->lock);
-       for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
-               INIT_LIST_HEAD(&fpq->processing[i]);
        INIT_LIST_HEAD(&fpq->io);
        fpq->connected = 1;
+       fpq->processing = NULL;
 }
 
-struct fuse_dev *fuse_dev_alloc(void)
+static struct fuse_dev *fuse_dev_alloc_no_pq(void)
 {
        struct fuse_dev *fud;
-       struct list_head *pq;
 
        fud = kzalloc_obj(struct fuse_dev);
        if (!fud)
                return NULL;
 
        refcount_set(&fud->ref, 1);
-       pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE);
-       if (!pq) {
-               kfree(fud);
-               return NULL;
-       }
-
-       fud->pq.processing = pq;
        fuse_pqueue_init(&fud->pq);
 
        return fud;
 }
+
+struct fuse_dev *fuse_dev_alloc(void)
+{
+       struct fuse_dev *fud __free(kfree) = fuse_dev_alloc_no_pq();
+       if (!fud)
+               return NULL;
+
+       fud->pq.processing = fuse_pqueue_alloc();
+       if (!fud->pq.processing)
+               return NULL;
+
+       return no_free_ptr(fud);
+}
 EXPORT_SYMBOL_GPL(fuse_dev_alloc);
 
-void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch)
+static void fuse_dev_install_with_pq(struct fuse_dev *fud, struct fuse_chan *fch,
+                                    struct list_head *pq)
 {
        struct fuse_chan *old_fch;
 
@@ -454,20 +473,33 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch)
                 *  - it was set to disconneted
                 */
                fch->connected = 0;
+               kfree(pq);
        } else {
+               if (pq) {
+                       WARN_ON(fud->pq.processing);
+                       fud->pq.processing = pq;
+               }
                list_add_tail(&fud->entry, &fch->devices);
                fuse_conn_get(fch->conn);
                wake_up_all(&fuse_dev_waitq);
        }
        spin_unlock(&fch->lock);
 }
+
+void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch)
+{
+       struct list_head *pq = fch->pq_prealloc;
+
+       fch->pq_prealloc = NULL;
+       fuse_dev_install_with_pq(fud, fch, pq);
+}
 EXPORT_SYMBOL_GPL(fuse_dev_install);
 
 struct fuse_dev *fuse_dev_alloc_install(struct fuse_chan *fch)
 {
        struct fuse_dev *fud;
 
-       fud = fuse_dev_alloc();
+       fud = fuse_dev_alloc_no_pq();
        if (!fud)
                return NULL;
 
@@ -1631,7 +1663,7 @@ out_end:
 
 static int fuse_dev_open(struct inode *inode, struct file *file)
 {
-       struct fuse_dev *fud = fuse_dev_alloc();
+       struct fuse_dev *fud = fuse_dev_alloc_no_pq();
 
        if (!fud)
                return -ENOMEM;
@@ -2204,20 +2236,21 @@ int fuse_dev_release(struct inode *inode, struct file *file)
                unsigned int i;
                bool last;
 
+               /* Make sure fuse_dev_install_with_pq() has finished */
+               spin_lock(&fch->lock);
                spin_lock(&fpq->lock);
                WARN_ON(!list_empty(&fpq->io));
                for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
                        list_splice_init(&fpq->processing[i], &to_end);
                spin_unlock(&fpq->lock);
 
-               fuse_dev_end_requests(&to_end);
-
-               spin_lock(&fch->lock);
                list_del(&fud->entry);
                /* Are we the last open device? */
                last = list_empty(&fch->devices);
                spin_unlock(&fch->lock);
 
+               fuse_dev_end_requests(&to_end);
+
                if (last) {
                        WARN_ON(fch->iq.fasync != NULL);
                        fuse_chan_abort(fch, false);
@@ -2244,6 +2277,7 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
 {
        int oldfd;
        struct fuse_dev *fud, *new_fud;
+       struct list_head *pq;
 
        if (get_user(oldfd, argp))
                return -EFAULT;
@@ -2267,7 +2301,11 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
        if (fuse_dev_chan_get(new_fud))
                return -EINVAL;
 
-       fuse_dev_install(new_fud, fud->chan);
+       pq = fuse_pqueue_alloc();
+       if (!pq)
+               return -ENOMEM;
+
+       fuse_dev_install_with_pq(new_fud, fud->chan, pq);
 
        return 0;
 }
index 99138e3ec83fee256596849e4455b04b0a302994..2901c7a5ff0549c8641553b6f5d5217442c8af25 100644 (file)
@@ -281,7 +281,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
        queue = kzalloc_obj(*queue, GFP_KERNEL_ACCOUNT);
        if (!queue)
                return NULL;
-       pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE);
+       pq = fuse_pqueue_alloc();
        if (!pq) {
                kfree(queue);
                return NULL;
@@ -299,8 +299,8 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
        INIT_LIST_HEAD(&queue->fuse_req_bg_queue);
        INIT_LIST_HEAD(&queue->ent_released);
 
-       queue->fpq.processing = pq;
        fuse_pqueue_init(&queue->fpq);
+       queue->fpq.processing = pq;
 
        spin_lock(&fch->lock);
        if (ring->queues[qid]) {
index dcfafac786fc56788f40b4ce7a32448dcf9d2b5d..430d292d31fbaa25a998d6b88cbebfacb07f34b8 100644 (file)
@@ -247,6 +247,9 @@ struct fuse_chan {
        /* Maximum number of pages that can be used in a single request */
        unsigned int max_pages;
 
+       /* Before being installed into fud, contains the preallocated pq array*/
+       struct list_head *pq_prealloc;
+
        /** Connection aborted via sysfs, respond with ECONNABORTED on device I/O */
        bool abort_with_err;
 
@@ -396,6 +399,8 @@ struct fuse_dev *fuse_dev_alloc(void);
 
 int fuse_dev_release(struct inode *inode, struct file *file);
 
+struct list_head *fuse_pqueue_alloc(void);
+
 /**
  * Initialize the fuse processing queue
  */