ANDROID: Incremental fs: Add FS_IOC_ENABLE_VERITY
Add FS_IOC_ENABLE_VERITY ioctl When called, calculate measurement, validate signature against fsverity, and set S_VERITY flag. This does not (yet) preserve the verity status once the inode is evicted. Bug: 160634504 Test: incfs_test passes Signed-off-by: Paul Lawrence <paullawrence@google.com> Change-Id: I88af2721f650098accc72a64528c7d85b753c7f6
This commit is contained in:
parent
0a1e2d3381
commit
5bb92dffc9
|
|
@ -8,3 +8,5 @@ incrementalfs-y := \
|
||||||
main.o \
|
main.o \
|
||||||
pseudo_files.o \
|
pseudo_files.o \
|
||||||
vfs.o
|
vfs.o
|
||||||
|
|
||||||
|
incrementalfs-$(CONFIG_FS_VERITY) += verity.o
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,8 @@ struct data_file *incfs_open_data_file(struct mount_info *mi, struct file *bf)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex_init(&df->df_enable_verity);
|
||||||
|
|
||||||
df->df_backing_file_context = bfc;
|
df->df_backing_file_context = bfc;
|
||||||
df->df_mount_info = mi;
|
df->df_mount_info = mi;
|
||||||
for (i = 0; i < ARRAY_SIZE(df->df_segments); i++)
|
for (i = 0; i < ARRAY_SIZE(df->df_segments); i++)
|
||||||
|
|
@ -329,6 +331,8 @@ void incfs_free_data_file(struct data_file *df)
|
||||||
incfs_free_mtree(df->df_hash_tree);
|
incfs_free_mtree(df->df_hash_tree);
|
||||||
incfs_free_bfc(df->df_backing_file_context);
|
incfs_free_bfc(df->df_backing_file_context);
|
||||||
kfree(df->df_signature);
|
kfree(df->df_signature);
|
||||||
|
kfree(df->df_verity_file_digest.data);
|
||||||
|
mutex_destroy(&df->df_enable_verity);
|
||||||
kfree(df);
|
kfree(df);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -273,9 +273,30 @@ struct data_file {
|
||||||
/* Offset to status metadata header */
|
/* Offset to status metadata header */
|
||||||
loff_t df_status_offset;
|
loff_t df_status_offset;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mutex acquired while enabling verity. Note that df_hash_tree is set
|
||||||
|
* by enable verity.
|
||||||
|
*
|
||||||
|
* The backing file mutex bc_mutex may be taken while this mutex is
|
||||||
|
* held.
|
||||||
|
*/
|
||||||
|
struct mutex df_enable_verity;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set either at construction time or during enabling verity. In the
|
||||||
|
* latter case, set via smp_store_release, so use smp_load_acquire to
|
||||||
|
* read it.
|
||||||
|
*/
|
||||||
struct mtree *df_hash_tree;
|
struct mtree *df_hash_tree;
|
||||||
|
|
||||||
|
/* Guaranteed set if df_hash_tree is set. */
|
||||||
struct incfs_df_signature *df_signature;
|
struct incfs_df_signature *df_signature;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The verity file digest, set when verity is enabled and the file has
|
||||||
|
* been opened
|
||||||
|
*/
|
||||||
|
struct mem_range df_verity_file_digest;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dir_file {
|
struct dir_file {
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,6 @@ static inline struct mem_range range(u8 *data, size_t len)
|
||||||
|
|
||||||
#define LOCK_REQUIRED(lock) WARN_ON_ONCE(!mutex_is_locked(&lock))
|
#define LOCK_REQUIRED(lock) WARN_ON_ONCE(!mutex_is_locked(&lock))
|
||||||
|
|
||||||
|
#define EFSCORRUPTED EUCLEAN
|
||||||
|
|
||||||
#endif /* _INCFS_INTERNAL_H */
|
#endif /* _INCFS_INTERNAL_H */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,318 @@
|
||||||
|
// 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 "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)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
|
||||||
|
inode_set_flags(inode, S_VERITY, S_VERITY);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 = kzalloc(sizeof(*desc), GFP_KERNEL);
|
||||||
|
int err;
|
||||||
|
struct mem_range verity_file_digest;
|
||||||
|
|
||||||
|
if (!desc)
|
||||||
|
return range(ERR_PTR(-ENOMEM), 0);
|
||||||
|
|
||||||
|
*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)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
verity_file_digest = incfs_calc_verity_digest_from_desc(inode, desc,
|
||||||
|
signature, signature_size);
|
||||||
|
|
||||||
|
out:
|
||||||
|
kfree(desc);
|
||||||
|
if (err)
|
||||||
|
return range(ERR_PTR(err), 0);
|
||||||
|
return verity_file_digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _INCFS_VERITY_H
|
||||||
|
#define _INCFS_VERITY_H
|
||||||
|
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
|
||||||
|
int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg);
|
||||||
|
|
||||||
|
#else /* !CONFIG_FS_VERITY */
|
||||||
|
|
||||||
|
static inline int incfs_ioctl_enable_verity(struct file *filp,
|
||||||
|
const void __user *uarg)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !CONFIG_FS_VERITY */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/fs_stack.h>
|
#include <linux/fs_stack.h>
|
||||||
#include <linux/fsnotify.h>
|
#include <linux/fsnotify.h>
|
||||||
|
#include <linux/fsverity.h>
|
||||||
#include <linux/namei.h>
|
#include <linux/namei.h>
|
||||||
#include <linux/parser.h>
|
#include <linux/parser.h>
|
||||||
#include <linux/seq_file.h>
|
#include <linux/seq_file.h>
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "pseudo_files.h"
|
#include "pseudo_files.h"
|
||||||
|
#include "verity.h"
|
||||||
|
|
||||||
static int incfs_remount_fs(struct super_block *sb, int *flags, char *data);
|
static int incfs_remount_fs(struct super_block *sb, int *flags, char *data);
|
||||||
|
|
||||||
|
|
@ -815,6 +817,8 @@ static long dispatch_ioctl(struct file *f, unsigned int req, unsigned long arg)
|
||||||
return ioctl_get_filled_blocks(f, (void __user *)arg);
|
return ioctl_get_filled_blocks(f, (void __user *)arg);
|
||||||
case INCFS_IOC_GET_BLOCK_COUNT:
|
case INCFS_IOC_GET_BLOCK_COUNT:
|
||||||
return ioctl_get_block_count(f, (void __user *)arg);
|
return ioctl_get_block_count(f, (void __user *)arg);
|
||||||
|
case FS_IOC_ENABLE_VERITY:
|
||||||
|
return incfs_ioctl_enable_verity(f, (const void __user *)arg);
|
||||||
default:
|
default:
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,11 @@
|
||||||
#include <linux/random.h>
|
#include <linux/random.h>
|
||||||
#include <linux/unistd.h>
|
#include <linux/unistd.h>
|
||||||
|
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
#include <kselftest.h>
|
#include <kselftest.h>
|
||||||
|
#include <include/uapi/linux/fsverity.h>
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
@ -73,6 +77,23 @@ struct {
|
||||||
#define TESTNE(statement, res) \
|
#define TESTNE(statement, res) \
|
||||||
TESTCOND((statement) != (res))
|
TESTCOND((statement) != (res))
|
||||||
|
|
||||||
|
void print_bytes(const void *data, size_t size)
|
||||||
|
{
|
||||||
|
const uint8_t *bytes = data;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < size; ++i) {
|
||||||
|
if (i % 0x10 == 0)
|
||||||
|
printf("%08x:", i);
|
||||||
|
printf("%02x ", (unsigned int) bytes[i]);
|
||||||
|
if (i % 0x10 == 0x0f)
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % 0x10 != 0)
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
struct hash_block {
|
struct hash_block {
|
||||||
char data[INCFS_DATA_FILE_BLOCK_SIZE];
|
char data[INCFS_DATA_FILE_BLOCK_SIZE];
|
||||||
};
|
};
|
||||||
|
|
@ -3620,6 +3641,266 @@ out:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static EVP_PKEY *create_key(void)
|
||||||
|
{
|
||||||
|
EVP_PKEY *pkey = NULL;
|
||||||
|
RSA *rsa = NULL;
|
||||||
|
BIGNUM *bn = NULL;
|
||||||
|
|
||||||
|
pkey = EVP_PKEY_new();
|
||||||
|
if (!pkey)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
bn = BN_new();
|
||||||
|
BN_set_word(bn, RSA_F4);
|
||||||
|
|
||||||
|
rsa = RSA_new();
|
||||||
|
if (!rsa)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
RSA_generate_key_ex(rsa, 4096, bn, NULL);
|
||||||
|
EVP_PKEY_assign_RSA(pkey, rsa);
|
||||||
|
|
||||||
|
BN_free(bn);
|
||||||
|
return pkey;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
BN_free(bn);
|
||||||
|
EVP_PKEY_free(pkey);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509 *get_cert(EVP_PKEY *key)
|
||||||
|
{
|
||||||
|
X509 *x509 = NULL;
|
||||||
|
X509_NAME *name = NULL;
|
||||||
|
|
||||||
|
x509 = X509_new();
|
||||||
|
if (!x509)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
|
||||||
|
X509_gmtime_adj(X509_get_notBefore(x509), 0);
|
||||||
|
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
|
||||||
|
X509_set_pubkey(x509, key);
|
||||||
|
|
||||||
|
name = X509_get_subject_name(x509);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
|
||||||
|
(const unsigned char *)"US", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC,
|
||||||
|
(const unsigned char *)"CA", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC,
|
||||||
|
(const unsigned char *)"San Jose", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
|
||||||
|
(const unsigned char *)"Example", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC,
|
||||||
|
(const unsigned char *)"Org", -1, -1, 0);
|
||||||
|
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||||
|
(const unsigned char *)"www.example.com", -1, -1, 0);
|
||||||
|
X509_set_issuer_name(x509, name);
|
||||||
|
|
||||||
|
if (!X509_sign(x509, key, EVP_sha256()))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return x509;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sign(EVP_PKEY *key, X509 *cert, const char *data, size_t len,
|
||||||
|
unsigned char **sig, size_t *sig_len)
|
||||||
|
{
|
||||||
|
const int pkcs7_flags = PKCS7_BINARY | PKCS7_NOATTR | PKCS7_PARTIAL |
|
||||||
|
PKCS7_DETACHED;
|
||||||
|
const EVP_MD *md = EVP_sha256();
|
||||||
|
|
||||||
|
int result = TEST_FAILURE;
|
||||||
|
|
||||||
|
BIO *bio = NULL;
|
||||||
|
PKCS7 *p7 = NULL;
|
||||||
|
unsigned char *bio_buffer;
|
||||||
|
|
||||||
|
TEST(bio = BIO_new_mem_buf(data, len), bio);
|
||||||
|
TEST(p7 = PKCS7_sign(NULL, NULL, NULL, bio, pkcs7_flags), p7);
|
||||||
|
TESTNE(PKCS7_sign_add_signer(p7, cert, key, md, pkcs7_flags), 0);
|
||||||
|
TESTEQUAL(PKCS7_final(p7, bio, pkcs7_flags), 1);
|
||||||
|
TEST(*sig_len = i2d_PKCS7(p7, NULL), *sig_len);
|
||||||
|
TEST(bio_buffer = malloc(*sig_len), bio_buffer);
|
||||||
|
*sig = bio_buffer;
|
||||||
|
TEST(*sig_len = i2d_PKCS7(p7, &bio_buffer), *sig_len);
|
||||||
|
TESTEQUAL(PKCS7_verify(p7, NULL, NULL, bio, NULL,
|
||||||
|
pkcs7_flags | PKCS7_NOVERIFY | PKCS7_NOSIGS), 1);
|
||||||
|
|
||||||
|
result = TEST_SUCCESS;
|
||||||
|
out:
|
||||||
|
PKCS7_free(p7);
|
||||||
|
BIO_free(bio);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int validate_verity(const char *mount_dir, struct test_file *file,
|
||||||
|
EVP_PKEY *key, X509 *cert, bool use_signatures)
|
||||||
|
{
|
||||||
|
int result = TEST_FAILURE;
|
||||||
|
char *filename = NULL;
|
||||||
|
int fd = -1;
|
||||||
|
struct fsverity_enable_arg fear = {
|
||||||
|
.version = 1,
|
||||||
|
.hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
|
||||||
|
.block_size = INCFS_DATA_FILE_BLOCK_SIZE,
|
||||||
|
.sig_size = 0,
|
||||||
|
.sig_ptr = 0,
|
||||||
|
};
|
||||||
|
unsigned char *sig = NULL;
|
||||||
|
size_t sig_len;
|
||||||
|
struct {
|
||||||
|
__u8 version; /* must be 1 */
|
||||||
|
__u8 hash_algorithm; /* Merkle tree hash algorithm */
|
||||||
|
__u8 log_blocksize; /* log2 of size of data and tree blocks */
|
||||||
|
__u8 salt_size; /* size of salt in bytes; 0 if none */
|
||||||
|
__le32 sig_size; /* must be 0 */
|
||||||
|
__le64 data_size; /* size of file the Merkle tree is built over */
|
||||||
|
__u8 root_hash[64]; /* Merkle tree root hash */
|
||||||
|
__u8 salt[32]; /* salt prepended to each hashed block */
|
||||||
|
__u8 __reserved[144]; /* must be 0's */
|
||||||
|
} __packed fsverity_descriptor = {
|
||||||
|
.version = 1,
|
||||||
|
.hash_algorithm = 1,
|
||||||
|
.log_blocksize = 12,
|
||||||
|
.data_size = file->size,
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
char magic[8]; /* must be "FSVerity" */
|
||||||
|
__le16 digest_algorithm;
|
||||||
|
__le16 digest_size;
|
||||||
|
__u8 digest[32];
|
||||||
|
} __packed fsverity_signed_digest = {
|
||||||
|
.digest_algorithm = 1,
|
||||||
|
.digest_size = 32
|
||||||
|
};
|
||||||
|
|
||||||
|
memcpy(fsverity_signed_digest.magic, "FSVerity", 8);
|
||||||
|
|
||||||
|
TEST(filename = concat_file_name(mount_dir, file->name), filename);
|
||||||
|
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
|
||||||
|
|
||||||
|
/* First try to enable verity with random digest */
|
||||||
|
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
|
||||||
|
sizeof(fsverity_signed_digest), &sig, &sig_len),
|
||||||
|
0);
|
||||||
|
|
||||||
|
fear.sig_size = sig_len;
|
||||||
|
fear.sig_ptr = ptr_to_u64(sig);
|
||||||
|
TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), -1);
|
||||||
|
|
||||||
|
/* Now try with correct digest */
|
||||||
|
memcpy(fsverity_descriptor.root_hash, file->root_hash, 32);
|
||||||
|
sha256((char *)&fsverity_descriptor, sizeof(fsverity_descriptor),
|
||||||
|
(char *)fsverity_signed_digest.digest);
|
||||||
|
|
||||||
|
if (ioctl(fd, FS_IOC_ENABLE_VERITY, NULL) == -1 &&
|
||||||
|
errno == EOPNOTSUPP) {
|
||||||
|
result = TEST_SUCCESS;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
|
||||||
|
sizeof(fsverity_signed_digest), &sig, &sig_len),
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (use_signatures) {
|
||||||
|
fear.sig_size = sig_len;
|
||||||
|
fear.sig_ptr = ptr_to_u64(sig);
|
||||||
|
} else {
|
||||||
|
fear.sig_size = 0;
|
||||||
|
fear.sig_ptr = 0;
|
||||||
|
}
|
||||||
|
TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), 0);
|
||||||
|
|
||||||
|
result = TEST_SUCCESS;
|
||||||
|
out:
|
||||||
|
free(sig);
|
||||||
|
close(fd);
|
||||||
|
free(filename);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int verity_test_optional_sigs(const char *mount_dir, bool use_signatures)
|
||||||
|
{
|
||||||
|
int result = TEST_FAILURE;
|
||||||
|
char *backing_dir = NULL;
|
||||||
|
int cmd_fd = -1;
|
||||||
|
int i;
|
||||||
|
struct test_files_set test = get_test_files_set();
|
||||||
|
const int file_num = test.files_count;
|
||||||
|
EVP_PKEY *key = NULL;
|
||||||
|
X509 *cert = NULL;
|
||||||
|
BIO *mem = NULL;
|
||||||
|
long len;
|
||||||
|
void *ptr;
|
||||||
|
FILE *proc_key_fd = NULL;
|
||||||
|
char *line = NULL;
|
||||||
|
size_t read = 0;
|
||||||
|
int key_id = -1;
|
||||||
|
|
||||||
|
TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
|
||||||
|
TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false),
|
||||||
|
0);
|
||||||
|
TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
|
||||||
|
TEST(key = create_key(), key);
|
||||||
|
TEST(cert = get_cert(key), cert);
|
||||||
|
|
||||||
|
TEST(proc_key_fd = fopen("/proc/keys", "r"), proc_key_fd != NULL);
|
||||||
|
while (getline(&line, &read, proc_key_fd) != -1)
|
||||||
|
if (strstr(line, ".fs-verity"))
|
||||||
|
key_id = strtol(line, NULL, 16);
|
||||||
|
|
||||||
|
TEST(mem = BIO_new(BIO_s_mem()), mem != NULL);
|
||||||
|
TESTEQUAL(i2d_X509_bio(mem, cert), 1);
|
||||||
|
TEST(len = BIO_get_mem_data(mem, &ptr), len != 0);
|
||||||
|
TESTCOND(key_id == -1
|
||||||
|
|| syscall(__NR_add_key, "asymmetric", "test:key", ptr, len,
|
||||||
|
key_id) != -1);
|
||||||
|
|
||||||
|
for (i = 0; i < file_num; i++) {
|
||||||
|
struct test_file *file = &test.files[i];
|
||||||
|
|
||||||
|
build_mtree(file);
|
||||||
|
TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file->name, &file->id,
|
||||||
|
file->size, file->root_hash,
|
||||||
|
file->sig.add_data), 0);
|
||||||
|
|
||||||
|
TESTEQUAL(emit_partial_test_file_hash(mount_dir, file), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < file_num; i++)
|
||||||
|
TESTEQUAL(validate_verity(mount_dir, &test.files[i], key, cert,
|
||||||
|
use_signatures),
|
||||||
|
0);
|
||||||
|
|
||||||
|
result = TEST_SUCCESS;
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(line);
|
||||||
|
BIO_free(mem);
|
||||||
|
X509_free(cert);
|
||||||
|
EVP_PKEY_free(key);
|
||||||
|
fclose(proc_key_fd);
|
||||||
|
close(cmd_fd);
|
||||||
|
umount(mount_dir);
|
||||||
|
free(backing_dir);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int verity_test(const char *mount_dir)
|
||||||
|
{
|
||||||
|
int result = TEST_FAILURE;
|
||||||
|
|
||||||
|
TESTEQUAL(verity_test_optional_sigs(mount_dir, true), TEST_SUCCESS);
|
||||||
|
TESTEQUAL(verity_test_optional_sigs(mount_dir, false), TEST_SUCCESS);
|
||||||
|
result = TEST_SUCCESS;
|
||||||
|
out:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static char *setup_mount_dir()
|
static char *setup_mount_dir()
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
@ -3734,6 +4015,7 @@ int main(int argc, char *argv[])
|
||||||
MAKE_TEST(hash_block_count_test),
|
MAKE_TEST(hash_block_count_test),
|
||||||
MAKE_TEST(per_uid_read_timeouts_test),
|
MAKE_TEST(per_uid_read_timeouts_test),
|
||||||
MAKE_TEST(inotify_test),
|
MAKE_TEST(inotify_test),
|
||||||
|
MAKE_TEST(verity_test),
|
||||||
};
|
};
|
||||||
#undef MAKE_TEST
|
#undef MAKE_TEST
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue