From: Greg Kroah-Hartman Date: Thu, 16 Jul 2026 13:11:11 +0000 (+0200) Subject: 6.18-stable patches X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3e523a14708d30acd97fdd31726c5cadd0cc57c2;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-stable patches added patches: fuse-avoid-32-bit-prune-notification-count-wrap.patch fuse-back-uncached-readdir-buffers-with-pages.patch fuse-clear-intr_entry-in-fuse_resend-and-fuse_remove_pending_req.patch fuse-fix-device-node-leak-in-cuse_process_init_reply.patch fuse-fix-io-uring-background-queue-dispatch-on-request-completion.patch fuse-re-lock-request-before-returning-from-fuse_ref_folio.patch fuse-uring-avoid-queue-stopped-races-and-set-read-that-value-under-lock.patch fuse-uring-avoid-use-after-free-in-fuse_uring_async_stop_queues.patch fuse-uring-end-fuse_req-on-io-uring-cancel-task-work.patch fuse-uring-fix-data-races-on-ring-ready.patch fuse-uring-fix-efault-clobber-in-fuse_uring_commit.patch fuse-uring-fix-moving-cancelled-entry-to-ent_in_userspace-list.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 --- diff --git a/queue-6.18/fuse-avoid-32-bit-prune-notification-count-wrap.patch b/queue-6.18/fuse-avoid-32-bit-prune-notification-count-wrap.patch new file mode 100644 index 0000000000..5e344056e7 --- /dev/null +++ b/queue-6.18/fuse-avoid-32-bit-prune-notification-count-wrap.patch @@ -0,0 +1,485 @@ +From 54243797cedf55447b4c5d560e8cd709900061ae Mon Sep 17 00:00:00 2001 +From: Samuel Moelius +Date: Wed, 10 Jun 2026 00:37:18 +0000 +Subject: fuse: avoid 32-bit prune notification count wrap + +From: Samuel Moelius + +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 +Reviewed-by: Joanne Koong +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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 ++ ++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; ++ } ++} diff --git a/queue-6.18/fuse-back-uncached-readdir-buffers-with-pages.patch b/queue-6.18/fuse-back-uncached-readdir-buffers-with-pages.patch new file mode 100644 index 0000000000..2da482153c --- /dev/null +++ b/queue-6.18/fuse-back-uncached-readdir-buffers-with-pages.patch @@ -0,0 +1,166 @@ +From 4dd6f6d3085a84e74b0a1efec3a05ed0b5125dce Mon Sep 17 00:00:00 2001 +From: "Matthew R. Ochs" +Date: Tue, 26 May 2026 08:20:21 -0700 +Subject: fuse: back uncached readdir buffers with pages + +From: Matthew R. Ochs + +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 +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + + 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; + } + diff --git a/queue-6.18/fuse-clear-intr_entry-in-fuse_resend-and-fuse_remove_pending_req.patch b/queue-6.18/fuse-clear-intr_entry-in-fuse_resend-and-fuse_remove_pending_req.patch new file mode 100644 index 0000000000..adba4afb9b --- /dev/null +++ b/queue-6.18/fuse-clear-intr_entry-in-fuse_resend-and-fuse_remove_pending_req.patch @@ -0,0 +1,61 @@ +From f8fce75fedf73ac72aa09163deb8f4291fdcaad2 Mon Sep 17 00:00:00 2001 +From: Ji'an Zhou +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 + +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 +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.18/fuse-fix-device-node-leak-in-cuse_process_init_reply.patch b/queue-6.18/fuse-fix-device-node-leak-in-cuse_process_init_reply.patch new file mode 100644 index 0000000000..1a31fa0df6 --- /dev/null +++ b/queue-6.18/fuse-fix-device-node-leak-in-cuse_process_init_reply.patch @@ -0,0 +1,68 @@ +From 9fa4f7a53406430ee9982f2f636a15b338185122 Mon Sep 17 00:00:00 2001 +From: Alberto Ruiz +Date: Wed, 8 Apr 2026 17:23:40 +0200 +Subject: fuse: fix device node leak in cuse_process_init_reply() + +From: Alberto Ruiz + +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/ +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 +Fixes: 151060ac1314 ("CUSE: implement CUSE - Character device in Userspace") +Cc: stable@vger.kernel.org +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.18/fuse-fix-io-uring-background-queue-dispatch-on-request-completion.patch b/queue-6.18/fuse-fix-io-uring-background-queue-dispatch-on-request-completion.patch new file mode 100644 index 0000000000..82d738e8d0 --- /dev/null +++ b/queue-6.18/fuse-fix-io-uring-background-queue-dispatch-on-request-completion.patch @@ -0,0 +1,117 @@ +From 31da059891bd3be9c6e59280b8e1777ead90db34 Mon Sep 17 00:00:00 2001 +From: Joanne Koong +Date: Wed, 8 Apr 2026 10:25:10 -0700 +Subject: fuse: fix io-uring background queue dispatch on request completion + +From: Joanne Koong + +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 +Fixes: 857b0263f30e ("fuse: Allow to queue bg requests through io-uring") +Cc: stable@vger.kernel.org +Signed-off-by: Joanne Koong +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.18/fuse-re-lock-request-before-returning-from-fuse_ref_folio.patch b/queue-6.18/fuse-re-lock-request-before-returning-from-fuse_ref_folio.patch new file mode 100644 index 0000000000..a49751b2ca --- /dev/null +++ b/queue-6.18/fuse-re-lock-request-before-returning-from-fuse_ref_folio.patch @@ -0,0 +1,37 @@ +From b5befa80fdbe287a98480effed9564712924add5 Mon Sep 17 00:00:00 2001 +From: Joanne Koong +Date: Mon, 18 May 2026 22:28:07 -0700 +Subject: fuse: re-lock request before returning from fuse_ref_folio() + +From: Joanne Koong + +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 +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + + /* diff --git a/queue-6.18/fuse-uring-avoid-queue-stopped-races-and-set-read-that-value-under-lock.patch b/queue-6.18/fuse-uring-avoid-queue-stopped-races-and-set-read-that-value-under-lock.patch new file mode 100644 index 0000000000..9408c651e4 --- /dev/null +++ b/queue-6.18/fuse-uring-avoid-queue-stopped-races-and-set-read-that-value-under-lock.patch @@ -0,0 +1,78 @@ +From b70a3aca16934c196f92abb17b01c1647b9bb63c Mon Sep 17 00:00:00 2001 +From: Bernd Schubert +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 + +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 +Reported-by: xlabai +Signed-off-by: Bernd Schubert +Reviewed-by: Joanne Koong +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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). diff --git a/queue-6.18/fuse-uring-avoid-use-after-free-in-fuse_uring_async_stop_queues.patch b/queue-6.18/fuse-uring-avoid-use-after-free-in-fuse_uring_async_stop_queues.patch new file mode 100644 index 0000000000..8ca843a354 --- /dev/null +++ b/queue-6.18/fuse-uring-avoid-use-after-free-in-fuse_uring_async_stop_queues.patch @@ -0,0 +1,45 @@ +From d351da75066955144515cb2f9aa959f24a04287a Mon Sep 17 00:00:00 2001 +From: Bernd Schubert +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 + +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 +Signed-off-by: Bernd Schubert +Reviewed-by: Joanne Koong +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.18/fuse-uring-end-fuse_req-on-io-uring-cancel-task-work.patch b/queue-6.18/fuse-uring-end-fuse_req-on-io-uring-cancel-task-work.patch new file mode 100644 index 0000000000..1551afdf04 --- /dev/null +++ b/queue-6.18/fuse-uring-end-fuse_req-on-io-uring-cancel-task-work.patch @@ -0,0 +1,85 @@ +From bea4fe98204b6ce7eb8e29f7bf867dd7619b3ddd Mon Sep 17 00:00:00 2001 +From: Chris Mason +Date: Mon, 8 Jun 2026 17:28:55 -0700 +Subject: fuse-uring: end fuse_req on io-uring cancel task work + +From: Chris Mason + +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 +Assisted-by: kres (claude-opus-4-7) +Signed-off-by: Chris Mason +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.18/fuse-uring-fix-data-races-on-ring-ready.patch b/queue-6.18/fuse-uring-fix-data-races-on-ring-ready.patch new file mode 100644 index 0000000000..492d8f7c48 --- /dev/null +++ b/queue-6.18/fuse-uring-fix-data-races-on-ring-ready.patch @@ -0,0 +1,69 @@ +From 46725a0056c884cf58a6897f222892807327d82d Mon Sep 17 00:00:00 2001 +From: Chris Mason +Date: Fri, 5 Jun 2026 12:27:07 -0700 +Subject: fuse-uring: fix data races on ring->ready + +From: Chris Mason + +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 +Assisted-by: kres (claude-opus-4-7) +Signed-off-by: Chris Mason +Reviewed-by: Bernd Schubert +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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 */ diff --git a/queue-6.18/fuse-uring-fix-efault-clobber-in-fuse_uring_commit.patch b/queue-6.18/fuse-uring-fix-efault-clobber-in-fuse_uring_commit.patch new file mode 100644 index 0000000000..a164f83a29 --- /dev/null +++ b/queue-6.18/fuse-uring-fix-efault-clobber-in-fuse_uring_commit.patch @@ -0,0 +1,70 @@ +From 3a0a8bc51a13951c5141262bf770eeea3e0b6228 Mon Sep 17 00:00:00 2001 +From: Chris Mason +Date: Fri, 5 Jun 2026 12:27:06 -0700 +Subject: fuse-uring: fix EFAULT clobber in fuse_uring_commit + +From: Chris Mason + +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 +Assisted-by: kres (claude-opus-4-7) +Signed-off-by: Chris Mason +Reviewed-by: Bernd Schubert +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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) { diff --git a/queue-6.18/fuse-uring-fix-moving-cancelled-entry-to-ent_in_userspace-list.patch b/queue-6.18/fuse-uring-fix-moving-cancelled-entry-to-ent_in_userspace-list.patch new file mode 100644 index 0000000000..07a0d25131 --- /dev/null +++ b/queue-6.18/fuse-uring-fix-moving-cancelled-entry-to-ent_in_userspace-list.patch @@ -0,0 +1,79 @@ +From 198f45eeb9f78b2a2d6d8be95e4e43468eb2c6bc Mon Sep 17 00:00:00 2001 +From: Joanne Koong +Date: Mon, 8 Jun 2026 12:21:49 -0700 +Subject: fuse-uring: fix moving cancelled entry to ent_in_userspace list + +From: Joanne Koong + +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 +Tested-by: Heechan Kang +Reviewed-by: Bernd Schubert +Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check") +Cc: stable@vger.kernel.org +Suggested-by: Jian Huang Li +Suggested-by: Horst Birthelmer +Signed-off-by: Joanne Koong +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.18/fuse-uring-make-a-fuse_req-on-sqe-commit-only-findable-after-memcpy.patch b/queue-6.18/fuse-uring-make-a-fuse_req-on-sqe-commit-only-findable-after-memcpy.patch new file mode 100644 index 0000000000..9dd3c6e781 --- /dev/null +++ b/queue-6.18/fuse-uring-make-a-fuse_req-on-sqe-commit-only-findable-after-memcpy.patch @@ -0,0 +1,110 @@ +From 1efd3d474fc0ba74dfd984249bca78807d739812 Mon Sep 17 00:00:00 2001 +From: Bernd Schubert +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 + +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 +Reported-by: Berkant Koc +Fixes: c090c8abae4b6b ("fuse: Add io-uring sqe commit and fetch support") +Cc: stable@kernel.org # 6.14 +Signed-off-by: Bernd Schubert +Reviewed-by: Joanne Koong +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.18/fuse-uring-remove-request-less-entries-from-ent_w_req_queue-to-fix-null-deref.patch b/queue-6.18/fuse-uring-remove-request-less-entries-from-ent_w_req_queue-to-fix-null-deref.patch new file mode 100644 index 0000000000..bec300e9fe --- /dev/null +++ b/queue-6.18/fuse-uring-remove-request-less-entries-from-ent_w_req_queue-to-fix-null-deref.patch @@ -0,0 +1,52 @@ +From 1c57a69be962d459c5e705f5cb4355b841b3461c Mon Sep 17 00:00:00 2001 +From: Joanne Koong +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 + +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 +Signed-off-by: Miklos Szeredi +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } diff --git a/queue-6.18/series b/queue-6.18/series index d43a328aa6..38becb1917 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -459,3 +459,17 @@ input-maplecontrol-set-driver-data-before-registering-input-device.patch 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