// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2020 Google LLC
 */

/*
 * fs-verity integration into incfs
 *
 * Since incfs has its own merkle tree implementation, most of fs-verity code
 * is not needed. The key part that is needed is the signature check, since
 * that is based on the private /proc/sys/fs/verity/require_signatures value
 * and a private keyring. Thus the first change is to modify verity code to
 * export a version of fsverity_verify_signature.
 *
 * fs-verity integration then consists of the following modifications:
 *
 * 1. Add the (optional) verity signature to the incfs file format
 * 2. Add a pointer to the digest of the fs-verity descriptor struct to the
 *    data_file struct that incfs attaches to each file inode.
 * 3. Add the following ioclts:
 *  - FS_IOC_ENABLE_VERITY
 *  - FS_IOC_GETFLAGS
 *  - FS_IOC_MEASURE_VERITY
 * 4. When FS_IOC_ENABLE_VERITY is called on a non-verity file, the
 *    fs-verity descriptor struct is populated and digested. If it passes the
 *    signature check or the signature is NULL and
 *    fs.verity.require_signatures=0, then the S_VERITY flag is set and the
 *    xattr incfs.verity is set. If the signature is non-NULL, an
 *    INCFS_MD_VERITY_SIGNATURE is added to the backing file containing the
 *    signature.
 * 5. When a file with an incfs.verity xattr's inode is initialized, the
 *    inode’s S_VERITY flag is set.
 * 6. When a file with the S_VERITY flag set on its inode is opened, the
 *    data_file is checked for its verity digest. If the file doesn’t have a
 *    digest, the file’s digest is calculated as above, checked, and set, or the
 *    open is denied if it is not valid.
 * 7. FS_IOC_GETFLAGS simply returns the value of the S_VERITY flag
 * 8. FS_IOC_MEASURE_VERITY simply returns the cached digest
 * 9. The final complication is that if FS_IOC_ENABLE_VERITY is called on a file
 *    which doesn’t have a merkle tree, the merkle tree is calculated before the
 *    rest of the process is completed.
 */

#include <crypto/hash.h>
#include <crypto/sha.h>
#include <linux/fsverity.h>
#include <linux/mount.h>

#include "verity.h"

#include "data_mgmt.h"
#include "format.h"
#include "integrity.h"
#include "vfs.h"

#define FS_VERITY_MAX_SIGNATURE_SIZE	16128

static int incfs_get_root_hash(struct file *filp, u8 *root_hash)
{
	struct data_file *df = get_incfs_data_file(filp);

	if (!df)
		return -EINVAL;

	memcpy(root_hash, df->df_hash_tree->root_hash,
	       df->df_hash_tree->alg->digest_size);

	return 0;
}

static int incfs_end_enable_verity(struct file *filp, u8 *sig, size_t sig_size)
{
	struct inode *inode = file_inode(filp);
	struct mem_range signature = {
		.data = sig,
		.len = sig_size,
	};
	struct data_file *df = get_incfs_data_file(filp);
	struct backing_file_context *bfc;
	int error;
	struct incfs_df_verity_signature *vs = NULL;
	loff_t offset;

	if (!df || !df->df_backing_file_context)
		return -EFSCORRUPTED;

	if (sig) {
		vs = kzalloc(sizeof(*vs), GFP_NOFS);
		if (!vs)
			return -ENOMEM;
	}

	bfc = df->df_backing_file_context;
	error = mutex_lock_interruptible(&bfc->bc_mutex);
	if (error)
		goto out;

	error = incfs_write_verity_signature_to_backing_file(bfc, signature,
							     &offset);
	mutex_unlock(&bfc->bc_mutex);
	if (error)
		goto out;

	/*
	 * Set verity xattr so we can set S_VERITY without opening backing file
	 */
	error = vfs_setxattr(bfc->bc_file->f_path.dentry,
			     INCFS_XATTR_VERITY_NAME, NULL, 0, XATTR_CREATE);
	if (error) {
		pr_warn("incfs: error setting verity xattr: %d\n", error);
		goto out;
	}

	if (sig) {
		*vs = (struct incfs_df_verity_signature) {
			.size = signature.len,
			.offset = offset,
		};

		df->df_verity_signature = vs;
		vs = NULL;
	}

	inode_set_flags(inode, S_VERITY, S_VERITY);

out:
	kfree(vs);
	return error;
}

