]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
fuse: allow synchronous FUSE_INIT
authorMiklos Szeredi <mszeredi@redhat.com>
Fri, 22 Aug 2025 11:10:44 +0000 (13:10 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 2 Sep 2025 09:14:15 +0000 (11:14 +0200)
FUSE_INIT has always been asynchronous with mount.  That means that the
server processed this request after the mount syscall returned.

This means that FUSE_INIT can't supply the root inode's ID, hence it
currently has a hardcoded value.  There are other limitations such as not
being able to perform getxattr during mount, which is needed by selinux.

To remove these limitations allow server to process FUSE_INIT while
initializing the in-core super block for the fuse filesystem.  This can
only be done if the server is prepared to handle this, so add
FUSE_DEV_IOC_SYNC_INIT ioctl, which

 a) lets the server know whether this feature is supported, returning
 ENOTTY othewrwise.

 b) lets the kernel know to perform a synchronous initialization

The implementation is slightly tricky, since fuse_dev/fuse_conn are set up
only during super block creation.  This is solved by setting the private
data of the fuse device file to a special value ((struct fuse_dev *) 1) and
waiting for this to be turned into a proper fuse_dev before commecing with
operations on the device file.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/cuse.c
fs/fuse/dev.c
fs/fuse/dev_uring.c
fs/fuse/fuse_dev_i.h
fs/fuse/fuse_i.h
fs/fuse/inode.c
include/uapi/linux/fuse.h

index b39844d75a806f787509275ffaf5d7f6dcc4ee6a..28c96961e85d1c763957d0493c734a9b701eec45 100644 (file)
@@ -52,6 +52,7 @@
 #include <linux/user_namespace.h>
 
 #include "fuse_i.h"
+#include "fuse_dev_i.h"
 
 #define CUSE_CONNTBL_LEN       64
 
@@ -547,7 +548,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
  */
 static int cuse_channel_release(struct inode *inode, struct file *file)
 {
-       struct fuse_dev *fud = file->private_data;
+       struct fuse_dev *fud = __fuse_get_dev(file);
        struct cuse_conn *cc = fc_to_cc(fud->fc);
 
        /* remove from the conntbl, no more access from this point on */
index f6259711ca1c671993483696232669e83ca3b5d2..2f1619e266e19d459920f14a668811f9cbdb9527 100644 (file)
@@ -1530,14 +1530,34 @@ static int fuse_dev_open(struct inode *inode, struct file *file)
        return 0;
 }
 
