ANDROID: Incremental fs: Add FS_IOC_READ_VERITY_METADATA

Bug: 180942327
Test: incfs_test passes
Signed-off-by: Paul Lawrence <paullawrence@google.com>
Change-Id: I6d6532496c072145f22bcf9ff4499ec3f52e94b5
This commit is contained in:
Paul Lawrence
2021-03-12 09:54:28 -08:00
parent 2869b8c844
commit d434336cff
6 changed files with 329 additions and 45 deletions

View File

@@ -1272,6 +1272,28 @@ out:
return result;
}
ssize_t incfs_read_merkle_tree_blocks(struct mem_range dst,
struct data_file *df, size_t offset)
{
struct backing_file_context *bfc = NULL;
struct incfs_df_signature *sig = NULL;
size_t to_read = dst.len;
if (!dst.data || !df)
return -EFAULT;
sig = df->df_signature;
bfc = df->df_backing_file_context;
if (offset > sig->hash_size)
return -ERANGE;
if (offset + to_read > sig->hash_size)
to_read = sig->hash_size - offset;
return incfs_kread(bfc, dst.data, to_read, sig->hash_offset + offset);
}
int incfs_process_new_data_block(struct data_file *df,
struct incfs_fill_block *block, u8 *data)
{

View File

@@ -377,6 +377,9 @@ ssize_t incfs_read_data_file_block(struct mem_range dst, struct file *f,
u32 min_pending_time_us, u32 max_pending_time_us,
struct mem_range tmp);
ssize_t incfs_read_merkle_tree_blocks(struct mem_range dst,
struct data_file *df, size_t offset);
int incfs_get_filled_blocks(struct data_file *df,
struct incfs_file_data *fd,
struct incfs_get_filled_blocks_args *arg);

View File

@@ -78,15 +78,17 @@ static int incfs_end_enable_verity(struct file *filp, u8 *sig, size_t sig_size)
struct data_file *df = get_incfs_data_file(filp);
struct backing_file_context *bfc;
int error;
struct incfs_df_verity_signature *vs;
struct incfs_df_verity_signature *vs = NULL;
loff_t offset;
if (!df || !df->df_backing_file_context)
return -EFSCORRUPTED;
vs = kzalloc(sizeof(*vs), GFP_NOFS);
if (!vs)
return -ENOMEM;
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);
@@ -109,13 +111,16 @@ static int incfs_end_enable_verity(struct file *filp, u8 *sig, size_t sig_size)
goto out;
}
*vs = (struct incfs_df_verity_signature) {
.size = signature.len,
.offset = offset,
};
if (sig) {
*vs = (struct incfs_df_verity_signature) {
.size = signature.len,
.offset = offset,
};
df->df_verity_signature = vs;
vs = NULL;
}
df->df_verity_signature = vs;
vs = NULL;
inode_set_flags(inode, S_VERITY, S_VERITY);
out:
@@ -245,17 +250,15 @@ out:
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)
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;
struct mem_range verity_file_digest;
if (!desc)
return range(ERR_PTR(-ENOMEM), 0);
return ERR_PTR(-ENOMEM);
*desc = (struct fsverity_descriptor) {
.version = 1,
@@ -265,16 +268,28 @@ static struct mem_range incfs_calc_verity_digest(
};
err = incfs_get_root_hash(filp, desc->root_hash);
if (err)
goto out;
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);
out:
kfree(desc);
if (err)
return range(ERR_PTR(err), 0);
return verity_file_digest;
}
@@ -611,6 +626,11 @@ static u8 *incfs_get_verity_signature(struct file *filp, size_t *sig_size)
return NULL;
}
if (!vs->size) {
*sig_size = 0;
return ERR_PTR(-EFSCORRUPTED);
}
signature = kzalloc(vs->size, GFP_KERNEL);
if (!signature)
return ERR_PTR(-ENOMEM);
@@ -720,3 +740,112 @@ int incfs_ioctl_measure_verity(struct file *filp, void __user *_uarg)
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;
}
}

View File