static int incfs_compute_file_digest(struct incfs_hash_alg *alg,
				struct fsverity_descriptor *desc,
				u8 *digest)
{
	SHASH_DESC_ON_STACK(d, alg->shash);

	d->tfm = alg->shash;
	return crypto_shash_digest(d, (u8 *)desc, sizeof(*desc), digest);
}

static enum incfs_hash_tree_algorithm incfs_convert_fsverity_hash_alg(
								int hash_alg)
{
	switch (hash_alg) {
	case FS_VERITY_HASH_ALG_SHA256:
		return INCFS_HASH_TREE_SHA256;
	default:
		return -EINVAL;
	}
}

static struct mem_range incfs_get_verity_digest(struct inode *inode)
{
	struct inode_info *node = get_incfs_node(inode);
	struct data_file *df;
	struct mem_range verity_file_digest;

	if (!node) {
		pr_warn("Invalid inode\n");
		return range(NULL, 0);
	}

	df = node->n_file;

	/*
	 * Pairs with the cmpxchg_release() in incfs_set_verity_digest().
	 * I.e., another task may publish ->df_verity_file_digest concurrently,
	 * executing a RELEASE barrier.  We need to use smp_load_acquire() here
	 * to safely ACQUIRE the memory the other task published.
	 */
	verity_file_digest.data = smp_load_acquire(
					&df->df_verity_file_digest.data);
	verity_file_digest.len = df->df_verity_file_digest.len;
	return verity_file_digest;
}

static void incfs_set_verity_digest(struct inode *inode,
				     struct mem_range verity_file_digest)
{
	struct inode_info *node = get_incfs_node(inode);
	struct data_file *df;

	if (!node) {
		pr_warn("Invalid inode\n");
		kfree(verity_file_digest.data);
		return;
	}

	df = node->n_file;
	df->df_verity_file_digest.len = verity_file_digest.len;

	/*
	 * Multiple tasks may race to set ->df_verity_file_digest.data, so use
	 * cmpxchg_release().  This pairs with the smp_load_acquire() in
	 * incfs_get_verity_digest().  I.e., here we publish
	 * ->df_verity_file_digest.data, with a RELEASE barrier so that other
	 * tasks can ACQUIRE it.
	 */
	if (cmpxchg_release(&df->df_verity_file_digest.data, NULL,
			    verity_file_digest.data) != NULL)
		/* Lost the race, so free the file_digest we allocated. */
		kfree(verity_file_digest.data);
}

/*
 * Calculate the digest of the fsverity_descriptor. The signature (if present)
 * is also checked.
 */
static struct mem_range incfs_calc_verity_digest_from_desc(
					const struct inode *inode,
					struct fsverity_descriptor *desc,
					u8 *signature, size_t sig_size)
{
	enum incfs_hash_tree_algorithm incfs_hash_alg;
	struct mem_range verity_file_digest;
	int err;
	struct incfs_hash_alg *hash_alg;

	incfs_hash_alg = incfs_convert_fsverity_hash_alg(desc->hash_algorithm);
	if (incfs_hash_alg < 0)
		return range(ERR_PTR(incfs_hash_alg), 0);

	hash_alg = incfs_get_hash_alg(incfs_hash_alg);
	if (IS_ERR(hash_alg))
		return range((u8 *)hash_alg, 0);

	verity_file_digest = range(kzalloc(hash_alg->digest_size, GFP_KERNEL),
				   hash_alg->digest_size);
	if (!verity_file_digest.data)
		return range(ERR_PTR(-ENOMEM), 0);

	err = incfs_compute_file_digest(hash_alg, desc,
					verity_file_digest.data);
	if (err) {
		pr_err("Error %d computing file digest", err);
		goto out;
	}
	pr_debug("Computed file digest: %s:%*phN\n",
		 hash_alg->name, (int) verity_file_digest.len,
		 verity_file_digest.data);

	err = __fsverity_verify_signature(inode, signature, sig_size,
					  verity_file_digest.data,
					  desc->hash_algorithm);
out:
	if (err) {
		kfree(verity_file_digest.data);
		verity_file_digest = range(ERR_PTR(err), 0);
	}
	return verity_file_digest;
}

static struct fsverity_descriptor *incfs_get_fsverity_descriptor(
					struct file *filp, int hash_algorithm)
{
	struct inode *inode = file_inode(filp);
	struct fsverity_descriptor *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
	int err;

	if (!desc)
		return ERR_PTR(-ENOMEM);