+struct fuse_dev *fuse_get_dev(struct file *file)
+{
+       struct fuse_dev *fud = __fuse_get_dev(file);
+       int err;
+
+       if (likely(fud))
+               return fud;
+
+       err = wait_event_interruptible(fuse_dev_waitq,
+                                      READ_ONCE(file->private_data) != FUSE_DEV_SYNC_INIT);
+       if (err)
+               return ERR_PTR(err);
+
+       fud = __fuse_get_dev(file);
+       if (!fud)
+               return ERR_PTR(-EPERM);
+
+       return fud;
+}
+
 static ssize_t fuse_dev_read(struct kiocb *iocb, struct iov_iter *to)
 {
        struct fuse_copy_state cs;
        struct file *file = iocb->ki_filp;
        struct fuse_dev *fud = fuse_get_dev(file);
 
-       if (!fud)
-               return -EPERM;
+       if (IS_ERR(fud))
+               return PTR_ERR(fud);
 
        if (!user_backed_iter(to))
                return -EINVAL;
@@ -1557,8 +1577,8 @@ static ssize_t fuse_dev_splice_read(struct file *in, loff_t *ppos,
        struct fuse_copy_state cs;
        struct fuse_dev *fud = fuse_get_dev(in);
 
-       if (!fud)
-               return -EPERM;
+       if (IS_ERR(fud))
+               return PTR_ERR(fud);
 
        bufs = kvmalloc_array(pipe->max_usage, sizeof(struct pipe_buffer),
                              GFP_KERNEL);
@@ -2231,7 +2251,7 @@ copy_finish:
 static ssize_t fuse_dev_write(struct kiocb *iocb, struct iov_iter *from)
 {
        struct fuse_copy_state cs;
-       struct fuse_dev *fud = fuse_get_dev(iocb->ki_filp);
+       struct fuse_dev *fud = __fuse_get_dev(iocb->ki_filp);
 
        if (!fud)
                return -EPERM;
@@ -2253,11 +2273,10 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
        unsigned idx;
        struct pipe_buffer *bufs;
        struct fuse_copy_state cs;
-       struct fuse_dev *fud;
+       struct fuse_dev *fud = __fuse_get_dev(out);
        size_t rem;
        ssize_t ret;
 
-       fud = fuse_get_dev(out);
        if (!fud)
                return -EPERM;
 
@@ -2343,7 +2362,7 @@ static __poll_t fuse_dev_poll(struct file *file, poll_table *wait)
        struct fuse_iqueue *fiq;
        struct fuse_dev *fud = fuse_get_dev(file);
 
-       if (!fud)
+       if (IS_ERR(fud))
                return EPOLLERR;
 
        fiq = &fud->fc->iq;
@@ -2490,7 +2509,7 @@ void fuse_wait_aborted(struct fuse_conn *fc)
 
 int fuse_dev_release(struct inode *inode, struct file *file)
 {
-       struct fuse_dev *fud = fuse_get_dev(file);
+       struct fuse_dev *fud = __fuse_get_dev(file);
 
        if (fud) {
                struct fuse_conn *fc = fud->fc;
@@ -2521,8 +2540,8 @@ static int fuse_dev_fasync(int fd, struct file *file, int on)
 {
        struct fuse_dev *fud = fuse_get_dev(file);
 
-       if (!fud)
-               return -EPERM;
+       if (IS_ERR(fud))
+               return PTR_ERR(fud);
 
        /* No locking - fasync_helper does its own locking */
        return fasync_helper(fd, file, on, &fud->fc->iq.fasync);
@@ -2532,7 +2551,7 @@ static int fuse_device_clone(struct fuse_conn *fc, struct file *new)
 {
        struct fuse_dev *fud;
 
-       if (new->private_data)
+       if (__fuse_get_dev(new))
                return -EINVAL;
 
        fud = fuse_dev_alloc_install(fc);
@@ -2563,7 +2582,7 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
         * uses the same ioctl handler.
         */
        if (fd_file(f)->f_op == file->f_op)
-               fud = fuse_get_dev(fd_file(f));
+               fud = __fuse_get_dev(fd_file(f));
 
        res = -EINVAL;
        if (fud) {
@@ -2581,8 +2600,8 @@ static long fuse_dev_ioctl_backing_open(struct file *file,
        struct fuse_dev *fud = fuse_get_dev(file);
        struct fuse_backing_map map;
 
-       if (!fud)
-               return -EPERM;
+       if (IS_ERR(fud))
+               return PTR_ERR(fud);
 
        if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
                return -EOPNOTSUPP;
@@ -2598,8 +2617,8 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
        struct fuse_dev *fud = fuse_get_dev(file);
        int backing_id;
 
-       if (!fud)
-               return -EPERM;
+       if (IS_ERR(fud))
+               return PTR_ERR(fud);
 
        if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
                return -EOPNOTSUPP;
@@ -2610,6 +2629,19 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
        return fuse_backing_close(fud->fc, backing_id);
 }
 
+static long fuse_dev_ioctl_sync_init(struct file *file)
+{
+       int err = -EINVAL;
+
+       mutex_lock(&fuse_mutex);
+       if (!__fuse_get_dev(file)) {
+               WRITE_ONCE(file->private_data, FUSE_DEV_SYNC_INIT);
+               err = 0;
+       }
+       mutex_unlock(&fuse_mutex);
+       return err;
+}
+
 static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
                           unsigned long arg)
 {
@@ -2625,6 +2657,9 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
        case FUSE_DEV_IOC_BACKING_CLOSE:
                return fuse_dev_ioctl_backing_close(file, argp);
 
+       case FUSE_DEV_IOC_SYNC_INIT:
+               return fuse_dev_ioctl_sync_init(file);
+
        default:
                return -ENOTTY;
        }
@@ -2633,7 +2668,7 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
 #ifdef CONFIG_PROC_FS
 static void fuse_dev_show_fdinfo(struct seq_file *seq, struct file *file)
 {
-       struct fuse_dev *fud = fuse_get_dev(file);
+       struct fuse_dev *fud = __fuse_get_dev(file);
        if (!fud)
                return;
 
index 249b210becb1cc2b40ae7b2fdf3a57dc57eaac42..bef38ed78249b055dd9852b2d909676f8a15fec0 100644 (file)
@@ -1139,9 +1139,9 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
                return -EINVAL;
 
        fud = fuse_get_dev(cmd->file);
-       if (!fud) {
+       if (IS_ERR(fud)) {
                pr_info_ratelimited("No fuse device found\n");
-               return -ENOTCONN;
+               return PTR_ERR(fud);
        }
        fc = fud->fc;
 
index 5a9bd771a3193dbe51c82179a628f1eb65d2253a..6e8373f970409e83efdc5d5cfc3d943a8948d3a7 100644 (file)
@@ -12,6 +12,8 @@
 #define FUSE_INT_REQ_BIT (1ULL << 0)
 #define FUSE_REQ_ID_STEP (1ULL << 1)
 
+extern struct wait_queue_head fuse_dev_waitq;
+
 struct fuse_arg;
 struct fuse_args;
 struct fuse_pqueue;
@@ -37,15 +39,22 @@ struct fuse_copy_state {
        } ring;
 };
 
-static inline struct fuse_dev *fuse_get_dev(struct file *file)
+#define FUSE_DEV_SYNC_INIT ((struct fuse_dev *) 1)
+#define FUSE_DEV_PTR_MASK (~1UL)
+
+static inline struct fuse_dev *__fuse_get_dev(struct file *file)
 {
        /*
         * Lockless access is OK, because file->private data is set
         * once during mount and is valid until the file is released.
         */
-       return READ_ONCE(file->private_data);
+       struct fuse_dev *fud = READ_ONCE(file->private_data);
+
+       return (typeof(fud)) ((unsigned long) fud & FUSE_DEV_PTR_MASK);
 }
 
+struct fuse_dev *fuse_get_dev(struct file *file);
+
 unsigned int fuse_req_hash(u64 unique);
 struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique);
 
index 486fa550c9510e4f26579bdeaaec09f495f21afb..233c6111f768f222c01b1759e29aa584c97e24e0 100644 (file)
@@ -904,6 +904,9 @@ struct fuse_conn {
        /* Is link not implemented by fs? */
        unsigned int no_link:1;
 
+       /* Is synchronous FUSE_INIT allowed? */
+       unsigned int sync_init:1;
+
        /* Use io_uring for communication */
        unsigned int io_uring;
 
@@ -1318,7 +1321,7 @@ struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc);
 struct fuse_dev *fuse_dev_alloc(void);
 void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc);
 void fuse_dev_free(struct fuse_dev *fud);
-void fuse_send_init(struct fuse_mount *fm);
+int fuse_send_init(struct fuse_mount *fm);
 
 /**
  * Fill in superblock and initialize fuse connection
index 9d26a5bc394d4a4db51c62c8cbbcef2f8dd54370..5b7897bf7e456fe52e99d9cc37d5afff10db3219 100644 (file)
@@ -7,6 +7,7 @@
 */
 
 #include "fuse_i.h"
+#include "fuse_dev_i.h"
 #include "dev_uring_i.h"
 
 #include <linux/dax.h>
@@ -34,6 +35,7 @@ MODULE_LICENSE("GPL");
 static struct kmem_cache *fuse_inode_cachep;
 struct list_head fuse_conn_list;
 DEFINE_MUTEX(fuse_mutex);
+DECLARE_WAIT_QUEUE_HEAD(fuse_dev_waitq);
 
 static int set_global_limit(const char *val, const struct kernel_param *kp);
 
@@ -1466,7 +1468,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
        wake_up_all(&fc->blocked_waitq);
 }
 
-void fuse_send_init(struct fuse_mount *fm)
+static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 {
        struct fuse_init_args *ia;
        u64 flags;
@@ -1525,10 +1527,30 @@ void fuse_send_init(struct fuse_mount *fm)
        ia->args.out_args[0].value = &ia->out;
        ia->args.force = true;
        ia->args.nocreds = true;
-       ia->args.end = process_init_reply;
 
-       if (fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0)
-               process_init_reply(fm, &ia->args, -ENOTCONN);
+       return ia;
+}
+
+int fuse_send_init(struct fuse_mount *fm)
+{
+       struct fuse_init_args *ia = fuse_new_init(fm);
+       int err;
+
+       if (fm->fc->sync_init) {
+               err = fuse_simple_request(fm, &ia->args);
+               /* Ignore size of init reply */
+               if (err > 0)
+                       err = 0;
+       } else {
+               ia->args.end = process_init_reply;
+               err = fuse_simple_background(fm, &ia->args, GFP_KERNEL);
+               if (!err)
+                       return 0;
+       }
+       process_init_reply(fm, &ia->args, err);
+       if (fm->fc->conn_error)
+               return -ENOTCONN;
+       return 0;
 }
 EXPORT_SYMBOL_GPL(fuse_send_init);
 
@@ -1867,8 +1889,12 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 
        mutex_lock(&fuse_mutex);
        err = -EINVAL;
-       if (ctx->fudptr && *ctx->fudptr)
-               goto err_unlock;
+       if (ctx->fudptr && *ctx->fudptr) {
+               if (*ctx->fudptr == FUSE_DEV_SYNC_INIT)
+                       fc->sync_init = 1;
+               else
+                       goto err_unlock;
+       }
 
        err = fuse_ctl_add_conn(fc);
        if (err)
@@ -1876,8 +1902,10 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
 
        list_add_tail(&fc->entry, &fuse_conn_list);
        sb->s_root = root_dentry;
-       if (ctx->fudptr)
+       if (ctx->fudptr) {
                *ctx->fudptr = fud;
+               wake_up_all(&fuse_dev_waitq);
+       }
        mutex_unlock(&fuse_mutex);
        return 0;
 
@@ -1898,6 +1926,7 @@ EXPORT_SYMBOL_GPL(fuse_fill_super_common);
 static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
 {
        struct fuse_fs_context *ctx = fsc->fs_private;
+       struct fuse_mount *fm;
        int err;
 
        if (!ctx->file || !ctx->rootmode_present ||
@@ -1918,8 +1947,10 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
                return err;
        /* file->private_data shall be visible on all CPUs after this */
        smp_mb();
-       fuse_send_init(get_fuse_mount_super(sb));
-       return 0;
+
+       fm = get_fuse_mount_super(sb);
+
+       return fuse_send_init(fm);
 }
 
 /*
@@ -1980,7 +2011,7 @@ static int fuse_get_tree(struct fs_context *fsc)
         * Allow creating a fuse mount with an already initialized fuse
         * connection
         */
-       fud = READ_ONCE(ctx->file->private_data);
+       fud = __fuse_get_dev(ctx->file);
        if (ctx->file->f_op == &fuse_dev_operations && fud) {
                fsc->sget_key = fud->fc;
                sb = sget_fc(fsc, fuse_test_super, fuse_set_no_super);
index 94621f68a5cc8dd2d9eedbf2f0080e53b4cd4161..6b9fb8b087689457f68fc6012718fcc76726e628 100644 (file)
@@ -1131,6 +1131,7 @@ struct fuse_backing_map {
 #define FUSE_DEV_IOC_BACKING_OPEN      _IOW(FUSE_DEV_IOC_MAGIC, 1, \
                                             struct fuse_backing_map)
 #define FUSE_DEV_IOC_BACKING_CLOSE     _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+#define FUSE_DEV_IOC_SYNC_INIT         _IO(FUSE_DEV_IOC_MAGIC, 3)
 
 struct fuse_lseek_in {
        uint64_t        fh;