| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Test libfsverity_compute_digest(). |
| * |
| * Copyright 2020 Google LLC |
| */ |
| |
| #include "utils.h" |
| |
| #include <ctype.h> |
| #include <inttypes.h> |
| |
| struct mem_file { |
| u8 *data; |
| size_t size; |
| size_t offset; |
| }; |
| |
| static int read_fn(void *fd, void *buf, size_t count) |
| { |
| struct mem_file *f = fd; |
| |
| ASSERT(count <= f->size - f->offset); |
| memcpy(buf, &f->data[f->offset], count); |
| f->offset += count; |
| return 0; |
| } |
| |
| static int error_read_fn(void *fd __attribute__((unused)), |
| void *buf __attribute__((unused)), |
| size_t count __attribute__((unused))) |
| { |
| return -EIO; |
| } |
| |
| static const struct test_case { |
| u32 hash_algorithm; |
| u32 block_size; |
| const char *salt; |
| u64 file_size; |
| const char *digest; |
| } test_cases[] = { |
| { /* large file */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1000000, |
| .block_size = 4096, |
| .digest = "\x48\xdf\x0c\x46\x23\x29\xcd\x87" |
| "\x96\x61\xbd\x05\xb3\x9a\xa8\x1b" |
| "\x05\xcc\x16\xaf\xd2\x7a\x71\x96" |
| "\xa5\x59\xda\x83\x53\x1d\x39\xd9", |
| }, { /* small file */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 100000, |
| .block_size = 4096, |
| .digest = "\xf2\x09\x6a\x36\xc5\xcd\xca\x4f" |
| "\xa3\x3e\xe8\x85\x28\x33\x15\x0b" |
| "\xb3\x24\x99\x2e\x54\x17\xa9\xd5" |
| "\x71\xf1\xbf\xff\xf7\x3b\x9e\xfc", |
| }, { /* single-block file */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 4096, |
| .block_size = 4096, |
| .digest = "\x6a\xc3\x99\x79\x01\x6e\x3d\xdf" |
| "\x3d\x39\xff\xf6\xcb\x98\x4f\x7c" |
| "\x11\x8a\xcd\xf1\x85\x29\x19\xf5" |
| "\xc1\x00\xc4\xb1\x42\xc1\x81\x8e", |
| }, { /* tiny file */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1, |
| .block_size = 4096, |
| .digest = "\xb8\x03\x42\x95\x03\xd9\x59\x15" |
| "\x82\x9b\x29\xfd\xbc\x8b\xba\xd1" |
| "\x42\xf3\xab\xfd\x11\xb1\xca\xdf" |
| "\x55\x26\x58\x2e\x68\x5c\x05\x51", |
| }, { /* empty file */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 0, |
| .block_size = 4096, |
| .digest = "\x3d\x24\x8c\xa5\x42\xa2\x4f\xc6" |
| "\x2d\x1c\x43\xb9\x16\xea\xe5\x01" |
| "\x68\x78\xe2\x53\x3c\x88\x23\x84" |
| "\x80\xb2\x61\x28\xa1\xf1\xaf\x95", |
| }, { /* salt */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1000000, |
| .block_size = 4096, |
| .salt = "abcd", |
| .digest = "\x91\x79\x00\xb0\xd2\x99\x45\x4a" |
| "\xa3\x04\xd5\xde\xbc\x6f\x39\xe4" |
| "\xaf\x7b\x5a\xbe\x33\xbd\xbc\x56" |
| "\x8d\x5d\x8f\x1e\x5c\x4d\x86\x52", |
| }, { /* max length salt (32 bytes) */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1000000, |
| .block_size = 4096, |
| .salt = "0123456789:;<=>?@ABCDEFGHIJKLMNO", |
| .digest = "\xbc\x2d\x70\x32\x4c\x04\x8c\x22" |
| "\x0a\x2c\xb1\x90\x83\x21\x40\x86" |
| "\x3e\xb2\x68\xe6\x80\x42\x79\x39" |
| "\xe5\xd4\x67\xbe\xa5\xec\x5a\xd9", |
| }, { /* 1K block size */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1000000, |
| .block_size = 1024, |
| .digest = "\xe9\xdf\x92\x7c\x14\xfc\xb9\x61" |
| "\xd5\xf5\x1c\x66\x6d\x8a\xe4\xc1" |
| "\x4f\xe4\xff\x98\xa3\x74\xc7\x33" |
| "\xe8\x98\xd0\x0c\x9e\x74\xa8\xe3", |
| }, { /* 512-byte block size */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1000000, |
| .block_size = 512, |
| .digest = "\x03\x93\xee\x3d\xfd\x4a\x28\x96" |
| "\x6e\x2a\xf4\xe0\x7c\xfa\x5b\x03" |
| "\x2c\x30\xda\x5b\xb8\xe8\xef\x63" |
| "\xb9\xa5\x5b\xf9\x63\x26\x23\x34", |
| }, { /* 64K block size */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 1000000, |
| .block_size = 65536, |
| .digest = "\xf3\xb6\x41\x8f\x26\xd4\xd0\xe7" |
| "\x47\x28\x19\x3b\xae\x76\xf1\x5c" |
| "\xb4\xbb\x2c\xe9\x77\x74\x48\xd7" |
| "\x6b\xd8\x13\x8b\x69\xec\x61\xc2", |
| }, { /* SHA-512 */ |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA512, |
| .file_size = 1000000, |
| .block_size = 4096, |
| .salt = "abcd", |
| .digest = "\x84\x25\xc6\xd0\xc9\x4f\x84\xed" |
| "\x90\x4c\x12\x93\x68\x45\xfb\xb7" |
| "\xaf\x99\x53\x75\x37\x89\x71\x2d" |
| "\xcc\x3b\xe1\x42\xdb\x3d\x4b\x6b" |
| "\x47\xa3\x99\xad\x52\xaa\x60\x92" |
| "\x56\xce\x29\xa9\x60\xbf\x4b\xb0" |
| "\xe5\x95\xec\x38\x6c\xa5\x8c\x06" |
| "\x51\x9d\x54\x6d\xc5\xb1\x97\xbb", |
| } |
| }; |
| |
| static void fix_digest_and_print(const struct test_case *t, |
| const struct libfsverity_digest *d) |
| { |
| char alg_name[32] = {}; |
| size_t i; |
| |
| strncpy(alg_name, libfsverity_get_hash_name(t->hash_algorithm), |
| sizeof(alg_name) - 1); |
| for (i = 0; i < sizeof(alg_name) - 1; i++) |
| alg_name[i] = toupper((u8)alg_name[i]); |
| |
| printf("\t}, {\n" |
| "\t\t.hash_algorithm = FS_VERITY_HASH_ALG_%s,\n" |
| "\t\t.file_size = %" PRIu64 ",\n" |
| "\t\t.block_size = %u,\n", |
| alg_name, t->file_size, t->block_size); |
| if (t->salt != NULL) |
| printf("\t\t.salt = \"%s\",\n", t->salt); |
| for (i = 0; i < d->digest_size; i++) { |
| if (i == 0) |
| printf("\t\t.digest = \""); |
| else if (i % 8 == 0) |
| printf("\t\t\t \""); |
| printf("\\x%02x", d->digest[i]); |
| if (i + 1 == d->digest_size) |
| printf("\",\n"); |
| else if (i % 8 == 7) |
| printf("\"\n"); |
| } |
| } |
| |
| static void test_invalid_params(void) |
| { |
| struct mem_file f = { .data = (u8 *)"abcd", .size = 4 }; |
| struct libfsverity_merkle_tree_params good_params = { |
| .version = 1, |
| .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| .file_size = 4, |
| .block_size = 4096, |
| }; |
| struct libfsverity_merkle_tree_params params; |
| struct libfsverity_digest *d = NULL; |
| |
| libfsverity_set_error_callback(NULL); |
| |
| ASSERT(libfsverity_compute_digest(&f, read_fn, &good_params, &d) == 0); |
| f.offset = 0; |
| free(d); |
| d = NULL; |
| |
| /* missing required arguments */ |
| ASSERT(libfsverity_compute_digest(&f, NULL, &good_params, &d) == -EINVAL); |
| ASSERT(libfsverity_compute_digest(&f, read_fn, NULL, &d) == -EINVAL); |
| ASSERT(libfsverity_compute_digest(&f, read_fn, &good_params, NULL) == -EINVAL); |
| |
| /* bad version */ |
| params = good_params; |
| params.version = 0; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params.version = 1000; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| |
| /* bad hash_algorithm */ |
| params = good_params; |
| params.hash_algorithm = 0; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params.hash_algorithm = 1000; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| |
| /* bad block_size */ |
| params = good_params; |
| params.block_size = 0; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params.block_size = 1; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params.block_size = 4097; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| |
| /* bad salt_size */ |
| params = good_params; |
| params.salt_size = 1000; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params.salt = (u8 *)""; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| |
| /* bad reserved fields */ |
| params = good_params; |
| params.reserved1[0] = 1; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params = good_params; |
| params.reserved1[ARRAY_SIZE(params.reserved1) - 1] = 1; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params = good_params; |
| params.reserved2[0] = 1; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| params = good_params; |
| params.reserved2[ARRAY_SIZE(params.reserved2) - 1] = 1; |
| ASSERT(libfsverity_compute_digest(&f, read_fn, ¶ms, &d) == -EINVAL); |
| |
| /* error reading file */ |
| ASSERT(libfsverity_compute_digest(&f, error_read_fn, &good_params, &d) == -EIO); |
| |
| ASSERT(d == NULL); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| const bool update = (argc == 2 && !strcmp(argv[1], "--update")); |
| size_t i; |
| struct mem_file f = {}; |
| struct libfsverity_merkle_tree_params params; |
| struct libfsverity_digest *d; |
| int err; |
| |
| install_libfsverity_error_handler(); |
| |
| for (i = 0; i < ARRAY_SIZE(test_cases); i++) |
| f.size = max(f.size, test_cases[i].file_size); |
| |
| f.data = xmalloc(f.size); |
| for (i = 0; i < f.size; i++) |
| f.data[i] = (i % 11) + (i % 439) + (i % 1103); |
| |
| for (i = 0; i < ARRAY_SIZE(test_cases); i++) { |
| |
| memset(¶ms, 0, sizeof(params)); |
| params.version = 1; |
| params.hash_algorithm = test_cases[i].hash_algorithm; |
| params.file_size = test_cases[i].file_size; |
| params.block_size = test_cases[i].block_size; |
| if (test_cases[i].salt) { |
| params.salt = (const u8 *)test_cases[i].salt; |
| params.salt_size = strlen(test_cases[i].salt); |
| } |
| |
| f.size = test_cases[i].file_size; |
| f.offset = 0; |
| |
| err = libfsverity_compute_digest(&f, read_fn, ¶ms, &d); |
| ASSERT(err == 0); |
| |
| ASSERT(d->digest_algorithm == test_cases[i].hash_algorithm); |
| ASSERT(d->digest_size == |
| libfsverity_get_digest_size(test_cases[i].hash_algorithm)); |
| if (update) |
| fix_digest_and_print(&test_cases[i], d); |
| else |
| ASSERT(!memcmp(d->digest, test_cases[i].digest, |
| d->digest_size)); |
| free(d); |
| d = NULL; |
| } |
| free(f.data); |
| if (update) { |
| printf("\t}\n"); |
| return 1; |
| } |
| |
| test_invalid_params(); |
| printf("test_compute_digest passed\n"); |
| return 0; |
| } |