ANDROID: Incremental fs: Build merkle tree when enabling verity

For incfs files that were created without a merkle tree, enabling verity
requires building a merkle tree first. Although this is the same logic
as verity performs, it is not that easy to reconcile the two given that
incfs has the merkle tree potentially when verity is not enabled.

Bug: 160634504
Test: incfs_test passes
Signed-off-by: Paul Lawrence <paullawrence@google.com>

Change-Id: Ia15a4051fa3362820846d65859e3af76b77f8cc4
This commit is contained in:
Paul Lawrence 2020-09-30 07:10:57 -07:00
parent 822f25ac84
commit 6d18d83e6c
6 changed files with 345 additions and 20 deletions

View File

@ -601,7 +601,11 @@ static int validate_hash_tree(struct backing_file_context *bfc, struct file *f,
int hash_per_block;
pgoff_t file_pages;
tree = df->df_hash_tree;
/*
* Memory barrier to make sure tree is fully present if added via enable
* verity
*/
tree = smp_load_acquire(&df->df_hash_tree);
sig = df->df_signature;
if (!tree || !sig)
return 0;
@ -1509,6 +1513,7 @@ static int incfs_scan_metadata_chain(struct data_file *df)
int records_count = 0;
int error = 0;
struct backing_file_context *bfc = NULL;
int nondata_block_count;
if (!df || !df->df_backing_file_context)
return -EFAULT;
@ -1543,15 +1548,25 @@ static int incfs_scan_metadata_chain(struct data_file *df)
} else
result = records_count;
nondata_block_count = df->df_total_block_count -
df->df_data_block_count;
if (df->df_hash_tree) {
int hash_block_count = get_blocks_count_for_size(
df->df_hash_tree->hash_tree_area_size);
if (df->df_data_block_count + hash_block_count !=
df->df_total_block_count)
/*
* Files that were created with a hash tree have the hash tree
* included in the block map, i.e. nondata_block_count ==
* hash_block_count. Files whose hash tree was added by
* FS_IOC_ENABLE_VERITY will still have the original block
* count, i.e. nondata_block_count == 0.
*/
if (nondata_block_count != hash_block_count &&
nondata_block_count != 0)
result = -EINVAL;
} else if (df->df_data_block_count != df->df_total_block_count)
} else if (nondata_block_count != 0) {
result = -EINVAL;
}
kfree(handler);
return result;

View File

@ -246,7 +246,8 @@ int incfs_write_blockmap_to_backing_file(struct backing_file_context *bfc,
}
int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
struct mem_range sig, u32 tree_size)
struct mem_range sig, u32 tree_size,
loff_t *tree_offset, loff_t *sig_offset)
{
struct incfs_file_signature sg = {};
int result = 0;
@ -265,12 +266,10 @@ int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
sg.sg_header.h_record_size = cpu_to_le16(sizeof(sg));
sg.sg_header.h_next_md_offset = cpu_to_le64(0);
if (sig.data != NULL && sig.len > 0) {
loff_t pos = incfs_get_end_offset(bfc->bc_file);
sg.sg_sig_size = cpu_to_le32(sig.len);
sg.sg_sig_offset = cpu_to_le64(pos);
sg.sg_sig_offset = cpu_to_le64(rollback_pos);
result = write_to_bf(bfc, sig.data, sig.len, pos);
result = write_to_bf(bfc, sig.data, sig.len, rollback_pos);
if (result)
goto err;
}
@ -306,6 +305,13 @@ int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
if (result)
/* Error, rollback file changes */
truncate_backing_file(bfc, rollback_pos);
else {
if (tree_offset)
*tree_offset = tree_area_pos;
if (sig_offset)
*sig_offset = rollback_pos;
}
return result;
}

View File

