This is an userspace utility that creates or verifies hashes for the
verity target.
The original utility was created by Google and it is located at
http://git.chromium.org/chromiumos/platform/dm-verity.git
The original utility has some problems:
* the code is really overengineered, they took the kernel code and built
an emulation layer in userspace that emulates some of the kernel
functions
* it dosn't use library implementations of hash functions, rather it
provides its own md5, sha1, sha256 and sha512 implementation
* it is not portable (produces bad result on big-endian machines)
This is much smaller implementation that is portable and uses the crypto
library.
This code creates compatible format with the original Google code under
these conditions:
- data block size and hash block size are 4096
- salt has exactly 32 bytes (64 hex digits)
Example use:
Create filesystem on /dev/sdc2 and fill it with some data. Block size must
be 4096
Unmount the filesystem
Run: ./verity -c /dev/sdc2 /dev/sdc3 sha256 --salt
12340000000000000000000000000000000000000000000000 00000000000000
- This creates hash tree on /dev/sdc3 and prints the root block hash
Run: dmsetup -r create verity --table "0 `blockdev --getsize /dev/sdc2`
verity 0 /dev/sdc2 /dev/sdc3 0 4096 sha256
f4c97f1f2e6b6757d033b2062268e0b6b42cbe4ff5a5fcc001 7305b145cae4de
12340000000000000000000000000000000000000000000000 00000000000000 "
(note: use the real hash reported by "verity" tool instead of f4c9...)
mount -o ro -t ext2 /dev/mapper/verity /mnt/test
Now, the device is mounted and dm-verity target is verifying the hashes.
All data integrity depends only on the root hash
(f4c97f1f2e6b6757d033b2062268e0b6b42cbe4ff5a5fcc00 17305b145cae4de) and
salt (1234000000000000000000000000000000000000000000000 000000000000000).
If either the data or hash partitions become silently corrupted and
you read invalid data, dm-verity will return -EIO.
static int data_block_size = 0;
static int hash_block_size = 0;
static long long hash_start = 0;
static long long data_blocks = 0;
static const char *salt_string = NULL;
static void calculate_positions(void)
{
unsigned long long hash_position;
int i;
hash_per_block_bits = 0;
while (((hash_block_size / digest_size) >> hash_per_block_bits) > 1)
hash_per_block_bits++;
if (!hash_per_block_bits)
exit_err("at least two hashes must fit in a hash file block");
levels = 0;
if (data_file_blocks) {
while (hash_per_block_bits * levels < 64 &&
(unsigned long long)(data_file_blocks - 1) >>
(hash_per_block_bits * levels))
levels++;
}
if (levels > DM_VERITY_MAX_LEVELS) exit_err("too many tree levels");
hash_position = hash_start * 512 / hash_block_size;
for (i = levels - 1; i >= 0; i--) {
off_t s;
hash_level_block[i] = hash_position;
s = verity_position_at_level(data_file_blocks, i);
s = (s >> hash_per_block_bits) +
!!(s & ((1 << hash_per_block_bits) - 1));
hash_level_size[i] = s;
if (hash_position + s < hash_position ||
(off_t)(hash_position + s) < 0 ||
(off_t)(hash_position + s) != hash_position + s)
exit_err("hash device offset overflow");
hash_position += s;
}
used_hash_blocks = hash_position;
}
static void create_or_verify_stream(FILE *rd, FILE *wr, int block_size, off_t blocks)
{
unsigned char *left_block = xmalloc(hash_block_size);
unsigned char *data_buffer = xmalloc(block_size);
unsigned char *read_digest = mode == MODE_VERIFY ? xmalloc(digest_size) : NULL;
off_t blocks_to_write = (blocks >> hash_per_block_bits) +
!!(blocks & ((1 << hash_per_block_bits) - 1));
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
memset(left_block, 0, hash_block_size);
while (blocks_to_write--) {
unsigned x;
unsigned left_bytes;
for (x = 0; x < 1 << hash_per_block_bits; x++) {
if (!blocks)
break;
blocks--;
if (fread(data_buffer, block_size, 1, rd) != 1)
stream_err(rd, "read");
if (EVP_DigestInit_ex(&ctx, evp, NULL) != 1)
exit_err("EVP_DigestInit_ex failed");
if (EVP_DigestUpdate(&ctx, data_buffer, block_size) != 1)
exit_err("EVP_DigestUpdate failed");
if (EVP_DigestUpdate(&ctx, salt_bytes, salt_size) != 1)
exit_err("EVP_DigestUpdate failed");
if (EVP_DigestFinal_ex(&ctx, calculated_digest, NULL) != 1)
exit_err("EVP_DigestFinal_ex failed");
if (!wr)
break;
if (mode == MODE_VERIFY) {
if (fread(read_digest, digest_size, 1, wr) != 1)
stream_err(wr, "read");
if (memcmp(read_digest, calculated_digest, digest_size)) {
retval = 1;
fprintf(stderr, "verification failed at position %lld in %s file
", (long long)ftello(rd) - block_size, rd == data_file ? "data" : "metadata");
}
} else {
if (fwrite(calculated_digest, digest_size, 1, wr) != 1)
stream_err(wr, "write");
}
}
left_bytes = hash_block_size - x * digest_size;
if (left_bytes && wr) {
if (mode == MODE_VERIFY) {
if (fread(left_block, left_bytes, 1, wr) != 1)
stream_err(wr, "read");
for (x = 0; x < left_bytes; x++) if (left_block[x]) {
retval = 1;
fprintf(stderr, "spare area is not zeroed at position %lld
", (long long)ftello(wr) - left_bytes);
}
} else {
if (fwrite(left_block, left_bytes, 1, wr) != 1)
stream_err(wr, "write");
}
}
}
if (mode != MODE_VERIFY && wr) {
if (fflush(wr)) {
perror("fflush");
exit(1);
}
if (ferror(wr)) {
stream_err(wr, "write");
}
}
if (EVP_MD_CTX_cleanup(&ctx) != 1)
exit_err("EVP_MD_CTX_cleanup failed");
free(left_block);
free(data_buffer);
if (mode == MODE_VERIFY) free(read_digest);
}
static void create_or_verify(void)
{
int i;
for (i = 0; i < levels; i++) {
block_fseek(hash_file, hash_level_block[i], hash_block_size);
if (!i) {
block_fseek(data_file, 0, data_block_size);
create_or_verify_stream(data_file, hash_file, data_block_size, data_file_blocks);
} else {
FILE *hash_file_2 = fopen(hash_device, "r");
if (!hash_file_2) {
perror(hash_device);
exit(2);
}
block_fseek(hash_file_2, hash_level_block[i - 1], hash_block_size);
create_or_verify_stream(hash_file_2, hash_file, hash_block_size, hash_level_size[i - 1]);
fclose(hash_file_2);
}
}
if ((unsigned long long)hash_start * 512 % hash_block_size) exit_err("hash start not aligned on block size");
if (data_file_blocks < data_blocks) exit_err("data file is too small");
if (data_blocks) data_file_blocks = data_blocks;
OpenSSL_add_all_digests();
evp = EVP_get_digestbyname(hash_algorithm);
if (!evp) exit_err("hash algorithm %s not found", hash_algorithm);
digest_size = EVP_MD_size(evp);