| // 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/ is not |
| * needed. incfs also only needs to support the case where |
| * CONFIG_FS_VERITY_BUILTIN_SIGNATURES=n. Therefore, the integration consists of |
| * the following modifications: |
| * |
| * 1. Add the (optional) verity signature to the incfs file format. (Not really |
| * needed anymore, but this is kept around since this is the behavior of |
| * fs/verity/ even when CONFIG_FS_VERITY_BUILTIN_SIGNATURES=n.) |
| * 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. 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/sha2.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(&nop_mnt_idmap, 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. */ |
| static struct mem_range incfs_calc_verity_digest_from_desc( |
| const struct inode *inode, |
| struct fsverity_descriptor *desc) |
| { |
| 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); |
| kfree(verity_file_digest.data); |
| return range(ERR_PTR(err), 0); |
| } |
| pr_debug("Computed file digest: %s:%*phN\n", |
| hash_alg->name, (int) verity_file_digest.len, |
| verity_file_digest.data); |
| 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, |
| 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); |
| 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 *)kzalloc(buf.len, GFP_NOFS); |
| tmp.data = (u8 *)kzalloc(tmp.len, GFP_NOFS); |
| 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: |
| kfree(tmp.data); |
| kfree(buf.data); |
| 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, |
| 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; |
| |
| /* 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; |
| |
| verity_file_digest = incfs_calc_verity_digest(inode, filp, |
| FS_VERITY_HASH_ALG_SHA256); |
| if (IS_ERR(verity_file_digest.data)) |
| return PTR_ERR(verity_file_digest.data); |
| |
| incfs_set_verity_digest(inode, verity_file_digest); |
| return 0; |
| } |
| |
| /** |
| * 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; |
| } |
| } |