	*desc = (struct fsverity_descriptor) {
		.version = 1,
		.hash_algorithm = hash_algorithm,
		.log_blocksize = ilog2(INCFS_DATA_FILE_BLOCK_SIZE),
		.data_size = cpu_to_le64(inode->i_size),
	};

	err = incfs_get_root_hash(filp, desc->root_hash);
	if (err) {
		kfree(desc);
		return ERR_PTR(err);
	}

	return desc;
}

static struct mem_range incfs_calc_verity_digest(
					struct inode *inode, struct file *filp,
					u8 *signature, size_t signature_size,
					int hash_algorithm)
{
	struct fsverity_descriptor *desc = incfs_get_fsverity_descriptor(filp,
							hash_algorithm);
	struct mem_range verity_file_digest;

	if (IS_ERR(desc))
		return range((u8 *)desc, 0);
	verity_file_digest = incfs_calc_verity_digest_from_desc(inode, desc,
						signature, signature_size);
	kfree(desc);
	return verity_file_digest;
}

static int incfs_build_merkle_tree(struct file *f, struct data_file *df,
			     struct backing_file_context *bfc,
			     struct mtree *hash_tree, loff_t hash_offset,
			     struct incfs_hash_alg *alg, struct mem_range hash)
{
	int error = 0;
	int limit, lvl, i, result;
	struct mem_range buf = {.len = INCFS_DATA_FILE_BLOCK_SIZE};
	struct mem_range tmp = {.len = 2 * INCFS_DATA_FILE_BLOCK_SIZE};

	buf.data = (u8 *)__get_free_pages(GFP_NOFS, get_order(buf.len));
	tmp.data = (u8 *)__get_free_pages(GFP_NOFS, get_order(tmp.len));
	if (!buf.data || !tmp.data) {
		error = -ENOMEM;
		goto out;
	}

	/*
	 * lvl - 1 is the level we are reading, lvl the level we are writing
	 * lvl == -1 means actual blocks
	 * lvl == hash_tree->depth means root hash
	 */
	limit = df->df_data_block_count;
	for (lvl = 0; lvl <= hash_tree->depth; lvl++) {
		for (i = 0; i < limit; ++i) {
			loff_t hash_level_offset;
			struct mem_range partial_buf = buf;

			if (lvl == 0)
				result = incfs_read_data_file_block(partial_buf,
						f, i, tmp, NULL, NULL);
			else {
				hash_level_offset = hash_offset +
				       hash_tree->hash_level_suboffset[lvl - 1];

				result = incfs_kread(bfc, partial_buf.data,
						partial_buf.len,
						hash_level_offset + i *
						INCFS_DATA_FILE_BLOCK_SIZE);
			}

			if (result < 0) {
				error = result;
				goto out;
			}

			partial_buf.len = result;
			error = incfs_calc_digest(alg, partial_buf, hash);
			if (error)
				goto out;

			/*
			 * last level - only one hash to take and it is stored
			 * in the incfs signature record
			 */
			if (lvl == hash_tree->depth)
				break;

			hash_level_offset = hash_offset +
				hash_tree->hash_level_suboffset[lvl];

			result = incfs_kwrite(bfc, hash.data, hash.len,
					hash_level_offset + hash.len * i);

			if (result < 0) {
				error = result;
				goto out;
			}

			if (result != hash.len) {
				error = -EIO;
				goto out;
			}
		}
		limit = DIV_ROUND_UP(limit,
				     INCFS_DATA_FILE_BLOCK_SIZE / hash.len);
	}

out:
	free_pages((unsigned long)tmp.data, get_order(tmp.len));
	free_pages((unsigned long)buf.data, get_order(buf.len));
	return error;
}

/*
 * incfs files have a signature record that is separate from the
 * verity_signature record. The signature record does not actually contain a
 * signature, rather it contains the size/offset of the hash tree, and a binary
 * blob which contains the root hash and potentially a signature.
 *
 * If the file was created with a signature record, then this function simply
 * returns.
 *
 * Otherwise it will create a signature record with a minimal binary blob as
 * defined by the structure below, create space for the hash tree and then
 * populate it using incfs_build_merkle_tree
 */
