1ced56a00Sopenharmony_ci// SPDX-License-Identifier: MIT 2ced56a00Sopenharmony_ci/* 3ced56a00Sopenharmony_ci * Implementation of libfsverity_compute_digest(). 4ced56a00Sopenharmony_ci * 5ced56a00Sopenharmony_ci * Copyright 2018 Google LLC 6ced56a00Sopenharmony_ci * Copyright (C) 2020 Facebook 7ced56a00Sopenharmony_ci * 8ced56a00Sopenharmony_ci * Use of this source code is governed by an MIT-style 9ced56a00Sopenharmony_ci * license that can be found in the LICENSE file or at 10ced56a00Sopenharmony_ci * https://opensource.org/licenses/MIT. 11ced56a00Sopenharmony_ci */ 12ced56a00Sopenharmony_ci 13ced56a00Sopenharmony_ci#include "lib_private.h" 14ced56a00Sopenharmony_ci 15ced56a00Sopenharmony_ci#include <stdlib.h> 16ced56a00Sopenharmony_ci#include <string.h> 17ced56a00Sopenharmony_ci 18ced56a00Sopenharmony_ci#define FS_VERITY_MAX_LEVELS 64 19ced56a00Sopenharmony_ci 20ced56a00Sopenharmony_cistruct block_buffer { 21ced56a00Sopenharmony_ci u32 filled; 22ced56a00Sopenharmony_ci u8 *data; 23ced56a00Sopenharmony_ci}; 24ced56a00Sopenharmony_ci 25ced56a00Sopenharmony_ci/* 26ced56a00Sopenharmony_ci * Hash a block, writing the result to the next level's pending block buffer. 27ced56a00Sopenharmony_ci */ 28ced56a00Sopenharmony_cistatic void hash_one_block(struct hash_ctx *hash, struct block_buffer *cur, 29ced56a00Sopenharmony_ci u32 block_size, const u8 *salt, u32 salt_size) 30ced56a00Sopenharmony_ci{ 31ced56a00Sopenharmony_ci struct block_buffer *next = cur + 1; 32ced56a00Sopenharmony_ci 33ced56a00Sopenharmony_ci /* Zero-pad the block if it's shorter than block_size. */ 34ced56a00Sopenharmony_ci memset(&cur->data[cur->filled], 0, block_size - cur->filled); 35ced56a00Sopenharmony_ci 36ced56a00Sopenharmony_ci libfsverity_hash_init(hash); 37ced56a00Sopenharmony_ci libfsverity_hash_update(hash, salt, salt_size); 38ced56a00Sopenharmony_ci libfsverity_hash_update(hash, cur->data, block_size); 39ced56a00Sopenharmony_ci libfsverity_hash_final(hash, &next->data[next->filled]); 40ced56a00Sopenharmony_ci 41ced56a00Sopenharmony_ci next->filled += hash->alg->digest_size; 42ced56a00Sopenharmony_ci cur->filled = 0; 43ced56a00Sopenharmony_ci} 44ced56a00Sopenharmony_ci 45ced56a00Sopenharmony_cistatic bool block_is_full(const struct block_buffer *block, u32 block_size, 46ced56a00Sopenharmony_ci struct hash_ctx *hash) 47ced56a00Sopenharmony_ci{ 48ced56a00Sopenharmony_ci /* Would the next hash put us over the limit? */ 49ced56a00Sopenharmony_ci return block->filled + hash->alg->digest_size > block_size; 50ced56a00Sopenharmony_ci} 51ced56a00Sopenharmony_ci 52ced56a00Sopenharmony_cistatic int report_merkle_tree_size(const struct libfsverity_metadata_callbacks *cbs, 53ced56a00Sopenharmony_ci u64 size) 54ced56a00Sopenharmony_ci{ 55ced56a00Sopenharmony_ci if (cbs && cbs->merkle_tree_size) { 56ced56a00Sopenharmony_ci int err = cbs->merkle_tree_size(cbs->ctx, size); 57ced56a00Sopenharmony_ci 58ced56a00Sopenharmony_ci if (err) { 59ced56a00Sopenharmony_ci libfsverity_error_msg("error processing Merkle tree size"); 60ced56a00Sopenharmony_ci return err; 61ced56a00Sopenharmony_ci } 62ced56a00Sopenharmony_ci } 63ced56a00Sopenharmony_ci return 0; 64ced56a00Sopenharmony_ci} 65ced56a00Sopenharmony_ci 66ced56a00Sopenharmony_cistatic int report_merkle_tree_block(const struct libfsverity_metadata_callbacks *cbs, 67ced56a00Sopenharmony_ci const struct block_buffer *block, 68ced56a00Sopenharmony_ci u32 block_size, u64 *level_offset) 69ced56a00Sopenharmony_ci{ 70ced56a00Sopenharmony_ci 71ced56a00Sopenharmony_ci if (cbs && cbs->merkle_tree_block) { 72ced56a00Sopenharmony_ci int err = cbs->merkle_tree_block(cbs->ctx, block->data, 73ced56a00Sopenharmony_ci block_size, 74ced56a00Sopenharmony_ci *level_offset * block_size); 75ced56a00Sopenharmony_ci 76ced56a00Sopenharmony_ci if (err) { 77ced56a00Sopenharmony_ci libfsverity_error_msg("error processing Merkle tree block"); 78ced56a00Sopenharmony_ci return err; 79ced56a00Sopenharmony_ci } 80ced56a00Sopenharmony_ci (*level_offset)++; 81ced56a00Sopenharmony_ci } 82ced56a00Sopenharmony_ci return 0; 83ced56a00Sopenharmony_ci} 84ced56a00Sopenharmony_ci 85ced56a00Sopenharmony_cistatic int report_descriptor(const struct libfsverity_metadata_callbacks *cbs, 86ced56a00Sopenharmony_ci const void *descriptor, size_t size) 87ced56a00Sopenharmony_ci{ 88ced56a00Sopenharmony_ci if (cbs && cbs->descriptor) { 89ced56a00Sopenharmony_ci int err = cbs->descriptor(cbs->ctx, descriptor, size); 90ced56a00Sopenharmony_ci 91ced56a00Sopenharmony_ci if (err) { 92ced56a00Sopenharmony_ci libfsverity_error_msg("error processing fs-verity descriptor"); 93ced56a00Sopenharmony_ci return err; 94ced56a00Sopenharmony_ci } 95ced56a00Sopenharmony_ci } 96ced56a00Sopenharmony_ci return 0; 97ced56a00Sopenharmony_ci} 98ced56a00Sopenharmony_ci 99ced56a00Sopenharmony_ci/* 100ced56a00Sopenharmony_ci * Compute the file's Merkle tree root hash using the given hash algorithm, 101ced56a00Sopenharmony_ci * block size, and salt. 102ced56a00Sopenharmony_ci */ 103ced56a00Sopenharmony_cistatic int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn, 104ced56a00Sopenharmony_ci u64 file_size, struct hash_ctx *hash, 105ced56a00Sopenharmony_ci u32 block_size, const u8 *salt, u32 salt_size, 106ced56a00Sopenharmony_ci const struct libfsverity_metadata_callbacks *metadata_cbs, 107ced56a00Sopenharmony_ci u8 *root_hash) 108ced56a00Sopenharmony_ci{ 109ced56a00Sopenharmony_ci const u32 hashes_per_block = block_size / hash->alg->digest_size; 110ced56a00Sopenharmony_ci const u32 padded_salt_size = roundup(salt_size, hash->alg->block_size); 111ced56a00Sopenharmony_ci u8 *padded_salt = NULL; 112ced56a00Sopenharmony_ci u64 blocks; 113ced56a00Sopenharmony_ci int num_levels = 0; 114ced56a00Sopenharmony_ci int level; 115ced56a00Sopenharmony_ci u64 level_offset[FS_VERITY_MAX_LEVELS]; 116ced56a00Sopenharmony_ci struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {}; 117ced56a00Sopenharmony_ci struct block_buffer *buffers = &_buffers[1]; 118ced56a00Sopenharmony_ci u64 offset; 119ced56a00Sopenharmony_ci int err = 0; 120ced56a00Sopenharmony_ci 121ced56a00Sopenharmony_ci /* Root hash of empty file is all 0's */ 122ced56a00Sopenharmony_ci if (file_size == 0) { 123ced56a00Sopenharmony_ci memset(root_hash, 0, hash->alg->digest_size); 124ced56a00Sopenharmony_ci return report_merkle_tree_size(metadata_cbs, 0); 125ced56a00Sopenharmony_ci } 126ced56a00Sopenharmony_ci 127ced56a00Sopenharmony_ci if (salt_size != 0) { 128ced56a00Sopenharmony_ci padded_salt = libfsverity_zalloc(padded_salt_size); 129ced56a00Sopenharmony_ci if (!padded_salt) 130ced56a00Sopenharmony_ci return -ENOMEM; 131ced56a00Sopenharmony_ci memcpy(padded_salt, salt, salt_size); 132ced56a00Sopenharmony_ci } 133ced56a00Sopenharmony_ci 134ced56a00Sopenharmony_ci /* Compute number of levels and the number of blocks in each level. */ 135ced56a00Sopenharmony_ci blocks = DIV_ROUND_UP(file_size, block_size); 136ced56a00Sopenharmony_ci while (blocks > 1) { 137ced56a00Sopenharmony_ci if (WARN_ON(num_levels >= FS_VERITY_MAX_LEVELS)) { 138ced56a00Sopenharmony_ci err = -EINVAL; 139ced56a00Sopenharmony_ci goto out; 140ced56a00Sopenharmony_ci } 141ced56a00Sopenharmony_ci blocks = DIV_ROUND_UP(blocks, hashes_per_block); 142ced56a00Sopenharmony_ci /* 143ced56a00Sopenharmony_ci * Temporarily use level_offset[] to store the number of blocks 144ced56a00Sopenharmony_ci * in each level. It will be overwritten later. 145ced56a00Sopenharmony_ci */ 146ced56a00Sopenharmony_ci level_offset[num_levels++] = blocks; 147ced56a00Sopenharmony_ci } 148ced56a00Sopenharmony_ci 149ced56a00Sopenharmony_ci /* 150ced56a00Sopenharmony_ci * Compute the starting block of each level, using the convention where 151ced56a00Sopenharmony_ci * the root level is first, i.e. the convention used by 152ced56a00Sopenharmony_ci * FS_IOC_READ_VERITY_METADATA. At the same time, compute the total 153ced56a00Sopenharmony_ci * size of the Merkle tree. These values are only needed for the 154ced56a00Sopenharmony_ci * metadata callbacks (if they were given), as the hash computation 155ced56a00Sopenharmony_ci * itself doesn't prescribe an ordering of the levels and doesn't 156ced56a00Sopenharmony_ci * prescribe any special meaning to the total size of the Merkle tree. 157ced56a00Sopenharmony_ci */ 158ced56a00Sopenharmony_ci offset = 0; 159ced56a00Sopenharmony_ci for (level = num_levels - 1; level >= 0; level--) { 160ced56a00Sopenharmony_ci blocks = level_offset[level]; 161ced56a00Sopenharmony_ci level_offset[level] = offset; 162ced56a00Sopenharmony_ci offset += blocks; 163ced56a00Sopenharmony_ci } 164ced56a00Sopenharmony_ci err = report_merkle_tree_size(metadata_cbs, offset * block_size); 165ced56a00Sopenharmony_ci if (err) 166ced56a00Sopenharmony_ci goto out; 167ced56a00Sopenharmony_ci 168ced56a00Sopenharmony_ci /* 169ced56a00Sopenharmony_ci * Allocate the block buffers. Buffer "-1" is for data blocks. 170ced56a00Sopenharmony_ci * Buffers 0 <= level < num_levels are for the actual tree levels. 171ced56a00Sopenharmony_ci * Buffer 'num_levels' is for the root hash. 172ced56a00Sopenharmony_ci */ 173ced56a00Sopenharmony_ci for (level = -1; level < num_levels; level++) { 174ced56a00Sopenharmony_ci buffers[level].data = libfsverity_zalloc(block_size); 175ced56a00Sopenharmony_ci if (!buffers[level].data) { 176ced56a00Sopenharmony_ci err = -ENOMEM; 177ced56a00Sopenharmony_ci goto out; 178ced56a00Sopenharmony_ci } 179ced56a00Sopenharmony_ci } 180ced56a00Sopenharmony_ci buffers[num_levels].data = root_hash; 181ced56a00Sopenharmony_ci 182ced56a00Sopenharmony_ci /* Hash each data block, also hashing the tree blocks as they fill up */ 183ced56a00Sopenharmony_ci for (offset = 0; offset < file_size; offset += block_size) { 184ced56a00Sopenharmony_ci buffers[-1].filled = min(block_size, file_size - offset); 185ced56a00Sopenharmony_ci 186ced56a00Sopenharmony_ci err = read_fn(fd, buffers[-1].data, buffers[-1].filled); 187ced56a00Sopenharmony_ci if (err) { 188ced56a00Sopenharmony_ci libfsverity_error_msg("error reading file"); 189ced56a00Sopenharmony_ci goto out; 190ced56a00Sopenharmony_ci } 191ced56a00Sopenharmony_ci 192ced56a00Sopenharmony_ci hash_one_block(hash, &buffers[-1], block_size, 193ced56a00Sopenharmony_ci padded_salt, padded_salt_size); 194ced56a00Sopenharmony_ci for (level = 0; level < num_levels; level++) { 195ced56a00Sopenharmony_ci if (!block_is_full(&buffers[level], block_size, hash)) 196ced56a00Sopenharmony_ci break; 197ced56a00Sopenharmony_ci hash_one_block(hash, &buffers[level], block_size, 198ced56a00Sopenharmony_ci padded_salt, padded_salt_size); 199ced56a00Sopenharmony_ci err = report_merkle_tree_block(metadata_cbs, 200ced56a00Sopenharmony_ci &buffers[level], 201ced56a00Sopenharmony_ci block_size, 202ced56a00Sopenharmony_ci &level_offset[level]); 203ced56a00Sopenharmony_ci if (err) 204ced56a00Sopenharmony_ci goto out; 205ced56a00Sopenharmony_ci } 206ced56a00Sopenharmony_ci } 207ced56a00Sopenharmony_ci /* Finish all nonempty pending tree blocks */ 208ced56a00Sopenharmony_ci for (level = 0; level < num_levels; level++) { 209ced56a00Sopenharmony_ci if (buffers[level].filled != 0) { 210ced56a00Sopenharmony_ci hash_one_block(hash, &buffers[level], block_size, 211ced56a00Sopenharmony_ci padded_salt, padded_salt_size); 212ced56a00Sopenharmony_ci err = report_merkle_tree_block(metadata_cbs, 213ced56a00Sopenharmony_ci &buffers[level], 214ced56a00Sopenharmony_ci block_size, 215ced56a00Sopenharmony_ci &level_offset[level]); 216ced56a00Sopenharmony_ci if (err) 217ced56a00Sopenharmony_ci goto out; 218ced56a00Sopenharmony_ci } 219ced56a00Sopenharmony_ci } 220ced56a00Sopenharmony_ci 221ced56a00Sopenharmony_ci /* Root hash was filled by the last call to hash_one_block() */ 222ced56a00Sopenharmony_ci if (WARN_ON(buffers[num_levels].filled != hash->alg->digest_size)) { 223ced56a00Sopenharmony_ci err = -EINVAL; 224ced56a00Sopenharmony_ci goto out; 225ced56a00Sopenharmony_ci } 226ced56a00Sopenharmony_ci err = 0; 227ced56a00Sopenharmony_ciout: 228ced56a00Sopenharmony_ci for (level = -1; level < num_levels; level++) 229ced56a00Sopenharmony_ci free(buffers[level].data); 230ced56a00Sopenharmony_ci free(padded_salt); 231ced56a00Sopenharmony_ci return err; 232ced56a00Sopenharmony_ci} 233ced56a00Sopenharmony_ci 234ced56a00Sopenharmony_ciLIBEXPORT int 235ced56a00Sopenharmony_cilibfsverity_compute_digest(void *fd, libfsverity_read_fn_t read_fn, 236ced56a00Sopenharmony_ci const struct libfsverity_merkle_tree_params *params, 237ced56a00Sopenharmony_ci struct libfsverity_digest **digest_ret) 238ced56a00Sopenharmony_ci{ 239ced56a00Sopenharmony_ci u32 alg_num; 240ced56a00Sopenharmony_ci u32 block_size; 241ced56a00Sopenharmony_ci const struct fsverity_hash_alg *hash_alg; 242ced56a00Sopenharmony_ci struct hash_ctx *hash = NULL; 243ced56a00Sopenharmony_ci struct libfsverity_digest *digest; 244ced56a00Sopenharmony_ci struct fsverity_descriptor desc; 245ced56a00Sopenharmony_ci int err; 246ced56a00Sopenharmony_ci 247ced56a00Sopenharmony_ci if (!read_fn || !params || !digest_ret) { 248ced56a00Sopenharmony_ci libfsverity_error_msg("missing required parameters for compute_digest"); 249ced56a00Sopenharmony_ci return -EINVAL; 250ced56a00Sopenharmony_ci } 251ced56a00Sopenharmony_ci if (params->version != 1) { 252ced56a00Sopenharmony_ci libfsverity_error_msg("unsupported version (%u)", 253ced56a00Sopenharmony_ci params->version); 254ced56a00Sopenharmony_ci return -EINVAL; 255ced56a00Sopenharmony_ci } 256ced56a00Sopenharmony_ci 257ced56a00Sopenharmony_ci alg_num = params->hash_algorithm ?: FS_VERITY_HASH_ALG_DEFAULT; 258ced56a00Sopenharmony_ci block_size = params->block_size ?: FS_VERITY_BLOCK_SIZE_DEFAULT; 259ced56a00Sopenharmony_ci 260ced56a00Sopenharmony_ci if (!is_power_of_2(block_size)) { 261ced56a00Sopenharmony_ci libfsverity_error_msg("unsupported block size (%u)", 262ced56a00Sopenharmony_ci block_size); 263ced56a00Sopenharmony_ci return -EINVAL; 264ced56a00Sopenharmony_ci } 265ced56a00Sopenharmony_ci if (params->salt_size > sizeof(desc.salt)) { 266ced56a00Sopenharmony_ci libfsverity_error_msg("unsupported salt size (%u)", 267ced56a00Sopenharmony_ci params->salt_size); 268ced56a00Sopenharmony_ci return -EINVAL; 269ced56a00Sopenharmony_ci } 270ced56a00Sopenharmony_ci if (params->salt_size && !params->salt) { 271ced56a00Sopenharmony_ci libfsverity_error_msg("salt_size specified, but salt is NULL"); 272ced56a00Sopenharmony_ci return -EINVAL; 273ced56a00Sopenharmony_ci } 274ced56a00Sopenharmony_ci if (!libfsverity_mem_is_zeroed(params->reserved1, 275ced56a00Sopenharmony_ci sizeof(params->reserved1)) || 276ced56a00Sopenharmony_ci !libfsverity_mem_is_zeroed(params->reserved2, 277ced56a00Sopenharmony_ci sizeof(params->reserved2))) { 278ced56a00Sopenharmony_ci libfsverity_error_msg("reserved bits set in merkle_tree_params"); 279ced56a00Sopenharmony_ci return -EINVAL; 280ced56a00Sopenharmony_ci } 281ced56a00Sopenharmony_ci 282ced56a00Sopenharmony_ci hash_alg = libfsverity_find_hash_alg_by_num(alg_num); 283ced56a00Sopenharmony_ci if (!hash_alg) { 284ced56a00Sopenharmony_ci libfsverity_error_msg("unknown hash algorithm: %u", alg_num); 285ced56a00Sopenharmony_ci return -EINVAL; 286ced56a00Sopenharmony_ci } 287ced56a00Sopenharmony_ci 288ced56a00Sopenharmony_ci if (block_size < 2 * hash_alg->digest_size) { 289ced56a00Sopenharmony_ci libfsverity_error_msg("block size (%u) too small for hash algorithm %s", 290ced56a00Sopenharmony_ci block_size, hash_alg->name); 291ced56a00Sopenharmony_ci return -EINVAL; 292ced56a00Sopenharmony_ci } 293ced56a00Sopenharmony_ci 294ced56a00Sopenharmony_ci hash = hash_alg->create_ctx(hash_alg); 295ced56a00Sopenharmony_ci if (!hash) 296ced56a00Sopenharmony_ci return -ENOMEM; 297ced56a00Sopenharmony_ci 298ced56a00Sopenharmony_ci memset(&desc, 0, sizeof(desc)); 299ced56a00Sopenharmony_ci desc.version = 1; 300ced56a00Sopenharmony_ci desc.hash_algorithm = alg_num; 301ced56a00Sopenharmony_ci desc.log_blocksize = ilog2(block_size); 302ced56a00Sopenharmony_ci desc.data_size = cpu_to_le64(params->file_size); 303ced56a00Sopenharmony_ci if (params->salt_size != 0) { 304ced56a00Sopenharmony_ci memcpy(desc.salt, params->salt, params->salt_size); 305ced56a00Sopenharmony_ci desc.salt_size = params->salt_size; 306ced56a00Sopenharmony_ci } 307ced56a00Sopenharmony_ci 308ced56a00Sopenharmony_ci err = compute_root_hash(fd, read_fn, params->file_size, hash, 309ced56a00Sopenharmony_ci block_size, params->salt, params->salt_size, 310ced56a00Sopenharmony_ci params->metadata_callbacks, desc.root_hash); 311ced56a00Sopenharmony_ci if (err) 312ced56a00Sopenharmony_ci goto out; 313ced56a00Sopenharmony_ci 314ced56a00Sopenharmony_ci err = report_descriptor(params->metadata_callbacks, 315ced56a00Sopenharmony_ci &desc, sizeof(desc)); 316ced56a00Sopenharmony_ci if (err) 317ced56a00Sopenharmony_ci goto out; 318ced56a00Sopenharmony_ci 319ced56a00Sopenharmony_ci digest = libfsverity_zalloc(sizeof(*digest) + hash_alg->digest_size); 320ced56a00Sopenharmony_ci if (!digest) { 321ced56a00Sopenharmony_ci err = -ENOMEM; 322ced56a00Sopenharmony_ci goto out; 323ced56a00Sopenharmony_ci } 324ced56a00Sopenharmony_ci digest->digest_algorithm = alg_num; 325ced56a00Sopenharmony_ci digest->digest_size = hash_alg->digest_size; 326ced56a00Sopenharmony_ci libfsverity_hash_full(hash, &desc, sizeof(desc), digest->digest); 327ced56a00Sopenharmony_ci *digest_ret = digest; 328ced56a00Sopenharmony_ci err = 0; 329ced56a00Sopenharmony_ciout: 330ced56a00Sopenharmony_ci libfsverity_free_hash_ctx(hash); 331ced56a00Sopenharmony_ci return err; 332ced56a00Sopenharmony_ci} 333