@@ -15,6 +15,8 @@ int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg);
int incfs_ioctl_measure_verity(struct file *filp, void __user *_uarg);
int incfs_fsverity_file_open(struct inode *inode, struct file *filp);
int incfs_ioctl_read_verity_metadata(struct file *filp,
const void __user *uarg);
#else /* !CONFIG_FS_VERITY */
@@ -36,6 +38,12 @@ static inline int incfs_fsverity_file_open(struct inode *inode,
return -EOPNOTSUPP;
}
static inline int incfs_ioctl_read_verity_metadata(struct file *filp,
const void __user *uarg)
{
return -EOPNOTSUPP;
}
#endif /* !CONFIG_FS_VERITY */
#endif

View File

@@ -899,6 +899,8 @@ static long dispatch_ioctl(struct file *f, unsigned int req, unsigned long arg)
return incfs_ioctl_get_flags(f, (void __user *) arg);
case FS_IOC_MEASURE_VERITY:
return incfs_ioctl_measure_verity(f, (void __user *)arg);
case FS_IOC_READ_VERITY_METADATA:
return incfs_ioctl_read_verity_metadata(f, (void __user *)arg);
default:
return -EINVAL;
}
@@ -918,6 +920,7 @@ static long incfs_compat_ioctl(struct file *file, unsigned int cmd,
case INCFS_IOC_GET_BLOCK_COUNT:
case FS_IOC_ENABLE_VERITY:
case FS_IOC_MEASURE_VERITY:
case FS_IOC_READ_VERITY_METADATA:
break;
default:
return -ENOIOCTLCMD;

View File

@@ -123,6 +123,8 @@ struct test_file {
struct hash_block *mtree;
int mtree_block_count;
struct test_signature sig;
unsigned char *verity_sig;
size_t verity_sig_size;
};
struct test_files_set {
@@ -215,6 +217,17 @@ static loff_t min(loff_t a, loff_t b)
return a < b ? a : b;
}
static int ilog2(size_t n)
{
int l = 0;
while (n > 1) {
++l;
n >>= 1;
}
return l;
}
static pid_t flush_and_fork(void)
{
fflush(stdout);
@@ -952,10 +965,8 @@ static int load_hash_tree(const char *mount_dir, struct test_file *file)
close(fd);
if (err < fill_blocks.count)
err = errno;
else {
else
err = 0;
free(file->mtree);
}
failure:
free(fill_block_array);
@@ -1379,7 +1390,6 @@ static int dynamic_files_and_data_test(const char *mount_dir)
struct test_file *file = &test.files[i];
int res;
build_mtree(file);
res = emit_file(cmd_fd, NULL, file->name, &file->id,
file->size, NULL);
if (res < 0) {
@@ -1592,7 +1602,6 @@ static int work_after_remount_test(const char *mount_dir)
for (i = 0; i < file_num_stage1; i++) {
struct test_file *file = &test.files[i];
build_mtree(file);
if (emit_file(cmd_fd, NULL, file->name, &file->id,
file->size, NULL))
goto failure;
@@ -1987,8 +1996,47 @@ failure:
return TEST_FAILURE;
}
static int validate_hash_tree(const char *mount_dir, struct test_file *file)
{
int result = TEST_FAILURE;
char *filename = NULL;
int fd = -1;
unsigned char *buf;
int i, err;
TEST(filename = concat_file_name(mount_dir, file->name), filename);
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
TEST(buf = malloc(INCFS_DATA_FILE_BLOCK_SIZE * 8), buf);
for (i = 0; i < file->mtree_block_count; ) {
int blocks_to_read = i % 7 + 1;
struct fsverity_read_metadata_arg args = {
.metadata_type = FS_VERITY_METADATA_TYPE_MERKLE_TREE,
.offset = i * INCFS_DATA_FILE_BLOCK_SIZE,
.length = blocks_to_read * INCFS_DATA_FILE_BLOCK_SIZE,
.buf_ptr = ptr_to_u64(buf),
};
TEST(err = ioctl(fd, FS_IOC_READ_VERITY_METADATA, &args),
err == min(args.length, (file->mtree_block_count - i) *
INCFS_DATA_FILE_BLOCK_SIZE));
TESTEQUAL(memcmp(buf, file->mtree[i].data, err), 0);
i += blocks_to_read;
}
result = TEST_SUCCESS;
out:
free(buf);
close(fd);
free(filename);
return result;
}
static int hash_tree_test(const char *mount_dir)
{
int result = TEST_FAILURE;
char *backing_dir;
struct test_files_set test = get_test_files_set();
const int file_num = test.files_count;
@@ -2053,6 +2101,8 @@ static int hash_tree_test(const char *mount_dir)
}
} else if (validate_test_file_content(mount_dir, file) < 0)
goto failure;
else if (validate_hash_tree(mount_dir, file) < 0)
goto failure;
}
/* Unmount and mount again, to that hashes are persistent. */
@@ -2090,21 +2140,19 @@ static int hash_tree_test(const char *mount_dir)
} else if (validate_test_file_content(mount_dir, file) < 0)
goto failure;
}
/* Final unmount */
close(cmd_fd);
cmd_fd = -1;
if (umount(mount_dir) != 0) {
print_error("Can't unmout FS");
goto failure;
}
return TEST_SUCCESS;
result = TEST_SUCCESS;
failure:
for (i = 0; i < file_num; i++) {
struct test_file *file = &test.files[i];
free(file->mtree);
}
close(cmd_fd);
free(backing_dir);
umount(mount_dir);
return TEST_FAILURE;
return result;
}
enum expected_log { FULL_LOG, NO_LOG, PARTIAL_LOG };
@@ -3780,8 +3828,6 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
.sig_size = 0,
.sig_ptr = 0,
};
unsigned char *sig = NULL;
size_t sig_len = 0;
struct {
__u8 version; /* must be 1 */
__u8 hash_algorithm; /* Merkle tree hash algorithm */
@@ -3807,6 +3853,8 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
.digest_algorithm = 1,
.digest_size = 32
};
unsigned char *sig = NULL;
size_t sig_size = 0;
uint64_t flags;
struct statx statxbuf = {};
@@ -3825,10 +3873,10 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
/* First try to enable verity with random digest */
if (key) {
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
sizeof(fsverity_signed_digest), &sig, &sig_len),
sizeof(fsverity_signed_digest), &sig, &sig_size),
0);
fear.sig_size = sig_len;
fear.sig_size = sig_size;
fear.sig_ptr = ptr_to_u64(sig);
TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), -1);
}
@@ -3844,14 +3892,21 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
goto out;
}
free(sig);
sig = NULL;
if (key)
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
sizeof(fsverity_signed_digest), &sig, &sig_len),
sizeof(fsverity_signed_digest),
&sig, &sig_size),
0);
if (use_signatures) {
fear.sig_size = sig_len;
fear.sig_size = sig_size;
file->verity_sig_size = sig_size;
fear.sig_ptr = ptr_to_u64(sig);
file->verity_sig = sig;
sig = NULL;
} else {
fear.sig_size = 0;
fear.sig_ptr = 0;
@@ -3866,6 +3921,16 @@ out:
return result;
}
static int memzero(const unsigned char *buf, size_t size)
{
size_t i;
for (i = 0; i < size; ++i)
if (buf[i])
return -1;
return 0;
}
static int validate_verity(const char *mount_dir, struct test_file *file)
{
int result = TEST_FAILURE;
@@ -3874,6 +3939,9 @@ static int validate_verity(const char *mount_dir, struct test_file *file)
uint64_t flags;
struct fsverity_digest *digest;
struct statx statxbuf = {};
struct fsverity_read_metadata_arg frma = {};
uint8_t *buf = NULL;
struct fsverity_descriptor desc;
TEST(digest = malloc(sizeof(struct fsverity_digest) +
INCFS_MAX_HASH_SIZE), digest != NULL);
@@ -3890,8 +3958,48 @@ static int validate_verity(const char *mount_dir, struct test_file *file)
TESTEQUAL(digest->digest_algorithm, FS_VERITY_HASH_ALG_SHA256);
TESTEQUAL(digest->digest_size, 32);
if (file->verity_sig) {
TEST(buf = malloc(file->verity_sig_size), buf);
frma = (struct fsverity_read_metadata_arg) {
.metadata_type = FS_VERITY_METADATA_TYPE_SIGNATURE,
.length = file->verity_sig_size,
.buf_ptr = ptr_to_u64(buf),
};
TESTEQUAL(ioctl(fd, FS_IOC_READ_VERITY_METADATA, &frma),
file->verity_sig_size);
TESTEQUAL(memcmp(buf, file->verity_sig, file->verity_sig_size),
0);
} else {
frma = (struct fsverity_read_metadata_arg) {
.metadata_type = FS_VERITY_METADATA_TYPE_SIGNATURE,
};
TESTEQUAL(ioctl(fd, FS_IOC_READ_VERITY_METADATA, &frma), -1);
TESTEQUAL(errno, ENODATA);
}
frma = (struct fsverity_read_metadata_arg) {
.metadata_type = FS_VERITY_METADATA_TYPE_DESCRIPTOR,
.length = sizeof(desc),
.buf_ptr = ptr_to_u64(&desc),
};
TESTEQUAL(ioctl(fd, FS_IOC_READ_VERITY_METADATA, &frma),
sizeof(desc));
TESTEQUAL(desc.version, 1);
TESTEQUAL(desc.hash_algorithm, FS_VERITY_HASH_ALG_SHA256);
TESTEQUAL(desc.log_blocksize, ilog2(INCFS_DATA_FILE_BLOCK_SIZE));
TESTEQUAL(desc.salt_size, 0);
TESTEQUAL(desc.__reserved_0x04, 0);
TESTEQUAL(desc.data_size, file->size);
TESTEQUAL(memcmp(desc.root_hash, file->root_hash, SHA256_DIGEST_SIZE),
0);
TESTEQUAL(memzero(desc.root_hash + SHA256_DIGEST_SIZE,
sizeof(desc.root_hash) - SHA256_DIGEST_SIZE), 0);
TESTEQUAL(memzero(desc.salt, sizeof(desc.salt)), 0);
TESTEQUAL(memzero(desc.__reserved, sizeof(desc.__reserved)), 0);
result = TEST_SUCCESS;
out:
free(buf);
close(fd);
free(filename);
free(digest);
@@ -3949,7 +4057,7 @@ static int verity_test_optional_sigs(const char *mount_dir, bool use_signatures)
file->size, file->root_hash,
file->sig.add_data), 0);
TESTEQUAL(emit_partial_test_file_hash(mount_dir, file), 0);
TESTEQUAL(load_hash_tree(mount_dir, file), 0);
TESTEQUAL(enable_verity(mount_dir, file, key, cert,
use_signatures),
0);
@@ -3969,6 +4077,16 @@ static int verity_test_optional_sigs(const char *mount_dir, bool use_signatures)
result = TEST_SUCCESS;
out:
for (i = 0; i < file_num; i++) {
struct test_file *file = &test.files[i];
free(file->mtree);
free(file->verity_sig);
file->mtree = NULL;
file->verity_sig = NULL;
}
free(line);
BIO_free(mem);
X509_free(cert);
@@ -4041,7 +4159,7 @@ static int enable_verity_test(const char *mount_dir)
TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id,
file->size, NULL), 0);
TESTEQUAL(emit_test_file_data(mount_dir, file), 0);
TESTEQUAL(enable_verity(mount_dir, file, NULL, NULL, true), 0);
TESTEQUAL(enable_verity(mount_dir, file, NULL, NULL, false), 0);
}
/* Check files are valid on disk */
@@ -4110,6 +4228,7 @@ static int mmap_test(const char *mount_dir)
result = TEST_SUCCESS;
out:
free(file.mtree);
close(fd);
free(filename);
close(cmd_fd);