@ -236,6 +236,10 @@ struct incfs_blockmap {
* definition of incfs_new_file_args::signature_info for an explanation of this
* blob. Specifically, it contains the root hash, but it does *not* contain
* anything that the kernel treats as a signature.
*
* When FS_IOC_ENABLE_VERITY is called on a file without this record, an APK V4
* signature blob and a hash tree are added to the file, and then this metadata
* record is created to record their locations.
*/
struct incfs_file_signature {
struct incfs_md_header sg_header;
@ -367,7 +371,8 @@ int incfs_write_hash_block_to_backing_file(struct backing_file_context *bfc,
loff_t file_size);
int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
struct mem_range sig, u32 tree_size);
struct mem_range sig, u32 tree_size,
loff_t *tree_offset, loff_t *sig_offset);
int incfs_write_status_to_backing_file(struct backing_file_context *bfc,
loff_t status_offset,

View File

@ -354,8 +354,9 @@ static int init_new_file(struct mount_info *mi, struct dentry *dentry,
goto out;
}
error = incfs_write_signature_to_backing_file(
bfc, raw_signature, hash_tree->hash_tree_area_size);
error = incfs_write_signature_to_backing_file(bfc,
raw_signature, hash_tree->hash_tree_area_size,
NULL, NULL);
if (error)
goto out;

View File

@ -278,6 +278,227 @@ static struct mem_range incfs_calc_verity_digest(
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, 0, 0, 0, tmp);
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)
{
@ -299,6 +520,10 @@ static int incfs_enable_verity(struct file *filp,
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),

View File

@ -3777,7 +3777,7 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
.sig_ptr = 0,
};
unsigned char *sig = NULL;
size_t sig_len;
size_t sig_len = 0;
struct {
__u8 version; /* must be 1 */
__u8 hash_algorithm; /* Merkle tree hash algorithm */
@ -3813,13 +3813,15 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
TESTEQUAL(flags & FS_VERITY_FL, 0);
/* First try to enable verity with random digest */
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
sizeof(fsverity_signed_digest), &sig, &sig_len),
0);
if (key) {
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);
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);
@ -3832,7 +3834,8 @@ static int enable_verity(const char *mount_dir, struct test_file *file,
goto out;
}
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
if (key)
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
sizeof(fsverity_signed_digest), &sig, &sig_len),
0);
@ -3973,6 +3976,75 @@ static int verity_test(const char *mount_dir)
return result;
}
static int verity_file_valid(const char *mount_dir, struct test_file *file)
{
int result = TEST_FAILURE;
char *filename = NULL;
int fd = -1;
uint8_t buffer[INCFS_DATA_FILE_BLOCK_SIZE];
struct incfs_get_file_sig_args gfsa = {
.file_signature = ptr_to_u64(buffer),
.file_signature_buf_size = sizeof(buffer),
};
int i;
TEST(filename = concat_file_name(mount_dir, file->name), filename);
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
TESTEQUAL(ioctl(fd, INCFS_IOC_READ_FILE_SIGNATURE, &gfsa), 0);
for (i = 0; i < file->size; i += sizeof(buffer))
TESTEQUAL(pread(fd, buffer, sizeof(buffer), i),
file->size - i > sizeof(buffer) ?
sizeof(buffer) : file->size - i);
result = TEST_SUCCESS;
out:
close(fd);
free(filename);
return result;
}
static int enable_verity_test(const char *mount_dir)
{
int result = TEST_FAILURE;
char *backing_dir = NULL;
bool installed;
int cmd_fd = -1;
struct test_files_set test = get_test_files_set();
int i;
TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0);
TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
TESTEQUAL(verity_installed(mount_dir, cmd_fd, &installed), 0);
if (!installed) {
result = TEST_SUCCESS;
goto out;
}
for (i = 0; i < test.files_count; ++i) {
struct test_file *file = &test.files[i];
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);
}
/* Check files are valid on disk */
close(cmd_fd);
cmd_fd = -1;
TESTEQUAL(umount(mount_dir), 0);
TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0);
for (i = 0; i < test.files_count; ++i)
TESTEQUAL(verity_file_valid(mount_dir, &test.files[i]), 0);
result = TEST_SUCCESS;
out:
close(cmd_fd);
umount(mount_dir);
free(backing_dir);
return result;
}
static char *setup_mount_dir()
{
struct stat st;
@ -4088,6 +4160,7 @@ int main(int argc, char *argv[])
MAKE_TEST(per_uid_read_timeouts_test),
MAKE_TEST(inotify_test),
MAKE_TEST(verity_test),
MAKE_TEST(enable_verity_test),
};
#undef MAKE_TEST