static int incfs_add_signature_record(struct file *f)
{
	/* See incfs_parse_signature */
	struct {
		__le32 version;
		__le32 size_of_hash_info_section;
		struct {
			__le32 hash_algorithm;
			u8 log2_blocksize;
			__le32 salt_size;
			u8 salt[0];
			__le32 hash_size;
			u8 root_hash[32];
		} __packed hash_section;
		__le32 size_of_signing_info_section;
		u8 signing_info_section[0];
	} __packed sig = {
		.version = cpu_to_le32(INCFS_SIGNATURE_VERSION),
		.size_of_hash_info_section =
			cpu_to_le32(sizeof(sig.hash_section)),
		.hash_section = {
			.hash_algorithm = cpu_to_le32(INCFS_HASH_TREE_SHA256),
			.log2_blocksize = ilog2(INCFS_DATA_FILE_BLOCK_SIZE),
			.hash_size = cpu_to_le32(SHA256_DIGEST_SIZE),
		},
	};

	struct data_file *df = get_incfs_data_file(f);
	struct mtree *hash_tree = NULL;
	struct backing_file_context *bfc;
	int error;
	loff_t hash_offset, sig_offset;
	struct incfs_hash_alg *alg = incfs_get_hash_alg(INCFS_HASH_TREE_SHA256);
	u8 hash_buf[INCFS_MAX_HASH_SIZE];
	int hash_size = alg->digest_size;
	struct mem_range hash = range(hash_buf, hash_size);
	int result;
	struct incfs_df_signature *signature = NULL;

	if (!df)
		return -EINVAL;

	if (df->df_header_flags & INCFS_FILE_MAPPED)
		return -EINVAL;

	/* Already signed? */
	if (df->df_signature && df->df_hash_tree)
		return 0;

	if (df->df_signature || df->df_hash_tree)
		return -EFSCORRUPTED;

	/* Add signature metadata record to file */
	hash_tree = incfs_alloc_mtree(range((u8 *)&sig, sizeof(sig)),
				      df->df_data_block_count);
	if (IS_ERR(hash_tree))
		return PTR_ERR(hash_tree);

	bfc = df->df_backing_file_context;
	if (!bfc) {
		error = -EFSCORRUPTED;
		goto out;
	}

	error = mutex_lock_interruptible(&bfc->bc_mutex);
	if (error)
		goto out;

	error = incfs_write_signature_to_backing_file(bfc,
				range((u8 *)&sig, sizeof(sig)),
				hash_tree->hash_tree_area_size,
				&hash_offset, &sig_offset);
	mutex_unlock(&bfc->bc_mutex);
	if (error)
		goto out;

	/* Populate merkle tree */
	error = incfs_build_merkle_tree(f, df, bfc, hash_tree, hash_offset, alg,
				  hash);
	if (error)
		goto out;

	/* Update signature metadata record */
	memcpy(sig.hash_section.root_hash, hash.data, alg->digest_size);
	result = incfs_kwrite(bfc, &sig, sizeof(sig), sig_offset);
	if (result < 0) {
		error = result;
		goto out;
	}

	if (result != sizeof(sig)) {
		error = -EIO;
		goto out;
	}

	/* Update in-memory records */
	memcpy(hash_tree->root_hash, hash.data, alg->digest_size);
	signature = kzalloc(sizeof(*signature), GFP_NOFS);
	if (!signature) {
		error = -ENOMEM;
		goto out;
	}
	*signature = (struct incfs_df_signature) {
		.hash_offset = hash_offset,
		.hash_size = hash_tree->hash_tree_area_size,
		.sig_offset = sig_offset,
		.sig_size = sizeof(sig),
	};
	df->df_signature = signature;
	signature = NULL;

	/*
	 * Use memory barrier to prevent readpage seeing the hash tree until
	 * it's fully there
	 */
	smp_store_release(&df->df_hash_tree, hash_tree);
	hash_tree = NULL;

out:
	kfree(signature);
	kfree(hash_tree);
	return error;
}

static int incfs_enable_verity(struct file *filp,
			 const struct fsverity_enable_arg *arg)
{
	struct inode *inode = file_inode(filp);
	struct data_file *df = get_incfs_data_file(filp);
	u8 *signature = NULL;
	struct mem_range verity_file_digest = range(NULL, 0);
	int err;

	if (!df)
		return -EFSCORRUPTED;

	err = mutex_lock_interruptible(&df->df_enable_verity);
	if (err)
		return err;

	if (IS_VERITY(inode)) {
		err = -EEXIST;
		goto out;
	}

	err = incfs_add_signature_record(filp);
	if (err)
		goto out;

	/* Get the signature if the user provided one */
	if (arg->sig_size) {
		signature = memdup_user(u64_to_user_ptr(arg->sig_ptr),
					arg->sig_size);
		if (IS_ERR(signature)) {
			err = PTR_ERR(signature);
			signature = NULL;
			goto out;
		}
	}

	verity_file_digest = incfs_calc_verity_digest(inode, filp, signature,
					arg->sig_size, arg->hash_algorithm);
	if (IS_ERR(verity_file_digest.data)) {
		err = PTR_ERR(verity_file_digest.data);
		verity_file_digest.data = NULL;
		goto out;
	}

	err = incfs_end_enable_verity(filp, signature, arg->sig_size);
	if (err)
		goto out;

	/* Successfully enabled verity */
	incfs_set_verity_digest(inode, verity_file_digest);
	verity_file_digest.data = NULL;
out:
	mutex_unlock(&df->df_enable_verity);
	kfree(signature);
	kfree(verity_file_digest.data);
	if (err)
		pr_err("%s failed with err %d\n", __func__, err);
	return err;
}

