FAQ Search Today's Posts Mark Forums Read
» Video Reviews

» Linux Archive

Linux-archive is a website aiming to archive linux email lists and to make them easily accessible for linux users/developers.


» Sponsor

» Partners

» Sponsor

Go Back   Linux Archive > Redhat > Device-mapper Development

 
 
LinkBack Thread Tools
 
Old 03-04-2012, 06:35 PM
Mikulas Patocka
 
Default userspace hashing utility for dm-verity

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.

Mikulas

---

/* link with -lpopt -lcrypto */

#define _FILE_OFFSET_BITS 64

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <popt.h>
#include <openssl/evp.h>

#define DEFAULT_BLOCK_SIZE 4096
#define DM_VERITY_MAX_LEVELS 63

#define MODE_VERIFY 0
#define MODE_CREATE 1

static int mode = -1;

static const char *data_device;
static const char *hash_device;
static const char *hash_algorithm;
static const char *root_hash;

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 FILE *data_file;
static FILE *hash_file;

static off_t data_file_blocks;
static off_t hash_file_blocks;
static off_t used_hash_blocks;

static const EVP_MD *evp;

static unsigned char *root_hash_bytes;
static unsigned char *calculated_digest;

static unsigned char *salt_bytes;
static unsigned salt_size;

static unsigned digest_size;
static unsigned char levels;
static unsigned char hash_per_block_bits;

static off_t hash_level_block[DM_VERITY_MAX_LEVELS];
static off_t hash_level_size[DM_VERITY_MAX_LEVELS];

static int retval = 0;

static void help(poptContext popt_context,
enum poptCallbackReason reason,
struct poptOption *key,
const char *arg,
void *data)
{
poptPrintHelp(popt_context, stdout, 0);
exit(0);
}

static struct poptOption popt_help_options[] = {
{ NULL, 0, POPT_ARG_CALLBACK, help, 0, NULL, NULL },
{ "help", '?', POPT_ARG_NONE, NULL, 0, "Show help", NULL },
POPT_TABLEEND
};

static struct poptOption popt_options[] = {
{ NULL, '', POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, NULL, NULL },
{ "create", 'c', POPT_ARG_VAL, &mode, MODE_CREATE, "Create hash", NULL },
{ "verify", 'v', POPT_ARG_VAL, &mode, MODE_VERIFY, "Verify integrity", NULL },
{ "data-block-size", 0, POPT_ARG_INT, &data_block_size, 0, "Block size on the data device", "bytes" },
{ "hash-block-size", 0, POPT_ARG_INT, &hash_block_size, 0, "Block size on the hash device", "bytes" },
{ "hash-start", 0, POPT_ARG_LONGLONG, &hash_start, 0, "Starting sector on the hash device", "sectors" },
{ "data-blocks", 0, POPT_ARG_LONGLONG, &data_blocks, 0, "The number of blocks in the data file", "blocks" },
{ "salt", 0, POPT_ARG_STRING, &salt_string, 0, "Salt", "hex string" },
POPT_TABLEEND
};

