All of lore.kernel.org
 help / color / mirror / Atom feed
* FAILED: patch "[PATCH] fuse: wait for FR_FINISHED on abort_on_kill to prevent" failed to apply to 6.18-stable tree
@ 2026-08-31 12:23 gregkh
  2026-08-31 16:42 ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Sasha Levin
  0 siblings, 1 reply; 6+ messages in thread
From: gregkh @ 2026-08-31 12:23 UTC (permalink / raw)
  To: rochan.avlur, mszeredi; +Cc: stable


The patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 64b0b5cacbd2fea88001464cb712c9dfc795b26e
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026083101-rimless-sadness-3898@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'

Possible dependencies:



thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From 64b0b5cacbd2fea88001464cb712c9dfc795b26e Mon Sep 17 00:00:00 2001
From: Rochan Avlur <rochan.avlur@gmail.com>
Date: Wed, 12 Aug 2026 20:58:36 -0700
Subject: [PATCH] fuse: wait for FR_FINISHED on abort_on_kill to prevent
 use-after-free

The abort_on_kill path in request_wait_answer() calls fuse_abort_conn()
and returns without waiting for FR_FINISHED.  If fuse_dev_do_write() is
concurrently processing the same request (FR_LOCKED set), the caller
frees req->args while it is still being accessed, causing a
use-after-free.

Fix this by jumping to the existing wait_event(FR_FINISHED) instead of
returning early.  The wait will not hang because fuse_abort_conn()
ensures all requests are ended.

Reported-by: syzbot+d6540a3fa1626e11360d@syzkaller.appspotmail.com
Fixes: 204aa22a686b ("fuse: abort on fatal signal during sync init")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 90dceb7da571..34106f6e66a0 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -724,7 +724,7 @@ static void request_wait_answer(struct fuse_req *req)
 
 		if (req->args->abort_on_kill) {
 			fuse_chan_abort(fch, false);
-			return;
+			goto wait_for_finish;
 		}
 
 		if (test_bit(FR_URING, &req->flags))
@@ -735,6 +735,7 @@ static void request_wait_answer(struct fuse_req *req)
 			return;
 	}
 
