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