static void exit_err(const char *msg, ...)
{
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fputc('
', stderr);
exit(2);
}

static void stream_err(FILE *f, const char *msg)
{
if (ferror(f)) {
perror(msg);
exit(2);
} else if (feof(f)) {
exit_err("eof on %s", msg);
} else {
exit_err("unknown error on %s", msg);
}
}

static void *xmalloc(size_t s)
{
void *ptr = malloc(s);
if (!ptr) exit_err("out of memory");
return ptr;
}

static off_t get_size(FILE *f, const char *name)
{
struct stat st;
int h = fileno(f);
if (h < 0) {
perror("fileno");
exit(2);
}
if (fstat(h, &st)) {
perror("fstat");
exit(2);
}
if (S_ISREG(st.st_mode)) {
return st.st_size;
} else if (S_ISBLK(st.st_mode)) {
unsigned long long size64;
unsigned long sizeul;
if (!ioctl(h, BLKGETSIZE64, &size64)) {
return_size64:
if ((off_t)size64 < 0 || (off_t)size64 != size64) {
size_overflow:
exit_err("%s: device size overflow", name);
}
return size64;
}
if (!ioctl(h, BLKGETSIZE, &sizeul)) {
size64 = (unsigned long long)sizeul * 512;
if (size64 / 512 != sizeul) goto size_overflow;
goto return_size64;
}
perror("BLKGETSIZE");
exit(2);
} else {
exit_err("%s is not a file or a block device", name);
}
return -1; /* never reached, shut up warning */
}

static void block_fseek(FILE *f, off_t block, int block_size)
{
unsigned long long pos = (unsigned long long)block * block_size;
if (pos / block_size != block ||
(off_t)pos < 0 ||
(off_t)pos != pos)
exit_err("seek position overflow");
if (fseeko(f, pos, SEEK_SET)) {
perror("fseek");
exit(2);
}
}

static off_t verity_position_at_level(off_t block, int level)
{
return block >> (level * hash_per_block_bits);
}

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 (levels) {
block_fseek(hash_file, hash_level_block[levels - 1], hash_block_size);
create_or_verify_stream(hash_file, NULL, hash_block_size, 1);
} else {
block_fseek(data_file, 0, data_block_size);
create_or_verify_stream(data_file, NULL, data_block_size, data_file_blocks);
}

if (mode == MODE_VERIFY) {
if (memcmp(calculated_digest, root_hash_bytes, digest_size)) {
fprintf(stderr, "verification failed in the root block
");
retval = 1;
}
if (!retval)
fprintf(stderr, "hash successfully verified
");
} else {
if (fsync(fileno(hash_file))) {
perror("fsync");
exit(1);
}
printf("hash device size: %llu
", (unsigned long long)used_hash_blocks * hash_block_size);
printf("data block size %u, hash block size %u, %u tree levels
", data_block_size, hash_block_size, levels);
printf("root hash: ");
for (i = 0; i < digest_size; i++) printf("%02x", calculated_digest[i]);
printf("
");
printf("device mapper target line: 0 %llu verity 0 %s %s %llu %u %s ",
(unsigned long long)data_file_blocks * data_block_size / 512,
data_device,
hash_device,
hash_start,
data_block_size,
hash_algorithm
);
for (i = 0; i < digest_size; i++) printf("%02x", calculated_digest[i]);
printf(" ");
if (!salt_size) printf("-");
else for (i = 0; i < salt_size; i++) printf("%02x", salt_bytes[i]);
if (hash_block_size != data_block_size) printf(" %u", hash_block_size);
printf("
");
if (data_block_size == 4096 && hash_block_size == 4096 && salt_size == 32)
printf("compatible with the original Google code
");
else {
printf("incompatible with the original Google code:
");
if (!(data_block_size == 4096 && hash_block_size == 4096)) printf(" data and hash block size must be 4096
");
if (!(salt_size == 32)) printf(" salt must have exactly 32 bytes (64 hex digits)
");
}
}
}

static void get_hex(const char *string, unsigned char **result, size_t len, const char *description)
{
size_t rl = strlen(string);
unsigned u;
if (strspn(string, "0123456789ABCDEFabcdef") != rl)
exit_err("invalid %s", description);
if (rl != len * 2)
exit_err("invalid length of %s", description);
*result = xmalloc(len);
memset(*result, 0, len);
for (u = 0; u < rl; u++) {
unsigned char c = (string[u] & 15) + (string[u] > '9' ? 9 : 0);
(*result)[u / 2] |= c << (((u & 1) ^ 1) << 2);
}
}

int main(int argc, const char **argv)
{
poptContext popt_context;
int r;
const char *s;

popt_context = poptGetContext("verity", argc, argv, popt_options, 0);

poptSetOtherOptionHelp(popt_context, "[-c | -v] <data device> <hash device> <algorithm> [<root hash> if verifying] [OPTION...]");

if (argc <= 1) {
poptPrintHelp(popt_context, stdout, 0);
exit(1);
}

r = poptGetNextOpt(popt_context);
if (r < -1) exit_err("bad option %s", poptBadOption(popt_context, 0));

if (mode < 0) exit_err("verify or create mode not specified");

if (!data_block_size) data_block_size = DEFAULT_BLOCK_SIZE;
if (!hash_block_size) hash_block_size = data_block_size;

if (data_block_size <= 0 || (data_block_size & (data_block_size - 1)))
exit_err("invalid data block size");

if (hash_block_size <= 0 || (hash_block_size & (hash_block_size - 1)))
exit_err("invalid hash block size");

if (hash_start < 0 ||
(unsigned long long)hash_start * 512 / 512 != hash_start) exit_err("invalid hash start");
if (data_blocks < 0 || (off_t)data_blocks < 0 || (off_t)data_blocks != data_blocks) exit_err("invalid number of data blocks");

data_device = poptGetArg(popt_context);
if (!data_device) exit_err("data device is missing");

hash_device = poptGetArg(popt_context);
if (!hash_device) exit_err("metadata device is missing");

hash_algorithm = poptGetArg(popt_context);
if (!hash_algorithm) exit_err("hash algorithm not specified");

if (mode == MODE_VERIFY) {
root_hash = poptGetArg(popt_context);
if (!root_hash) exit_err("root hash not specified");
}

s = poptGetArg(popt_context);
if (s) exit_err("extra argument %s", s);

data_file = fopen(data_device, "r");
if (!data_file) {
perror(data_device);
exit(2);
}

hash_file = fopen(hash_device, mode == MODE_VERIFY ? "r" : "r+");
if (!hash_file && errno == ENOENT && mode != MODE_VERIFY)
hash_file = fopen(hash_device, "w+");
if (!hash_file) {
perror(hash_device);
exit(2);
}

data_file_blocks = get_size(data_file, data_device) / data_block_size;
hash_file_blocks = get_size(hash_file, hash_device) / hash_block_size;

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);

salt_size = 0;
if (salt_string && *salt_string) {
salt_size = strlen(salt_string) / 2;
get_hex(salt_string, &salt_bytes, salt_size, "salt");
}

calculated_digest = xmalloc(digest_size);

if (mode == MODE_VERIFY) {
get_hex(root_hash, &root_hash_bytes, digest_size, "root_hash");
}

calculate_positions();

create_or_verify();

fclose(data_file);
fclose(hash_file);

if (salt_size)
free(salt_bytes);
free(calculated_digest);
if (mode == MODE_VERIFY)
free(root_hash_bytes);
poptFreeContext(popt_context);

return retval;
}

--
dm-devel mailing list
dm-devel@redhat.com
https://www.redhat.com/mailman/listinfo/dm-devel
 

Thread Tools




All times are GMT. The time now is 06:53 AM.

VBulletin, Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.
Content Relevant URLs by vBSEO ©2007, Crawlability, Inc.
Copyright 2007 - 2008, www.linux-archive.org