File: compute_digest.c

package info (click to toggle)
fsverity-utils 1.6-1.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 316 kB
  • sloc: ansic: 2,683; sh: 324; makefile: 191
file content (332 lines) | stat: -rw-r--r-- 9,406 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// SPDX-License-Identifier: MIT
/*
 * Implementation of libfsverity_compute_digest().
 *
 * Copyright 2018 Google LLC
 * Copyright (C) 2020 Facebook
 *
 * Use of this source code is governed by an MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT.
 */

#include "lib_private.h"

#include <stdlib.h>
#include <string.h>

#define FS_VERITY_MAX_LEVELS	64

struct block_buffer {
	u32 filled;
	u8 *data;
};

/*
 * Hash a block, writing the result to the next level's pending block buffer.
 */
static void hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
			   u32 block_size, const u8 *salt, u32 salt_size)
{
	struct block_buffer *next = cur + 1;

	/* Zero-pad the block if it's shorter than block_size. */
	memset(&cur->data[cur->filled], 0, block_size - cur->filled);

	libfsverity_hash_init(hash);
	libfsverity_hash_update(hash, salt, salt_size);
	libfsverity_hash_update(hash, cur->data, block_size);
	libfsverity_hash_final(hash, &next->data[next->filled]);

	next->filled += hash->alg->digest_size;
	cur->filled = 0;
}

static bool block_is_full(const struct block_buffer *block, u32 block_size,
			  struct hash_ctx *hash)
{
	/* Would the next hash put us over the limit? */
	return block->filled + hash->alg->digest_size > block_size;
}

static int report_merkle_tree_size(const struct libfsverity_metadata_callbacks *cbs,
				   u64 size)
{
	if (cbs && cbs->merkle_tree_size) {
		int err = cbs->merkle_tree_size(cbs->ctx, size);

		if (err) {
			libfsverity_error_msg("error processing Merkle tree size");
			return err;
		}
	}
	return 0;
}

static int report_merkle_tree_block(const struct libfsverity_metadata_callbacks *cbs,
				    const struct block_buffer *block,
				    u32 block_size, u64 *level_offset)
{

	if (cbs && cbs->merkle_tree_block) {
		int err = cbs->merkle_tree_block(cbs->ctx, block->data,
						 block_size,
						 *level_offset * block_size);

		if (err) {
			libfsverity_error_msg("error processing Merkle tree block");
			return err;
		}
		(*level_offset)++;
	}
	return 0;
}

static int report_descriptor(const struct libfsverity_metadata_callbacks *cbs,
			     const void *descriptor, size_t size)
{
	if (cbs && cbs->descriptor) {
		int err = cbs->descriptor(cbs->ctx, descriptor, size);

		if (err) {
			libfsverity_error_msg("error processing fs-verity descriptor");
			return err;
		}
	}
	return 0;
}

/*
 * Compute the file's Merkle tree root hash using the given hash algorithm,
 * block size, and salt.
 */
static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
			     u64 file_size, struct hash_ctx *hash,
			     u32 block_size, const u8 *salt, u32 salt_size,
			     const struct libfsverity_metadata_callbacks *metadata_cbs,
			     u8 *root_hash)
{
	const u32 hashes_per_block = block_size / hash->alg->digest_size;
	const u32 padded_salt_size = roundup(salt_size, hash->alg->block_size);
	u8 *padded_salt = NULL;
	u64 blocks;
	int num_levels = 0;
	int level;
	u64 level_offset[FS_VERITY_MAX_LEVELS];
	struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {};
	struct block_buffer *buffers = &_buffers[1];
	u64 offset;
	int err = 0;

	/* Root hash of empty file is all 0's */
	if (file_size == 0) {
		memset(root_hash, 0, hash->alg->digest_size);
		return report_merkle_tree_size(metadata_cbs, 0);
	}

	if (salt_size != 0) {
		padded_salt = libfsverity_zalloc(padded_salt_size);
		if (!padded_salt)
			return -ENOMEM;
		memcpy(padded_salt, salt, salt_size);
	}

	/* Compute number of levels and the number of blocks in each level. */
	blocks = DIV_ROUND_UP(file_size, block_size);
	while (blocks > 1)  {
		if (WARN_ON(num_levels >= FS_VERITY_MAX_LEVELS)) {
			err = -EINVAL;
			goto out;
		}
		blocks = DIV_ROUND_UP(blocks, hashes_per_block);
		/*
		 * Temporarily use level_offset[] to store the number of blocks
		 * in each level.  It will be overwritten later.
		 */
		level_offset[num_levels++] = blocks;
	}

	/*
	 * Compute the starting block of each level, using the convention where
	 * the root level is first, i.e. the convention used by
	 * FS_IOC_READ_VERITY_METADATA.  At the same time, compute the total
	 * size of the Merkle tree.  These values are only needed for the
	 * metadata callbacks (if they were given), as the hash computation
	 * itself doesn't prescribe an ordering of the levels and doesn't
	 * prescribe any special meaning to the total size of the Merkle tree.
	 */
	offset = 0;
	for (level = num_levels - 1; level >= 0; level--) {
		blocks = level_offset[level];
		level_offset[level] = offset;
		offset += blocks;
	}
	err = report_merkle_tree_size(metadata_cbs, offset * block_size);
	if (err)
		goto out;

	/*
	 * Allocate the block buffers.  Buffer "-1" is for data blocks.
	 * Buffers 0 <= level < num_levels are for the actual tree levels.
	 * Buffer 'num_levels' is for the root hash.
	 */
	for (level = -1; level < num_levels; level++) {
		buffers[level].data = libfsverity_zalloc(block_size);
		if (!buffers[level].data) {
			err = -ENOMEM;
			goto out;
		}
	}
	buffers[num_levels].data = root_hash;

	/* Hash each data block, also hashing the tree blocks as they fill up */
	for (offset = 0; offset < file_size; offset += block_size) {
		buffers[-1].filled = min(block_size, file_size - offset);

		err = read_fn(fd, buffers[-1].data, buffers[-1].filled);
		if (err) {
			libfsverity_error_msg("error reading file");
			goto out;
		}

		hash_one_block(hash, &buffers[-1], block_size,
			       padded_salt, padded_salt_size);
		for (level = 0; level < num_levels; level++) {
			if (!block_is_full(&buffers[level], block_size, hash))
				break;
			hash_one_block(hash, &buffers[level], block_size,
				       padded_salt, padded_salt_size);
			err = report_merkle_tree_block(metadata_cbs,
						       &buffers[level],
						       block_size,
						       &level_offset[level]);
			if (err)
				goto out;
		}
	}
	/* Finish all nonempty pending tree blocks */
	for (level = 0; level < num_levels; level++) {
		if (buffers[level].filled != 0) {
			hash_one_block(hash, &buffers[level], block_size,
				       padded_salt, padded_salt_size);
			err = report_merkle_tree_block(metadata_cbs,
						       &buffers[level],
						       block_size,
						       &level_offset[level]);
			if (err)
				goto out;
		}
	}

	/* Root hash was filled by the last call to hash_one_block() */
	if (WARN_ON(buffers[num_levels].filled != hash->alg->digest_size)) {
		err = -EINVAL;
		goto out;
	}
	err = 0;
out:
	for (level = -1; level < num_levels; level++)
		free(buffers[level].data);
	free(padded_salt);
	return err;
}

