| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2019 Google LLC | |
| */ | |
| #include <crypto/sha2.h> | |
| #include <crypto/hash.h> | |
| #include <linux/err.h> | |
| #include <linux/version.h> | |
| #include "integrity.h" | |
| struct incfs_hash_alg *incfs_get_hash_alg(enum incfs_hash_tree_algorithm id) | |
| { | |
| static struct incfs_hash_alg sha256 = { | |
| .name = "sha256", | |
| .digest_size = SHA256_DIGEST_SIZE, | |
| .id = INCFS_HASH_TREE_SHA256 | |
| }; | |
| struct incfs_hash_alg *result = NULL; | |
| struct crypto_shash *shash; | |
| if (id == INCFS_HASH_TREE_SHA256) { | |
| BUILD_BUG_ON(INCFS_MAX_HASH_SIZE < SHA256_DIGEST_SIZE); | |
| result = &sha256; | |
| } | |
| if (result == NULL) | |
| return ERR_PTR(-ENOENT); | |
| /* pairs with cmpxchg_release() below */ | |
| shash = smp_load_acquire(&result->shash); | |
| if (shash) | |
| return result; | |
| shash = crypto_alloc_shash(result->name, 0, 0); | |
| if (IS_ERR(shash)) { | |
| int err = PTR_ERR(shash); | |
| pr_err("Can't allocate hash alg %s, error code:%d", | |
| result->name, err); | |
| return ERR_PTR(err); | |
| } | |
| /* pairs with smp_load_acquire() above */ | |
| if (cmpxchg_release(&result->shash, NULL, shash) != NULL) | |
| crypto_free_shash(shash); | |
| 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 bool read_u32(u8 **p, u8 *top, u32 *result) | |
| { | |
| if (*p + sizeof(u32) > top) | |
| return false; | |
| *result = le32_to_cpu(*(__le32 *)*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; | |
| } | |
| 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; | |
| int lvl; | |
| int total_blocks = 0; | |
| int blocks_in_level[INCFS_MAX_MTREE_LEVELS]; | |
| int blocks = data_block_count; | |
| if (data_block_count <= 0) | |
| return ERR_PTR(-EINVAL); | |
| 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 (si.root_hash.len < hash_alg->digest_size) | |
| return ERR_PTR(-EINVAL); | |
| result = kzalloc(sizeof(*result), GFP_NOFS); | |
| if (!result) | |
| return ERR_PTR(-ENOMEM); | |
| result->alg = hash_alg; | |
| hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / result->alg->digest_size; | |
| /* Calculating tree geometry. */ | |
| /* First pass: calculate how many blocks in each tree level. */ | |
| for (lvl = 0; blocks > 1; lvl++) { | |
| if (lvl >= INCFS_MAX_MTREE_LEVELS) { | |
| pr_err("incfs: too much data in mtree"); | |
| goto err; | |
| } | |
| blocks = (blocks + hash_per_block - 1) / hash_per_block; | |
| blocks_in_level[lvl] = blocks; | |
| total_blocks += blocks; | |
| } | |
| result->depth = lvl; | |
| result->hash_tree_area_size = total_blocks * INCFS_DATA_FILE_BLOCK_SIZE; | |
| if (result->hash_tree_area_size > INCFS_MAX_HASH_AREA_SIZE) | |
| goto err; | |
| blocks = 0; | |
| /* Second pass: calculate offset of each level. 0th level goes last. */ | |
| for (lvl = 0; lvl < result->depth; lvl++) { | |
| u32 suboffset; | |
| blocks += blocks_in_level[lvl]; | |
| suboffset = (total_blocks - blocks) | |
| * INCFS_DATA_FILE_BLOCK_SIZE; | |
| result->hash_level_suboffset[lvl] = suboffset; | |
| } | |
| /* Root hash is stored separately from the rest of the tree. */ | |
| memcpy(result->root_hash, si.root_hash.data, hash_alg->digest_size); | |
| return result; | |
| err: | |
| kfree(result); | |
| return ERR_PTR(-E2BIG); | |
| } | |
| void incfs_free_mtree(struct mtree *tree) | |
| { | |
| kfree(tree); | |
| } | |
| int incfs_calc_digest(struct incfs_hash_alg *alg, struct mem_range data, | |
| struct mem_range digest) | |
| { | |
| SHASH_DESC_ON_STACK(desc, alg->shash); | |
| if (!alg || !alg->shash || !data.data || !digest.data) | |
| return -EFAULT; | |
| if (alg->digest_size > digest.len) | |
| return -EINVAL; | |
| desc->tfm = alg->shash; | |
| if (data.len < INCFS_DATA_FILE_BLOCK_SIZE) { | |
| int err; | |
| void *buf = kzalloc(INCFS_DATA_FILE_BLOCK_SIZE, GFP_NOFS); | |
| if (!buf) | |
| return -ENOMEM; | |
| memcpy(buf, data.data, data.len); | |
| err = crypto_shash_digest(desc, buf, INCFS_DATA_FILE_BLOCK_SIZE, | |
| digest.data); | |
| kfree(buf); | |
| return err; | |
| } | |
| return crypto_shash_digest(desc, data.data, data.len, digest.data); | |
| } | |