diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2023-02-27 09:53:58 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2023-02-27 09:53:58 -0800 |
commit | d40b2f4c94f221bd5aab205f945e6f88d3df0929 (patch) | |
tree | cbfaadf0a8842e6d408642d29eb7afe58acf63d1 /fs/fuse | |
parent | da15efe1a8513e0a949f429338155a56667dc9c6 (diff) | |
parent | 1cc4606d19e3710bfab3f6704b87ff9580493c69 (diff) |
Merge tag 'fuse-update-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse
Pull fuse updates from Miklos Szeredi:
- Fix regression in fileattr permission checking
- Fix possible hang during PID namespace destruction
- Add generic support for request extensions
- Add supplementary group list extension
- Add limited support for supplying supplementary groups in create
requests
- Documentation fixes
* tag 'fuse-update-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse:
fuse: add inode/permission checks to fileattr_get/fileattr_set
fuse: fix all W=1 kernel-doc warnings
fuse: in fuse_flush only wait if someone wants the return code
fuse: optional supplementary group in create requests
fuse: add request extension
Diffstat (limited to 'fs/fuse')
-rw-r--r-- | fs/fuse/cuse.c | 2 | ||||
-rw-r--r-- | fs/fuse/dev.c | 4 | ||||
-rw-r--r-- | fs/fuse/dir.c | 126 | ||||
-rw-r--r-- | fs/fuse/file.c | 91 | ||||
-rw-r--r-- | fs/fuse/fuse_i.h | 9 | ||||
-rw-r--r-- | fs/fuse/inode.c | 4 | ||||
-rw-r--r-- | fs/fuse/ioctl.c | 6 |
7 files changed, 181 insertions, 61 deletions
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index a06fbb1a8a5b..179a5c5e28fd 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -256,7 +256,7 @@ static int cuse_parse_one(char **pp, char *end, char **keyp, char **valp) } /** - * cuse_parse_dev_info - parse device info + * cuse_parse_devinfo - parse device info * @p: device info string * @len: length of device info string * @devinfo: out parameter for parsed device info diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index e8b60ce72c9a..eb4f88e3dc97 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -204,7 +204,7 @@ static unsigned int fuse_req_hash(u64 unique) return hash_long(unique & ~FUSE_INT_REQ_BIT, FUSE_PQ_HASH_BITS); } -/** +/* * A new request is available, wake fiq->waitq */ static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq) @@ -476,6 +476,8 @@ static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args) req->in.h.opcode = args->opcode; req->in.h.nodeid = args->nodeid; req->args = args; + if (args->is_ext) + req->in.h.total_extlen = args->in_args[args->ext_idx].size / 8; if (args->end) __set_bit(FR_ASYNC, &req->flags); } diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index cd1eae61e84c..35bc174f9ba2 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -145,7 +145,7 @@ static void fuse_dir_changed(struct inode *dir) inode_maybe_inc_iversion(dir, false); } -/** +/* * Mark the attributes as stale due to an atime change. Avoid the invalidate if * atime is not used. */ @@ -466,7 +466,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, } static int get_security_context(struct dentry *entry, umode_t mode, - void **security_ctx, u32 *security_ctxlen) + struct fuse_in_arg *ext) { struct fuse_secctx *fctx; struct fuse_secctx_header *header; @@ -513,14 +513,100 @@ static int get_security_context(struct dentry *entry, umode_t mode, memcpy(ptr, ctx, ctxlen); } - *security_ctxlen = total_len; - *security_ctx = header; + ext->size = total_len; + ext->value = header; err = 0; out_err: kfree(ctx); return err; } +static void *extend_arg(struct fuse_in_arg *buf, u32 bytes) +{ + void *p; + u32 newlen = buf->size + bytes; + + p = krealloc(buf->value, newlen, GFP_KERNEL); + if (!p) { + kfree(buf->value); + buf->size = 0; + buf->value = NULL; + return NULL; + } + + memset(p + buf->size, 0, bytes); + buf->value = p; + buf->size = newlen; + + return p + newlen - bytes; +} + +static u32 fuse_ext_size(size_t size) +{ + return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size); +} + +/* + * This adds just a single supplementary group that matches the parent's group. + */ +static int get_create_supp_group(struct inode *dir, struct fuse_in_arg *ext) +{ + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_ext_header *xh; + struct fuse_supp_groups *sg; + kgid_t kgid = dir->i_gid; + gid_t parent_gid = from_kgid(fc->user_ns, kgid); + u32 sg_len = fuse_ext_size(sizeof(*sg) + sizeof(sg->groups[0])); + + if (parent_gid == (gid_t) -1 || gid_eq(kgid, current_fsgid()) || + !in_group_p(kgid)) + return 0; + + xh = extend_arg(ext, sg_len); + if (!xh) + return -ENOMEM; + + xh->size = sg_len; + xh->type = FUSE_EXT_GROUPS; + + sg = (struct fuse_supp_groups *) &xh[1]; + sg->nr_groups = 1; + sg->groups[0] = parent_gid; + + return 0; +} + +static int get_create_ext(struct fuse_args *args, + struct inode *dir, struct dentry *dentry, + umode_t mode) +{ + struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb); + struct fuse_in_arg ext = { .size = 0, .value = NULL }; + int err = 0; + + if (fc->init_security) + err = get_security_context(dentry, mode, &ext); + if (!err && fc->create_supp_group) + err = get_create_supp_group(dir, &ext); + + if (!err && ext.size) { + WARN_ON(args->in_numargs >= ARRAY_SIZE(args->in_args)); + args->is_ext = true; + args->ext_idx = args->in_numargs++; + args->in_args[args->ext_idx] = ext; + } else { + kfree(ext.value); + } + + return err; +} + +static void free_ext_value(struct fuse_args *args) +{ + if (args->is_ext) + kfree(args->in_args[args->ext_idx].value); +} + /* * Atomic create+open operation * @@ -541,8 +627,6 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, struct fuse_entry_out outentry; struct fuse_inode *fi; struct fuse_file *ff; - void *security_ctx = NULL; - u32 security_ctxlen; bool trunc = flags & O_TRUNC; /* Userspace expects S_IFREG in create mode */ @@ -586,19 +670,12 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, args.out_args[1].size = sizeof(outopen); args.out_args[1].value = &outopen; - if (fm->fc->init_security) { - err = get_security_context(entry, mode, &security_ctx, - &security_ctxlen); - if (err) - goto out_put_forget_req; - - args.in_numargs = 3; - args.in_args[2].size = security_ctxlen; - args.in_args[2].value = security_ctx; - } + err = get_create_ext(&args, dir, entry, mode); + if (err) + goto out_put_forget_req; err = fuse_simple_request(fm, &args); - kfree(security_ctx); + free_ext_value(&args); if (err) goto out_free_ff; @@ -705,8 +782,6 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args, struct dentry *d; int err; struct fuse_forget_link *forget; - void *security_ctx = NULL; - u32 security_ctxlen; if (fuse_is_bad(dir)) return -EIO; @@ -721,21 +796,14 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args, args->out_args[0].size = sizeof(outarg); args->out_args[0].value = &outarg; - if (fm->fc->init_security && args->opcode != FUSE_LINK) { - err = get_security_context(entry, mode, &security_ctx, - &security_ctxlen); + if (args->opcode != FUSE_LINK) { + err = get_create_ext(args, dir, entry, mode); if (err) goto out_put_forget_req; - - BUG_ON(args->in_numargs != 2); - - args->in_numargs = 3; - args->in_args[2].size = security_ctxlen; - args->in_args[2].value = security_ctx; } err = fuse_simple_request(fm, args); - kfree(security_ctx); + free_ext_value(args); if (err) goto out_put_forget_req; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index ff0b3ef774d4..de37a3a06a71 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -19,6 +19,7 @@ #include <linux/uio.h> #include <linux/fs.h> #include <linux/filelock.h> +#include <linux/file.h> static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, unsigned int open_flags, int opcode, @@ -478,48 +479,36 @@ static void fuse_sync_writes(struct inode *inode) fuse_release_nowrite(inode); } -static int fuse_flush(struct file *file, fl_owner_t id) -{ - struct inode *inode = file_inode(file); - struct fuse_mount *fm = get_fuse_mount(inode); - struct fuse_file *ff = file->private_data; +struct fuse_flush_args { + struct fuse_args args; struct fuse_flush_in inarg; - FUSE_ARGS(args); - int err; - - if (fuse_is_bad(inode)) - return -EIO; + struct work_struct work; + struct file *file; +}; - if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache) - return 0; +static int fuse_do_flush(struct fuse_flush_args *fa) +{ + int err; + struct inode *inode = file_inode(fa->file); + struct fuse_mount *fm = get_fuse_mount(inode); err = write_inode_now(inode, 1); if (err) - return err; + goto out; inode_lock(inode); fuse_sync_writes(inode); inode_unlock(inode); - err = filemap_check_errors(file->f_mapping); + err = filemap_check_errors(fa->file->f_mapping); if (err) - return err; + goto out; err = 0; if (fm->fc->no_flush) goto inval_attr_out; - memset(&inarg, 0, sizeof(inarg)); - inarg.fh = ff->fh; - inarg.lock_owner = fuse_lock_owner_id(fm->fc, id); - args.opcode = FUSE_FLUSH; - args.nodeid = get_node_id(inode); - args.in_numargs = 1; - args.in_args[0].size = sizeof(inarg); - args.in_args[0].value = &inarg; - args.force = true; - - err = fuse_simple_request(fm, &args); + err = fuse_simple_request(fm, &fa->args); if (err == -ENOSYS) { fm->fc->no_flush = 1; err = 0; @@ -532,9 +521,57 @@ inval_attr_out: */ if (!err && fm->fc->writeback_cache) fuse_invalidate_attr_mask(inode, STATX_BLOCKS); + +out: + fput(fa->file); + kfree(fa); return err; } +static void fuse_flush_async(struct work_struct *work) +{ + struct fuse_flush_args *fa = container_of(work, typeof(*fa), work); + + fuse_do_flush(fa); +} + +static int fuse_flush(struct file *file, fl_owner_t id) +{ + struct fuse_flush_args *fa; + struct inode *inode = file_inode(file); + struct fuse_mount *fm = get_fuse_mount(inode); + struct fuse_file *ff = file->private_data; + + if (fuse_is_bad(inode)) + return -EIO; + + if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache) + return 0; + + fa = kzalloc(sizeof(*fa), GFP_KERNEL); + if (!fa) + return -ENOMEM; + + fa->inarg.fh = ff->fh; + fa->inarg.lock_owner = fuse_lock_owner_id(fm->fc, id); + fa->args.opcode = FUSE_FLUSH; + fa->args.nodeid = get_node_id(inode); + fa->args.in_numargs = 1; + fa->args.in_args[0].size = sizeof(fa->inarg); + fa->args.in_args[0].value = &fa->inarg; + fa->args.force = true; + fa->file = get_file(file); + + /* Don't wait if the task is exiting */ + if (current->flags & PF_EXITING) { + INIT_WORK(&fa->work, fuse_flush_async); + schedule_work(&fa->work); + return 0; + } + + return fuse_do_flush(fa); +} + int fuse_fsync_common(struct file *file, loff_t start, loff_t end, int datasync, int opcode) { @@ -653,7 +690,7 @@ static ssize_t fuse_get_res_by_io(struct fuse_io_priv *io) return io->bytes < 0 ? io->size : io->bytes; } -/** +/* * In case of short read, the caller sets 'pos' to the position of * actual end of fuse request in IO request. Otherwise, if bytes_requested * == bytes_transferred or rw == WRITE, the caller sets 'pos' to -1. diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 9b5058cf5bc3..9b7fc7d3c7f1 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -249,8 +249,9 @@ struct fuse_page_desc { struct fuse_args { uint64_t nodeid; uint32_t opcode; - unsigned short in_numargs; - unsigned short out_numargs; + uint8_t in_numargs; + uint8_t out_numargs; + uint8_t ext_idx; bool force:1; bool noreply:1; bool nocreds:1; @@ -261,6 +262,7 @@ struct fuse_args { bool page_zeroing:1; bool page_replace:1; bool may_block:1; + bool is_ext:1; struct fuse_in_arg in_args[3]; struct fuse_arg out_args[2]; void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); @@ -781,6 +783,9 @@ struct fuse_conn { /* Initialize security xattrs when creating a new inode */ unsigned int init_security:1; + /* Add supplementary group info when creating a new inode */ + unsigned int create_supp_group:1; + /* Does the filesystem support per inode DAX? */ unsigned int inode_dax:1; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index de9b9ec5ce81..d66070af145d 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1207,6 +1207,8 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, fc->setxattr_ext = 1; if (flags & FUSE_SECURITY_CTX) fc->init_security = 1; + if (flags & FUSE_CREATE_SUPP_GROUP) + fc->create_supp_group = 1; } else { ra_pages = fc->max_read / PAGE_SIZE; fc->no_lock = 1; @@ -1252,7 +1254,7 @@ void fuse_send_init(struct fuse_mount *fm) FUSE_ABORT_ERROR | FUSE_MAX_PAGES | FUSE_CACHE_SYMLINKS | FUSE_NO_OPENDIR_SUPPORT | FUSE_EXPLICIT_INVAL_DATA | FUSE_HANDLE_KILLPRIV_V2 | FUSE_SETXATTR_EXT | FUSE_INIT_EXT | - FUSE_SECURITY_CTX; + FUSE_SECURITY_CTX | FUSE_CREATE_SUPP_GROUP; #ifdef CONFIG_FUSE_DAX if (fm->fc->dax) flags |= FUSE_MAP_ALIGNMENT; diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index e50a18ee6cc6..8e01bfdfc430 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -419,6 +419,12 @@ static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode) struct fuse_mount *fm = get_fuse_mount(inode); bool isdir = S_ISDIR(inode->i_mode); + if (!fuse_allow_current_process(fm->fc)) + return ERR_PTR(-EACCES); + + if (fuse_is_bad(inode)) + return ERR_PTR(-EIO); + if (!S_ISREG(inode->i_mode) && !isdir) return ERR_PTR(-ENOTTY); |