162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* Filesystem access-by-fd.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/fs_context.h>
962306a36Sopenharmony_ci#include <linux/fs_parser.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/uaccess.h>
1262306a36Sopenharmony_ci#include <linux/syscalls.h>
1362306a36Sopenharmony_ci#include <linux/security.h>
1462306a36Sopenharmony_ci#include <linux/anon_inodes.h>
1562306a36Sopenharmony_ci#include <linux/namei.h>
1662306a36Sopenharmony_ci#include <linux/file.h>
1762306a36Sopenharmony_ci#include <uapi/linux/mount.h>
1862306a36Sopenharmony_ci#include "internal.h"
1962306a36Sopenharmony_ci#include "mount.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * Allow the user to read back any error, warning or informational messages.
2362306a36Sopenharmony_ci */
2462306a36Sopenharmony_cistatic ssize_t fscontext_read(struct file *file,
2562306a36Sopenharmony_ci			      char __user *_buf, size_t len, loff_t *pos)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	struct fs_context *fc = file->private_data;
2862306a36Sopenharmony_ci	struct fc_log *log = fc->log.log;
2962306a36Sopenharmony_ci	unsigned int logsize = ARRAY_SIZE(log->buffer);
3062306a36Sopenharmony_ci	ssize_t ret;
3162306a36Sopenharmony_ci	char *p;
3262306a36Sopenharmony_ci	bool need_free;
3362306a36Sopenharmony_ci	int index, n;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	ret = mutex_lock_interruptible(&fc->uapi_mutex);
3662306a36Sopenharmony_ci	if (ret < 0)
3762306a36Sopenharmony_ci		return ret;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	if (log->head == log->tail) {
4062306a36Sopenharmony_ci		mutex_unlock(&fc->uapi_mutex);
4162306a36Sopenharmony_ci		return -ENODATA;
4262306a36Sopenharmony_ci	}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	index = log->tail & (logsize - 1);
4562306a36Sopenharmony_ci	p = log->buffer[index];
4662306a36Sopenharmony_ci	need_free = log->need_free & (1 << index);
4762306a36Sopenharmony_ci	log->buffer[index] = NULL;
4862306a36Sopenharmony_ci	log->need_free &= ~(1 << index);
4962306a36Sopenharmony_ci	log->tail++;
5062306a36Sopenharmony_ci	mutex_unlock(&fc->uapi_mutex);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	ret = -EMSGSIZE;
5362306a36Sopenharmony_ci	n = strlen(p);
5462306a36Sopenharmony_ci	if (n > len)
5562306a36Sopenharmony_ci		goto err_free;
5662306a36Sopenharmony_ci	ret = -EFAULT;
5762306a36Sopenharmony_ci	if (copy_to_user(_buf, p, n) != 0)
5862306a36Sopenharmony_ci		goto err_free;
5962306a36Sopenharmony_ci	ret = n;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cierr_free:
6262306a36Sopenharmony_ci	if (need_free)
6362306a36Sopenharmony_ci		kfree(p);
6462306a36Sopenharmony_ci	return ret;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic int fscontext_release(struct inode *inode, struct file *file)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct fs_context *fc = file->private_data;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (fc) {
7262306a36Sopenharmony_ci		file->private_data = NULL;
7362306a36Sopenharmony_ci		put_fs_context(fc);
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ciconst struct file_operations fscontext_fops = {
7962306a36Sopenharmony_ci	.read		= fscontext_read,
8062306a36Sopenharmony_ci	.release	= fscontext_release,
8162306a36Sopenharmony_ci	.llseek		= no_llseek,
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/*
8562306a36Sopenharmony_ci * Attach a filesystem context to a file and an fd.
8662306a36Sopenharmony_ci */
8762306a36Sopenharmony_cistatic int fscontext_create_fd(struct fs_context *fc, unsigned int o_flags)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	int fd;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	fd = anon_inode_getfd("[fscontext]", &fscontext_fops, fc,
9262306a36Sopenharmony_ci			      O_RDWR | o_flags);
9362306a36Sopenharmony_ci	if (fd < 0)
9462306a36Sopenharmony_ci		put_fs_context(fc);
9562306a36Sopenharmony_ci	return fd;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int fscontext_alloc_log(struct fs_context *fc)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	fc->log.log = kzalloc(sizeof(*fc->log.log), GFP_KERNEL);
10162306a36Sopenharmony_ci	if (!fc->log.log)
10262306a36Sopenharmony_ci		return -ENOMEM;
10362306a36Sopenharmony_ci	refcount_set(&fc->log.log->usage, 1);
10462306a36Sopenharmony_ci	fc->log.log->owner = fc->fs_type->owner;
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/*
10962306a36Sopenharmony_ci * Open a filesystem by name so that it can be configured for mounting.
11062306a36Sopenharmony_ci *
11162306a36Sopenharmony_ci * We are allowed to specify a container in which the filesystem will be
11262306a36Sopenharmony_ci * opened, thereby indicating which namespaces will be used (notably, which
11362306a36Sopenharmony_ci * network namespace will be used for network filesystems).
11462306a36Sopenharmony_ci */
11562306a36Sopenharmony_ciSYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct file_system_type *fs_type;
11862306a36Sopenharmony_ci	struct fs_context *fc;
11962306a36Sopenharmony_ci	const char *fs_name;
12062306a36Sopenharmony_ci	int ret;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	if (!may_mount())
12362306a36Sopenharmony_ci		return -EPERM;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (flags & ~FSOPEN_CLOEXEC)
12662306a36Sopenharmony_ci		return -EINVAL;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	fs_name = strndup_user(_fs_name, PAGE_SIZE);
12962306a36Sopenharmony_ci	if (IS_ERR(fs_name))
13062306a36Sopenharmony_ci		return PTR_ERR(fs_name);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	fs_type = get_fs_type(fs_name);
13362306a36Sopenharmony_ci	kfree(fs_name);
13462306a36Sopenharmony_ci	if (!fs_type)
13562306a36Sopenharmony_ci		return -ENODEV;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	fc = fs_context_for_mount(fs_type, 0);
13862306a36Sopenharmony_ci	put_filesystem(fs_type);
13962306a36Sopenharmony_ci	if (IS_ERR(fc))
14062306a36Sopenharmony_ci		return PTR_ERR(fc);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	fc->phase = FS_CONTEXT_CREATE_PARAMS;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	ret = fscontext_alloc_log(fc);
14562306a36Sopenharmony_ci	if (ret < 0)
14662306a36Sopenharmony_ci		goto err_fc;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return fscontext_create_fd(fc, flags & FSOPEN_CLOEXEC ? O_CLOEXEC : 0);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cierr_fc:
15162306a36Sopenharmony_ci	put_fs_context(fc);
15262306a36Sopenharmony_ci	return ret;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/*
15662306a36Sopenharmony_ci * Pick a superblock into a context for reconfiguration.
15762306a36Sopenharmony_ci */
15862306a36Sopenharmony_ciSYSCALL_DEFINE3(fspick, int, dfd, const char __user *, path, unsigned int, flags)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct fs_context *fc;
16162306a36Sopenharmony_ci	struct path target;
16262306a36Sopenharmony_ci	unsigned int lookup_flags;
16362306a36Sopenharmony_ci	int ret;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (!may_mount())
16662306a36Sopenharmony_ci		return -EPERM;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if ((flags & ~(FSPICK_CLOEXEC |
16962306a36Sopenharmony_ci		       FSPICK_SYMLINK_NOFOLLOW |
17062306a36Sopenharmony_ci		       FSPICK_NO_AUTOMOUNT |
17162306a36Sopenharmony_ci		       FSPICK_EMPTY_PATH)) != 0)
17262306a36Sopenharmony_ci		return -EINVAL;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
17562306a36Sopenharmony_ci	if (flags & FSPICK_SYMLINK_NOFOLLOW)
17662306a36Sopenharmony_ci		lookup_flags &= ~LOOKUP_FOLLOW;
17762306a36Sopenharmony_ci	if (flags & FSPICK_NO_AUTOMOUNT)
17862306a36Sopenharmony_ci		lookup_flags &= ~LOOKUP_AUTOMOUNT;
17962306a36Sopenharmony_ci	if (flags & FSPICK_EMPTY_PATH)
18062306a36Sopenharmony_ci		lookup_flags |= LOOKUP_EMPTY;
18162306a36Sopenharmony_ci	ret = user_path_at(dfd, path, lookup_flags, &target);
18262306a36Sopenharmony_ci	if (ret < 0)
18362306a36Sopenharmony_ci		goto err;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	ret = -EINVAL;
18662306a36Sopenharmony_ci	if (target.mnt->mnt_root != target.dentry)
18762306a36Sopenharmony_ci		goto err_path;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	fc = fs_context_for_reconfigure(target.dentry, 0, 0);
19062306a36Sopenharmony_ci	if (IS_ERR(fc)) {
19162306a36Sopenharmony_ci		ret = PTR_ERR(fc);
19262306a36Sopenharmony_ci		goto err_path;
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	fc->phase = FS_CONTEXT_RECONF_PARAMS;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	ret = fscontext_alloc_log(fc);
19862306a36Sopenharmony_ci	if (ret < 0)
19962306a36Sopenharmony_ci		goto err_fc;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	path_put(&target);
20262306a36Sopenharmony_ci	return fscontext_create_fd(fc, flags & FSPICK_CLOEXEC ? O_CLOEXEC : 0);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cierr_fc:
20562306a36Sopenharmony_ci	put_fs_context(fc);
20662306a36Sopenharmony_cierr_path:
20762306a36Sopenharmony_ci	path_put(&target);
20862306a36Sopenharmony_cierr:
20962306a36Sopenharmony_ci	return ret;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic int vfs_cmd_create(struct fs_context *fc, bool exclusive)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct super_block *sb;
21562306a36Sopenharmony_ci	int ret;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	if (fc->phase != FS_CONTEXT_CREATE_PARAMS)
21862306a36Sopenharmony_ci		return -EBUSY;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	if (!mount_capable(fc))
22162306a36Sopenharmony_ci		return -EPERM;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	/* require the new mount api */
22462306a36Sopenharmony_ci	if (exclusive && fc->ops == &legacy_fs_context_ops)
22562306a36Sopenharmony_ci		return -EOPNOTSUPP;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	fc->phase = FS_CONTEXT_CREATING;
22862306a36Sopenharmony_ci	fc->exclusive = exclusive;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ret = vfs_get_tree(fc);
23162306a36Sopenharmony_ci	if (ret) {
23262306a36Sopenharmony_ci		fc->phase = FS_CONTEXT_FAILED;
23362306a36Sopenharmony_ci		return ret;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	sb = fc->root->d_sb;
23762306a36Sopenharmony_ci	ret = security_sb_kern_mount(sb);
23862306a36Sopenharmony_ci	if (unlikely(ret)) {
23962306a36Sopenharmony_ci		fc_drop_locked(fc);
24062306a36Sopenharmony_ci		fc->phase = FS_CONTEXT_FAILED;
24162306a36Sopenharmony_ci		return ret;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	/* vfs_get_tree() callchains will have grabbed @s_umount */
24562306a36Sopenharmony_ci	up_write(&sb->s_umount);
24662306a36Sopenharmony_ci	fc->phase = FS_CONTEXT_AWAITING_MOUNT;
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int vfs_cmd_reconfigure(struct fs_context *fc)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct super_block *sb;
25362306a36Sopenharmony_ci	int ret;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	if (fc->phase != FS_CONTEXT_RECONF_PARAMS)
25662306a36Sopenharmony_ci		return -EBUSY;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	fc->phase = FS_CONTEXT_RECONFIGURING;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	sb = fc->root->d_sb;
26162306a36Sopenharmony_ci	if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) {
26262306a36Sopenharmony_ci		fc->phase = FS_CONTEXT_FAILED;
26362306a36Sopenharmony_ci		return -EPERM;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	down_write(&sb->s_umount);
26762306a36Sopenharmony_ci	ret = reconfigure_super(fc);
26862306a36Sopenharmony_ci	up_write(&sb->s_umount);
26962306a36Sopenharmony_ci	if (ret) {
27062306a36Sopenharmony_ci		fc->phase = FS_CONTEXT_FAILED;
27162306a36Sopenharmony_ci		return ret;
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	vfs_clean_context(fc);
27562306a36Sopenharmony_ci	return 0;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci/*
27962306a36Sopenharmony_ci * Check the state and apply the configuration.  Note that this function is
28062306a36Sopenharmony_ci * allowed to 'steal' the value by setting param->xxx to NULL before returning.
28162306a36Sopenharmony_ci */
28262306a36Sopenharmony_cistatic int vfs_fsconfig_locked(struct fs_context *fc, int cmd,
28362306a36Sopenharmony_ci			       struct fs_parameter *param)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	int ret;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	ret = finish_clean_context(fc);
28862306a36Sopenharmony_ci	if (ret)
28962306a36Sopenharmony_ci		return ret;
29062306a36Sopenharmony_ci	switch (cmd) {
29162306a36Sopenharmony_ci	case FSCONFIG_CMD_CREATE:
29262306a36Sopenharmony_ci		return vfs_cmd_create(fc, false);
29362306a36Sopenharmony_ci	case FSCONFIG_CMD_CREATE_EXCL:
29462306a36Sopenharmony_ci		return vfs_cmd_create(fc, true);
29562306a36Sopenharmony_ci	case FSCONFIG_CMD_RECONFIGURE:
29662306a36Sopenharmony_ci		return vfs_cmd_reconfigure(fc);
29762306a36Sopenharmony_ci	default:
29862306a36Sopenharmony_ci		if (fc->phase != FS_CONTEXT_CREATE_PARAMS &&
29962306a36Sopenharmony_ci		    fc->phase != FS_CONTEXT_RECONF_PARAMS)
30062306a36Sopenharmony_ci			return -EBUSY;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci		return vfs_parse_fs_param(fc, param);
30362306a36Sopenharmony_ci	}
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci/**
30762306a36Sopenharmony_ci * sys_fsconfig - Set parameters and trigger actions on a context
30862306a36Sopenharmony_ci * @fd: The filesystem context to act upon
30962306a36Sopenharmony_ci * @cmd: The action to take
31062306a36Sopenharmony_ci * @_key: Where appropriate, the parameter key to set
31162306a36Sopenharmony_ci * @_value: Where appropriate, the parameter value to set
31262306a36Sopenharmony_ci * @aux: Additional information for the value
31362306a36Sopenharmony_ci *
31462306a36Sopenharmony_ci * This system call is used to set parameters on a context, including
31562306a36Sopenharmony_ci * superblock settings, data source and security labelling.
31662306a36Sopenharmony_ci *
31762306a36Sopenharmony_ci * Actions include triggering the creation of a superblock and the
31862306a36Sopenharmony_ci * reconfiguration of the superblock attached to the specified context.
31962306a36Sopenharmony_ci *
32062306a36Sopenharmony_ci * When setting a parameter, @cmd indicates the type of value being proposed
32162306a36Sopenharmony_ci * and @_key indicates the parameter to be altered.
32262306a36Sopenharmony_ci *
32362306a36Sopenharmony_ci * @_value and @aux are used to specify the value, should a value be required:
32462306a36Sopenharmony_ci *
32562306a36Sopenharmony_ci * (*) fsconfig_set_flag: No value is specified.  The parameter must be boolean
32662306a36Sopenharmony_ci *     in nature.  The key may be prefixed with "no" to invert the
32762306a36Sopenharmony_ci *     setting. @_value must be NULL and @aux must be 0.
32862306a36Sopenharmony_ci *
32962306a36Sopenharmony_ci * (*) fsconfig_set_string: A string value is specified.  The parameter can be
33062306a36Sopenharmony_ci *     expecting boolean, integer, string or take a path.  A conversion to an
33162306a36Sopenharmony_ci *     appropriate type will be attempted (which may include looking up as a
33262306a36Sopenharmony_ci *     path).  @_value points to a NUL-terminated string and @aux must be 0.
33362306a36Sopenharmony_ci *
33462306a36Sopenharmony_ci * (*) fsconfig_set_binary: A binary blob is specified.  @_value points to the
33562306a36Sopenharmony_ci *     blob and @aux indicates its size.  The parameter must be expecting a
33662306a36Sopenharmony_ci *     blob.
33762306a36Sopenharmony_ci *
33862306a36Sopenharmony_ci * (*) fsconfig_set_path: A non-empty path is specified.  The parameter must be
33962306a36Sopenharmony_ci *     expecting a path object.  @_value points to a NUL-terminated string that
34062306a36Sopenharmony_ci *     is the path and @aux is a file descriptor at which to start a relative
34162306a36Sopenharmony_ci *     lookup or AT_FDCWD.
34262306a36Sopenharmony_ci *
34362306a36Sopenharmony_ci * (*) fsconfig_set_path_empty: As fsconfig_set_path, but with AT_EMPTY_PATH
34462306a36Sopenharmony_ci *     implied.
34562306a36Sopenharmony_ci *
34662306a36Sopenharmony_ci * (*) fsconfig_set_fd: An open file descriptor is specified.  @_value must be
34762306a36Sopenharmony_ci *     NULL and @aux indicates the file descriptor.
34862306a36Sopenharmony_ci */
34962306a36Sopenharmony_ciSYSCALL_DEFINE5(fsconfig,
35062306a36Sopenharmony_ci		int, fd,
35162306a36Sopenharmony_ci		unsigned int, cmd,
35262306a36Sopenharmony_ci		const char __user *, _key,
35362306a36Sopenharmony_ci		const void __user *, _value,
35462306a36Sopenharmony_ci		int, aux)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	struct fs_context *fc;
35762306a36Sopenharmony_ci	struct fd f;
35862306a36Sopenharmony_ci	int ret;
35962306a36Sopenharmony_ci	int lookup_flags = 0;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	struct fs_parameter param = {
36262306a36Sopenharmony_ci		.type	= fs_value_is_undefined,
36362306a36Sopenharmony_ci	};
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	if (fd < 0)
36662306a36Sopenharmony_ci		return -EINVAL;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	switch (cmd) {
36962306a36Sopenharmony_ci	case FSCONFIG_SET_FLAG:
37062306a36Sopenharmony_ci		if (!_key || _value || aux)
37162306a36Sopenharmony_ci			return -EINVAL;
37262306a36Sopenharmony_ci		break;
37362306a36Sopenharmony_ci	case FSCONFIG_SET_STRING:
37462306a36Sopenharmony_ci		if (!_key || !_value || aux)
37562306a36Sopenharmony_ci			return -EINVAL;
37662306a36Sopenharmony_ci		break;
37762306a36Sopenharmony_ci	case FSCONFIG_SET_BINARY:
37862306a36Sopenharmony_ci		if (!_key || !_value || aux <= 0 || aux > 1024 * 1024)
37962306a36Sopenharmony_ci			return -EINVAL;
38062306a36Sopenharmony_ci		break;
38162306a36Sopenharmony_ci	case FSCONFIG_SET_PATH:
38262306a36Sopenharmony_ci	case FSCONFIG_SET_PATH_EMPTY:
38362306a36Sopenharmony_ci		if (!_key || !_value || (aux != AT_FDCWD && aux < 0))
38462306a36Sopenharmony_ci			return -EINVAL;
38562306a36Sopenharmony_ci		break;
38662306a36Sopenharmony_ci	case FSCONFIG_SET_FD:
38762306a36Sopenharmony_ci		if (!_key || _value || aux < 0)
38862306a36Sopenharmony_ci			return -EINVAL;
38962306a36Sopenharmony_ci		break;
39062306a36Sopenharmony_ci	case FSCONFIG_CMD_CREATE:
39162306a36Sopenharmony_ci	case FSCONFIG_CMD_CREATE_EXCL:
39262306a36Sopenharmony_ci	case FSCONFIG_CMD_RECONFIGURE:
39362306a36Sopenharmony_ci		if (_key || _value || aux)
39462306a36Sopenharmony_ci			return -EINVAL;
39562306a36Sopenharmony_ci		break;
39662306a36Sopenharmony_ci	default:
39762306a36Sopenharmony_ci		return -EOPNOTSUPP;
39862306a36Sopenharmony_ci	}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	f = fdget(fd);
40162306a36Sopenharmony_ci	if (!f.file)
40262306a36Sopenharmony_ci		return -EBADF;
40362306a36Sopenharmony_ci	ret = -EINVAL;
40462306a36Sopenharmony_ci	if (f.file->f_op != &fscontext_fops)
40562306a36Sopenharmony_ci		goto out_f;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	fc = f.file->private_data;
40862306a36Sopenharmony_ci	if (fc->ops == &legacy_fs_context_ops) {
40962306a36Sopenharmony_ci		switch (cmd) {
41062306a36Sopenharmony_ci		case FSCONFIG_SET_BINARY:
41162306a36Sopenharmony_ci		case FSCONFIG_SET_PATH:
41262306a36Sopenharmony_ci		case FSCONFIG_SET_PATH_EMPTY:
41362306a36Sopenharmony_ci		case FSCONFIG_SET_FD:
41462306a36Sopenharmony_ci			ret = -EOPNOTSUPP;
41562306a36Sopenharmony_ci			goto out_f;
41662306a36Sopenharmony_ci		}
41762306a36Sopenharmony_ci	}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	if (_key) {
42062306a36Sopenharmony_ci		param.key = strndup_user(_key, 256);
42162306a36Sopenharmony_ci		if (IS_ERR(param.key)) {
42262306a36Sopenharmony_ci			ret = PTR_ERR(param.key);
42362306a36Sopenharmony_ci			goto out_f;
42462306a36Sopenharmony_ci		}
42562306a36Sopenharmony_ci	}
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	switch (cmd) {
42862306a36Sopenharmony_ci	case FSCONFIG_SET_FLAG:
42962306a36Sopenharmony_ci		param.type = fs_value_is_flag;
43062306a36Sopenharmony_ci		break;
43162306a36Sopenharmony_ci	case FSCONFIG_SET_STRING:
43262306a36Sopenharmony_ci		param.type = fs_value_is_string;
43362306a36Sopenharmony_ci		param.string = strndup_user(_value, 256);
43462306a36Sopenharmony_ci		if (IS_ERR(param.string)) {
43562306a36Sopenharmony_ci			ret = PTR_ERR(param.string);
43662306a36Sopenharmony_ci			goto out_key;
43762306a36Sopenharmony_ci		}
43862306a36Sopenharmony_ci		param.size = strlen(param.string);
43962306a36Sopenharmony_ci		break;
44062306a36Sopenharmony_ci	case FSCONFIG_SET_BINARY:
44162306a36Sopenharmony_ci		param.type = fs_value_is_blob;
44262306a36Sopenharmony_ci		param.size = aux;
44362306a36Sopenharmony_ci		param.blob = memdup_user_nul(_value, aux);
44462306a36Sopenharmony_ci		if (IS_ERR(param.blob)) {
44562306a36Sopenharmony_ci			ret = PTR_ERR(param.blob);
44662306a36Sopenharmony_ci			goto out_key;
44762306a36Sopenharmony_ci		}
44862306a36Sopenharmony_ci		break;
44962306a36Sopenharmony_ci	case FSCONFIG_SET_PATH_EMPTY:
45062306a36Sopenharmony_ci		lookup_flags = LOOKUP_EMPTY;
45162306a36Sopenharmony_ci		fallthrough;
45262306a36Sopenharmony_ci	case FSCONFIG_SET_PATH:
45362306a36Sopenharmony_ci		param.type = fs_value_is_filename;
45462306a36Sopenharmony_ci		param.name = getname_flags(_value, lookup_flags, NULL);
45562306a36Sopenharmony_ci		if (IS_ERR(param.name)) {
45662306a36Sopenharmony_ci			ret = PTR_ERR(param.name);
45762306a36Sopenharmony_ci			goto out_key;
45862306a36Sopenharmony_ci		}
45962306a36Sopenharmony_ci		param.dirfd = aux;
46062306a36Sopenharmony_ci		param.size = strlen(param.name->name);
46162306a36Sopenharmony_ci		break;
46262306a36Sopenharmony_ci	case FSCONFIG_SET_FD:
46362306a36Sopenharmony_ci		param.type = fs_value_is_file;
46462306a36Sopenharmony_ci		ret = -EBADF;
46562306a36Sopenharmony_ci		param.file = fget(aux);
46662306a36Sopenharmony_ci		if (!param.file)
46762306a36Sopenharmony_ci			goto out_key;
46862306a36Sopenharmony_ci		break;
46962306a36Sopenharmony_ci	default:
47062306a36Sopenharmony_ci		break;
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	ret = mutex_lock_interruptible(&fc->uapi_mutex);
47462306a36Sopenharmony_ci	if (ret == 0) {
47562306a36Sopenharmony_ci		ret = vfs_fsconfig_locked(fc, cmd, &param);
47662306a36Sopenharmony_ci		mutex_unlock(&fc->uapi_mutex);
47762306a36Sopenharmony_ci	}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	/* Clean up the our record of any value that we obtained from
48062306a36Sopenharmony_ci	 * userspace.  Note that the value may have been stolen by the LSM or
48162306a36Sopenharmony_ci	 * filesystem, in which case the value pointer will have been cleared.
48262306a36Sopenharmony_ci	 */
48362306a36Sopenharmony_ci	switch (cmd) {
48462306a36Sopenharmony_ci	case FSCONFIG_SET_STRING:
48562306a36Sopenharmony_ci	case FSCONFIG_SET_BINARY:
48662306a36Sopenharmony_ci		kfree(param.string);
48762306a36Sopenharmony_ci		break;
48862306a36Sopenharmony_ci	case FSCONFIG_SET_PATH:
48962306a36Sopenharmony_ci	case FSCONFIG_SET_PATH_EMPTY:
49062306a36Sopenharmony_ci		if (param.name)
49162306a36Sopenharmony_ci			putname(param.name);
49262306a36Sopenharmony_ci		break;
49362306a36Sopenharmony_ci	case FSCONFIG_SET_FD:
49462306a36Sopenharmony_ci		if (param.file)
49562306a36Sopenharmony_ci			fput(param.file);
49662306a36Sopenharmony_ci		break;
49762306a36Sopenharmony_ci	default:
49862306a36Sopenharmony_ci		break;
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ciout_key:
50162306a36Sopenharmony_ci	kfree(param.key);
50262306a36Sopenharmony_ciout_f:
50362306a36Sopenharmony_ci	fdput(f);
50462306a36Sopenharmony_ci	return ret;
50562306a36Sopenharmony_ci}
506