--- /dev/null
+From 54243797cedf55447b4c5d560e8cd709900061ae Mon Sep 17 00:00:00 2001
+From: Samuel Moelius <sam.moelius@trailofbits.com>
+Date: Wed, 10 Jun 2026 00:37:18 +0000
+Subject: fuse: avoid 32-bit prune notification count wrap
+
+From: Samuel Moelius <sam.moelius@trailofbits.com>
+
+commit 54243797cedf55447b4c5d560e8cd709900061ae upstream.
+
+FUSE_NOTIFY_PRUNE validates the nodeid payload length with:
+
+ size - sizeof(outarg) != outarg.count * sizeof(u64)
+
+On 32-bit kernels, size_t is also 32 bits, so the daemon-controlled
+count multiplication can wrap. A prune notification with count
+0x20000000 and no nodeid payload passes the check, enters the copy
+loop, and asks the device copy path to read nodeids that are not
+present in the userspace write buffer. In QEMU this reaches the
+fuse_copy_fill() BUG_ON(!err) path.
+
+Validate the payload length with array_size() instead. That accepts
+exactly the same valid messages, but avoids wrapping arithmetic before
+the copy loop consumes the count.
+
+Assisted-by: Codex:gpt-5.5-cyber-preview
+Fixes: 3f29d59e92a9 ("fuse: add prune notification")
+Cc: stable@vger.kernel.org
+Signed-off-by: Samuel Moelius <sam.moelius@trailofbits.com>
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev.c | 2
+ fs/fuse/notify.c | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 435 insertions(+), 1 deletion(-)
+ create mode 100644 fs/fuse/notify.c
+
+--- a/fs/fuse/dev.c
++++ b/fs/fuse/dev.c
+@@ -2082,7 +2082,7 @@ static int fuse_notify_prune(struct fuse
+ if (err)
+ return err;
+
+- if (size - sizeof(outarg) != outarg.count * sizeof(u64))
++ if (size - sizeof(outarg) != array_size(outarg.count, sizeof(u64)))
+ return -EINVAL;
+
+ for (; outarg.count; outarg.count -= num) {
+--- /dev/null
++++ b/fs/fuse/notify.c
+@@ -0,0 +1,434 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include "dev.h"
++#include "fuse_i.h"
++#include <linux/pagemap.h>
++
++static int fuse_notify_poll(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_poll_wakeup_out outarg;
++ int err;
++
++ if (size != sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++
++ fuse_copy_finish(cs);
++ return fuse_notify_poll_wakeup(fc, &outarg);
++}
++
++static int fuse_notify_inval_inode(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_inval_inode_out outarg;
++ int err;
++
++ if (size != sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++ fuse_copy_finish(cs);
++
++ down_read(&fc->killsb);
++ err = fuse_reverse_inval_inode(fc, outarg.ino,
++ outarg.off, outarg.len);
++ up_read(&fc->killsb);
++ return err;
++}
++
++static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_inval_entry_out outarg;
++ int err;
++ char *buf;
++ struct qstr name;
++
++ if (size < sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++
++ if (outarg.namelen > fc->name_max)
++ return -ENAMETOOLONG;
++
++ err = -EINVAL;
++ if (size != sizeof(outarg) + outarg.namelen + 1)
++ return -EINVAL;
++
++ buf = kzalloc(outarg.namelen + 1, GFP_KERNEL);
++ if (!buf)
++ return -ENOMEM;
++
++ name.name = buf;
++ name.len = outarg.namelen;
++ err = fuse_copy_one(cs, buf, outarg.namelen + 1);
++ if (err)
++ goto err;
++ fuse_copy_finish(cs);
++ buf[outarg.namelen] = 0;
++
++ down_read(&fc->killsb);
++ err = fuse_reverse_inval_entry(fc, outarg.parent, 0, &name, outarg.flags);
++ up_read(&fc->killsb);
++err:
++ kfree(buf);
++ return err;
++}
++
++static int fuse_notify_delete(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_delete_out outarg;
++ int err;
++ char *buf;
++ struct qstr name;
++
++ if (size < sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++
++ if (outarg.namelen > fc->name_max)
++ return -ENAMETOOLONG;
++
++ if (size != sizeof(outarg) + outarg.namelen + 1)
++ return -EINVAL;
++
++ buf = kzalloc(outarg.namelen + 1, GFP_KERNEL);
++ if (!buf)
++ return -ENOMEM;
++
++ name.name = buf;
++ name.len = outarg.namelen;
++ err = fuse_copy_one(cs, buf, outarg.namelen + 1);
++ if (err)
++ goto err;
++ fuse_copy_finish(cs);
++ buf[outarg.namelen] = 0;
++
++ down_read(&fc->killsb);
++ err = fuse_reverse_inval_entry(fc, outarg.parent, outarg.child, &name, 0);
++ up_read(&fc->killsb);
++err:
++ kfree(buf);
++ return err;
++}
++
++static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_store_out outarg;
++ struct inode *inode;
++ struct address_space *mapping;
++ u64 nodeid;
++ int err;
++ unsigned int num;
++ loff_t file_size;
++ loff_t pos;
++ loff_t end;
++
++ if (size < sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++
++ if (size - sizeof(outarg) != outarg.size)
++ return -EINVAL;
++
++ if (outarg.offset >= MAX_LFS_FILESIZE)
++ return -EINVAL;
++
++ nodeid = outarg.nodeid;
++ pos = outarg.offset;
++ num = min(outarg.size, MAX_LFS_FILESIZE - pos);
++
++ down_read(&fc->killsb);
++
++ err = -ENOENT;
++ inode = fuse_ilookup(fc, nodeid, NULL);
++ if (!inode)
++ goto out_up_killsb;
++
++ mapping = inode->i_mapping;
++ file_size = i_size_read(inode);
++ end = pos + num;
++ if (end > file_size) {
++ file_size = end;
++ fuse_write_update_attr(inode, file_size, num);
++ }
++
++ while (num) {
++ struct folio *folio;
++ unsigned int folio_offset;
++ unsigned int nr_bytes;
++ pgoff_t index = pos >> PAGE_SHIFT;
++
++ folio = filemap_grab_folio(mapping, index);
++ err = PTR_ERR(folio);
++ if (IS_ERR(folio))
++ goto out_iput;
++
++ folio_offset = offset_in_folio(folio, pos);
++ nr_bytes = min(num, folio_size(folio) - folio_offset);
++
++ err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0);
++ if (!folio_test_uptodate(folio) && !err && folio_offset == 0 &&
++ (nr_bytes == folio_size(folio) || file_size == end)) {
++ folio_zero_segment(folio, nr_bytes, folio_size(folio));
++ folio_mark_uptodate(folio);
++ }
++ folio_unlock(folio);
++ folio_put(folio);
++
++ if (err)
++ goto out_iput;
++
++ pos += nr_bytes;
++ num -= nr_bytes;
++ }
++
++ err = 0;
++
++out_iput:
++ iput(inode);
++out_up_killsb:
++ up_read(&fc->killsb);
++ return err;
++}
++
++struct fuse_retrieve_args {
++ struct fuse_args_pages ap;
++ struct fuse_notify_retrieve_in inarg;
++};
++
++static void fuse_retrieve_end(struct fuse_args *args, int error)
++{
++ struct fuse_retrieve_args *ra =
++ container_of(args, typeof(*ra), ap.args);
++
++ release_pages(ra->ap.folios, ra->ap.num_folios);
++ kfree(ra);
++}
++
++static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
++ struct fuse_notify_retrieve_out *outarg)
++{
++ int err;
++ struct address_space *mapping = inode->i_mapping;
++ loff_t file_size;
++ unsigned int num;
++ unsigned int offset;
++ size_t total_len = 0;
++ unsigned int num_pages;
++ struct fuse_conn *fc = fm->fc;
++ struct fuse_retrieve_args *ra;
++ size_t args_size = sizeof(*ra);
++ struct fuse_args_pages *ap;
++ struct fuse_args *args;
++ loff_t pos = outarg->offset;
++
++ offset = offset_in_page(pos);
++ file_size = i_size_read(inode);
++
++ num = min(outarg->size, fc->max_write);
++ if (pos > file_size)
++ num = 0;
++ else if (num > file_size - pos)
++ num = file_size - pos;
++
++ num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE);
++ num_pages = min(num_pages, fc->max_pages);
++ num = min(num, num_pages << PAGE_SHIFT);
++
++ args_size += num_pages * (sizeof(ap->folios[0]) + sizeof(ap->descs[0]));
++
++ ra = kzalloc(args_size, GFP_KERNEL);
++ if (!ra)
++ return -ENOMEM;
++
++ ap = &ra->ap;
++ ap->folios = (void *) (ra + 1);
++ ap->descs = (void *) (ap->folios + num_pages);
++
++ args = &ap->args;
++ args->nodeid = outarg->nodeid;
++ args->opcode = FUSE_NOTIFY_REPLY;
++ args->in_numargs = 3;
++ args->in_pages = true;
++ args->end = fuse_retrieve_end;
++
++ while (num && ap->num_folios < num_pages) {
++ struct folio *folio;
++ unsigned int folio_offset;
++ unsigned int nr_bytes;
++ pgoff_t index = pos >> PAGE_SHIFT;
++
++ folio = filemap_get_folio(mapping, index);
++ if (IS_ERR(folio))
++ break;
++
++ folio_offset = offset_in_folio(folio, pos);
++ nr_bytes = min(folio_size(folio) - folio_offset, num);
++
++ ap->folios[ap->num_folios] = folio;
++ ap->descs[ap->num_folios].offset = folio_offset;
++ ap->descs[ap->num_folios].length = nr_bytes;
++ ap->num_folios++;
++
++ pos += nr_bytes;
++ num -= nr_bytes;
++ total_len += nr_bytes;
++ }
++ ra->inarg.offset = outarg->offset;
++ ra->inarg.size = total_len;
++ fuse_set_zero_arg0(args);
++ args->in_args[1].size = sizeof(ra->inarg);
++ args->in_args[1].value = &ra->inarg;
++ args->in_args[2].size = total_len;
++
++ err = fuse_simple_notify_reply(fm, args, outarg->notify_unique);
++ if (err)
++ fuse_retrieve_end(args, err);
++
++ return err;
++}
++
++static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_retrieve_out outarg;
++ struct fuse_mount *fm;
++ struct inode *inode;
++ u64 nodeid;
++ int err;
++
++ if (size != sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++
++ fuse_copy_finish(cs);
++
++ if (outarg.offset >= MAX_LFS_FILESIZE)
++ return -EINVAL;
++
++ down_read(&fc->killsb);
++ err = -ENOENT;
++ nodeid = outarg.nodeid;
++
++ inode = fuse_ilookup(fc, nodeid, &fm);
++ if (inode) {
++ err = fuse_retrieve(fm, inode, &outarg);
++ iput(inode);
++ }
++ up_read(&fc->killsb);
++
++ return err;
++}
++
++static int fuse_notify_resend(struct fuse_conn *fc)
++{
++ fuse_chan_resend(fc->chan);
++ return 0;
++}
++
++/*
++ * Increments the fuse connection epoch. This will result of dentries from
++ * previous epochs to be invalidated. Additionally, if inval_wq is set, a work
++ * queue is scheduled to trigger the invalidation.
++ */
++static int fuse_notify_inc_epoch(struct fuse_conn *fc)
++{
++ atomic_inc(&fc->epoch);
++ if (inval_wq)
++ schedule_work(&fc->epoch_work);
++
++ return 0;
++}
++
++static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size,
++ struct fuse_copy_state *cs)
++{
++ struct fuse_notify_prune_out outarg;
++ const unsigned int batch = 512;
++ u64 *nodeids __free(kfree) = kmalloc(sizeof(u64) * batch, GFP_KERNEL);
++ unsigned int num, i;
++ int err;
++
++ if (!nodeids)
++ return -ENOMEM;
++
++ if (size < sizeof(outarg))
++ return -EINVAL;
++
++ err = fuse_copy_one(cs, &outarg, sizeof(outarg));
++ if (err)
++ return err;
++
++ if (size - sizeof(outarg) != array_size(outarg.count, sizeof(u64)))
++ return -EINVAL;
++
++ for (; outarg.count; outarg.count -= num) {
++ num = min(batch, outarg.count);
++ err = fuse_copy_one(cs, nodeids, num * sizeof(u64));
++ if (err)
++ return err;
++
++ scoped_guard(rwsem_read, &fc->killsb) {
++ for (i = 0; i < num; i++)
++ fuse_try_prune_one_inode(fc, nodeids[i]);
++ }
++ }
++ return 0;
++}
++
++int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
++ unsigned int size, struct fuse_copy_state *cs)
++{
++ switch (code) {
++ case FUSE_NOTIFY_POLL:
++ return fuse_notify_poll(fc, size, cs);
++
++ case FUSE_NOTIFY_INVAL_INODE:
++ return fuse_notify_inval_inode(fc, size, cs);
++
++ case FUSE_NOTIFY_INVAL_ENTRY:
++ return fuse_notify_inval_entry(fc, size, cs);
++
++ case FUSE_NOTIFY_STORE:
++ return fuse_notify_store(fc, size, cs);
++
++ case FUSE_NOTIFY_RETRIEVE:
++ return fuse_notify_retrieve(fc, size, cs);
++
++ case FUSE_NOTIFY_DELETE:
++ return fuse_notify_delete(fc, size, cs);
++
++ case FUSE_NOTIFY_RESEND:
++ return fuse_notify_resend(fc);
++
++ case FUSE_NOTIFY_INC_EPOCH:
++ return fuse_notify_inc_epoch(fc);
++
++ case FUSE_NOTIFY_PRUNE:
++ return fuse_notify_prune(fc, size, cs);
++
++ default:
++ return -EINVAL;
++ }
++}
--- /dev/null
+From 4dd6f6d3085a84e74b0a1efec3a05ed0b5125dce Mon Sep 17 00:00:00 2001
+From: "Matthew R. Ochs" <mochs@nvidia.com>
+Date: Tue, 26 May 2026 08:20:21 -0700
+Subject: fuse: back uncached readdir buffers with pages
+
+From: Matthew R. Ochs <mochs@nvidia.com>
+
+commit 4dd6f6d3085a84e74b0a1efec3a05ed0b5125dce upstream.
+
+Commit dabb90391028 ("fuse: increase readdir buffer size") changed
+fuse_readdir_uncached() to size its temporary buffer from ctx->count.
+This is useful for overlayfs and other in-kernel callers that use
+INT_MAX to indicate an unlimited directory read.
+
+The larger buffer is currently supplied as a kvec output argument. For
+virtiofs, kvec arguments are copied through req->argbuf, which is
+allocated with kmalloc(..., GFP_ATOMIC). A large uncached readdir buffer
+can therefore require a multi-megabyte contiguous atomic allocation
+before the request is queued.
+
+Avoid the large bounce-buffer allocation by backing uncached readdir
+output with pages and setting out_pages. Transports such as virtiofs can
+then pass the pages as scatter-gather entries instead of copying the
+output through argbuf.
+
+Map the pages with vm_map_ram() only while parsing the returned dirents.
+The existing parser can then continue to use a linear kernel mapping.
+
+[SzM: separate allocation of pages into a helper function]
+
+Fixes: dabb90391028 ("fuse: increase readdir buffer size")
+Cc: stable@vger.kernel.org
+Signed-off-by: Matthew R. Ochs <mochs@nvidia.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/readdir.c | 85 ++++++++++++++++++++++++++++++++++++++++++------------
+ 1 file changed, 67 insertions(+), 18 deletions(-)
+
+--- a/fs/fuse/readdir.c
++++ b/fs/fuse/readdir.c
+@@ -12,6 +12,7 @@
+ #include <linux/posix_acl.h>
+ #include <linux/pagemap.h>
+ #include <linux/highmem.h>
++#include <linux/vmalloc.h>
+
+ static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
+ {
+@@ -335,6 +336,43 @@ static int parse_dirplusfile(char *buf,
+ return 0;
+ }
+
++static struct page **fuse_readdir_alloc_buf(struct fuse_args_pages *ap, size_t *bufsize)
++{
++ unsigned int i, nr_alloc, nr_pages = DIV_ROUND_UP(*bufsize, PAGE_SIZE);
++ struct page **pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL);
++
++ if (!pages)
++ return NULL;
++
++ nr_alloc = alloc_pages_bulk(GFP_KERNEL, nr_pages, pages);
++ if (!nr_alloc)
++ goto free_array;
++
++ if (nr_alloc < nr_pages) {
++ nr_pages = nr_alloc;
++ *bufsize = (size_t) nr_pages << PAGE_SHIFT;
++ }
++
++ ap->folios = fuse_folios_alloc(nr_pages, GFP_KERNEL, &ap->descs);
++ if (!ap->folios)
++ goto release_pages;
++
++ for (i = 0; i < nr_pages; i++) {
++ ap->folios[i] = page_folio(pages[i]);
++ ap->descs[i].length = min_t(size_t, *bufsize - (size_t)i * PAGE_SIZE, PAGE_SIZE);
++ }
++ ap->num_folios = nr_pages;
++ ap->args.out_pages = true;
++
++ return pages;
++
++release_pages:
++ release_pages(pages, nr_pages);
++free_array:
++ kfree(pages);
++ return NULL;
++}
++
+ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
+ {
+ int plus;
+@@ -343,18 +381,16 @@ static int fuse_readdir_uncached(struct
+ struct fuse_mount *fm = get_fuse_mount(inode);
+ struct fuse_conn *fc = fm->fc;
+ struct fuse_io_args ia = {};
+- struct fuse_args *args = &ia.ap.args;
++ struct fuse_args_pages *ap = &ia.ap;
+ void *buf;
+ size_t bufsize = clamp((unsigned int) ctx->count, PAGE_SIZE, fc->max_pages << PAGE_SHIFT);
+ u64 attr_version = 0, evict_ctr = 0;
+ bool locked;
++ struct page **pages = fuse_readdir_alloc_buf(ap, &bufsize);
+
+- buf = kvmalloc(bufsize, GFP_KERNEL);
+- if (!buf)
++ if (!pages)
+ return -ENOMEM;
+
+- args->out_args[0].value = buf;
+-
+ plus = fuse_use_readdirplus(inode, ctx);
+ if (plus) {
+ attr_version = fuse_get_attr_version(fm->fc);
+@@ -364,24 +400,37 @@ static int fuse_readdir_uncached(struct
+ fuse_read_args_fill(&ia, file, ctx->pos, bufsize, FUSE_READDIR);
+ }
+ locked = fuse_lock_inode(inode);
+- res = fuse_simple_request(fm, args);
++ res = fuse_simple_request(fm, &ap->args);
+ fuse_unlock_inode(inode, locked);
+- if (res >= 0) {
+- if (!res) {
+- struct fuse_file *ff = file->private_data;
+-
+- if (ff->open_flags & FOPEN_CACHE_DIR)
+- fuse_readdir_cache_end(file, ctx->pos);
+- } else if (plus) {
+- res = parse_dirplusfile(buf, res, file, ctx, attr_version,
+- evict_ctr);
+- } else {
++ if (res < 0)
++ goto out;
++
++ if (!res) {
++ struct fuse_file *ff = file->private_data;
++
++ if (ff->open_flags & FOPEN_CACHE_DIR)
++ fuse_readdir_cache_end(file, ctx->pos);
++ goto out;
++ }
++
++ buf = vm_map_ram(pages, ap->num_folios, -1);
++ if (!buf) {
++ res = -ENOMEM;
++ } else {
++ if (plus)
++ res = parse_dirplusfile(buf, res, file, ctx, attr_version, evict_ctr);
++ else
+ res = parse_dirfile(buf, res, file, ctx);
+- }
++
++ vm_unmap_ram(buf, ap->num_folios);
+ }
++out:
++ kfree(ap->folios);
++ release_pages(pages, ap->num_folios);
++ kfree(pages);
+
+- kvfree(buf);
+ fuse_invalidate_atime(inode);
++
+ return res;
+ }
+
--- /dev/null
+From f8fce75fedf73ac72aa09163deb8f4291fdcaad2 Mon Sep 17 00:00:00 2001
+From: Ji'an Zhou <eilaimemedsnaimel@gmail.com>
+Date: Tue, 9 Jun 2026 09:58:51 +0000
+Subject: fuse: clear intr_entry in fuse_resend and fuse_remove_pending_req
+
+From: Ji'an Zhou <eilaimemedsnaimel@gmail.com>
+
+commit f8fce75fedf73ac72aa09163deb8f4291fdcaad2 upstream.
+
+When fuse_resend() moves a request from fpq->processing back to
+fiq->pending, it sets FR_PENDING and clears FR_SENT but does not
+remove the requests intr_entry from fiq->interrupts. If the
+request had FR_INTERRUPTED set from a prior signal, intr_entry
+remains dangling on fiq->interrupts. When the requesting task
+then receives a fatal signal, fuse_remove_pending_req() sees
+FR_PENDING=1, removes the request from fiq->pending and frees it
+via the refcount path, also without cleaning intr_entry. The
+stale intr_entry causes use-after-free when fuse_read_interrupt()
+iterates fiq->interrupts:
+ - list_del_init(&req->intr_entry) -> UAF write on freed slab
+ - req->in.h.unique -> UAF read, data leaked to userspace
+
+Remove intr_entry from fiq->interrupts in fuse_resend() for
+interrupted requests before they are placed back on fiq->pending.
+
+Add a WARN_ON if the intr_entry is not empty on request destruction.
+
+Fixes: 760eac73f9f6 ("fuse: Introduce a new notification type for resend pending requests")
+Cc: stable@vger.kernel.org # 6.9
+Signed-off-by: Ji'an Zhou <eilaimemedsnaimel@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/fs/fuse/dev.c
++++ b/fs/fuse/dev.c
+@@ -148,6 +148,7 @@ static struct fuse_req *fuse_request_all
+
+ static void fuse_request_free(struct fuse_req *req)
+ {
++ WARN_ON(!list_empty(&req->intr_entry));
+ kmem_cache_free(fuse_req_cachep, req);
+ }
+
+@@ -2046,6 +2047,14 @@ static void fuse_resend(struct fuse_conn
+ fuse_dev_end_requests(&to_queue);
+ return;
+ }
++ /*
++ * Remove interrupt entries for resent requests to prevent stale
++ * intr_entry on fiq->interrupts after the request is re-queued.
++ */
++ list_for_each_entry(req, &to_queue, list) {
++ if (test_bit(FR_INTERRUPTED, &req->flags))
++ list_del_init(&req->intr_entry);
++ }
+ /* iq and pq requests are both oldest to newest */
+ list_splice(&to_queue, &fiq->pending);
+ fuse_dev_wake_and_unlock(fiq);
--- /dev/null
+From 9fa4f7a53406430ee9982f2f636a15b338185122 Mon Sep 17 00:00:00 2001
+From: Alberto Ruiz <aruiz@redhat.com>
+Date: Wed, 8 Apr 2026 17:23:40 +0200
+Subject: fuse: fix device node leak in cuse_process_init_reply()
+
+From: Alberto Ruiz <aruiz@redhat.com>
+
+commit 9fa4f7a53406430ee9982f2f636a15b338185122 upstream.
+
+If device_add() succeeds during CUSE initialization but a subsequent
+step (cdev_alloc() or cdev_add()) fails, the error path calls
+put_device() without first calling device_del(). This leaks the
+devtmpfs entry created by device_add(), leaving a stale /dev/<name>
+node that persists until reboot.
+
+Since the cuse_conn is never linked into cuse_conntbl on the failure
+path, cuse_channel_release() sees cc->dev == NULL and skips
+device_unregister(), so no other code path cleans up the node.
+
+This has several consequences:
+
+ - The device name is permanently poisoned: any subsequent attempt to
+ create a CUSE device with the same name hits the stale sysfs entry,
+ device_add() fails, and the new device is aborted.
+
+ - The collision manifests as ENODEV returned to userspace with no
+ dmesg diagnostic, making it very difficult to debug.
+
+ - The failure is self-perpetuating: once a name is leaked, all future
+ attempts with that name fail identically.
+
+Fix this by introducing an err_dev label that calls device_del() to
+undo device_add() before falling through to err_unlock. The existing
+err_unlock path from a device_add() failure correctly skips device_del()
+since the device was never added.
+
+Testing instructions can be found at the lore link below.
+
+Link: https://lore.kernel.org/all/20260408-wip-cuse-leak-fix-v1-0-1c028d575e97@redhat.com/
+Signed-off-by: Alberto Ruiz <aruiz@redhat.com>
+Fixes: 151060ac1314 ("CUSE: implement CUSE - Character device in Userspace")
+Cc: stable@vger.kernel.org
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/cuse.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/fs/fuse/cuse.c
++++ b/fs/fuse/cuse.c
+@@ -391,7 +391,7 @@ static void cuse_process_init_reply(stru
+ rc = -ENOMEM;
+ cdev = cdev_alloc();
+ if (!cdev)
+- goto err_unlock;
++ goto err_dev;
+
+ cdev->owner = THIS_MODULE;
+ cdev->ops = &cuse_frontend_fops;
+@@ -417,6 +417,8 @@ out:
+
+ err_cdev:
+ cdev_del(cdev);
++err_dev:
++ device_del(dev);
+ err_unlock:
+ mutex_unlock(&cuse_lock);
+ put_device(dev);
--- /dev/null
+From 31da059891bd3be9c6e59280b8e1777ead90db34 Mon Sep 17 00:00:00 2001
+From: Joanne Koong <joannelkoong@gmail.com>
+Date: Wed, 8 Apr 2026 10:25:10 -0700
+Subject: fuse: fix io-uring background queue dispatch on request completion
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+commit 31da059891bd3be9c6e59280b8e1777ead90db34 upstream.
+
+When a background request completes via the io_uring path, the
+background queue gets flushed to dispatch pending background requests,
+but this is done before the connection-level background counters
+(fc->num_background, fc->active_background) are properly accounted,
+which may reduce effective queue depth to one.
+
+The connection-level counters are decremented in fuse_request_end(), but
+flush_bg_queue() flushes the /dev/fuse path queue (fc->bg_queue), not
+the io_uring per-queue bg one, which means pending uring background
+requests on the queue are never dispatched in this path.
+
+Fix this by accounting the connection-level background counters first
+before flushing the queue's background queue. Since
+fuse_request_bg_finish() clears FR_BACKGROUND, fuse_request_end() will
+skip the background cleanup branch entirely, which avoids any
+double-decrements; it will call the wake_up(&req->waitq) branch but this
+is effectively a no-op as background requests have no waiters on
+req->waitq.
+
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Fixes: 857b0263f30e ("fuse: Allow to queue bg requests through io-uring")
+Cc: stable@vger.kernel.org
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev.c | 41 ++++++++++++++++++++++++-----------------
+ fs/fuse/dev_uring.c | 1 +
+ fs/fuse/fuse_dev_i.h | 1 +
+ 3 files changed, 26 insertions(+), 17 deletions(-)
+
+--- a/fs/fuse/dev.c
++++ b/fs/fuse/dev.c
+@@ -447,6 +447,29 @@ static void flush_bg_queue(struct fuse_c
+ }
+ }
+
++void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req)
++{
++ lockdep_assert_held(&fc->bg_lock);
++
++ clear_bit(FR_BACKGROUND, &req->flags);
++ if (fc->num_background == fc->max_background) {
++ fc->blocked = 0;
++ wake_up(&fc->blocked_waitq);
++ } else if (!fc->blocked) {
++ /*
++ * Wake up next waiter, if any. It's okay to use
++ * waitqueue_active(), as we've already synced up
++ * fc->blocked with waiters with the wake_up() call
++ * above.
++ */
++ if (waitqueue_active(&fc->blocked_waitq))
++ wake_up(&fc->blocked_waitq);
++ }
++
++ fc->num_background--;
++ fc->active_background--;
++}
++
+ /*
+ * This function is called when a request is finished. Either a reply
+ * has arrived or it was aborted (and not yet sent) or some error
+@@ -479,23 +502,7 @@ void fuse_request_end(struct fuse_req *r
+ WARN_ON(test_bit(FR_SENT, &req->flags));
+ if (test_bit(FR_BACKGROUND, &req->flags)) {
+ spin_lock(&fc->bg_lock);
+- clear_bit(FR_BACKGROUND, &req->flags);
+- if (fc->num_background == fc->max_background) {
+- fc->blocked = 0;
+- wake_up(&fc->blocked_waitq);
+- } else if (!fc->blocked) {
+- /*
+- * Wake up next waiter, if any. It's okay to use
+- * waitqueue_active(), as we've already synced up
+- * fc->blocked with waiters with the wake_up() call
+- * above.
+- */
+- if (waitqueue_active(&fc->blocked_waitq))
+- wake_up(&fc->blocked_waitq);
+- }
+-
+- fc->num_background--;
+- fc->active_background--;
++ fuse_request_bg_finish(fc, req);
+ flush_bg_queue(fc);
+ spin_unlock(&fc->bg_lock);
+ } else {
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -90,6 +90,7 @@ static void fuse_uring_req_end(struct fu
+ if (test_bit(FR_BACKGROUND, &req->flags)) {
+ queue->active_background--;
+ spin_lock(&fc->bg_lock);
++ fuse_request_bg_finish(fc, req);
+ fuse_uring_flush_bg(queue);
+ spin_unlock(&fc->bg_lock);
+ }
+--- a/fs/fuse/fuse_dev_i.h
++++ b/fs/fuse/fuse_dev_i.h
+@@ -59,6 +59,7 @@ unsigned int fuse_req_hash(u64 unique);
+ struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique);
+
+ void fuse_dev_end_requests(struct list_head *head);
++void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req);
+
+ void fuse_copy_init(struct fuse_copy_state *cs, bool write,
+ struct iov_iter *iter);
--- /dev/null
+From b5befa80fdbe287a98480effed9564712924add5 Mon Sep 17 00:00:00 2001
+From: Joanne Koong <joannelkoong@gmail.com>
+Date: Mon, 18 May 2026 22:28:07 -0700
+Subject: fuse: re-lock request before returning from fuse_ref_folio()
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+commit b5befa80fdbe287a98480effed9564712924add5 upstream.
+
+fuse_ref_folio() unlocks the request but does not re-lock it before
+returning. fuse_chan_abort() can end the request and the async end
+callback (eg fuse_writepage_free()) can free the args while the
+subsequent copy chain logic after fuse_ref_folio() accesses them,
+leading to use-after-free issues.
+
+Fix this by locking the request in fuse_ref_folio() before returning.
+
+Fixes: c3021629a0d8 ("fuse: support splice() reading from fuse device")
+Cc: stable@vger.kernel.org
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/fs/fuse/dev.c
++++ b/fs/fuse/dev.c
+@@ -1106,7 +1106,7 @@ static int fuse_ref_folio(struct fuse_co
+ cs->nr_segs++;
+ cs->len = 0;
+
+- return 0;
++ return lock_request(cs->req);
+ }
+
+ /*
--- /dev/null
+From b70a3aca16934c196f92abb17b01c1647b9bb63c Mon Sep 17 00:00:00 2001
+From: Bernd Schubert <bernd@bsbernd.com>
+Date: Mon, 8 Jun 2026 23:03:44 +0200
+Subject: fuse-uring: Avoid queue->stopped races and set/read that value under lock
+
+From: Bernd Schubert <bernd@bsbernd.com>
+
+commit b70a3aca16934c196f92abb17b01c1647b9bb63c upstream.
+
+There are several readers of queue->stopped that check the value
+under lock, but fuse_uring_commit_fetch() did not and actually
+the value was not set under the lock in fuse_uring_abort_end_requests()
+either. Especially in fuse_uring_commit_fetch it is important
+to check under a lock, because due to races 'struct fuse_req'
+might be freed with fuse_request_end, but another thread/cpu
+might already do teardown work.
+
+Cc: stable@kernel.org # 6.14
+Fixes: 4a9bfb9b6850fec ("fuse: {io-uring} Handle teardown of ring entries")
+Reported-by: Berkant Koc <me@berkoc.com>
+Reported-by: xlabai <xlabai@tencent.com>
+Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 14 +++++++++-----
+ 1 file changed, 9 insertions(+), 5 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -131,10 +131,9 @@ void fuse_uring_abort_end_requests(struc
+ if (!queue)
+ continue;
+
+- queue->stopped = true;
+-
+ WARN_ON_ONCE(ring->fc->max_background != UINT_MAX);
+ spin_lock(&queue->lock);
++ queue->stopped = true;
+ spin_lock(&fc->bg_lock);
+ fuse_uring_flush_bg(queue);
+ spin_unlock(&fc->bg_lock);
+@@ -462,7 +461,7 @@ static void fuse_uring_async_stop_queues
+ FUSE_URING_TEARDOWN_INTERVAL);
+ } else {
+ wake_up_all(&ring->stop_waitq);
+- fuse_conn_put(ring->chan->conn);
++ fuse_conn_put(ring->fc);
+ }
+ }
+
+@@ -483,7 +482,7 @@ void fuse_uring_stop_queues(struct fuse_
+ }
+
+ if (atomic_read(&ring->queue_refs) > 0) {
+- fuse_conn_get(ring->chan->conn);
++ fuse_conn_get(ring->fc);
+ ring->teardown_time = jiffies;
+ INIT_DELAYED_WORK(&ring->async_teardown_work,
+ fuse_uring_async_stop_queues);
+@@ -903,10 +902,15 @@ static int fuse_uring_commit_fetch(struc
+ return err;
+ fpq = &queue->fpq;
+
+- if (!READ_ONCE(fc->connected) || READ_ONCE(queue->stopped))
++ if (!READ_ONCE(fc->connected))
+ return err;
+
+ spin_lock(&queue->lock);
++ if (unlikely(queue->stopped)) {
++ spin_unlock(&queue->lock);
++ return err;
++ }
++
+ /* Find a request based on the unique ID of the fuse request
+ * This should get revised, as it needs a hash calculation and list
+ * search. And full struct fuse_pqueue is needed (memory overhead).
--- /dev/null
+From d351da75066955144515cb2f9aa959f24a04287a Mon Sep 17 00:00:00 2001
+From: Bernd Schubert <bernd@bsbernd.com>
+Date: Mon, 8 Jun 2026 23:03:43 +0200
+Subject: fuse-uring: Avoid use-after-free in fuse_uring_async_stop_queues
+
+From: Bernd Schubert <bernd@bsbernd.com>
+
+commit d351da75066955144515cb2f9aa959f24a04287a upstream.
+
+fuse_uring_async_stop_queues() might run when the last reference
+on ring->queue_refs was already dropped.
+
+In order to avoid an early destruction a reference on struct fuse_conn
+is now taken before starting fuse_uring_async_stop_queues() and that
+reference is only released when that delayed work queue terminates.
+
+Fixes: 4a9bfb9b6850 ("fuse: {io-uring} Handle teardown of ring entries")
+Cc: stable@kernel.org # 6.14
+Reported-by: Berkant Koc <me@berkoc.com>
+Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -462,6 +462,7 @@ static void fuse_uring_async_stop_queues
+ FUSE_URING_TEARDOWN_INTERVAL);
+ } else {
+ wake_up_all(&ring->stop_waitq);
++ fuse_conn_put(ring->chan->conn);
+ }
+ }
+
+@@ -482,6 +483,7 @@ void fuse_uring_stop_queues(struct fuse_
+ }
+
+ if (atomic_read(&ring->queue_refs) > 0) {
++ fuse_conn_get(ring->chan->conn);
+ ring->teardown_time = jiffies;
+ INIT_DELAYED_WORK(&ring->async_teardown_work,
+ fuse_uring_async_stop_queues);
--- /dev/null
+From bea4fe98204b6ce7eb8e29f7bf867dd7619b3ddd Mon Sep 17 00:00:00 2001
+From: Chris Mason <clm@meta.com>
+Date: Mon, 8 Jun 2026 17:28:55 -0700
+Subject: fuse-uring: end fuse_req on io-uring cancel task work
+
+From: Chris Mason <clm@meta.com>
+
+commit bea4fe98204b6ce7eb8e29f7bf867dd7619b3ddd upstream.
+
+When io_uring delivers task work with tw.cancel set (PF_EXITING,
+PF_KTHREAD fallback, or percpu_ref_is_dying on the ring context),
+fuse_uring_send_in_task() takes the cancel branch, assigns
+-ECANCELED, and falls through to fuse_uring_send(). That path only
+flips the entry to FRRS_USERSPACE and completes the io_uring cmd;
+it never discharges the ring entry's owning reference to the
+fuse_req that fuse_uring_add_req_to_ring_ent() handed it at
+dispatch time.
+
+ fuse_uring_send_in_task()
+ tw.cancel == true
+ err = -ECANCELED
+ fuse_uring_send(ent, cmd, err, issue_flags)
+ ent->state = FRRS_USERSPACE
+ list_move(&ent->list, &queue->ent_in_userspace)
+ ent->cmd = NULL
+ io_uring_cmd_done(-ECANCELED)
+ /* ent->fuse_req still set, req still hashed */
+
+The fuse_req stays linked on fpq->processing[hash] and
+fuse_request_end() is never invoked. The originating syscall
+thread blocks in D-state in request_wait_answer() until
+fuse_abort_conn() runs, which can be the entire connection
+lifetime. For FR_BACKGROUND requests fc->num_background is never
+decremented either, so repeated cancels inflate the counter until
+max_background is hit and all later background ops stall. tw.cancel does
+not imply a connection abort (e.g. a single io_uring worker thread exits
+while the fuse connection stays up), so this cannot be left for
+fuse_abort_conn() to clean up.
+
+Ending the req but still routing the entry through fuse_uring_send()
+is not enough: that leaves a req-less entry on ent_in_userspace, and
+ent_list_request_expired() dereferences ent->fuse_req unconditionally
+on the head of that list, which would then NULL-deref.
+
+Fix the cancel branch to release the entry directly. Remove it from the
+queue, complete the io_uring cmd, end the fuse_req, free the entry, and
+drop its queue_refs (waking the teardown waiter if it was the last).
+
+Fixes: c2c9af9a0b13 ("fuse: Allow to queue fg requests through io-uring")
+Cc: stable@vger.kernel.org
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Assisted-by: kres (claude-opus-4-7)
+Signed-off-by: Chris Mason <clm@meta.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -1226,11 +1226,21 @@ static void fuse_uring_send_in_task(stru
+ fuse_uring_next_fuse_req(ent, queue, issue_flags);
+ return;
+ }
++ fuse_uring_send(ent, cmd, err, issue_flags);
+ } else {
+ err = -ECANCELED;
+- }
+
+- fuse_uring_send(ent, cmd, err, issue_flags);
++ spin_lock(&queue->lock);
++ list_del_init(&ent->list);
++ spin_unlock(&queue->lock);
++
++ io_uring_cmd_done(cmd, err, issue_flags);
++
++ fuse_uring_req_end(ent, ent->fuse_req, err);
++ kfree(ent);
++ if (atomic_dec_and_test(&queue->ring->queue_refs))
++ wake_up_all(&queue->ring->stop_waitq);
++ }
+ }
+
+ static struct fuse_ring_queue *fuse_uring_task_to_queue(struct fuse_ring *ring)
--- /dev/null
+From 46725a0056c884cf58a6897f222892807327d82d Mon Sep 17 00:00:00 2001
+From: Chris Mason <clm@meta.com>
+Date: Fri, 5 Jun 2026 12:27:07 -0700
+Subject: fuse-uring: fix data races on ring->ready
+
+From: Chris Mason <clm@meta.com>
+
+commit 46725a0056c884cf58a6897f222892807327d82d upstream.
+
+On weakly-ordered architectures, the store to fiq->ops can be
+reordered past the store to ring->ready, allowing a CPU that sees
+ring->ready == true via fuse_uring_ready() to dispatch requests
+through a stale fiq->ops pointer. Upgrade the store to
+smp_store_release() and the load in fuse_uring_ready() to
+smp_load_acquire() so that the preceding WRITE_ONCE(fiq->ops, ...)
+is visible to any CPU that observes ring->ready == true.
+
+Additionally, fuse_uring_do_register() publishes ring->ready with
+WRITE_ONCE() but the fast-path check reads it with a plain load.
+This is a marked-vs-unmarked access that KCSAN will flag. Wrap it in
+READ_ONCE() to mark it without adding unnecessary ordering.
+
+Also wrap the fc->ring load in fuse_uring_ready() in READ_ONCE() to
+prevent the compiler from reloading it between the NULL check and the
+dereference.
+
+Fixes: c2c9af9a0b13 ("fuse: Allow to queue fg requests through io-uring")
+Cc: stable@vger.kernel.org
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Assisted-by: kres (claude-opus-4-7)
+Signed-off-by: Chris Mason <clm@meta.com>
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 4 ++--
+ fs/fuse/dev_uring_i.h | 4 +++-
+ 2 files changed, 5 insertions(+), 3 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -992,12 +992,12 @@ static void fuse_uring_do_register(struc
+ fuse_uring_ent_avail(ent, queue);
+ spin_unlock(&queue->lock);
+
+- if (!ring->ready) {
++ if (!READ_ONCE(ring->ready)) {
+ bool ready = is_ring_ready(ring, queue->qid);
+
+ if (ready) {
+ WRITE_ONCE(fiq->ops, &fuse_io_uring_ops);
+- WRITE_ONCE(ring->ready, true);
++ smp_store_release(&ring->ready, true);
+ wake_up_all(&fc->blocked_waitq);
+ }
+ }
+--- a/fs/fuse/dev_uring_i.h
++++ b/fs/fuse/dev_uring_i.h
+@@ -169,7 +169,9 @@ static inline void fuse_uring_wait_stopp
+
+ static inline bool fuse_uring_ready(struct fuse_conn *fc)
+ {
+- return fc->ring && fc->ring->ready;
++ struct fuse_ring *ring = READ_ONCE(fc->ring);
++
++ return ring && smp_load_acquire(&ring->ready);
+ }
+
+ #else /* CONFIG_FUSE_IO_URING */
--- /dev/null
+From 3a0a8bc51a13951c5141262bf770eeea3e0b6228 Mon Sep 17 00:00:00 2001
+From: Chris Mason <clm@meta.com>
+Date: Fri, 5 Jun 2026 12:27:06 -0700
+Subject: fuse-uring: fix EFAULT clobber in fuse_uring_commit
+
+From: Chris Mason <clm@meta.com>
+
+commit 3a0a8bc51a13951c5141262bf770eeea3e0b6228 upstream.
+
+copy_from_user() returns the number of bytes not copied as an unsigned
+residual on failure (1..sizeof(struct fuse_out_header)). fuse_uring_commit
+stores that residual in ssize_t err, sets req->out.h.error to -EFAULT,
+then jumps to out: with err still holding the positive residual.
+
+ err = copy_from_user(&req->out.h, &ent->headers->in_out,
+ sizeof(req->out.h));
+ if (err) {
+ req->out.h.error = -EFAULT;
+ goto out; /* err is the positive residual */
+ }
+ ...
+ out:
+ fuse_uring_req_end(ent, req, err);
+
+fuse_uring_req_end() then runs
+
+ if (error)
+ req->out.h.error = error;
+
+which overwrites the just-assigned -EFAULT with the positive residual.
+FUSE callers such as fuse_simple_request() test err < 0 to detect
+failure, so the positive value is interpreted as success and the
+caller proceeds with an uninitialised or partial req->out.args.
+
+Fix by assigning err = -EFAULT in the failure branch before jumping
+to out, so fuse_uring_req_end() receives a negative errno and sets
+req->out.h.error to -EFAULT.
+
+Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
+Cc: stable@vger.kernel.org
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Assisted-by: kres (claude-opus-4-7)
+Signed-off-by: Chris Mason <clm@meta.com>
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 9 +++------
+ 1 file changed, 3 insertions(+), 6 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -818,14 +818,11 @@ static void fuse_uring_commit(struct fus
+ {
+ struct fuse_ring *ring = ent->queue->ring;
+ struct fuse_conn *fc = ring->fc;
+- ssize_t err = 0;
++ ssize_t err = -EFAULT;
+
+- err = copy_from_user(&req->out.h, &ent->headers->in_out,
+- sizeof(req->out.h));
+- if (err) {
+- req->out.h.error = -EFAULT;
++ if (copy_from_user(&req->out.h, &ent->headers->in_out,
++ sizeof(req->out.h)))
+ goto out;
+- }
+
+ err = fuse_uring_out_header_has_err(&req->out.h, req, fc);
+ if (err) {
--- /dev/null
+From 198f45eeb9f78b2a2d6d8be95e4e43468eb2c6bc Mon Sep 17 00:00:00 2001
+From: Joanne Koong <joannelkoong@gmail.com>
+Date: Mon, 8 Jun 2026 12:21:49 -0700
+Subject: fuse-uring: fix moving cancelled entry to ent_in_userspace list
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+commit 198f45eeb9f78b2a2d6d8be95e4e43468eb2c6bc upstream.
+
+fuse_uring_cancel() moves entries that are available (these have no reqs
+attached) to the ent_in_userspace list. ent_list_request_expired()
+checks the first entry on ent_in_userspace and dereferences
+ent->fuse_req unconditionally, which will crash on a cancelled entry
+that was moved to this list.
+
+Fix this by freeing the entry and dropping queue_refs directly in
+fuse_uring_cancel(). This is safe because cancel is the cancel handler
+itself - after io_uring_cmd_done(), no more cancels will be dispatched
+for this command, and teardown serializes with cancel via queue->lock.
+
+Since cancel now decrements queue_refs, fuse_uring_abort() must no
+longer gate fuse_uring_abort_end_requests() on queue_refs > 0, as
+cancelled entries may have already dropped queue_refs while requests are
+still queued. Remove the gate so abort always flushes requests and stops
+queues.
+
+Reported-by: Heechan Kang <gganji11@naver.com>
+Tested-by: Heechan Kang <gganji11@naver.com>
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check")
+Cc: stable@vger.kernel.org
+Suggested-by: Jian Huang Li <ali@ddn.com>
+Suggested-by: Horst Birthelmer <horst@birthelmer.de>
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 6 ++++--
+ fs/fuse/dev_uring_i.h | 6 +++---
+ 2 files changed, 7 insertions(+), 5 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -512,8 +512,7 @@ static void fuse_uring_cancel(struct io_
+ queue = ent->queue;
+ spin_lock(&queue->lock);
+ if (ent->state == FRRS_AVAILABLE) {
+- ent->state = FRRS_USERSPACE;
+- list_move_tail(&ent->list, &queue->ent_in_userspace);
++ list_del_init(&ent->list);
+ need_cmd_done = true;
+ ent->cmd = NULL;
+ }
+@@ -522,6 +521,9 @@ static void fuse_uring_cancel(struct io_
+ if (need_cmd_done) {
+ /* no queue lock to avoid lock order issues */
+ io_uring_cmd_done(cmd, -ENOTCONN, issue_flags);
++ kfree(ent);
++ if (atomic_dec_and_test(&queue->ring->queue_refs))
++ wake_up_all(&queue->ring->stop_waitq);
+ }
+ }
+
+--- a/fs/fuse/dev_uring_i.h
++++ b/fs/fuse/dev_uring_i.h
+@@ -152,10 +152,10 @@ static inline void fuse_uring_abort(stru
+ if (ring == NULL)
+ return;
+
+- if (atomic_read(&ring->queue_refs) > 0) {
+- fuse_uring_abort_end_requests(ring);
++ fuse_uring_abort_end_requests(ring);
++
++ if (atomic_read(&ring->queue_refs) > 0)
+ fuse_uring_stop_queues(ring);
+- }
+ }
+
+ static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc)
--- /dev/null
+From 1efd3d474fc0ba74dfd984249bca78807d739812 Mon Sep 17 00:00:00 2001
+From: Bernd Schubert <bernd@bsbernd.com>
+Date: Mon, 8 Jun 2026 23:03:45 +0200
+Subject: fuse-uring: make a fuse_req on SQE commit only findable after memcpy
+
+From: Bernd Schubert <bernd@bsbernd.com>
+
+commit 1efd3d474fc0ba74dfd984249bca78807d739812 upstream.
+
+Bad userspace might try to trick us and send commit SQEs request
+unique / commit-id of requests that are not even send to
+fuse-server (io_uring_cmd_done() not called) yet.
+
+fuse_uring_commit_fetch() ends the fuse request when the ring entry
+has a wrong state, but that could have caused a use-after-free
+with the memcpy operations in fuse_uring_send_in_task().
+In order to avoid such races the call of fuse_uring_add_to_pq()
+is moved after the copy operations and just before completing
+the io-uring request - malicious userspace cannot find the request
+anymore until all prepration work in fuse-client/kernel is completed.
+
+This also moves fuse_uring_add_to_pq() a bit up in the code to
+avoid a forward declaration. Also not with a preparation commit,
+to make it easier to back port to older kernels.
+
+Reported-by: xlabai <xlabai@tencent.com>
+Reported-by: Berkant Koc <me@berkoc.com>
+Fixes: c090c8abae4b6b ("fuse: Add io-uring sqe commit and fetch support")
+Cc: stable@kernel.org # 6.14
+Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
+Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 33 +++++++++++++++++++--------------
+ 1 file changed, 19 insertions(+), 14 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -718,6 +718,19 @@ static int fuse_uring_prepare_send(struc
+ return err;
+ }
+
++/* Used to find the request on SQE commit */
++static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent)
++{
++ struct fuse_ring_queue *queue = ent->queue;
++ struct fuse_pqueue *fpq = &queue->fpq;
++ unsigned int hash;
++ struct fuse_req *req = ent->fuse_req;
++
++ req->ring_entry = ent;
++ hash = fuse_req_hash(req->in.h.unique);
++ list_move_tail(&req->list, &fpq->processing[hash]);
++}
++
+ /*
+ * Write data to the ring buffer and send the request to userspace,
+ * userspace will read it
+@@ -740,6 +753,7 @@ static int fuse_uring_send_next_to_ring(
+ ent->cmd = NULL;
+ ent->state = FRRS_USERSPACE;
+ list_move_tail(&ent->list, &queue->ent_in_userspace);
++ fuse_uring_add_to_pq(ent);
+ spin_unlock(&queue->lock);
+
+ io_uring_cmd_done(cmd, 0, issue_flags);
+@@ -757,19 +771,6 @@ static void fuse_uring_ent_avail(struct
+ ent->state = FRRS_AVAILABLE;
+ }
+
+-/* Used to find the request on SQE commit */
+-static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent,
+- struct fuse_req *req)
+-{
+- struct fuse_ring_queue *queue = ent->queue;
+- struct fuse_pqueue *fpq = &queue->fpq;
+- unsigned int hash;
+-
+- req->ring_entry = ent;
+- hash = fuse_req_hash(req->in.h.unique);
+- list_move_tail(&req->list, &fpq->processing[hash]);
+-}
+-
+ /*
+ * Assign a fuse queue entry to the given entry
+ */
+@@ -787,10 +788,13 @@ static void fuse_uring_add_req_to_ring_e
+ }
+
+ clear_bit(FR_PENDING, &req->flags);
++
++ /* Until fuse_uring_add_to_pq() the req is not attached to any list */
++ list_del_init(&req->list);
++
+ ent->fuse_req = req;
+ ent->state = FRRS_FUSE_REQ;
+ list_move_tail(&ent->list, &queue->ent_w_req_queue);
+- fuse_uring_add_to_pq(ent, req);
+ }
+
+ /* Fetch the next fuse request if available */
+@@ -1209,6 +1213,7 @@ static void fuse_uring_send(struct fuse_
+ ent->state = FRRS_USERSPACE;
+ list_move_tail(&ent->list, &queue->ent_in_userspace);
+ ent->cmd = NULL;
++ fuse_uring_add_to_pq(ent);
+ spin_unlock(&queue->lock);
+
+ io_uring_cmd_done(cmd, ret, issue_flags);
--- /dev/null
+From 1c57a69be962d459c5e705f5cb4355b841b3461c Mon Sep 17 00:00:00 2001
+From: Joanne Koong <joannelkoong@gmail.com>
+Date: Tue, 9 Jun 2026 14:36:58 -0700
+Subject: fuse-uring: remove request-less entries from ent_w_req_queue to fix NULL deref
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+commit 1c57a69be962d459c5e705f5cb4355b841b3461c upstream.
+
+If a copy into the userspace ring buffer fails, a request will be
+terminated and fuse_uring_req_end() will set ent->fuse_req to NULL but
+it will leave the entry on ent_w_req_queue in FRRS_FUSE_REQ state. This
+can lead to a NULL deref if the request expiration logic scans
+ent_w_req_queue in the window before the entry is moved off it.
+
+Fix this by taking the entry off ent_w_req_queue and changing its state
+from FRRS_FUSE_REQ to FRRS_INVALID before terminating the request.
+
+Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check")
+Cc: stable@kernel.org
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -710,10 +710,20 @@ static int fuse_uring_prepare_send(struc
+ int err;
+
+ err = fuse_uring_copy_to_ring(ent, req);
+- if (!err)
++ if (!err) {
+ set_bit(FR_SENT, &req->flags);
+- else
++ } else {
++ /*
++ * Copying the request failed. Remove the entry from the
++ * ent_w_req_queue list and terminate the request
++ */
++ spin_lock(&ent->queue->lock);
++ list_del_init(&ent->list);
++ ent->state = FRRS_INVALID;
++ spin_unlock(&ent->queue->lock);
++
+ fuse_uring_req_end(ent, req, err);
++ }
+
+ return err;
+ }
rdma-rtrs-srv-bound-rdma-write-length-to-chunk-size-in-rdma_write_sg.patch
rdma-core-fix-broadcast-address-falsely-detected-as-local.patch
rdma-siw-bound-read-response-placement-to-the-rread-length.patch
+fuse-back-uncached-readdir-buffers-with-pages.patch
+fuse-avoid-32-bit-prune-notification-count-wrap.patch
+fuse-fix-device-node-leak-in-cuse_process_init_reply.patch
+fuse-re-lock-request-before-returning-from-fuse_ref_folio.patch
+fuse-fix-io-uring-background-queue-dispatch-on-request-completion.patch
+fuse-clear-intr_entry-in-fuse_resend-and-fuse_remove_pending_req.patch
+fuse-uring-fix-efault-clobber-in-fuse_uring_commit.patch
+fuse-uring-fix-data-races-on-ring-ready.patch
+fuse-uring-fix-moving-cancelled-entry-to-ent_in_userspace-list.patch
+fuse-uring-end-fuse_req-on-io-uring-cancel-task-work.patch
+fuse-uring-avoid-use-after-free-in-fuse_uring_async_stop_queues.patch
+fuse-uring-avoid-queue-stopped-races-and-set-read-that-value-under-lock.patch
+fuse-uring-make-a-fuse_req-on-sqe-commit-only-findable-after-memcpy.patch
+fuse-uring-remove-request-less-entries-from-ent_w_req_queue-to-fix-null-deref.patch