LIBEXPORT int
libfsverity_compute_digest(void *fd, libfsverity_read_fn_t read_fn,
			   const struct libfsverity_merkle_tree_params *params,
			   struct libfsverity_digest **digest_ret)
{
	u32 alg_num;
	u32 block_size;
	const struct fsverity_hash_alg *hash_alg;
	struct hash_ctx *hash = NULL;
	struct libfsverity_digest *digest;
	struct fsverity_descriptor desc;
	int err;

	if (!read_fn || !params || !digest_ret) {
		libfsverity_error_msg("missing required parameters for compute_digest");
		return -EINVAL;
	}
	if (params->version != 1) {
		libfsverity_error_msg("unsupported version (%u)",
				      params->version);
		return -EINVAL;
	}

	alg_num = params->hash_algorithm ?: FS_VERITY_HASH_ALG_DEFAULT;
	block_size = params->block_size ?: FS_VERITY_BLOCK_SIZE_DEFAULT;

	if (!is_power_of_2(block_size)) {
		libfsverity_error_msg("unsupported block size (%u)",
				      block_size);
		return -EINVAL;
	}
	if (params->salt_size > sizeof(desc.salt)) {
		libfsverity_error_msg("unsupported salt size (%u)",
				      params->salt_size);
		return -EINVAL;
	}
	if (params->salt_size && !params->salt) {
		libfsverity_error_msg("salt_size specified, but salt is NULL");
		return -EINVAL;
	}
	if (!libfsverity_mem_is_zeroed(params->reserved1,
				       sizeof(params->reserved1)) ||
	    !libfsverity_mem_is_zeroed(params->reserved2,
				       sizeof(params->reserved2))) {
		libfsverity_error_msg("reserved bits set in merkle_tree_params");
		return -EINVAL;
	}

	hash_alg = libfsverity_find_hash_alg_by_num(alg_num);
	if (!hash_alg) {
		libfsverity_error_msg("unknown hash algorithm: %u", alg_num);
		return -EINVAL;
	}

	if (block_size < 2 * hash_alg->digest_size) {
		libfsverity_error_msg("block size (%u) too small for hash algorithm %s",
				      block_size, hash_alg->name);
		return -EINVAL;
	}

	hash = hash_alg->create_ctx(hash_alg);
	if (!hash)
		return -ENOMEM;

	memset(&desc, 0, sizeof(desc));
	desc.version = 1;
	desc.hash_algorithm = alg_num;
	desc.log_blocksize = ilog2(block_size);
	desc.data_size = cpu_to_le64(params->file_size);
	if (params->salt_size != 0) {
		memcpy(desc.salt, params->salt, params->salt_size);
		desc.salt_size = params->salt_size;
	}

	err = compute_root_hash(fd, read_fn, params->file_size, hash,
				block_size, params->salt, params->salt_size,
				params->metadata_callbacks, desc.root_hash);
	if (err)
		goto out;

	err = report_descriptor(params->metadata_callbacks,
				&desc, sizeof(desc));
	if (err)
		goto out;

	digest = libfsverity_zalloc(sizeof(*digest) + hash_alg->digest_size);
	if (!digest) {
		err = -ENOMEM;
		goto out;
	}
	digest->digest_algorithm = alg_num;
	digest->digest_size = hash_alg->digest_size;
	libfsverity_hash_full(hash, &desc, sizeof(desc), digest->digest);
	*digest_ret = digest;
	err = 0;
out:
	libfsverity_free_hash_ctx(hash);
	return err;
}