// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2019 Google LLC */ #include #include #include #include #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); }