123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851 |
- // 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);
- 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;
- }
- }
|