int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg)
{
	struct inode *inode = file_inode(filp);
	struct fsverity_enable_arg arg;

	if (copy_from_user(&arg, uarg, sizeof(arg)))
		return -EFAULT;

	if (arg.version != 1)
		return -EINVAL;

	if (arg.__reserved1 ||
	    memchr_inv(arg.__reserved2, 0, sizeof(arg.__reserved2)))
		return -EINVAL;

	if (arg.hash_algorithm != FS_VERITY_HASH_ALG_SHA256)
		return -EINVAL;

	if (arg.block_size != PAGE_SIZE)
		return -EINVAL;

	if (arg.salt_size)
		return -EINVAL;

	if (arg.sig_size > FS_VERITY_MAX_SIGNATURE_SIZE)
		return -EMSGSIZE;

	if (S_ISDIR(inode->i_mode))
		return -EISDIR;

	if (!S_ISREG(inode->i_mode))
		return -EINVAL;

	return incfs_enable_verity(filp, &arg);
}

static u8 *incfs_get_verity_signature(struct file *filp, size_t *sig_size)
{
	struct data_file *df = get_incfs_data_file(filp);
	struct incfs_df_verity_signature *vs;
	u8 *signature;
	int res;

	if (!df || !df->df_backing_file_context)
		return ERR_PTR(-EFSCORRUPTED);

	vs = df->df_verity_signature;
	if (!vs) {
		*sig_size = 0;
		return NULL;
	}

	if (!vs->size) {
		*sig_size = 0;
		return ERR_PTR(-EFSCORRUPTED);
	}

	signature = kzalloc(vs->size, GFP_KERNEL);
	if (!signature)
		return ERR_PTR(-ENOMEM);

	res = incfs_kread(df->df_backing_file_context,
			  signature, vs->size, vs->offset);

	if (res < 0)
		goto err_out;

	if (res != vs->size) {
		res = -EINVAL;
		goto err_out;
	}

	*sig_size = vs->size;
	return signature;

err_out:
	kfree(signature);
	return ERR_PTR(res);
}

/* Ensure data_file->df_verity_file_digest is populated */
static int ensure_verity_info(struct inode *inode, struct file *filp)
{
	struct mem_range verity_file_digest;
	u8 *signature = NULL;
	size_t sig_size;
	int err = 0;

	/* See if this file's verity file digest is already cached */
	verity_file_digest = incfs_get_verity_digest(inode);
	if (verity_file_digest.data)
		return 0;

	signature = incfs_get_verity_signature(filp, &sig_size);
	if (IS_ERR(signature))
		return PTR_ERR(signature);

	verity_file_digest = incfs_calc_verity_digest(inode, filp, signature,
						     sig_size,
						     FS_VERITY_HASH_ALG_SHA256);
	if (IS_ERR(verity_file_digest.data)) {
		err = PTR_ERR(verity_file_digest.data);
		goto out;
	}

	incfs_set_verity_digest(inode, verity_file_digest);

out:
	kfree(signature);
	return err;
}

/**
 * incfs_fsverity_file_open() - prepare to open a file that may be
 * verity-enabled
 * @inode: the inode being opened
 * @filp: the struct file being set up
 *
 * When opening a verity file, set up data_file->df_verity_file_digest if not
 * already done. Note that incfs does not allow opening for writing, so there is
 * no need for that check.
 *
 * Return: 0 on success, -errno on failure
 */
int incfs_fsverity_file_open(struct inode *inode, struct file *filp)
{
	if (IS_VERITY(inode))
		return ensure_verity_info(inode, filp);

	return 0;
}

