fuse: fix NULL pointer dereference in fuse_request_find

fuse_dev_install_with_pq() publishes fud->chan before it stores
fud->pq.processing. The write/splice_write and read paths treat a non-NULL
fud->chan as "installed" and then walk fpq->processing[] without taking
fch->lock, so they can see the new chan while processing is still NULL and
oops in fuse_request_find().

Fix by taking fpq->lock around the cmpxchg + fud->pq.processing assignment.
This protects all call sites that dereference fpq->processing, since these
have fpq->lock held.

Fixes: 48649c0603 ("fuse: alloc pqueue before installing fch in fuse_dev")
Reported-by: Cen Zhang <zzzccc427@gmail.com>
Closes: https://lore.kernel.org/fuse-devel/pm-fuse-full-objects-candidate-0002-v2-17d6013e5066de33396c@gmail.com/
Reported-by: 马云龙 <yunlong_ma@buaa.edu.cn>
Closes: https://lore.kernel.org/all/5ddc8efe.1e6d2.1a11fbec860.Coremail.yunlong_ma@buaa.edu.cn/
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
Miklos Szeredi 2026-10-15 16:07:01 +02:00
parent 294de5bbbc
commit f8cef5c74d

View File

@ -466,24 +466,27 @@ static bool fuse_dev_install_with_pq(struct fuse_dev *fud, struct fuse_chan *fch
struct fuse_chan *old_fch;
guard(spinlock)(&fch->lock);
/*
* Pairs with:
* - xchg() in fuse_dev_release()
* - smp_load_acquire() in fuse_dev_fc_get()
*/
old_fch = cmpxchg(&fud->chan, NULL, fch);
if (old_fch) {
/* protect fud->pq.processing assignment */
scoped_guard(spinlock, &fud->pq.lock) {
/*
* failed to set fud->chan because
* - it was already set to a different fc
* - it was set to disconneted
* Pairs with:
* - xchg() in fuse_dev_release()
* - smp_load_acquire() in fuse_dev_fc_get()
*/
kfree(pq);
return false;
}
if (pq) {
WARN_ON(fud->pq.processing);
fud->pq.processing = pq;
old_fch = cmpxchg(&fud->chan, NULL, fch);
if (old_fch) {
/*
* failed to set fud->chan because
* - it was already set to a different fc
* - it was set to disconneted
*/
kfree(pq);
return false;
}
if (pq) {
WARN_ON(fud->pq.processing);
fud->pq.processing = pq;
}
}
list_add_tail(&fud->entry, &fch->devices);
fuse_conn_get(fch->conn);