+wait_for_finish:
 	/*
 	 * Either request is already in userspace, or it was forced.
 	 * Wait it out.


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 6.18.y 1/4] fuse: abort related layering cleanup
  2026-08-31 12:23 FAILED: patch "[PATCH] fuse: wait for FR_FINISHED on abort_on_kill to prevent" failed to apply to 6.18-stable tree gregkh
@ 2026-08-31 16:42 ` Sasha Levin
  2026-08-31 16:42   ` [PATCH 6.18.y 2/4] fuse: split off fuse_args and related definitions into a separate header Sasha Levin
                     ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Sasha Levin @ 2026-08-31 16:42 UTC (permalink / raw)
  To: stable; +Cc: Miklos Szeredi, Sasha Levin

From: Miklos Szeredi <mszeredi@redhat.com>

[ Upstream commit 4eeb5e6cb0fd89b343fcf755ae3aad76fdb5b2c2 ]

 - rename fuse_abort_conn() to fuse_chan_abort(), pass fuse_chan pointer
   instead of fuse_conn

 - pass an abort_with_err argument that tells fuse_dev_(read|write) to
   return with ECONNABORTED instead of ENODEV

 - move fc->aborted to fch->abort_with_err

 - rename fuse_wait_aborted() to fuse_chan_wait_aborted()

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

[ 6.18 has no "struct fuse_chan" -- the connection object still plays
  that role, and the surrounding series that splits it out is not in
  stable.  Adapted as follows:

  - the renames (fuse_abort_conn() -> fuse_chan_abort(),
    fuse_wait_aborted() -> fuse_chan_wait_aborted(), the "abort_conn"
    label in fuse_check_timeout() -> "chan_abort") and the new
    abort_with_err argument are taken as-is, but both helpers keep
    taking "struct fuse_conn *".

  - the "move fc->aborted to fch->abort_with_err" bullet is dropped:
    there is no fuse_chan to move the flag into, so fc->aborted stays
    in struct fuse_conn.  fuse_chan_abort() only ever *sets* it, as
    control.c did before, rather than assigning the new argument
    unconditionally the way upstream does: on this tree the flag is
    write-once and is what makes fuse_dev_do_read() and
    fuse_uring_cmd() report ECONNABORTED instead of ENODEV, so letting
    a later abort with abort_with_err=false clear it (umount -f,
    fuse_conn_destroy(), fuse_dev_release(), the timeout worker) would
    change what userspace sees after a sysfs abort on an abort_err
    mount.  Consequently the fuse_dev_i.h and dev_uring.c hunks, which
    only rename the field access, are dropped as well.

  - fs/fuse/dev.h and fs/fuse/req_timeout.c do not exist here: the
    declarations stay in fuse_i.h and fuse_check_timeout() lives in
    dev.c, so those two hunks were replayed there.

  - in request_wait_answer() the connection pointer is named "fch",
    matching the name upstream gives it in 28d733cc3ecad ("fuse: change
    req->fm to req->chan"), so that the follow-up fix applies here
    unchanged. ]

[ sashal: Reduced backport -- upstream 4eeb5e6cb0fd8 touches 9 file(s), this
  backport carries 5. Not backported here:
  fs/fuse/dev.h
  fs/fuse/dev_uring.c
  fs/fuse/fuse_dev_i.h
  fs/fuse/req_timeout.c
  This note is generated from the file lists only; see the resolution record
  for the reasoning. ]

Stable-dep-of: 64b0b5cacbd2 ("fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 fs/fuse/control.c |  4 +---
 fs/fuse/cuse.c    |  4 ++--
 fs/fuse/dev.c     | 35 +++++++++++++++++++----------------
 fs/fuse/fuse_i.h  |  4 ++--
 fs/fuse/inode.c   |  6 +++---
 5 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/fs/fuse/control.c b/fs/fuse/control.c
index 2e300f6d646c3..5536d6ee65514 100644
--- a/fs/fuse/control.c
+++ b/fs/fuse/control.c
@@ -37,9 +37,7 @@ static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf,
 {
 	struct fuse_conn *fc = fuse_ctl_file_conn_get(file);
 	if (fc) {
-		if (fc->abort_err)
-			fc->aborted = true;
-		fuse_abort_conn(fc);
+		fuse_chan_abort(fc, fc->abort_err);
 		fuse_conn_put(fc);
 	}
 	return count;
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index 2bb98bf81b079..d90f138321fe9 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -425,7 +425,7 @@ static void cuse_process_init_reply(struct fuse_mount *fm,
 err_region:
 	unregister_chrdev_region(devt, 1);
 err:
-	fuse_abort_conn(fc);
+	fuse_chan_abort(fc, false);
 	goto out;
 }
 
@@ -593,7 +593,7 @@ static ssize_t cuse_class_abort_store(struct device *dev,
 {
 	struct cuse_conn *cc = dev_get_drvdata(dev);
 
-	fuse_abort_conn(&cc->fc);
+	fuse_chan_abort(&cc->fc, false);
 	return count;
 }
 static DEVICE_ATTR(abort, 0200, NULL, cuse_class_abort_store);
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 77c3982bca3d7..934f9ac5056ed 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -87,13 +87,13 @@ void fuse_check_timeout(struct work_struct *work)
 	expired = fuse_request_expired(fc, &fiq->pending);
 	spin_unlock(&fiq->lock);
 	if (expired)
-		goto abort_conn;
+		goto chan_abort;
 
 	spin_lock(&fc->bg_lock);
 	expired = fuse_request_expired(fc, &fc->bg_queue);
 	spin_unlock(&fc->bg_lock);
 	if (expired)
-		goto abort_conn;
+		goto chan_abort;
 
 	spin_lock(&fc->lock);
 	if (!fc->connected) {
@@ -107,7 +107,7 @@ void fuse_check_timeout(struct work_struct *work)
 		    fuse_fpq_processing_expired(fc, fpq->processing)) {
 			spin_unlock(&fpq->lock);
 			spin_unlock(&fc->lock);
-			goto abort_conn;
+			goto chan_abort;
 		}
 
 		spin_unlock(&fpq->lock);
@@ -115,15 +115,15 @@ void fuse_check_timeout(struct work_struct *work)
 	spin_unlock(&fc->lock);
 
 	if (fuse_uring_request_expired(fc))
-	    goto abort_conn;
+	    goto chan_abort;
 
 out:
 	queue_delayed_work(system_percpu_wq, &fc->timeout.work,
 			   fuse_timeout_timer_freq);
 	return;
 
-abort_conn:
-	fuse_abort_conn(fc);
+chan_abort:
+	fuse_chan_abort(fc, false);
 }
 
 static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req)
@@ -180,7 +180,7 @@ static void fuse_drop_waiting(struct fuse_conn *fc)
 {
 	/*
 	 * lockess check of fc->connected is okay, because atomic_dec_and_test()
-	 * provides a memory barrier matched with the one in fuse_wait_aborted()
+	 * provides a memory barrier matched with the one in fuse_chan_wait_aborted()
 	 * to ensure no wake-up is missed.
 	 */
 	if (atomic_dec_and_test(&fc->num_waiting) &&
@@ -551,11 +551,11 @@ bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock)
 
 static void request_wait_answer(struct fuse_req *req)
 {
-	struct fuse_conn *fc = req->fm->fc;
-	struct fuse_iqueue *fiq = &fc->iq;
+	struct fuse_conn *fch = req->fm->fc;
+	struct fuse_iqueue *fiq = &fch->iq;
 	int err;
 
-	if (!fc->no_interrupt) {
+	if (!fch->no_interrupt) {
 		/* Any signal may interrupt this */
 		err = wait_event_interruptible(req->waitq,
 					test_bit(FR_FINISHED, &req->flags));
@@ -579,7 +579,7 @@ static void request_wait_answer(struct fuse_req *req)
 			return;
 
 		if (req->args->abort_on_kill) {
-			fuse_abort_conn(fc);
+			fuse_chan_abort(fch, false);
 			return;
 		}
 
@@ -1491,7 +1491,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
 	spin_lock(&fpq->lock);
 	/*
 	 *  Must not put request on fpq->io queue after having been shut down by
-	 *  fuse_abort_conn()
+	 *  fuse_chan_abort()
 	 */
 	if (!fpq->connected) {
 		req->out.h.error = err = -ECONNABORTED;
@@ -2475,10 +2475,13 @@ static void end_polls(struct fuse_conn *fc)
  * is OK, the request will in that case be removed from the list before we touch
  * it.
  */
-void fuse_abort_conn(struct fuse_conn *fc)
+void fuse_chan_abort(struct fuse_conn *fc, bool abort_with_err)
 {
 	struct fuse_iqueue *fiq = &fc->iq;
 
+	if (abort_with_err)
+		fc->aborted = true;
+
 	spin_lock(&fc->lock);
 	if (fc->connected) {
 		struct fuse_dev *fud;
@@ -2547,9 +2550,9 @@ void fuse_abort_conn(struct fuse_conn *fc)
 		spin_unlock(&fc->lock);
 	}
 }
-EXPORT_SYMBOL_GPL(fuse_abort_conn);
+EXPORT_SYMBOL_GPL(fuse_chan_abort);
 
-void fuse_wait_aborted(struct fuse_conn *fc)
+void fuse_chan_wait_aborted(struct fuse_conn *fc)
 {
 	/* matches implicit memory barrier in fuse_drop_waiting() */
 	smp_mb();
@@ -2579,7 +2582,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
 		/* Are we the last open device? */
 		if (atomic_dec_and_test(&fc->dev_count)) {
 			WARN_ON(fc->iq.fasync != NULL);
-			fuse_abort_conn(fc);
+			fuse_chan_abort(fc, false);
 		}
 		fuse_dev_free(fud);
 	}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index c288f28f6c6ea..8b745045f1ab3 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1272,8 +1272,8 @@ void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req);
 void fuse_request_end(struct fuse_req *req);
 
 /* Abort all requests */
-void fuse_abort_conn(struct fuse_conn *fc);
-void fuse_wait_aborted(struct fuse_conn *fc);
+void fuse_chan_abort(struct fuse_conn *fc, bool abort_with_err);
+void fuse_chan_wait_aborted(struct fuse_conn *fc);
 
 /* Check if any requests timed out */
 void fuse_check_timeout(struct work_struct *work);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index e9ed693fc7b37..399298816dea0 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -621,7 +621,7 @@ static void fuse_umount_begin(struct super_block *sb)
 	if (fc->no_force_umount)
 		return;
 
-	fuse_abort_conn(fc);
+	fuse_chan_abort(fc, false);
 
 	// Only retire block-device-based superblocks.
 	if (sb->s_bdev != NULL)
@@ -2099,8 +2099,8 @@ void fuse_conn_destroy(struct fuse_mount *fm)
 	if (fc->destroy)
 		fuse_send_destroy(fm);
 
-	fuse_abort_conn(fc);
-	fuse_wait_aborted(fc);
+	fuse_chan_abort(fc, false);
+	fuse_chan_wait_aborted(fc);
 
 	if (!list_empty(&fc->entry)) {
 		mutex_lock(&fuse_mutex);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 6.18.y 2/4] fuse: split off fuse_args and related definitions into a separate header
  2026-08-31 16:42 ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Sasha Levin
@ 2026-08-31 16:42   ` Sasha Levin
  2026-08-31 16:42   ` [PATCH 6.18.y 3/4] fuse: remove fm arg of args->end callback Sasha Levin
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-08-31 16:42 UTC (permalink / raw)
  To: stable; +Cc: Miklos Szeredi, Sasha Levin

From: Miklos Szeredi <mszeredi@redhat.com>

[ Upstream commit 229f9b9b66ab5be9e015422cf30b97740cfbdc8d ]

This is going to be used by both layers (transport and filesystem)

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Stable-dep-of: 64b0b5cacbd2 ("fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 fs/fuse/args.h   | 65 ++++++++++++++++++++++++++++++++++++++++++++++++
 fs/fuse/fuse_i.h | 53 +--------------------------------------
 2 files changed, 66 insertions(+), 52 deletions(-)
 create mode 100644 fs/fuse/args.h

diff --git a/fs/fuse/args.h b/fs/fuse/args.h
new file mode 100644
index 0000000000000..12c94ece569fb
--- /dev/null
+++ b/fs/fuse/args.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _FS_FUSE_ARGS_H
+#define _FS_FUSE_ARGS_H
+
+#include <linux/types.h>
+
+struct fuse_mount;
+
+/** One input argument of a request */
+struct fuse_in_arg {
+	unsigned size;
+	const void *value;
+};
+
+/** One output argument of a request */
+struct fuse_arg {
+	unsigned size;
+	void *value;
+};
+
+struct fuse_args {
+	u64 nodeid;
+	u32 opcode;
+	u32 uid;
+	u32 gid;
+	u32 pid;
+	u8 in_numargs;
+	u8 out_numargs;
+	u8 ext_idx;
+	bool force:1;
+	bool noreply:1;
+	bool nocreds:1;
+	bool in_pages:1;
+	bool out_pages:1;
+	bool user_pages:1;
+	bool out_argvar:1;
+	bool page_zeroing:1;
+	bool page_replace:1;
+	bool may_block:1;
+	bool is_ext:1;
+	bool is_pinned:1;
+	bool invalidate_vmap:1;
+	bool abort_on_kill:1;
+	struct fuse_in_arg in_args[4];
+	struct fuse_arg out_args[2];
+	void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
+	/* Used for kvec iter backed by vmalloc address */
+	void *vmap_base;
+};
+
+/** FUSE folio descriptor */
+struct fuse_folio_desc {
+	unsigned int length;
+	unsigned int offset;
+};
+
+struct fuse_args_pages {
+	struct fuse_args args;
+	struct folio **folios;
+	struct fuse_folio_desc *descs;
+	unsigned int num_folios;
+};
+
+#endif /* _FS_FUSE_ARGS_H */
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 8b745045f1ab3..aa8216c9308f8 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -13,6 +13,7 @@
 # define pr_fmt(fmt) "fuse: " fmt
 #endif
 
+#include "args.h"
 #include <linux/fuse.h>
 #include <linux/fs.h>
 #include <linux/mount.h>
@@ -296,58 +297,6 @@ struct fuse_file {
 	bool flock:1;
 };
 
-/** One input argument of a request */
-struct fuse_in_arg {
-	unsigned size;
-	const void *value;
-};
-
-/** One output argument of a request */
-struct fuse_arg {
-	unsigned size;
-	void *value;
-};
-
-/** FUSE folio descriptor */
-struct fuse_folio_desc {
-	unsigned int length;
-	unsigned int offset;
-};
-
-struct fuse_args {
-	uint64_t nodeid;
-	uint32_t opcode;
-	uint8_t in_numargs;
-	uint8_t out_numargs;
-	uint8_t ext_idx;
-	bool force:1;
-	bool noreply:1;
-	bool nocreds:1;
-	bool in_pages:1;
-	bool out_pages:1;
-	bool user_pages:1;
-	bool out_argvar:1;
-	bool page_zeroing:1;
-	bool page_replace:1;
-	bool may_block:1;
-	bool is_ext:1;
-	bool is_pinned:1;
-	bool invalidate_vmap:1;
-	bool abort_on_kill:1;
-	struct fuse_in_arg in_args[4];
-	struct fuse_arg out_args[2];
-	void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
-	/* Used for kvec iter backed by vmalloc address */
-	void *vmap_base;
-};
-
-struct fuse_args_pages {
-	struct fuse_args args;
-	struct folio **folios;
-	struct fuse_folio_desc *descs;
-	unsigned int num_folios;
-};
-
 struct fuse_release_args {
 	struct fuse_args args;
 	struct fuse_release_in inarg;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 6.18.y 3/4] fuse: remove fm arg of args->end callback
  2026-08-31 16:42 ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Sasha Levin
  2026-08-31 16:42   ` [PATCH 6.18.y 2/4] fuse: split off fuse_args and related definitions into a separate header Sasha Levin
@ 2026-08-31 16:42   ` Sasha Levin
  2026-08-31 16:42   ` [PATCH 6.18.y 4/4] fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free Sasha Levin
  2026-09-09 12:33   ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Greg KH
  3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-08-31 16:42 UTC (permalink / raw)
  To: stable; +Cc: Miklos Szeredi, Sasha Levin

From: Miklos Szeredi <mszeredi@redhat.com>

[ Upstream commit a697d95fcdbb3bbe25cdc29db5542ddcebb831c1 ]

Only used by FUSE_INIT and CUSE_INIT, these can store the relevant pointer
in their structs derived from fuse_args.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Stable-dep-of: 64b0b5cacbd2 ("fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 fs/fuse/args.h  |  2 +-
 fs/fuse/cuse.c  |  7 ++++---
 fs/fuse/dev.c   |  7 +++----
 fs/fuse/file.c  | 22 +++++++++-------------
 fs/fuse/inode.c | 10 ++++++----
 5 files changed, 23 insertions(+), 25 deletions(-)

diff --git a/fs/fuse/args.h b/fs/fuse/args.h
index 12c94ece569fb..ecfe51a192af0 100644
--- a/fs/fuse/args.h
+++ b/fs/fuse/args.h
@@ -44,7 +44,7 @@ struct fuse_args {
 	bool abort_on_kill:1;
 	struct fuse_in_arg in_args[4];
 	struct fuse_arg out_args[2];
-	void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
+	void (*end)(struct fuse_args *args, int error);
 	/* Used for kvec iter backed by vmalloc address */
 	void *vmap_base;
 };
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index d90f138321fe9..7d9925dd2879a 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -306,6 +306,7 @@ struct cuse_init_args {
 	struct cuse_init_out out;
 	struct folio *folio;
 	struct fuse_folio_desc desc;
+	struct fuse_conn *fc;
 };
 
 /**
@@ -319,11 +320,10 @@ struct cuse_init_args {
  * required data structures for it.  Please read the comment at the
  * top of this file for high level overview.
  */
-static void cuse_process_init_reply(struct fuse_mount *fm,
-				    struct fuse_args *args, int error)
+static void cuse_process_init_reply(struct fuse_args *args, int error)
 {
-	struct fuse_conn *fc = fm->fc;
 	struct cuse_init_args *ia = container_of(args, typeof(*ia), ap.args);
+	struct fuse_conn *fc = ia->fc;
 	struct fuse_args_pages *ap = &ia->ap;
 	struct cuse_conn *cc = fc_to_cc(fc), *pos;
 	struct cuse_init_out *arg = &ia->out;
@@ -468,6 +468,7 @@ static int cuse_send_init(struct cuse_conn *cc)
 	ap->descs = &ia->desc;
 	ia->folio = folio;
 	ia->desc.length = ap->args.out_args[1].size;
+	ia->fc = &cc->fc;
 	ap->args.end = cuse_process_init_reply;
 
 	rc = fuse_simple_background(fm, &ap->args, GFP_KERNEL);
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 934f9ac5056ed..50012b0b1258a 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -512,7 +512,7 @@ void fuse_request_end(struct fuse_req *req)
 	}
 
 	if (test_bit(FR_ASYNC, &req->flags))
-		req->args->end(fm, req->args, req->out.h.error);
+		req->args->end(req->args, req->out.h.error);
 put_request:
 	fuse_put_request(req);
 }
@@ -1859,8 +1859,7 @@ struct fuse_retrieve_args {
 	struct fuse_notify_retrieve_in inarg;
 };
 
-static void fuse_retrieve_end(struct fuse_mount *fm, struct fuse_args *args,
-			      int error)
+static void fuse_retrieve_end(struct fuse_args *args, int error)
 {
 	struct fuse_retrieve_args *ra =
 		container_of(args, typeof(*ra), ap.args);
@@ -1955,7 +1954,7 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
 
 	err = fuse_simple_notify_reply(fm, args, outarg->notify_unique);
 	if (err)
-		fuse_retrieve_end(fm, args, err);
+		fuse_retrieve_end(args, err);
 
 	return err;
 }
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 585dd90361b65..f3b4d6ae0858a 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -91,8 +91,7 @@ static struct fuse_file *fuse_file_get(struct fuse_file *ff)
 	return ff;
 }
 
-static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
-			     int error)
+static void fuse_release_end(struct fuse_args *args, int error)
 {
 	struct fuse_release_args *ra = container_of(args, typeof(*ra), args);
 
@@ -112,15 +111,15 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
 		if (!args) {
 			/* Do nothing when server does not implement 'opendir' */
 		} else if (args->opcode == FUSE_RELEASE && ff->fm->fc->no_open) {
-			fuse_release_end(ff->fm, args, 0);
+			fuse_release_end(args, 0);
 		} else if (sync) {
 			fuse_simple_request(ff->fm, args);
-			fuse_release_end(ff->fm, args, 0);
+			fuse_release_end(args, 0);
 		} else {
 			args->end = fuse_release_end;
 			if (fuse_simple_background(ff->fm, args,
 						   GFP_KERNEL | __GFP_NOFAIL))
-				fuse_release_end(ff->fm, args, -ENOTCONN);
+				fuse_release_end(args, -ENOTCONN);
 		}
 		kfree(ff);
 	}
@@ -709,8 +708,7 @@ static void fuse_io_free(struct fuse_io_args *ia)
 	kfree(ia);
 }
 
-static void fuse_aio_complete_req(struct fuse_mount *fm, struct fuse_args *args,
-				  int err)
+static void fuse_aio_complete_req(struct fuse_args *args, int err)
 {
 	struct fuse_io_args *ia = container_of(args, typeof(*ia), ap.args);
 	struct fuse_io_priv *io = ia->io;
@@ -758,7 +756,7 @@ static ssize_t fuse_async_req_send(struct fuse_mount *fm,
 	ia->ap.args.may_block = io->should_dirty;
 	err = fuse_simple_background(fm, &ia->ap.args, GFP_KERNEL);
 	if (err)
-		fuse_aio_complete_req(fm, &ia->ap.args, err);
+		fuse_aio_complete_req(&ia->ap.args, err);
 
 	return num_bytes;
 }
@@ -881,8 +879,7 @@ static int fuse_iomap_read_folio_range(const struct iomap_iter *iter,
 	return fuse_do_readfolio(file, folio, off, len);
 }
 
-static void fuse_readpages_end(struct fuse_mount *fm, struct fuse_args *args,
-			       int err)
+static void fuse_readpages_end(struct fuse_args *args, int err)
 {
 	int i;
 	struct fuse_io_args *ia = container_of(args, typeof(*ia), ap.args);
@@ -947,7 +944,7 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file,
 		res = fuse_simple_request(fm, &ap->args);
 		err = res < 0 ? res : 0;
 	}
-	fuse_readpages_end(fm, &ap->args, err);
+	fuse_readpages_end(&ap->args, err);
 }
 
 static void fuse_readahead(struct readahead_control *rac)
@@ -1950,8 +1947,7 @@ __acquires(fi->lock)
 	}
 }
 
-static void fuse_writepage_end(struct fuse_mount *fm, struct fuse_args *args,
-			       int error)
+static void fuse_writepage_end(struct fuse_args *args, int error)
 {
 	struct fuse_writepage_args *wpa =
 		container_of(args, typeof(*wpa), ia.ap.args);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 399298816dea0..7464ee8622ac9 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1311,13 +1311,14 @@ struct fuse_init_args {
 	struct fuse_args args;
 	struct fuse_init_in in;
 	struct fuse_init_out out;
+	struct fuse_mount *fm;
 };
 
-static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
-			       int error)
+static void process_init_reply(struct fuse_args *args, int error)
 {
-	struct fuse_conn *fc = fm->fc;
 	struct fuse_init_args *ia = container_of(args, typeof(*ia), args);
+	struct fuse_mount *fm = ia->fm;
+	struct fuse_conn *fc = fm->fc;
 	struct fuse_init_out *arg = &ia->out;
 	bool ok = true;
 
@@ -1486,6 +1487,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 
 	ia = kzalloc(sizeof(*ia), GFP_KERNEL | __GFP_NOFAIL);
 
+	ia->fm = fm;
 	ia->in.major = FUSE_KERNEL_VERSION;
 	ia->in.minor = FUSE_KERNEL_MINOR_VERSION;
 	ia->in.max_readahead = fm->sb->s_bdi->ra_pages * PAGE_SIZE;
@@ -1559,7 +1561,7 @@ int fuse_send_init(struct fuse_mount *fm)
 		if (!err)
 			return 0;
 	}
-	process_init_reply(fm, &ia->args, err);
+	process_init_reply(&ia->args, err);
 	if (fm->fc->conn_error)
 		return -ENOTCONN;
 	return 0;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 6.18.y 4/4] fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free
  2026-08-31 16:42 ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Sasha Levin
  2026-08-31 16:42   ` [PATCH 6.18.y 2/4] fuse: split off fuse_args and related definitions into a separate header Sasha Levin
  2026-08-31 16:42   ` [PATCH 6.18.y 3/4] fuse: remove fm arg of args->end callback Sasha Levin
@ 2026-08-31 16:42   ` Sasha Levin
  2026-09-09 12:33   ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Greg KH
  3 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-08-31 16:42 UTC (permalink / raw)
  To: stable
  Cc: Rochan Avlur, syzbot+d6540a3fa1626e11360d, Miklos Szeredi,
	Sasha Levin

From: Rochan Avlur <rochan.avlur@gmail.com>

[ Upstream commit 64b0b5cacbd2fea88001464cb712c9dfc795b26e ]

The abort_on_kill path in request_wait_answer() calls fuse_abort_conn()
and returns without waiting for FR_FINISHED.  If fuse_dev_do_write() is
concurrently processing the same request (FR_LOCKED set), the caller
frees req->args while it is still being accessed, causing a
use-after-free.

Fix this by jumping to the existing wait_event(FR_FINISHED) instead of
returning early.  The wait will not hang because fuse_abort_conn()
ensures all requests are ended.

Reported-by: syzbot+d6540a3fa1626e11360d@syzkaller.appspotmail.com
Fixes: 204aa22a686b ("fuse: abort on fatal signal during sync init")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 fs/fuse/dev.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 50012b0b1258a..9f1f81e66cace 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -580,7 +580,7 @@ static void request_wait_answer(struct fuse_req *req)
 
 		if (req->args->abort_on_kill) {
 			fuse_chan_abort(fch, false);
-			return;
+			goto wait_for_finish;
 		}
 
 		if (test_bit(FR_URING, &req->flags))
@@ -591,6 +591,7 @@ static void request_wait_answer(struct fuse_req *req)
 			return;
 	}
 
+wait_for_finish:
 	/*
 	 * Either request is already in userspace, or it was forced.
 	 * Wait it out.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 6.18.y 1/4] fuse: abort related layering cleanup
  2026-08-31 16:42 ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Sasha Levin
                     ` (2 preceding siblings ...)
  2026-08-31 16:42   ` [PATCH 6.18.y 4/4] fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free Sasha Levin
@ 2026-09-09 12:33   ` Greg KH
  3 siblings, 0 replies; 6+ messages in thread
From: Greg KH @ 2026-09-09 12:33 UTC (permalink / raw)
  To: Sasha Levin; +Cc: stable, Miklos Szeredi

On Mon, Aug 31, 2026 at 12:42:53PM -0400, Sasha Levin wrote:
> From: Miklos Szeredi <mszeredi@redhat.com>
> 
> [ Upstream commit 4eeb5e6cb0fd89b343fcf755ae3aad76fdb5b2c2 ]
> 
>  - rename fuse_abort_conn() to fuse_chan_abort(), pass fuse_chan pointer
>    instead of fuse_conn
> 
>  - pass an abort_with_err argument that tells fuse_dev_(read|write) to
>    return with ECONNABORTED instead of ENODEV
> 
>  - move fc->aborted to fch->abort_with_err
> 
>  - rename fuse_wait_aborted() to fuse_chan_wait_aborted()
> 
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> 
> [ 6.18 has no "struct fuse_chan" -- the connection object still plays
>   that role, and the surrounding series that splits it out is not in
>   stable.  Adapted as follows:
> 
>   - the renames (fuse_abort_conn() -> fuse_chan_abort(),
>     fuse_wait_aborted() -> fuse_chan_wait_aborted(), the "abort_conn"
>     label in fuse_check_timeout() -> "chan_abort") and the new
>     abort_with_err argument are taken as-is, but both helpers keep
>     taking "struct fuse_conn *".
> 
>   - the "move fc->aborted to fch->abort_with_err" bullet is dropped:
>     there is no fuse_chan to move the flag into, so fc->aborted stays
>     in struct fuse_conn.  fuse_chan_abort() only ever *sets* it, as
>     control.c did before, rather than assigning the new argument
>     unconditionally the way upstream does: on this tree the flag is
>     write-once and is what makes fuse_dev_do_read() and
>     fuse_uring_cmd() report ECONNABORTED instead of ENODEV, so letting
>     a later abort with abort_with_err=false clear it (umount -f,
>     fuse_conn_destroy(), fuse_dev_release(), the timeout worker) would
>     change what userspace sees after a sysfs abort on an abort_err
>     mount.  Consequently the fuse_dev_i.h and dev_uring.c hunks, which
>     only rename the field access, are dropped as well.
> 
>   - fs/fuse/dev.h and fs/fuse/req_timeout.c do not exist here: the
>     declarations stay in fuse_i.h and fuse_check_timeout() lives in
>     dev.c, so those two hunks were replayed there.
> 
>   - in request_wait_answer() the connection pointer is named "fch",
>     matching the name upstream gives it in 28d733cc3ecad ("fuse: change
>     req->fm to req->chan"), so that the follow-up fix applies here
>     unchanged. ]
> 
> [ sashal: Reduced backport -- upstream 4eeb5e6cb0fd8 touches 9 file(s), this
>   backport carries 5. Not backported here:
>   fs/fuse/dev.h
>   fs/fuse/dev_uring.c
>   fs/fuse/fuse_dev_i.h
>   fs/fuse/req_timeout.c
>   This note is generated from the file lists only; see the resolution record
>   for the reasoning. ]
> 
> Stable-dep-of: 64b0b5cacbd2 ("fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free")
> Signed-off-by: Sasha Levin <sashal@kernel.org>
> ---
>  fs/fuse/control.c |  4 +---
>  fs/fuse/cuse.c    |  4 ++--
>  fs/fuse/dev.c     | 35 +++++++++++++++++++----------------
>  fs/fuse/fuse_i.h  |  4 ++--
>  fs/fuse/inode.c   |  6 +++---
>  5 files changed, 27 insertions(+), 26 deletions(-)
> 
> diff --git a/fs/fuse/control.c b/fs/fuse/control.c
> index 2e300f6d646c3..5536d6ee65514 100644
> --- a/fs/fuse/control.c
> +++ b/fs/fuse/control.c
> @@ -37,9 +37,7 @@ static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf,
>  {
>  	struct fuse_conn *fc = fuse_ctl_file_conn_get(file);
>  	if (fc) {
> -		if (fc->abort_err)
> -			fc->aborted = true;
> -		fuse_abort_conn(fc);
> +		fuse_chan_abort(fc, fc->abort_err);
>  		fuse_conn_put(fc);
>  	}
>  	return count;
> diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
> index 2bb98bf81b079..d90f138321fe9 100644
> --- a/fs/fuse/cuse.c
> +++ b/fs/fuse/cuse.c
> @@ -425,7 +425,7 @@ static void cuse_process_init_reply(struct fuse_mount *fm,
>  err_region:
>  	unregister_chrdev_region(devt, 1);
>  err:
> -	fuse_abort_conn(fc);
> +	fuse_chan_abort(fc, false);
>  	goto out;
>  }
>  
> @@ -593,7 +593,7 @@ static ssize_t cuse_class_abort_store(struct device *dev,
>  {
>  	struct cuse_conn *cc = dev_get_drvdata(dev);
>  
> -	fuse_abort_conn(&cc->fc);
> +	fuse_chan_abort(&cc->fc, false);
>  	return count;
>  }
>  static DEVICE_ATTR(abort, 0200, NULL, cuse_class_abort_store);
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 77c3982bca3d7..934f9ac5056ed 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -87,13 +87,13 @@ void fuse_check_timeout(struct work_struct *work)
>  	expired = fuse_request_expired(fc, &fiq->pending);
>  	spin_unlock(&fiq->lock);
>  	if (expired)
> -		goto abort_conn;
> +		goto chan_abort;
>  
>  	spin_lock(&fc->bg_lock);
>  	expired = fuse_request_expired(fc, &fc->bg_queue);
>  	spin_unlock(&fc->bg_lock);
>  	if (expired)
> -		goto abort_conn;
> +		goto chan_abort;
>  
>  	spin_lock(&fc->lock);
>  	if (!fc->connected) {
> @@ -107,7 +107,7 @@ void fuse_check_timeout(struct work_struct *work)
>  		    fuse_fpq_processing_expired(fc, fpq->processing)) {
>  			spin_unlock(&fpq->lock);
>  			spin_unlock(&fc->lock);
> -			goto abort_conn;
> +			goto chan_abort;
>  		}
>  
>  		spin_unlock(&fpq->lock);
> @@ -115,15 +115,15 @@ void fuse_check_timeout(struct work_struct *work)
>  	spin_unlock(&fc->lock);
>  
>  	if (fuse_uring_request_expired(fc))
> -	    goto abort_conn;
> +	    goto chan_abort;
>  
>  out:
>  	queue_delayed_work(system_percpu_wq, &fc->timeout.work,
>  			   fuse_timeout_timer_freq);
>  	return;
>  
> -abort_conn:
> -	fuse_abort_conn(fc);
> +chan_abort:
> +	fuse_chan_abort(fc, false);
>  }
>  
>  static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req)
> @@ -180,7 +180,7 @@ static void fuse_drop_waiting(struct fuse_conn *fc)
>  {
>  	/*
>  	 * lockess check of fc->connected is okay, because atomic_dec_and_test()
> -	 * provides a memory barrier matched with the one in fuse_wait_aborted()
> +	 * provides a memory barrier matched with the one in fuse_chan_wait_aborted()
>  	 * to ensure no wake-up is missed.
>  	 */
>  	if (atomic_dec_and_test(&fc->num_waiting) &&
> @@ -551,11 +551,11 @@ bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock)
>  
>  static void request_wait_answer(struct fuse_req *req)
>  {
> -	struct fuse_conn *fc = req->fm->fc;
> -	struct fuse_iqueue *fiq = &fc->iq;
> +	struct fuse_conn *fch = req->fm->fc;
> +	struct fuse_iqueue *fiq = &fch->iq;
>  	int err;
>  
> -	if (!fc->no_interrupt) {
> +	if (!fch->no_interrupt) {
>  		/* Any signal may interrupt this */
>  		err = wait_event_interruptible(req->waitq,
>  					test_bit(FR_FINISHED, &req->flags));
> @@ -579,7 +579,7 @@ static void request_wait_answer(struct fuse_req *req)
>  			return;
>  
>  		if (req->args->abort_on_kill) {
> -			fuse_abort_conn(fc);
> +			fuse_chan_abort(fch, false);
>  			return;
>  		}
>  
> @@ -1491,7 +1491,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
>  	spin_lock(&fpq->lock);
>  	/*
>  	 *  Must not put request on fpq->io queue after having been shut down by
> -	 *  fuse_abort_conn()
> +	 *  fuse_chan_abort()
>  	 */
>  	if (!fpq->connected) {
>  		req->out.h.error = err = -ECONNABORTED;
> @@ -2475,10 +2475,13 @@ static void end_polls(struct fuse_conn *fc)
>   * is OK, the request will in that case be removed from the list before we touch
>   * it.
>   */
> -void fuse_abort_conn(struct fuse_conn *fc)
> +void fuse_chan_abort(struct fuse_conn *fc, bool abort_with_err)
>  {
>  	struct fuse_iqueue *fiq = &fc->iq;
>  
> +	if (abort_with_err)
> +		fc->aborted = true;
> +
>  	spin_lock(&fc->lock);
>  	if (fc->connected) {
>  		struct fuse_dev *fud;
> @@ -2547,9 +2550,9 @@ void fuse_abort_conn(struct fuse_conn *fc)
>  		spin_unlock(&fc->lock);
>  	}
>  }
> -EXPORT_SYMBOL_GPL(fuse_abort_conn);
> +EXPORT_SYMBOL_GPL(fuse_chan_abort);
>  
> -void fuse_wait_aborted(struct fuse_conn *fc)
> +void fuse_chan_wait_aborted(struct fuse_conn *fc)
>  {
>  	/* matches implicit memory barrier in fuse_drop_waiting() */
>  	smp_mb();
> @@ -2579,7 +2582,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
>  		/* Are we the last open device? */
>  		if (atomic_dec_and_test(&fc->dev_count)) {
>  			WARN_ON(fc->iq.fasync != NULL);
> -			fuse_abort_conn(fc);
> +			fuse_chan_abort(fc, false);
>  		}
>  		fuse_dev_free(fud);
>  	}
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index c288f28f6c6ea..8b745045f1ab3 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1272,8 +1272,8 @@ void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req);
>  void fuse_request_end(struct fuse_req *req);
>  
>  /* Abort all requests */
> -void fuse_abort_conn(struct fuse_conn *fc);
> -void fuse_wait_aborted(struct fuse_conn *fc);
> +void fuse_chan_abort(struct fuse_conn *fc, bool abort_with_err);
> +void fuse_chan_wait_aborted(struct fuse_conn *fc);
>  
>  /* Check if any requests timed out */
>  void fuse_check_timeout(struct work_struct *work);
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index e9ed693fc7b37..399298816dea0 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -621,7 +621,7 @@ static void fuse_umount_begin(struct super_block *sb)
>  	if (fc->no_force_umount)
>  		return;
>  
> -	fuse_abort_conn(fc);
> +	fuse_chan_abort(fc, false);
>  
>  	// Only retire block-device-based superblocks.
>  	if (sb->s_bdev != NULL)
> @@ -2099,8 +2099,8 @@ void fuse_conn_destroy(struct fuse_mount *fm)
>  	if (fc->destroy)
>  		fuse_send_destroy(fm);
>  
> -	fuse_abort_conn(fc);
> -	fuse_wait_aborted(fc);
> +	fuse_chan_abort(fc, false);
> +	fuse_chan_wait_aborted(fc);
>  
>  	if (!list_empty(&fc->entry)) {
>  		mutex_lock(&fuse_mutex);
> -- 
> 2.53.0
> 
> 

Series did not apply :(

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-09 12:34 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 12:23 FAILED: patch "[PATCH] fuse: wait for FR_FINISHED on abort_on_kill to prevent" failed to apply to 6.18-stable tree gregkh
2026-08-31 16:42 ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Sasha Levin
2026-08-31 16:42   ` [PATCH 6.18.y 2/4] fuse: split off fuse_args and related definitions into a separate header Sasha Levin
2026-08-31 16:42   ` [PATCH 6.18.y 3/4] fuse: remove fm arg of args->end callback Sasha Levin
2026-08-31 16:42   ` [PATCH 6.18.y 4/4] fuse: wait for FR_FINISHED on abort_on_kill to prevent use-after-free Sasha Levin
2026-09-09 12:33   ` [PATCH 6.18.y 1/4] fuse: abort related layering cleanup Greg KH

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.