int incfs_ioctl_measure_verity(struct file *filp, void __user *_uarg)
{
	struct inode *inode = file_inode(filp);
	struct mem_range verity_file_digest = incfs_get_verity_digest(inode);
	struct fsverity_digest __user *uarg = _uarg;
	struct fsverity_digest arg;

	if (!verity_file_digest.data || !verity_file_digest.len)
		return -ENODATA; /* not a verity file */

	/*
	 * The user specifies the digest_size their buffer has space for; we can
	 * return the digest if it fits in the available space.  We write back
	 * the actual size, which may be shorter than the user-specified size.
	 */

	if (get_user(arg.digest_size, &uarg->digest_size))
		return -EFAULT;
	if (arg.digest_size < verity_file_digest.len)
		return -EOVERFLOW;

	memset(&arg, 0, sizeof(arg));
	arg.digest_algorithm = FS_VERITY_HASH_ALG_SHA256;
	arg.digest_size = verity_file_digest.len;

	if (copy_to_user(uarg, &arg, sizeof(arg)))
		return -EFAULT;

	if (copy_to_user(uarg->digest, verity_file_digest.data,
			 verity_file_digest.len))
		return -EFAULT;

	return 0;
}

static int incfs_read_merkle_tree(struct file *filp, void __user *buf,
				  u64 start_offset, int length)
{
	struct mem_range tmp_buf;
	size_t offset;
	int retval = 0;
	int err = 0;
	struct data_file *df = get_incfs_data_file(filp);

	if (!df)
		return -EINVAL;

	tmp_buf = (struct mem_range) {
		.data = kzalloc(INCFS_DATA_FILE_BLOCK_SIZE, GFP_NOFS),
		.len = INCFS_DATA_FILE_BLOCK_SIZE,
	};
	if (!tmp_buf.data)
		return -ENOMEM;

	for (offset = start_offset; offset < start_offset + length;
	     offset += tmp_buf.len) {
		err = incfs_read_merkle_tree_blocks(tmp_buf, df, offset);

		if (err < 0)
			break;

		if (err != tmp_buf.len)
			break;

		if (copy_to_user(buf, tmp_buf.data, tmp_buf.len))
			break;

		buf += tmp_buf.len;
		retval += tmp_buf.len;
	}

	kfree(tmp_buf.data);
	return retval ? retval : err;
}

static int incfs_read_descriptor(struct file *filp,
				 void __user *buf, u64 offset, int length)
{
	int err;
	struct fsverity_descriptor *desc = incfs_get_fsverity_descriptor(filp,
						FS_VERITY_HASH_ALG_SHA256);

	if (IS_ERR(desc))
		return PTR_ERR(desc);
	length = min_t(u64, length, sizeof(*desc));
	err = copy_to_user(buf, desc, length);
	kfree(desc);
	return err ? err : length;
}

static int incfs_read_signature(struct file *filp,
				void __user *buf, u64 offset, int length)
{
	size_t sig_size;
	static u8 *signature;
	int err;

	signature = incfs_get_verity_signature(filp, &sig_size);
	if (IS_ERR(signature))
		return PTR_ERR(signature);

	if (!signature)
		return -ENODATA;

	length = min_t(u64, length, sig_size);
	err = copy_to_user(buf, signature, length);
	kfree(signature);
	return err ? err : length;
}

int incfs_ioctl_read_verity_metadata(struct file *filp,
				     const void __user *uarg)
{
	struct fsverity_read_metadata_arg arg;
	int length;
	void __user *buf;

	if (copy_from_user(&arg, uarg, sizeof(arg)))
		return -EFAULT;

	if (arg.__reserved)
		return -EINVAL;

	/* offset + length must not overflow. */
	if (arg.offset + arg.length < arg.offset)
		return -EINVAL;

	/* Ensure that the return value will fit in INT_MAX. */
	length = min_t(u64, arg.length, INT_MAX);

	buf = u64_to_user_ptr(arg.buf_ptr);

	switch (arg.metadata_type) {
	case FS_VERITY_METADATA_TYPE_MERKLE_TREE:
		return incfs_read_merkle_tree(filp, buf, arg.offset, length);
	case FS_VERITY_METADATA_TYPE_DESCRIPTOR:
		return incfs_read_descriptor(filp, buf, arg.offset, length);
	case FS_VERITY_METADATA_TYPE_SIGNATURE:
		return incfs_read_signature(filp, buf, arg.offset, length);
	default:
		return -EINVAL;
	}
}
