From: "Darrick J. Wong" <djwong@kernel.org>
To: Miklos Szeredi <mszeredi@redhat.com>
Cc: linux-fsdevel@vger.kernel.org, Bernd Schubert <bernd@bsbernd.com>
Subject: Re: [PATCH v2 3/6] fuse: don't require /dev/fuse fd to be kept open during mount
Date: Fri, 13 Mar 2026 16:09:34 -0700 [thread overview]
Message-ID: <20260313230934.GG1742010@frogsfrogsfrogs> (raw)
In-Reply-To: <20260312194019.3077902-4-mszeredi@redhat.com>
On Thu, Mar 12, 2026 at 08:40:01PM +0100, Miklos Szeredi wrote:
> With the new mount API the sequence of syscalls would be:
>
> fs_fd = fsopen("fuse", 0);
> snprintf(opt, sizeof(opt), "%i", devfd);
> fsconfig(fs_fd, FSCONFIG_SET_STRING, "fd", opt, 0);
> /* ... */
> fsconfig(fs_fd, FSCONFIG_CMD_CREATE, 0, 0, 0);
>
> Current mount code just stores the value of devfd in the fs_context and
> uses it in during FSCONFIG_CMD_CREATE.
>
> This is not very elegant, but there's a bigger problem: when sync init is
> used and the server exits for some reason (error, crash) while processing
> FUSE_INIT, the filesystem creation will hang.
Hrmm, is this https://github.com/libfuse/libfuse/pull/1367 ?
Which syscall causes the synchronous FUSE_INIT to be sent? Is it
FSCONFIG_CMD_CREATE?
> The reason is that while all other threads will exit, the mounting
> thread (or process) will keep the device fd open, which will prevent
> an abort from happening.
So I guess what's happening here is that the main thread calls
FSCONFIG_CMD_CREATE, which sends the synchronous FUSE_INIT to the worker
pool. One of the worker threads starts working on the FUSE_INIT reply
and crashes. All the *workers* terminate and close the fuse dev fd,
leaving just the main thread.
AFAICT, the main thread is stuck in fsconfig() here:
/* Any signal may interrupt this */
err = wait_event_interruptible(req->waitq,
test_bit(FR_FINISHED, &req->flags))
so there's still an open ref to the fuse dev fd, which prevents anyone
from aborting the FUSE_INIT request so that FR_FINISH gets set? And now
we're just hosed? Wouldn't the SIGCHLD interrupt the wait? Or are we
stuck someplace else?
> This is a regression from the async mount case, where the mount was done
> first, and the FUSE_INIT processing afterwards, in which case there's no
> such recursive syscall keeping the fd open.
Is this hang possible if you're using mount(2) with synchronous
FUSE_INIT?
> The solution is twofold:
>
> a) use unshare(CLONE_FILES) in the mounting thread
Is this after starting up the worker threads? I guess that means the
worker threads retain their fuse dev fds even though...
> and close the device fd
> after fsconfig(fs_fd, FSCONFIG_SET_STRING, "fd", ...)
...the main thread closes the fuse dev fd before FSCONFIG_CMD_CREATE.
What if that main thread needs to use the fuse dev fd after this point?
Or, what if userspace doesn't cooperate and unshare()/close()? Can this
hang be broken by kill -9, at least?
> b) only reference the fuse_dev from fs_context not the device file itself
Can you set an abort timeout on the FUSE_INIT request?
Perhaps my broader question is, what /does/ happen if a fuse server
thread starts processing a request and crashes out before replying? I
guess that means the request is never completed, but in the case of
!(synchronous FUSE_INIT) we'd just see the whole server terminate, which
would then release the fuse dev fd?
Sorry this isn't a review so much as me asking a lot of annoying
questions. :/
--D
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
> fs/fuse/fuse_i.h | 4 +---
> fs/fuse/inode.c | 54 +++++++++++++++++++++++++++---------------------
> 2 files changed, 31 insertions(+), 27 deletions(-)
>
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 66ec92f06497..b77b384b0385 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -603,13 +603,11 @@ static inline bool fuse_is_inode_dax_mode(enum fuse_dax_mode mode)
> }
>
> struct fuse_fs_context {
> - int fd;
> - struct file *file;
> + struct fuse_dev *fud;
> unsigned int rootmode;
> kuid_t user_id;
> kgid_t group_id;
> bool is_bdev:1;
> - bool fd_present:1;
> bool rootmode_present:1;
> bool user_id_present:1;
> bool group_id_present:1;
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index ba845092e7f2..45abcfec03a4 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -800,6 +800,26 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
> {}
> };
>
> +static int fuse_opt_fd(struct fs_context *fsc, int fd)
> +{
> + struct file *file __free(fput) = fget(fd);
> + struct fuse_fs_context *ctx = fsc->fs_private;
> +
> + if (file->f_op != &fuse_dev_operations)
> + return invalfc(fsc, "fd is not a fuse device");
> + /*
> + * Require mount to happen from the same user namespace which
> + * opened /dev/fuse to prevent potential attacks.
> + */
> + if (file->f_cred->user_ns != fsc->user_ns)
> + return invalfc(fsc, "wrong user namespace for fuse device");
> +
> + ctx->fud = file->private_data;
> + refcount_inc(&ctx->fud->ref);
> +
> + return 0;
> +}
> +
> static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
> {
> struct fs_parse_result result;
> @@ -839,9 +859,7 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
> return 0;
>
> case OPT_FD:
> - ctx->fd = result.uint_32;
> - ctx->fd_present = true;
> - break;
> + return fuse_opt_fd(fsc, result.uint_32);
>
> case OPT_ROOTMODE:
> if (!fuse_valid_type(result.uint_32))
> @@ -904,6 +922,8 @@ static void fuse_free_fsc(struct fs_context *fsc)
> struct fuse_fs_context *ctx = fsc->fs_private;
>
> if (ctx) {
> + if (ctx->fud)
> + fuse_dev_put(ctx->fud);
> kfree(ctx->subtype);
> kfree(ctx);
> }
> @@ -1847,7 +1867,7 @@ EXPORT_SYMBOL_GPL(fuse_init_fs_context_submount);
>
> int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
> {
> - struct fuse_dev *fud = ctx->file ? fuse_file_to_fud(ctx->file) : NULL;
> + struct fuse_dev *fud = ctx->fud;
> struct fuse_mount *fm = get_fuse_mount_super(sb);
> struct fuse_conn *fc = fm->fc;
> struct inode *root;
> @@ -1950,18 +1970,10 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
> struct fuse_mount *fm;
> int err;
>
> - if (!ctx->file || !ctx->rootmode_present ||
> + if (!ctx->fud || !ctx->rootmode_present ||
> !ctx->user_id_present || !ctx->group_id_present)
> return -EINVAL;
>
> - /*
> - * Require mount to happen from the same user namespace which
> - * opened /dev/fuse to prevent potential attacks.
> - */
> - if ((ctx->file->f_op != &fuse_dev_operations) ||
> - (ctx->file->f_cred->user_ns != sb->s_user_ns))
> - return -EINVAL;
> -
> err = fuse_fill_super_common(sb, ctx);
> if (err)
> return err;
> @@ -1989,8 +2001,7 @@ static int fuse_test_super(struct super_block *sb, struct fs_context *fsc)
> static int fuse_get_tree(struct fs_context *fsc)
> {
> struct fuse_fs_context *ctx = fsc->fs_private;
> - struct fuse_dev *fud;
> - struct fuse_conn *fc;
> + struct fuse_conn *fc, *key;
> struct fuse_mount *fm;
> struct super_block *sb;
> int err;
> @@ -2010,9 +2021,6 @@ static int fuse_get_tree(struct fs_context *fsc)
>
> fsc->s_fs_info = fm;
>
> - if (ctx->fd_present)
> - ctx->file = fget(ctx->fd);
> -
> if (IS_ENABLED(CONFIG_BLOCK) && ctx->is_bdev) {
> err = get_tree_bdev(fsc, fuse_fill_super);
> goto out;
> @@ -2022,16 +2030,16 @@ static int fuse_get_tree(struct fs_context *fsc)
> * (found by device name), normal fuse mounts can't
> */
> err = -EINVAL;
> - if (!ctx->file)
> + if (!ctx->fud)
> goto out;
>
> /*
> * Allow creating a fuse mount with an already initialized fuse
> * connection
> */
> - fud = __fuse_get_dev(ctx->file);
> - if (ctx->file->f_op == &fuse_dev_operations && fud) {
> - fsc->sget_key = fud->fc;
> + key = fuse_dev_fc_get(ctx->fud);
> + if (key) {
> + fsc->sget_key = key;
> sb = sget_fc(fsc, fuse_test_super, fuse_set_no_super);
> err = PTR_ERR_OR_ZERO(sb);
> if (!IS_ERR(sb))
> @@ -2042,8 +2050,6 @@ static int fuse_get_tree(struct fs_context *fsc)
> out:
> if (fsc->s_fs_info)
> fuse_mount_destroy(fm);
> - if (ctx->file)
> - fput(ctx->file);
> return err;
> }
>
> --
> 2.53.0
>
>
next prev parent reply other threads:[~2026-03-13 23:09 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-12 19:39 [PATCH v2 0/6] fuse: fix hang with sync init Miklos Szeredi
2026-03-12 19:39 ` [PATCH v2 1/6] fuse: create fuse_dev on /dev/fuse open instead of mount Miklos Szeredi
2026-03-13 22:05 ` Darrick J. Wong
2026-03-16 10:04 ` Miklos Szeredi
2026-03-19 23:36 ` Mark Brown
2026-03-12 19:40 ` [PATCH v2 2/6] fuse: add refcount to fuse_dev Miklos Szeredi
2026-03-13 22:28 ` Darrick J. Wong
2026-03-16 10:50 ` Miklos Szeredi
2026-03-12 19:40 ` [PATCH v2 3/6] fuse: don't require /dev/fuse fd to be kept open during mount Miklos Szeredi
2026-03-13 23:09 ` Darrick J. Wong [this message]
2026-03-16 11:29 ` Miklos Szeredi
2026-03-17 23:39 ` Darrick J. Wong
2026-03-17 23:49 ` Joanne Koong
2026-03-18 22:15 ` Bernd Schubert
2026-03-18 23:18 ` Joanne Koong
2026-03-12 19:40 ` [PATCH v2 4/6] fuse: clean up device cloning Miklos Szeredi
2026-03-13 23:59 ` Darrick J. Wong
2026-03-16 11:40 ` Miklos Szeredi
2026-03-12 19:40 ` [PATCH v2 5/6] fuse: alloc pqueue before installing fc Miklos Szeredi
2026-03-12 19:40 ` [PATCH v2 6/6] fuse: support FSCONFIG_SET_FD for "fd" option Miklos Szeredi
2026-03-13 12:03 ` kernel test robot
2026-03-14 0:10 ` Darrick J. Wong
2026-03-14 17:22 ` kernel test robot
2026-03-12 23:04 ` [PATCH v2 0/6] fuse: fix hang with sync init Bernd Schubert
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260313230934.GG1742010@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=bernd@bsbernd.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=mszeredi@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox