From: Hengyu Liang Commit 794e811d1443 ("fuse: split out filesystem part of request sending") moved the INIT failure check into fuse_req_prep(), before the channel checks whether it is still connected. If a daemon closes /dev/fuse before replying to INIT, aborting the channel completes INIT with an error. stat() and open() on the mount then return ECONNREFUSED instead of the previous ENOTCONN. The same change occurs when INIT is rejected before the daemon closes the fd. Record INIT failure in the channel before publishing initialized, and check it in fuse_get_req() after waiting for initialization and checking for disconnection. This preserves ECONNREFUSED for failed INIT while the device remains open, and restores ENOTCONN after disconnection. Set the failure flag only while the channel is connected, since abort also marks it initialized to wake waiters after a successful INIT. Fixes: 794e811d1443 ("fuse: split out filesystem part of request sending") Cc: stable@vger.kernel.org Signed-off-by: Hengyu Liang --- Tested on x86-64 KVM with KASAN, comparing v7.1, unpatched v7.3-rc7 and patched v7.3-rc7. Checked stat/open before INIT replies, after INIT refusal with the device open/closed, and after successful INIT and disconnect. Also checked pending INIT requests, 400 concurrent INIT-reply/abort iterations, normal FUSE directory operations and CUSE open/read/write/close. Results match v7.1; no kernel diagnostics. fs/fuse/dev.c | 8 ++++++++ fs/fuse/fuse_dev_i.h | 3 +++ fs/fuse/req.c | 14 +++----------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 4fec31fc0b84..168ad7e00510 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -80,6 +80,10 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa fuse_uring_conn_init(fch); } + /* An aborted channel is also marked initialized to wake waiters. */ + if (!param && READ_ONCE(fch->connected)) + fch->conn_error = true; + /* Pairs with smp_load_acquire() readers of fch->initialized */ smp_store_release(&fch->initialized, 1); wake_up_all(&fch->blocked_waitq); @@ -130,6 +134,10 @@ static struct fuse_req *fuse_get_req(struct fuse_chan *fch, bool for_background) if (!fch->connected) goto out; + err = -ECONNREFUSED; + if (fch->conn_error) + goto out; + req = fuse_request_alloc(fch, GFP_KERNEL); err = -ENOMEM; if (!req) { diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 4b412a76225f..0ffdc7f57963 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -245,6 +245,9 @@ struct fuse_chan { /** Is interrupt not implemented by fs? */ bool no_interrupt; + /** Initialization failed */ + bool conn_error; + /* Use io_uring for communication */ unsigned int io_uring; diff --git a/fs/fuse/req.c b/fs/fuse/req.c index a01ee743d31e..eede265b06c1 100644 --- a/fs/fuse/req.c +++ b/fs/fuse/req.c @@ -49,19 +49,11 @@ static int fuse_fill_creds(struct fuse_mount *fm, struct fuse_args *args, struct return 0; } -static int fuse_req_prep(struct fuse_mount *fm, struct fuse_args *args, struct mnt_idmap *idmap) -{ - if (!args->force && fm->fc->conn_error) - return -ECONNREFUSED; - - return fuse_fill_creds(fm, args, idmap); -} - ssize_t __fuse_simple_request(struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_args *args) { struct fuse_conn *fc = fm->fc; - int err = fuse_req_prep(fm, args, idmap); + int err = fuse_fill_creds(fm, args, idmap); if (err) return err; @@ -76,7 +68,7 @@ int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t WARN_ON(args->force && !args->nocreds); - err = fuse_req_prep(fm, args, &invalid_mnt_idmap); + err = fuse_fill_creds(fm, args, &invalid_mnt_idmap); if (err) return err; @@ -91,7 +83,7 @@ int fuse_simple_notify_reply(struct fuse_mount *fm, struct fuse_args *args, u64 WARN_ON(args->force && !args->nocreds); - err = fuse_req_prep(fm, args, &invalid_mnt_idmap); + err = fuse_fill_creds(fm, args, &invalid_mnt_idmap); if (err) return err; -- 2.53.0