Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73e7d656 authored by Paul Lawrence's avatar Paul Lawrence
Browse files

ANDROID: Incremental fs: Remove signature checks from kernel



Test: selftests pass
Bug: 133435829
Signed-off-by: default avatarPaul Lawrence <paullawrence@google.com>
Change-Id: Ia7e69b1b0176202da4b418ea815b370cbdacd5c2
parent 53809634
Loading
Loading
Loading
Loading
+46 −108
Original line number Diff line number Diff line
@@ -250,7 +250,7 @@ static int validate_hash_tree(struct file *bf, struct data_file *df,
{
	u8 digest[INCFS_MAX_HASH_SIZE] = {};
	struct mtree *tree = NULL;
	struct ondisk_signature *sig = NULL;
	struct incfs_df_signature *sig = NULL;
	struct mem_range calc_digest_rng;
	struct mem_range saved_digest_rng;
	struct mem_range root_hash_rng;
@@ -273,8 +273,8 @@ static int validate_hash_tree(struct file *bf, struct data_file *df,
		return res;

	for (lvl = 0; lvl < tree->depth; lvl++) {
		loff_t lvl_off = tree->hash_level_suboffset[lvl] +
					sig->mtree_offset;
		loff_t lvl_off =
			tree->hash_level_suboffset[lvl] + sig->hash_offset;
		loff_t hash_block_off = lvl_off +
			round_down(hash_block_index * digest_size,
				INCFS_DATA_FILE_BLOCK_SIZE);
@@ -322,72 +322,6 @@ static int validate_hash_tree(struct file *bf, struct data_file *df,
	return 0;
}

static int revalidate_signature(struct file *bf, struct data_file *df)
{
	struct ondisk_signature *sig = df->df_signature;
	struct mem_range root_hash = {};
	int result = 0;
	u8 *sig_buf = NULL;
	u8 *add_data_buf = NULL;
	ssize_t read_res;

	/* File has no signature. */
	if (!sig || !df->df_hash_tree || sig->sig_size == 0)
		return 0;

	/* Signature has already been validated. */
	if (df->df_signature_validated)
		return 0;

	add_data_buf = kzalloc(sig->add_data_size, GFP_NOFS);
	if (!add_data_buf) {
		result = -ENOMEM;
		goto out;
	}

	read_res = incfs_kread(bf, add_data_buf, sig->add_data_size,
				sig->add_data_offset);
	if (read_res < 0) {
		result = read_res;
		goto out;
	}
	if (read_res != sig->add_data_size) {
		result = -EIO;
		goto out;
	}

	sig_buf = kzalloc(sig->sig_size, GFP_NOFS);
	if (!sig_buf) {
		result = -ENOMEM;
		goto out;
	}

	read_res = incfs_kread(bf, sig_buf, sig->sig_size, sig->sig_offset);
	if (read_res < 0) {
		result = read_res;
		goto out;
	}
	if (read_res != sig->sig_size) {
		result = -EIO;
		goto out;
	}

	root_hash = range(df->df_hash_tree->root_hash,
		df->df_hash_tree->alg->digest_size);

	result = incfs_validate_pkcs7_signature(
		range(sig_buf, sig->sig_size),
		root_hash,
		range(add_data_buf, sig->add_data_size));

	if (result == 0)
		df->df_signature_validated = true;
out:
	kfree(sig_buf);
	kfree(add_data_buf);
	return result;
}

static struct data_file_segment *get_file_segment(struct data_file *df,
						  int block_index)
{
@@ -683,13 +617,6 @@ ssize_t incfs_read_data_file_block(struct mem_range dst, struct data_file *df,
			result = err;
	}

	if (result > 0) {
		int err = revalidate_signature(bf, df);

		if (err < 0)
			result = err;
	}

	if (result >= 0)
		log_block_read(mi, &df->df_id, index, false /*timed out*/);

@@ -755,7 +682,7 @@ int incfs_process_new_data_block(struct data_file *df,
int incfs_read_file_signature(struct data_file *df, struct mem_range dst)
{
	struct file *bf = df->df_backing_file_context->bc_file;
	struct ondisk_signature *sig;
	struct incfs_df_signature *sig;
	int read_res = 0;

	if (!dst.data)
@@ -785,7 +712,7 @@ int incfs_process_new_hash_block(struct data_file *df,
	struct backing_file_context *bfc = NULL;
	struct mount_info *mi = NULL;
	struct mtree *hash_tree = NULL;
	struct ondisk_signature *sig = NULL;
	struct incfs_df_signature *sig = NULL;
	loff_t hash_area_base = 0;
	loff_t hash_area_size = 0;
	int error = 0;
@@ -804,11 +731,11 @@ int incfs_process_new_hash_block(struct data_file *df,

	hash_tree = df->df_hash_tree;
	sig = df->df_signature;
	if (!hash_tree || !sig || sig->mtree_offset == 0)
	if (!hash_tree || !sig || sig->hash_offset == 0)
		return -ENOTSUPP;

	hash_area_base = sig->mtree_offset;
	hash_area_size = sig->mtree_size;
	hash_area_base = sig->hash_offset;
	hash_area_size = sig->hash_size;
	if (hash_area_size < block->block_index * INCFS_DATA_FILE_BLOCK_SIZE
				+ block->data_len) {
		/* Hash block goes beyond dedicated hash area of this file. */
@@ -866,58 +793,69 @@ static int process_file_signature_md(struct incfs_file_signature *sg,
{
	struct data_file *df = handler->context;
	struct mtree *hash_tree = NULL;
	struct ondisk_signature *signature = NULL;
	int error = 0;
	loff_t base_tree_off = le64_to_cpu(sg->sg_hash_tree_offset);
	u32 tree_size = le32_to_cpu(sg->sg_hash_tree_size);
	loff_t sig_off = le64_to_cpu(sg->sg_sig_offset);
	u32 sig_size = le32_to_cpu(sg->sg_sig_size);
	loff_t add_data_off = le64_to_cpu(sg->sg_add_data_offset);
	u32 add_data_size = le32_to_cpu(sg->sg_add_data_size);
	struct incfs_df_signature *signature =
		kzalloc(sizeof(*signature), GFP_NOFS);
	void *buf = 0;
	ssize_t read;

	if (!df || !df->df_backing_file_context ||
	    !df->df_backing_file_context->bc_file) {
		error = -ENOENT;
		goto out;
	}

	if (!df)
		return -ENOENT;
	signature->hash_offset = le64_to_cpu(sg->sg_hash_tree_offset);
	signature->hash_size = le32_to_cpu(sg->sg_hash_tree_size);
	signature->sig_offset = le64_to_cpu(sg->sg_sig_offset);
	signature->sig_size = le32_to_cpu(sg->sg_sig_size);

	signature = kzalloc(sizeof(*signature), GFP_NOFS);
	if (!signature) {
	buf = kzalloc(signature->sig_size, GFP_NOFS);
	if (!buf) {
		error = -ENOMEM;
		goto out;
	}

	signature->add_data_offset = add_data_off;
	signature->add_data_size = add_data_size;
	signature->sig_offset = sig_off;
	signature->sig_size = sig_size;
	signature->mtree_offset = base_tree_off;
	signature->mtree_size = tree_size;
	read = incfs_kread(df->df_backing_file_context->bc_file, buf,
			   signature->sig_size, signature->sig_offset);
	if (read < 0) {
		error = read;
		goto out;
	}

	if (read != signature->sig_size) {
		error = -EINVAL;
		goto out;
	}

	hash_tree = incfs_alloc_mtree(sg->sg_hash_alg, df->df_block_count,
			range(sg->sg_root_hash, sizeof(sg->sg_root_hash)));
	hash_tree = incfs_alloc_mtree(range(buf, signature->sig_size),
				      df->df_block_count);
	if (IS_ERR(hash_tree)) {
		error = PTR_ERR(hash_tree);
		hash_tree = NULL;
		goto out;
	}
	if (hash_tree->hash_tree_area_size != tree_size) {
	if (hash_tree->hash_tree_area_size != signature->hash_size) {
		error = -EINVAL;
		goto out;
	}
	if (tree_size > 0 && handler->md_record_offset <= base_tree_off) {
	if (signature->hash_size > 0 &&
	    handler->md_record_offset <= signature->hash_offset) {
		error = -EINVAL;
		goto out;
	}
	if (handler->md_record_offset <= signature->add_data_offset ||
	    handler->md_record_offset <= signature->sig_offset) {
	if (handler->md_record_offset <= signature->sig_offset) {
		error = -EINVAL;
		goto out;
	}
	df->df_hash_tree = hash_tree;
	hash_tree = NULL;
	df->df_signature = signature;
	signature = NULL;
out:
	if (error) {
	incfs_free_mtree(hash_tree);
	kfree(signature);
	}
	kfree(buf);

	return error;
}
+1 −4
Original line number Diff line number Diff line
@@ -189,10 +189,7 @@ struct data_file {

	struct mtree *df_hash_tree;

	struct ondisk_signature *df_signature;

	/* True, if file signature has already been validated. */
	bool df_signature_validated;
	struct incfs_df_signature *df_signature;
};

struct dir_file {
+2 −20
Original line number Diff line number Diff line
@@ -299,9 +299,7 @@ int incfs_write_file_attr_to_backing_file(struct backing_file_context *bfc,
}

int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
		u8 hash_alg, u32 tree_size,
		struct mem_range root_hash, struct mem_range add_data,
		struct mem_range sig)
					  struct mem_range sig, u32 tree_size)
{
	struct incfs_file_signature sg = {};
	int result = 0;
@@ -311,8 +309,6 @@ int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,

	if (!bfc)
		return -EFAULT;
	if (root_hash.len > sizeof(sg.sg_root_hash))
		return -E2BIG;

	LOCK_REQUIRED(bfc->bc_mutex);

@@ -321,7 +317,6 @@ int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
	sg.sg_header.h_md_entry_type = INCFS_MD_SIGNATURE;
	sg.sg_header.h_record_size = cpu_to_le16(sizeof(sg));
	sg.sg_header.h_next_md_offset = cpu_to_le64(0);
	sg.sg_hash_alg = hash_alg;
	if (sig.data != NULL && sig.len > 0) {
		loff_t pos = incfs_get_end_offset(bfc->bc_file);

@@ -333,20 +328,8 @@ int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
			goto err;
	}

	if (add_data.len > 0) {
		loff_t pos = incfs_get_end_offset(bfc->bc_file);

		sg.sg_add_data_size = cpu_to_le32(add_data.len);
		sg.sg_add_data_offset = cpu_to_le64(pos);

		result = write_to_bf(bfc, add_data.data,
			add_data.len, pos, false);
		if (result)
			goto err;
	}

	tree_area_pos = incfs_get_end_offset(bfc->bc_file);
	if (hash_alg && tree_size > 0) {
	if (tree_size > 0) {
		if (tree_size > 5 * INCFS_DATA_FILE_BLOCK_SIZE) {
			/*
			 * If hash tree is big enough, it makes sense to
@@ -369,7 +352,6 @@ int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
		sg.sg_hash_tree_size = cpu_to_le32(tree_size);
		sg.sg_hash_tree_offset = cpu_to_le64(tree_area_pos);
	}
	memcpy(sg.sg_root_hash, root_hash.data, root_hash.len);

	/* Write a hash tree metadata record pointing to the hash tree above. */
	result = append_md_to_backing_file(bfc, &sg.sg_header);
+13 −32
Original line number Diff line number Diff line
@@ -217,27 +217,27 @@ struct incfs_file_attr {
	__le32 fa_crc;
} __packed;

/* Metadata record for file attribute. Type = INCFS_MD_SIGNATURE */
/* Metadata record for file signature. Type = INCFS_MD_SIGNATURE */
struct incfs_file_signature {
	struct incfs_md_header sg_header;

	__u8 sg_hash_alg; /* Value from incfs_hash_tree_algorithm */
	__le32 sg_sig_size; /* The size of the signature. */

	__le64 sg_sig_offset; /* Signature's offset in the backing file */

	__le32 sg_hash_tree_size; /* The size of the hash tree. */

	__le64 sg_hash_tree_offset; /* Hash tree offset in the backing file */

	__u8 sg_root_hash[INCFS_MAX_HASH_SIZE];

	__le32 sg_sig_size; /* The size of the pkcs7 signature. */

	__le64 sg_sig_offset; /* pkcs7 signature's offset in the backing file */

	__le32 sg_add_data_size; /* The size of the additional data. */

	__le64 sg_add_data_offset; /* Additional data's offset */
} __packed;

/* In memory version of above */
struct incfs_df_signature {
	u32 sig_size;
	u64 sig_offset;
	u32 hash_size;
	u64 hash_offset;
};

/* State of the backing file. */
struct backing_file_context {
	/* Protects writes to bc_file */
@@ -253,23 +253,6 @@ struct backing_file_context {
	loff_t bc_last_md_record_offset;
};


/* Backing file locations of things required for signature validation. */
struct ondisk_signature {

	loff_t add_data_offset; /* Additional data's offset */

	loff_t sig_offset; /* pkcs7 signature's offset in the backing file */

	loff_t mtree_offset; /* Backing file offset of the hash tree. */

	u32 add_data_size; /* The size of the additional data. */

	u32 sig_size; /* The size of the pkcs7 signature. */

	u32 mtree_size; /* The size of the hash tree. */
};

struct metadata_handler {
	loff_t md_record_offset;
	loff_t md_prev_record_offset;
@@ -319,9 +302,7 @@ int incfs_write_file_attr_to_backing_file(struct backing_file_context *bfc,
		struct mem_range value, struct incfs_file_attr *attr);

int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
		u8 hash_alg, u32 tree_size,
		struct mem_range root_hash, struct mem_range add_data,
		struct mem_range sig);
					  struct mem_range sig, u32 tree_size);

int incfs_make_empty_backing_file(struct backing_file_context *bfc,
				  incfs_uuid_t *uuid, u64 file_size);
+89 −80
Original line number Diff line number Diff line
@@ -10,70 +10,6 @@

#include "integrity.h"

int incfs_validate_pkcs7_signature(struct mem_range pkcs7_blob,
	struct mem_range root_hash, struct mem_range add_data)
{
	struct pkcs7_message *pkcs7 = NULL;
	const void *data = NULL;
	size_t data_len = 0;
	const char *p;
	int err;

	pkcs7 = pkcs7_parse_message(pkcs7_blob.data, pkcs7_blob.len);
	if (IS_ERR(pkcs7)) {
		pr_debug("PKCS#7 parsing error. ptr=%p size=%ld err=%ld\n",
			pkcs7_blob.data, pkcs7_blob.len, -PTR_ERR(pkcs7));
		return PTR_ERR(pkcs7);
	}

	err = pkcs7_get_content_data(pkcs7, &data, &data_len, NULL);
	if (err || data_len == 0 || data == NULL) {
		pr_debug("PKCS#7 message does not contain data\n");
		err = -EBADMSG;
		goto out;
	}

	if (root_hash.len == 0) {
		pr_debug("Root hash is empty.\n");
		err = -EBADMSG;
		goto out;
	}

	if (data_len != root_hash.len + add_data.len) {
		pr_debug("PKCS#7 data size doesn't match arguments.\n");
		err = -EKEYREJECTED;
		goto out;
	}

	p = data;
	if (memcmp(p, root_hash.data, root_hash.len) != 0) {
		pr_debug("Root hash mismatch.\n");
		err = -EKEYREJECTED;
		goto out;
	}
	p += root_hash.len;
	if (memcmp(p, add_data.data, add_data.len) != 0) {
		pr_debug("Additional data mismatch.\n");
		err = -EKEYREJECTED;
		goto out;
	}

	err = pkcs7_verify(pkcs7, VERIFYING_UNSPECIFIED_SIGNATURE);
	if (err)
		pr_debug("PKCS#7 signature verification error: %d\n", -err);

	/*
	 * RSA signature verification sometimes returns unexpected error codes
	 * when signature doesn't match.
	 */
	if (err == -ERANGE || err == -EINVAL)
		err = -EBADMSG;

out:
	pkcs7_free_message(pkcs7);
	return err;
}

struct incfs_hash_alg *incfs_get_hash_alg(enum incfs_hash_tree_algorithm id)
{
	static struct incfs_hash_alg sha256 = {
@@ -113,11 +49,90 @@ struct incfs_hash_alg *incfs_get_hash_alg(enum incfs_hash_tree_algorithm id)
	return result;
}

struct signature_info {
	u32 version;
	enum incfs_hash_tree_algorithm hash_algorithm;
	u8 log2_blocksize;
	struct mem_range salt;
	struct mem_range root_hash;
};

static u32 read_u32(u8 **p, u8 *top, u32 *result)
{
	if (*p + sizeof(u32) > top)
		return false;

	*result = le32_to_cpu(*(u32 *)*p);
	*p += sizeof(u32);
	return true;
}

static bool read_u8(u8 **p, u8 *top, u8 *result)
{
	if (*p + sizeof(u8) > top)
		return false;

	*result = *(u8 *)*p;
	*p += sizeof(u8);
	return true;
}

struct mtree *incfs_alloc_mtree(enum incfs_hash_tree_algorithm id,
				int data_block_count,
				struct mem_range root_hash)
static bool read_mem_range(u8 **p, u8 *top, struct mem_range *range)
{
	u32 len;

	if (!read_u32(p, top, &len) || *p + len > top)
		return false;

	range->len = len;
	range->data = *p;
	*p += len;
	return true;
}

static int incfs_parse_signature(struct mem_range signature,
				 struct signature_info *si)
{
	u8 *p = signature.data;
	u8 *top = signature.data + signature.len;
	u32 hash_section_size;

	if (signature.len > INCFS_MAX_SIGNATURE_SIZE)
		return -EINVAL;

	if (!read_u32(&p, top, &si->version) ||
	    si->version != INCFS_SIGNATURE_VERSION)
		return -EINVAL;

	if (!read_u32(&p, top, &hash_section_size) ||
	    p + hash_section_size > top)
		return -EINVAL;
	top = p + hash_section_size;

	if (!read_u32(&p, top, &si->hash_algorithm) ||
	    si->hash_algorithm != INCFS_HASH_TREE_SHA256)
		return -EINVAL;

	if (!read_u8(&p, top, &si->log2_blocksize) || si->log2_blocksize != 12)
		return -EINVAL;

	if (!read_mem_range(&p, top, &si->salt))
		return -EINVAL;

	if (!read_mem_range(&p, top, &si->root_hash))
		return -EINVAL;

	if (p != top)
		return -EINVAL;

	return 0;
}

struct mtree *incfs_alloc_mtree(struct mem_range signature,
				int data_block_count)
{
	int error;
	struct signature_info si;
	struct mtree *result = NULL;
	struct incfs_hash_alg *hash_alg = NULL;
	int hash_per_block;
@@ -129,11 +144,15 @@ struct mtree *incfs_alloc_mtree(enum incfs_hash_tree_algorithm id,
	if (data_block_count <= 0)
		return ERR_PTR(-EINVAL);

	hash_alg = incfs_get_hash_alg(id);
	error = incfs_parse_signature(signature, &si);
	if (error)
		return ERR_PTR(error);

	hash_alg = incfs_get_hash_alg(si.hash_algorithm);
	if (IS_ERR(hash_alg))
		return ERR_PTR(PTR_ERR(hash_alg));

	if (root_hash.len < hash_alg->digest_size)
	if (si.root_hash.len < hash_alg->digest_size)
		return ERR_PTR(-EINVAL);

	result = kzalloc(sizeof(*result), GFP_NOFS);
@@ -173,7 +192,7 @@ struct mtree *incfs_alloc_mtree(enum incfs_hash_tree_algorithm id,
	}

	/* Root hash is stored separately from the rest of the tree. */
	memcpy(result->root_hash, root_hash.data, hash_alg->digest_size);
	memcpy(result->root_hash, si.root_hash.data, hash_alg->digest_size);
	return result;

err:
@@ -215,13 +234,3 @@ int incfs_calc_digest(struct incfs_hash_alg *alg, struct mem_range data,
	return crypto_shash_digest(desc, data.data, data.len, digest.data);
}
void incfs_free_signature_info(struct signature_info *si)
{
	if (!si)
		return;
	kfree(si->root_hash.data);
	kfree(si->additional_data.data);
	kfree(si->signature.data);
	kfree(si);
}
Loading