From d434336cff91b07ab7029f407e24f9cc0ce3ec37 Mon Sep 17 00:00:00 2001 From: Paul Lawrence Date: Fri, 12 Mar 2021 09:54:28 -0800 Subject: [PATCH] ANDROID: Incremental fs: Add FS_IOC_READ_VERITY_METADATA Bug: 180942327 Test: incfs_test passes Signed-off-by: Paul Lawrence Change-Id: I6d6532496c072145f22bcf9ff4499ec3f52e94b5 --- fs/incfs/data_mgmt.c | 22 +++ fs/incfs/data_mgmt.h | 3 + fs/incfs/verity.c | 173 +++++++++++++++--- fs/incfs/verity.h | 8 + fs/incfs/vfs.c | 3 + .../selftests/filesystems/incfs/incfs_test.c | 165 ++++++++++++++--- 6 files changed, 329 insertions(+), 45 deletions(-) diff --git a/fs/incfs/data_mgmt.c b/fs/incfs/data_mgmt.c index b7727e3a8e43..349230ac425b 100644 --- a/fs/incfs/data_mgmt.c +++ b/fs/incfs/data_mgmt.c @@ -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) { diff --git a/fs/incfs/data_mgmt.h b/fs/incfs/data_mgmt.h index 516e2e0dd5da..3581c295fecf 100644 --- a/fs/incfs/data_mgmt.h +++ b/fs/incfs/data_mgmt.h @@ -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); diff --git a/fs/incfs/verity.c b/fs/incfs/verity.c index 50f7bff20b8a..4b22053e70fc 100644 --- a/fs/incfs/verity.c +++ b/fs/incfs/verity.c @@ -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; + } +} diff --git a/fs/incfs/verity.h b/fs/incfs/verity.h index 3ba2306002f4..8fcdbc8b93d6 100644 --- a/fs/incfs/verity.h +++ b/fs/incfs/verity.h @@ -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 diff --git a/fs/incfs/vfs.c b/fs/incfs/vfs.c index c95b194b1e5c..b2b2c313cc65 100644 --- a/fs/incfs/vfs.c +++ b/fs/incfs/vfs.c @@ -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; diff --git a/tools/testing/selftests/filesystems/incfs/incfs_test.c b/tools/testing/selftests/filesystems/incfs/incfs_test.c index d48b65ea9aae..5a145b395580 100644 --- a/tools/testing/selftests/filesystems/incfs/incfs_test.c +++ b/tools/testing/selftests/filesystems/incfs/incfs_test.c @@ -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);