diff --git a/configure.in b/configure.in index 96af3e08..7d92dd15 100644 --- a/configure.in +++ b/configure.in @@ -351,6 +351,7 @@ lib/libcryptsetup.pc lib/crypto_backend/Makefile lib/luks1/Makefile lib/loopaes/Makefile +lib/verity/Makefile src/Makefile po/Makefile.in man/Makefile diff --git a/lib/Makefile.am b/lib/Makefile.am index 4e16ea46..9855c7f5 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = crypto_backend luks1 loopaes +SUBDIRS = crypto_backend luks1 loopaes verity moduledir = $(libdir)/cryptsetup @@ -10,6 +10,7 @@ INCLUDES = \ -I$(top_srcdir)/lib/crypto_backend \ -I$(top_srcdir)/lib/luks1 \ -I$(top_srcdir)/lib/loopaes \ + -I$(top_srcdir)/lib/verity \ -DDATADIR=\""$(datadir)"\" \ -DLIBDIR=\""$(libdir)"\" \ -DPREFIX=\""$(prefix)"\" \ @@ -24,7 +25,8 @@ lib_LTLIBRARIES = libcryptsetup.la common_ldadd = \ crypto_backend/libcrypto_backend.la \ luks1/libluks1.la \ - loopaes/libloopaes.la + loopaes/libloopaes.la \ + verity/libverity.la libcryptsetup_la_DEPENDENCIES = $(common_ldadd) libcryptsetup.sym diff --git a/lib/internal.h b/lib/internal.h index 1469c4ed..9b2459b9 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -38,6 +38,8 @@ #include "utils_fips.h" #include "crypto_backend.h" +#include "libcryptsetup.h" + /* to silent gcc -Wcast-qual for const cast */ #define CONST_CAST(x) (x)(uintptr_t) diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index e4b384f3..f281a9bc 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -301,6 +301,8 @@ int crypt_memory_lock(struct crypt_device *cd, int lock); #define CRYPT_LUKS1 "LUKS1" /** loop-AES compatibility mode */ #define CRYPT_LOOPAES "LOOPAES" +/** dm-verity mode */ +#define CRYPT_VERITY "VERITY" /** * Get device type @@ -351,6 +353,33 @@ struct crypt_params_loopaes { uint64_t skip; /**< IV offset / initialization sector */ }; +/** + * + * Structure used as parameter for dm-verity device type + * + * @see crypt_format, crypt_load + * + */ +/** No on-disk header (only hashes) */ +#define CRYPT_VERITY_NO_HEADER (1 << 0) +/** Verity hash in userspace before activation */ +#define CRYPT_VERITY_CHECK_HASH (1 << 1) +/** Create hash - format hash device */ +#define CRYPT_VERITY_CREATE_HASH (1 << 2) + +struct crypt_params_verity { + const char *hash_name; /**< hash function */ + const char *data_device; /**< data_device (CRYPT_VERITY_CREATE_HASH) */ + const char *salt; /**< salt */ + uint64_t salt_size; /**< salt size (in bytes) */ + uint32_t data_block_size; /**< data block size (in bytes) */ + uint32_t hash_block_size; /**< hash block size (in bytes) */ + uint64_t data_size; /**< data area size (in data blocks) */ + uint64_t hash_area_offset; /**< hash/header offset (in bytes) */ + uint32_t version; /**< in-kernel hash version */ + uint32_t flags; /**< CRYPT_VERITY* flags */ +}; + /** @} */ /** diff --git a/lib/libdevmapper.c b/lib/libdevmapper.c index 7510dec5..0174f1fc 100644 --- a/lib/libdevmapper.c +++ b/lib/libdevmapper.c @@ -28,12 +28,12 @@ #include #include "internal.h" -#include "luks.h" #define DM_UUID_LEN 129 #define DM_UUID_PREFIX "CRYPT-" #define DM_UUID_PREFIX_LEN 6 #define DM_CRYPT_TARGET "crypt" +#define DM_VERITY_TARGET "verity" #define RETRY_COUNT 5 /* Set if dm-crypt version was probed */ @@ -241,7 +241,7 @@ static void hex_key(char *hexkey, size_t key_size, const char *key) sprintf(&hexkey[i * 2], "%02x", (unsigned char)key[i]); } -static char *get_params(struct crypt_dm_active_device *dmd) +static char *get_dm_crypt_params(struct crypt_dm_active_device *dmd) { int r, max_size, null_cipher = 0; char *params, *hexkey; @@ -284,6 +284,47 @@ out: crypt_safe_free(hexkey); return params; } +static char *get_dm_verity_params(struct crypt_params_verity *vp, + struct crypt_dm_active_verity *dmd) +{ + int max_size, r; + char *params = NULL, *hexroot = NULL, *hexsalt = NULL; + + hexroot = crypt_safe_alloc(dmd->root_hash_size * 2 + 1); + if (!hexroot) + goto out; + hex_key(hexroot, dmd->root_hash_size, dmd->root_hash); + + hexsalt = crypt_safe_alloc(vp->salt_size * 2 + 1); + if (!hexsalt) + goto out; + hex_key(hexsalt, vp->salt_size, vp->salt); + + max_size = strlen(hexroot) + strlen(hexsalt) + + strlen(dmd->data_device) + strlen(dmd->hash_device) + + strlen(vp->hash_name) + 128; + + params = crypt_safe_alloc(max_size); + if (!params) + goto out; + + r = snprintf(params, max_size, + "%u %s %s %u %u %" PRIu64 " %" PRIu64 " %s %s %s", + vp->version, dmd->data_device, dmd->hash_device, + vp->data_block_size, vp->hash_block_size, + vp->data_size, dmd->hash_offset, + vp->hash_name, hexroot, hexsalt); + if (r < 0 || r >= max_size) { + crypt_safe_free(params); + params = NULL; + } + log_dbg("TABLE: %s", params); +out: + crypt_safe_free(hexroot); + crypt_safe_free(hexsalt); + return params; + +} /* DM helpers */ static int _dm_simple(int task, const char *name, int udev_wait) @@ -423,25 +464,20 @@ static void dm_prepare_uuid(const char *name, const char *type, const char *uuid log_err(NULL, _("DM-UUID for device %s was truncated.\n"), name); } -int dm_create_device(const char *name, - const char *type, - struct crypt_dm_active_device *dmd, - int reload) +static int _dm_create_device(const char *name, const char *type, + const char *device, uint32_t flags, + const char *uuid, uint64_t size, + char *params, int reload) { struct dm_task *dmt = NULL; struct dm_info dmi; - char *params = NULL; char dev_uuid[DM_UUID_LEN] = {0}; int r = -EINVAL; uint32_t read_ahead = 0; uint32_t cookie = 0; uint16_t udev_flags = 0; - params = get_params(dmd); - if (!params) - goto out_no_removal; - - if (dmd->flags & CRYPT_ACTIVATE_PRIVATE) + if (flags & CRYPT_ACTIVATE_PRIVATE) udev_flags = CRYPT_TEMP_UDEV_FLAGS; /* All devices must have DM_UUID, only resize on old device is exception */ @@ -452,7 +488,7 @@ int dm_create_device(const char *name, if (!dm_task_set_name(dmt, name)) goto out_no_removal; } else { - dm_prepare_uuid(name, type, dmd->uuid, dev_uuid, sizeof(dev_uuid)); + dm_prepare_uuid(name, type, uuid, dev_uuid, sizeof(dev_uuid)); if (!(dmt = dm_task_create(DM_DEVICE_CREATE))) goto out_no_removal; @@ -469,13 +505,15 @@ int dm_create_device(const char *name, if ((dm_flags() & DM_SECURE_SUPPORTED) && !dm_task_secure_data(dmt)) goto out_no_removal; - if ((dmd->flags & CRYPT_ACTIVATE_READONLY) && !dm_task_set_ro(dmt)) + if ((flags & CRYPT_ACTIVATE_READONLY) && !dm_task_set_ro(dmt)) goto out_no_removal; - if (!dm_task_add_target(dmt, 0, dmd->size, DM_CRYPT_TARGET, params)) + + if (!dm_task_add_target(dmt, 0, size, + !strcmp("VERITY", type) ? DM_VERITY_TARGET : DM_CRYPT_TARGET, params)) goto out_no_removal; #ifdef DM_READ_AHEAD_MINIMUM_FLAG - if (device_read_ahead(dmd->device, &read_ahead) && + if (device_read_ahead(device, &read_ahead) && !dm_task_set_read_ahead(dmt, read_ahead, DM_READ_AHEAD_MINIMUM_FLAG)) goto out_no_removal; #endif @@ -489,7 +527,7 @@ int dm_create_device(const char *name, goto out; if (!dm_task_set_name(dmt, name)) goto out; - if (dmd->uuid && !dm_task_set_uuid(dmt, dev_uuid)) + if (uuid && !dm_task_set_uuid(dmt, dev_uuid)) goto out; if (_dm_use_udev() && !_dm_task_set_cookie(dmt, &cookie, udev_flags)) goto out; @@ -523,11 +561,41 @@ out_no_removal: return r; } -static int dm_status_dmi(const char *name, struct dm_info *dmi) +int dm_create_device(const char *name, + const char *type, + struct crypt_dm_active_device *dmd, + int reload) +{ + char *table_params = NULL; + + table_params = get_dm_crypt_params(dmd); + if (!table_params) + return -EINVAL; + + return _dm_create_device(name, type, dmd->device, dmd->flags, + dmd->uuid, dmd->size, table_params, reload); +} + +int dm_create_verity(const char *name, + struct crypt_params_verity *params, + struct crypt_dm_active_verity *dmd) +{ + char *table_params = NULL; + + table_params = get_dm_verity_params(params, dmd); + if (!table_params) + return -EINVAL; + + return _dm_create_device(name, CRYPT_VERITY, dmd->data_device, dmd->flags, + NULL, dmd->size, table_params, 0); +} + +static int dm_status_dmi(const char *name, struct dm_info *dmi, + const char *target, char **status_line) { struct dm_task *dmt; uint64_t start, length; - char *target_type, *params; + char *target_type, *params = NULL; void *next = NULL; int r = -EINVAL; @@ -550,12 +618,15 @@ static int dm_status_dmi(const char *name, struct dm_info *dmi) next = dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); - if (!target_type || strcmp(target_type, DM_CRYPT_TARGET) != 0 || + if (!target_type || strcmp(target_type, target) != 0 || start != 0 || next) r = -EINVAL; else r = 0; out: + if (!r && status_line && !(*status_line = strdup(params))) + r = -ENOMEM; + if (dmt) dm_task_destroy(dmt); @@ -567,7 +638,7 @@ int dm_status_device(const char *name) int r; struct dm_info dmi; - r = dm_status_dmi(name, &dmi); + r = dm_status_dmi(name, &dmi, DM_CRYPT_TARGET, NULL); if (r < 0) return r; @@ -579,13 +650,32 @@ int dm_status_suspended(const char *name) int r; struct dm_info dmi; - r = dm_status_dmi(name, &dmi); + r = dm_status_dmi(name, &dmi, DM_CRYPT_TARGET, NULL); if (r < 0) return r; return dmi.suspended ? 1 : 0; } +int dm_status_verity_ok(const char *name) +{ + int r; + struct dm_info dmi; + char *status_line = NULL; + + r = dm_status_dmi(name, &dmi, DM_VERITY_TARGET, &status_line); + if (r < 0 || !status_line) { + free(status_line); + return r; + } + + log_dbg("Verity volume %s status is %s.", name, status_line ?: ""); + r = status_line[0] == 'V' ? 1 : 0; + free(status_line); + + return r; +} + int dm_query_device(const char *name, uint32_t get_flags, struct crypt_dm_active_device *dmd) { @@ -731,6 +821,14 @@ out: return r; } +int dm_query_verity(const char *name, + struct crypt_dm_active_verity *dmd) +{ + int r = -EINVAL; + + return r; +} + static int _dm_message(const char *name, const char *msg) { int r = 0; diff --git a/lib/setup.c b/lib/setup.c index fa9b174e..3e6f4cbc 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -29,6 +29,7 @@ #include "libcryptsetup.h" #include "luks.h" #include "loopaes.h" +#include "verity.h" #include "internal.h" struct crypt_device { @@ -64,6 +65,12 @@ struct crypt_device { char *loopaes_uuid; unsigned int loopaes_key_size; + /* used in CRYPT_VERITY */ + struct crypt_params_verity verity_hdr; + uint32_t verity_flags; + char *verity_root_hash; + uint64_t verity_root_hash_size; + /* callbacks definitions */ void (*log)(int level, const char *msg, void *usrptr); void *log_usrptr; @@ -222,6 +229,11 @@ static int isLOOPAES(const char *type) return (type && !strcmp(CRYPT_LOOPAES, type)); } +static int isVERITY(const char *type) +{ + return (type && !strcmp(CRYPT_VERITY, type)); +} + /* keyslot helpers */ static int keyslot_verify_or_find_empty(struct crypt_device *cd, int *keyslot) { @@ -583,7 +595,7 @@ int crypt_set_data_device(struct crypt_device *cd, const char *device) log_dbg("Setting ciphertext data device to %s.", device ?: "(none)"); - if (!isLUKS(cd->type)) { + if (!isLUKS(cd->type) && !isVERITY(cd->type)) { log_err(cd, _("This operation is not supported for this device type.\n")); return -EINVAL; } @@ -630,6 +642,37 @@ static int _crypt_load_luks1(struct crypt_device *cd, int require_header, int re return r; } +static int _crypt_load_verity(struct crypt_device *cd, struct crypt_params_verity *params) +{ + int r; + size_t sb_offset = 0; + + r = init_crypto(cd); + if (r < 0) + return r; + + if (params) + sb_offset = params->hash_area_offset; + + r = VERITY_read_sb(cd, mdata_device(cd), sb_offset, &cd->verity_hdr); + if (r < 0) + return r; + + if (params) + cd->verity_flags = params->flags; + + if (params && params->data_device && + (r = crypt_set_data_device(cd, params->data_device)) < 0) + return r; + + cd->verity_root_hash_size = crypt_hash_size(cd->verity_hdr.hash_name); + + if (!cd->type && !(cd->type = strdup(CRYPT_VERITY))) + return -ENOMEM; + + return r; +} + int crypt_init_by_name_and_header(struct crypt_device **cd, const char *name, const char *header_device) @@ -909,6 +952,85 @@ static int _crypt_format_loopaes(struct crypt_device *cd, return 0; } +static int _crypt_format_verity(struct crypt_device *cd, + struct crypt_params_verity *params) +{ + int r = 0; + uint64_t data_device_size; + + if (!mdata_device(cd)) { + log_err(cd, _("Can't format VERITY without device.\n")); + return -EINVAL; + } + + if (!params || !params->data_device) + return -EINVAL; + + if (params->version > 1) + return -EINVAL; + + /* set dat device */ + cd->type = CRYPT_VERITY; + r = crypt_set_data_device(cd, params->data_device); + cd->type = NULL; + if (r) + return r; + if (!params->data_size) { + r = device_size(params->data_device, &data_device_size); + if (r < 0) + return r; + + cd->verity_hdr.data_size = data_device_size / params->data_block_size; + } else + cd->verity_hdr.data_size = params->data_size; + + + cd->verity_root_hash_size = crypt_hash_size(params->hash_name); + if (!cd->verity_root_hash_size) + return -EINVAL; + + cd->verity_flags = params->flags; + cd->verity_root_hash = malloc(cd->verity_root_hash_size); + if (!cd->verity_root_hash) + return -ENOMEM; + + cd->verity_hdr.hash_name = strdup(params->hash_name); + cd->verity_hdr.data_device = NULL; + cd->verity_hdr.data_block_size = params->data_block_size; + cd->verity_hdr.hash_block_size = params->hash_block_size; + cd->verity_hdr.hash_area_offset = params->hash_area_offset; + cd->verity_hdr.version = params->version; + cd->verity_hdr.flags = params->flags; + cd->verity_hdr.salt_size = params->salt_size; + cd->verity_hdr.salt = malloc(params->salt_size); + if (params->salt) + memcpy(CONST_CAST(char*)cd->verity_hdr.salt, params->salt, + params->salt_size); + else + r = crypt_random_get(cd, CONST_CAST(char*)cd->verity_hdr.salt, + params->salt_size, CRYPT_RND_SALT); + if (r) + goto out; + + log_dbg("Creating verity hash on device %s.", mdata_device(cd)); + r = VERITY_create(cd, &cd->verity_hdr, cd->device, mdata_device(cd), + cd->verity_root_hash, cd->verity_root_hash_size); + if (r) + goto out; + + r = VERITY_write_sb(cd, mdata_device(cd), + cd->verity_hdr.hash_area_offset, + &cd->verity_hdr); +out: + if (r) { + free(cd->verity_root_hash); + free(CONST_CAST(char*)cd->verity_hdr.hash_name); + free(CONST_CAST(char*)cd->verity_hdr.salt); + } + + return r; +} + int crypt_format(struct crypt_device *cd, const char *type, const char *cipher, @@ -942,6 +1064,8 @@ int crypt_format(struct crypt_device *cd, uuid, volume_key, volume_key_size, params); else if (isLOOPAES(type)) r = _crypt_format_loopaes(cd, cipher, uuid, volume_key_size, params); + else if (isVERITY(type)) + r = _crypt_format_verity(cd, params); else { /* FIXME: allow plugins here? */ log_err(cd, _("Unknown crypt device type %s requested.\n"), type); @@ -961,7 +1085,7 @@ int crypt_format(struct crypt_device *cd, int crypt_load(struct crypt_device *cd, const char *requested_type, - void *params __attribute__((unused))) + void *params) { int r; @@ -971,15 +1095,22 @@ int crypt_load(struct crypt_device *cd, if (!mdata_device(cd)) return -EINVAL; - if (requested_type && !isLUKS(requested_type)) + if (!requested_type || isLUKS(requested_type)) { + if (cd->type && !isLUKS(cd->type)) { + log_dbg("Context is already initialised to type %s", cd->type); + return -EINVAL; + } + + r = _crypt_load_luks1(cd, 1, 0); + } else if (isVERITY(requested_type)) { + if (cd->type && !isVERITY(cd->type)) { + log_dbg("Context is already initialised to type %s", cd->type); + return -EINVAL; + } + r = _crypt_load_verity(cd, params); + } else return -EINVAL; - if (cd->type && !isLUKS(cd->type)) { - log_dbg("Context is already initialised to type %s", cd->type); - return -EINVAL; - } - - r = _crypt_load_luks1(cd, 1, 0); if (r < 0) return r; @@ -1159,6 +1290,11 @@ void crypt_free(struct crypt_device *cd) free(cd->loopaes_cipher); free(cd->loopaes_uuid); + /* used in verity device only */ + free(CONST_CAST(void*)cd->verity_hdr.hash_name); + free(CONST_CAST(void*)cd->verity_hdr.salt); + free(cd->verity_root_hash); + free(cd); } } @@ -1783,6 +1919,27 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, if (!r && name) r = LUKS1_activate(cd, name, vk, flags); + } else if (isVERITY(cd->type)) { + /* volume_key == root hash */ + if (!volume_key || !volume_key_size) { + log_err(cd, _("Incorrect root hash specified for verity device.\n")); + return -EINVAL; + } + + r = VERITY_activate(cd, name, mdata_device(cd), + volume_key, volume_key_size, + &cd->verity_hdr, cd->verity_flags); + + if (r == -EPERM) { + free(cd->verity_root_hash); + cd->verity_root_hash = NULL; + } if (!r) { + cd->verity_root_hash_size = volume_key_size; + if (!cd->verity_root_hash) + cd->verity_root_hash = malloc(volume_key_size); + if (cd->verity_root_hash) + memcpy(cd->verity_root_hash, volume_key, volume_key_size); + } } else log_err(cd, _("Device type is not properly initialised.\n")); @@ -1968,20 +2125,16 @@ crypt_status_info crypt_status(struct crypt_device *cd, const char *name) return CRYPT_INACTIVE; } -static void hexprintICB(struct crypt_device *cd, char *d, int n) +static void hexprint(struct crypt_device *cd, const char *d, int n, const char *sep) { int i; for(i = 0; i < n; i++) - log_std(cd, "%02hhx ", (char)d[i]); + log_std(cd, "%02hhx%s", (const char)d[i], sep); } -int crypt_dump(struct crypt_device *cd) +static int _luks_dump(struct crypt_device *cd) { int i; - if (!isLUKS(cd->type)) { //FIXME - log_err(cd, _("This operation is supported only for LUKS device.\n")); - return -EINVAL; - } log_std(cd, "LUKS header information for %s\n\n", mdata_device(cd)); log_std(cd, "Version: \t%d\n", cd->hdr.version); @@ -1991,12 +2144,12 @@ int crypt_dump(struct crypt_device *cd) log_std(cd, "Payload offset:\t%d\n", cd->hdr.payloadOffset); log_std(cd, "MK bits: \t%d\n", cd->hdr.keyBytes * 8); log_std(cd, "MK digest: \t"); - hexprintICB(cd, cd->hdr.mkDigest, LUKS_DIGESTSIZE); + hexprint(cd, cd->hdr.mkDigest, LUKS_DIGESTSIZE, " "); log_std(cd, "\n"); log_std(cd, "MK salt: \t"); - hexprintICB(cd, cd->hdr.mkDigestSalt, LUKS_SALTSIZE/2); + hexprint(cd, cd->hdr.mkDigestSalt, LUKS_SALTSIZE/2, " "); log_std(cd, "\n \t"); - hexprintICB(cd, cd->hdr.mkDigestSalt+LUKS_SALTSIZE/2, LUKS_SALTSIZE/2); + hexprint(cd, cd->hdr.mkDigestSalt+LUKS_SALTSIZE/2, LUKS_SALTSIZE/2, " "); log_std(cd, "\n"); log_std(cd, "MK iterations: \t%d\n", cd->hdr.mkDigestIterations); log_std(cd, "UUID: \t%s\n\n", cd->hdr.uuid); @@ -2006,11 +2159,11 @@ int crypt_dump(struct crypt_device *cd) log_std(cd, "\tIterations: \t%d\n", cd->hdr.keyblock[i].passwordIterations); log_std(cd, "\tSalt: \t"); - hexprintICB(cd, cd->hdr.keyblock[i].passwordSalt, - LUKS_SALTSIZE/2); + hexprint(cd, cd->hdr.keyblock[i].passwordSalt, + LUKS_SALTSIZE/2, " "); log_std(cd, "\n\t \t"); - hexprintICB(cd, cd->hdr.keyblock[i].passwordSalt + - LUKS_SALTSIZE/2, LUKS_SALTSIZE/2); + hexprint(cd, cd->hdr.keyblock[i].passwordSalt + + LUKS_SALTSIZE/2, LUKS_SALTSIZE/2, " "); log_std(cd, "\n"); log_std(cd, "\tKey material offset:\t%d\n", @@ -2021,10 +2174,42 @@ int crypt_dump(struct crypt_device *cd) else log_std(cd, "Key Slot %d: DISABLED\n", i); } - return 0; } +static int _verity_dump(struct crypt_device *cd) +{ + log_std(cd, "VERITY header information for %s\n", mdata_device(cd)); + log_std(cd, "Version: \t%u\n", cd->verity_hdr.version); + log_std(cd, "Data blocks: \t%" PRIu64 "\n", cd->verity_hdr.data_size); + log_std(cd, "Data block size: \t%u\n", cd->verity_hdr.data_block_size); + log_std(cd, "Hash block size: \t%u\n", cd->verity_hdr.hash_block_size); + log_std(cd, "Hash algorithm: \t%s\n", cd->verity_hdr.hash_name); + log_std(cd, "Salt: \t"); + if (cd->verity_hdr.salt_size) + hexprint(cd, cd->verity_hdr.salt, cd->verity_hdr.salt_size, ""); + else + log_std(cd, "-"); + log_std(cd, "\n"); + if (cd->verity_root_hash) { + log_std(cd, "Root hash: \t"); + hexprint(cd, cd->verity_root_hash, cd->verity_root_hash_size, ""); + log_std(cd, "\n"); + } + return 0; +} + +int crypt_dump(struct crypt_device *cd) +{ + if (isLUKS(cd->type)) + return _luks_dump(cd); + else if (isVERITY(cd->type)) + return _verity_dump(cd); + + log_err(cd, _("Dump operation is not supported for this device type.\n")); + return -EINVAL; +} + const char *crypt_get_cipher(struct crypt_device *cd) { if (isPLAIN(cd->type)) @@ -2072,7 +2257,6 @@ const char *crypt_get_device_name(struct crypt_device *cd) return cd->device; } - int crypt_get_volume_key_size(struct crypt_device *cd) { if (isPLAIN(cd->type)) @@ -2084,6 +2268,9 @@ int crypt_get_volume_key_size(struct crypt_device *cd) if (isLOOPAES(cd->type)) return cd->loopaes_key_size; + if (isVERITY(cd->type)) + return cd->verity_root_hash_size; + return 0; } diff --git a/lib/utils_dm.h b/lib/utils_dm.h index 39e5710c..85e09414 100644 --- a/lib/utils_dm.h +++ b/lib/utils_dm.h @@ -27,6 +27,7 @@ struct crypt_device; struct volume_key; +struct crypt_params_verity; /* Device mapper backend - kernel support flags */ #define DM_KEY_WIPE_SUPPORTED (1 << 0) /* key wipe message */ @@ -57,18 +58,36 @@ struct crypt_dm_active_device { uint32_t flags; /* activation flags */ }; +struct crypt_dm_active_verity { + const char *data_device; + const char *hash_device; + + const char *root_hash; + size_t root_hash_size; + + uint64_t hash_offset; /* hash offset (not header) */ + uint64_t size; /* active device size */ + uint32_t flags; /* activation flags */ +}; + const char *dm_get_dir(void); int dm_init(struct crypt_device *context, int check_kernel); void dm_exit(void); int dm_remove_device(const char *name, int force, uint64_t size); int dm_status_device(const char *name); int dm_status_suspended(const char *name); +int dm_status_verity_ok(const char *name); int dm_query_device(const char *name, uint32_t get_flags, struct crypt_dm_active_device *dmd); +int dm_query_verity(const char *name, + struct crypt_dm_active_verity *dmd); int dm_create_device(const char *name, const char *type, struct crypt_dm_active_device *dmd, int reload); +int dm_create_verity(const char *name, + struct crypt_params_verity *params, + struct crypt_dm_active_verity *dmd); int dm_suspend_and_wipe_key(const char *name); int dm_resume_and_reinstate_key(const char *name, size_t key_size, diff --git a/lib/verity/Makefile.am b/lib/verity/Makefile.am new file mode 100644 index 00000000..e229cfb8 --- /dev/null +++ b/lib/verity/Makefile.am @@ -0,0 +1,16 @@ +moduledir = $(libdir)/cryptsetup + +noinst_LTLIBRARIES = libverity.la + +libverity_la_CFLAGS = -Wall @CRYPTO_CFLAGS@ + +libverity_la_SOURCES = \ + verity_hash.c \ + verity.c \ + verity.h + +INCLUDES = -D_GNU_SOURCE \ + -D_LARGEFILE64_SOURCE \ + -D_FILE_OFFSET_BITS=64 \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/lib/crypto_backend diff --git a/lib/verity/verity.c b/lib/verity/verity.c new file mode 100644 index 00000000..f66b9b96 --- /dev/null +++ b/lib/verity/verity.c @@ -0,0 +1,203 @@ +/* + * dm-verity volume handling + * + * Copyright (C) 2012, Red Hat, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libcryptsetup.h" +#include "verity.h" +#include "internal.h" + +/* Read verity superblock from disk */ +int VERITY_read_sb(struct crypt_device *cd, + const char *device, + size_t sb_offset, + struct crypt_params_verity *params) +{ + struct verity_sb sb = {}; + ssize_t hdr_size = sizeof(struct verity_sb); + int devfd = 0; + long long sb_data_blocks; + + log_dbg("Reading VERITY header of size %d on device %s, offset %u.", + sizeof(struct verity_sb), device, (unsigned)sb_offset); + + devfd = open(device ,O_RDONLY | O_DIRECT); + if(devfd == -1) { + log_err(cd, _("Cannot open device %s.\n"), device); + return -EINVAL; + } + + if(lseek(devfd, sb_offset, SEEK_SET) < 0 || + read_blockwise(devfd, &sb, hdr_size) < hdr_size) { + close(devfd); + return -EIO; + } + close(devfd); + + if (memcmp(sb.signature, VERITY_SIGNATURE, sizeof(sb.signature))) { + log_err(cd, _("Device %s is not a valid VERITY device.\n"), device); + return -EINVAL; + } + + if (sb.version > 1) { + log_err(cd, _("Unsupported VERITY version %d.\n"), sb.version); + return -EINVAL; + } + + if (sb.data_block_bits < 9 || sb.data_block_bits >= 31 || + sb.hash_block_bits < 9 || sb.hash_block_bits >= 31 || + !memchr(sb.algorithm, 0, sizeof(sb.algorithm)) || + ntohs(sb.salt_size) > VERITY_MAX_SALT_SIZE) { + log_err(cd, _("VERITY header corrupted.\n")); + return -EINVAL; + } + + sb_data_blocks = ((unsigned long long)ntohl(sb.data_blocks_hi) << 31 << 1) | + ntohl(sb.data_blocks_lo); + if (sb_data_blocks < 0 || + (off_t)sb_data_blocks < 0 || + (off_t)sb_data_blocks != sb_data_blocks) { + log_err(cd, _("VERITY header data block size mismatch.\n")); + return -EINVAL; + } + + // FIXME alloc error + params->hash_name = strdup((const char*)sb.algorithm); + params->data_block_size = 1 << sb.data_block_bits; + params->hash_block_size = 1 << sb.hash_block_bits; + params->data_size = sb_data_blocks; + params->salt_size = ntohs(sb.salt_size); + params->salt = malloc(params->salt_size); + memcpy(CONST_CAST(char*)params->salt, sb.salt, params->salt_size); + params->hash_area_offset = sb_offset; + params->version = sb.version; + + return 0; +} + +/* Write verity superblock to disk */ +int VERITY_write_sb(struct crypt_device *cd, + const char *device, + size_t sb_offset, + struct crypt_params_verity *params) +{ + struct verity_sb sb = {}; + ssize_t hdr_size = sizeof(struct verity_sb); + int r, devfd = 0; + + log_dbg("Updating VERITY header of size %d on device %s, offset %u.", + sizeof(struct verity_sb), device, (unsigned)sb_offset); + + devfd = open(device, O_RDWR | O_DIRECT); + if(devfd == -1) { + log_err(cd, _("Cannot open device %s.\n"), device); + return -EINVAL; + } + + memcpy(&sb.signature, VERITY_SIGNATURE, sizeof(sb.signature)); + sb.version = params->version; + sb.data_block_bits = ffs(params->data_block_size) - 1; + sb.hash_block_bits = ffs(params->hash_block_size) - 1; + sb.salt_size = htons(params->salt_size); + sb.data_blocks_hi = htonl(params->data_size >> 31 >> 1); + sb.data_blocks_lo = htonl(params->data_size & 0xFFFFFFFF); + strncpy((char *)sb.algorithm, params->hash_name, sizeof(sb.algorithm)); + memcpy(sb.salt, params->salt, params->salt_size); + + r = write_lseek_blockwise(devfd, (char*)&sb, hdr_size, sb_offset) < hdr_size ? -EIO : 0; + if (r) + log_err(cd, _("Error during update of verity header on device %s.\n"), device); + close(devfd); + + return r; +} + +/* Calculate hash offset in hash blocks */ +uint64_t VERITY_hash_offset_block(struct crypt_params_verity *params) +{ + uint64_t hash_offset = params->hash_area_offset; + + if (params->flags & CRYPT_VERITY_NO_HEADER) + return hash_offset / params->hash_block_size; + + hash_offset += sizeof(struct verity_sb); + hash_offset += params->hash_block_size - 1; + + return hash_offset / params->hash_block_size; +} + +/* Activate verity device in kernel device-mapper */ +int VERITY_activate(struct crypt_device *cd, + const char *name, + const char *hash_device, + const char *root_hash, + size_t root_hash_size, + struct crypt_params_verity *verity_hdr, + uint32_t flags) +{ + struct crypt_dm_active_verity dmd; + uint64_t offset = 0; + int r; + + log_dbg("Trying to activate VERITY device %s using hash %s.", + name ?: "[none]", verity_hdr->hash_name); + + if (flags & CRYPT_VERITY_CHECK_HASH) { + r = VERITY_verify(cd, verity_hdr, + crypt_get_device_name(cd), hash_device, + root_hash, root_hash_size); + if (r < 0) + return r; + } + + if (!name) + return 0; + + dmd.data_device = crypt_get_device_name(cd); + dmd.hash_device = hash_device; + dmd.root_hash = root_hash; + dmd.root_hash_size = root_hash_size; + dmd.hash_offset = VERITY_hash_offset_block(verity_hdr), + dmd.flags = CRYPT_ACTIVATE_READONLY; + dmd.size = verity_hdr->data_size * verity_hdr->data_block_size / 512; + + r = device_check_and_adjust(cd, dmd.data_device, DEV_EXCL, + &dmd.size, &offset, &dmd.flags); + if (r) + return r; + + r = dm_create_verity(name, verity_hdr, &dmd); + if (r < 0) + return r; + + r = dm_status_verity_ok(name); + if (r < 0) + return r; + + if (!r) + log_err(cd, _("Verity device detected corruption after activation.\n")); + return 0; +} diff --git a/lib/verity/verity.h b/lib/verity/verity.h new file mode 100644 index 00000000..63ecfb92 --- /dev/null +++ b/lib/verity/verity.h @@ -0,0 +1,83 @@ +/* + * dm-verity volume handling + * + * Copyright (C) 2012, Red Hat, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _VERITY_H +#define _VERITY_H + +#include +#include "config.h" + +#define VERITY_SIGNATURE "verity\0\0" +#define VERITY_MAX_LEVELS 63 +#define VERITY_MAX_SALT_SIZE 384 + +struct crypt_device; +struct crypt_params_verity; + +/* FIXME: not yet final on-disk format! Add UUID etc */ +struct verity_sb { + uint8_t signature[8]; + uint8_t version; + uint8_t data_block_bits; + uint8_t hash_block_bits; + uint8_t pad1[1]; + uint16_t salt_size; + uint8_t pad2[2]; + uint32_t data_blocks_hi; + uint32_t data_blocks_lo; + uint8_t algorithm[16]; + uint8_t salt[VERITY_MAX_SALT_SIZE]; + uint8_t pad3[88]; +}; + +int VERITY_read_sb(struct crypt_device *cd, + const char *device, + size_t sb_offset, + struct crypt_params_verity *params); + +int VERITY_write_sb(struct crypt_device *cd, + const char *device, + size_t sb_offset, + struct crypt_params_verity *params); + +int VERITY_activate(struct crypt_device *cd, + const char *name, + const char *hash_device, + const char *root_hash, + size_t root_hash_size, + struct crypt_params_verity *verity_hdr, + uint32_t flags); + +int VERITY_verify(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *data_device, + const char *hash_device, + const char *root_hash, + size_t root_hash_size); + +int VERITY_create(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *data_device, + const char *hash_device, + char *root_hash, + size_t root_hash_size); + +uint64_t VERITY_hash_offset_block(struct crypt_params_verity *params); + +#endif diff --git a/lib/verity/verity_hash.c b/lib/verity/verity_hash.c new file mode 100644 index 00000000..768cf500 --- /dev/null +++ b/lib/verity/verity_hash.c @@ -0,0 +1,376 @@ +/* + * dm-verity volume handling + * + * Copyright (C) 2012, Red Hat, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +#include "verity.h" +#include "internal.h" + +static unsigned get_bits_up(unsigned u) +{ + unsigned i = 0; + while ((1 << i) < u) + i++; + return i; +} + +static unsigned get_bits_down(unsigned u) +{ + unsigned i = 0; + while ((u >> i) > 1) + i++; + return i; +} + +static int verify_zero(struct crypt_device *cd, FILE *wr, unsigned bytes) +{ + unsigned i; + char block[bytes]; + + if (fread(block, bytes, 1, wr) != 1) + return -EIO; + for (i = 0; i < bytes; i++) + if (block[i]) { + log_err(cd, "spare area is not zeroed at position %lld\n", + (long long)ftello(wr) - bytes); + return -EPERM; + } + return 0; +} + +static int verify_hash_block(const char *hash_name, int version, + char *hash, size_t hash_size, + const char *data, size_t data_size, + const char *salt, size_t salt_size) +{ + struct crypt_hash *ctx = NULL; + int r; + + if (crypt_hash_init(&ctx, hash_name)) + return -EINVAL; + + if (version == 1 && (r = crypt_hash_write(ctx, salt, salt_size))) + goto out; + + if ((r = crypt_hash_write(ctx, data, data_size))) + goto out; + + if (version == 0 && (r = crypt_hash_write(ctx, salt, salt_size))) + goto out; + + r = crypt_hash_final(ctx, hash, hash_size); +out: + crypt_hash_destroy(ctx); + return r; +} + +static int create_or_verify(struct crypt_device *cd, FILE *rd, FILE *wr, + off_t data_block, int data_block_size, + off_t hash_block, int hash_block_size, + off_t blocks, int version, + const char *hash_name, int verify, + char *calculated_digest, unsigned digest_size, + const char *salt, unsigned salt_size) +{ + char left_block[hash_block_size]; + char data_buffer[data_block_size]; + char read_digest[digest_size]; + off_t hash_per_block = 1 << get_bits_down(hash_block_size / digest_size); + off_t blocks_to_write = (blocks + hash_per_block - 1) / hash_per_block; + unsigned i, left_bytes; + off_t digest_size_full = 1 << get_bits_up(digest_size); + int r; + unsigned long long pos_rd = (unsigned long long)data_block * data_block_size; + unsigned long long pos_wr = (unsigned long long)hash_block * hash_block_size; + + if (fseeko(rd, pos_rd, SEEK_SET)) + return -EIO; + + if (wr && fseeko(wr, pos_wr, SEEK_SET)) + return -EIO; + + memset(left_block, 0, hash_block_size); + while (blocks_to_write--) { + left_bytes = hash_block_size; + for (i = 0; i < hash_per_block; i++) { + if (!blocks) + break; + blocks--; + if (fread(data_buffer, data_block_size, 1, rd) != 1) + return -EIO; + + if (verify_hash_block(hash_name, version, + calculated_digest, digest_size, + data_buffer, data_block_size, + salt, salt_size)) + return -EINVAL; + + if (!wr) + break; + if (verify) { + if (fread(read_digest, digest_size, 1, wr) != 1) + return -EIO; + if (memcmp(read_digest, calculated_digest, digest_size)) { + log_err(cd, "verification failed at position %lld\n", + (long long)ftello(rd) - data_block_size); + return -EPERM; + } + } else { + if (fwrite(calculated_digest, digest_size, 1, wr) != 1) + return -EIO; + } + if (version == 0) { + left_bytes -= digest_size; + } else { + if (digest_size_full - digest_size) { + if (verify) { + r = verify_zero(cd, wr, digest_size_full - digest_size); + if (r) + return r; + } else if (fwrite(left_block, digest_size_full - digest_size, 1, wr) != 1) + return -EIO; + } + left_bytes -= digest_size_full; + } + } + if (wr && left_bytes) { + if (verify) { + r = verify_zero(cd , wr, left_bytes); + if (r) + return r; + } else if (fwrite(left_block, left_bytes, 1, wr) != 1) + return -EIO; + } + } + + return 0; +} + +static int VERITY_create_or_verify_hash(struct crypt_device *cd, + int verify, + int version, + const char *hash_name, + const char *hash_device, + const char *data_device, + int hash_block_size, + int data_block_size, + off_t data_blocks, + long long hash_position, + char *root_hash, + unsigned digest_size, + const char *salt, + unsigned salt_size) +{ + static FILE *data_file = NULL; + static FILE *hash_file = NULL, *hash_file_2; + + int i, r; + char calculated_digest[digest_size]; + off_t hash_level_block[VERITY_MAX_LEVELS]; + off_t hash_level_size[VERITY_MAX_LEVELS]; + off_t data_file_blocks; + uint64_t data_device_size; + unsigned long long hash_per_block, hash_per_block_bits; + unsigned levels; + + log_dbg("Userspace hash %s %s, data device %s, data blocks %u, hash device %s, offset %u.", + verify ? "verification" : "creation", hash_name, data_device, + (unsigned)data_blocks, hash_device, (unsigned)hash_position); + + if (!data_blocks) { + r = device_size(data_device, &data_device_size); + if (r < 0) + return r; + + data_file_blocks = data_device_size / data_block_size; + } else + data_file_blocks = data_blocks; + + hash_per_block_bits = get_bits_down(hash_block_size / digest_size); + hash_per_block = 1 << hash_per_block_bits; + if (!hash_per_block_bits) { + log_err(cd, "at least two hashes must fit in a hash file block\n"); + return -EINVAL; + } + + 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 > VERITY_MAX_LEVELS) { + log_err(cd, "too many tree levels\n"); + return -EINVAL; + } + + for (i = levels - 1; i >= 0; i--) { + off_t s; + hash_level_block[i] = hash_position; + // verity position of block data_file_blocks at level i + s = data_file_blocks >> (i * hash_per_block_bits); + s = (s + hash_per_block - 1) / hash_per_block; + 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) { + log_err(cd, "hash device offset overflow\n"); + return -EINVAL; + } + hash_position += s; + } + + data_file = fopen(data_device, "r"); + if (!data_file) { + log_err(cd, "Cannot open %s.\n", data_device); + r = -EIO; + goto out; + } + + hash_file = fopen(hash_device, verify ? "r" : "r+"); + if (!hash_file) { + log_err(cd, "Cannot open %s.\n", hash_device); + r = -EIO; + goto out; + } + + memset(calculated_digest, 0, digest_size); + + for (i = 0; i < levels; i++) { + if (!i) { + r = create_or_verify(cd, data_file, hash_file, + 0, data_block_size, + hash_level_block[i], hash_block_size, + data_file_blocks, version, hash_name, verify, + calculated_digest, digest_size, salt, salt_size); + if (r) + goto out; + } else { + hash_file_2 = fopen(hash_device, "r"); + if (!hash_file_2) { + r = -EIO; + goto out; + } + r = create_or_verify(cd, hash_file_2, hash_file, + hash_level_block[i - 1], hash_block_size, + hash_level_block[i], hash_block_size, + hash_level_size[i - 1], version, hash_name, verify, + calculated_digest, digest_size, salt, salt_size); + fclose(hash_file_2); + if (r) + goto out; + } + } + + if (levels) + r = create_or_verify(cd, hash_file, NULL, + hash_level_block[levels - 1], hash_block_size, + 0, 0, + 1, version, hash_name, verify, + calculated_digest, digest_size, salt, salt_size); + else + r = create_or_verify(cd, data_file, NULL, + 0, data_block_size, + 0, 0, + data_file_blocks, version, hash_name, verify, + calculated_digest, digest_size, salt, salt_size); + + if (r) { + log_err(cd, "Hash of data area verification failed.\n"); + goto out; + } else + log_dbg("Hash of data area successfully verified."); + + /* root hash verification */ + if (verify) { + r = memcmp(root_hash, calculated_digest, digest_size) ? -EPERM : 0; + if (r) + log_err(cd, "Root hash verification failed.\n"); + else + log_dbg("Root hash successfully verified."); + } else { + fsync(fileno(hash_file)); + memcpy(root_hash, calculated_digest, digest_size); + } +out: + if (data_file) + fclose(data_file); + if (hash_file) + fclose(hash_file); + return r; +} + +/* Verify verity device using userspace crypto backend */ +int VERITY_verify(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *data_device, + const char *hash_device, + const char *root_hash, + size_t root_hash_size) +{ + int r = VERITY_create_or_verify_hash(cd, 1, + verity_hdr->version, + verity_hdr->hash_name, + hash_device, + data_device, + verity_hdr->hash_block_size, + verity_hdr->data_block_size, + verity_hdr->data_size, + VERITY_hash_offset_block(verity_hdr), + CONST_CAST(char*)root_hash, + root_hash_size, + verity_hdr->salt, + verity_hdr->salt_size); + + if (r == -EPERM) + log_err(cd, "Userspace hash verification failed.\n"); + + return r; +} + +int VERITY_create(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *data_device, + const char *hash_device, + char *root_hash, + size_t root_hash_size) +{ + if (verity_hdr->salt_size > VERITY_MAX_SALT_SIZE) + return -EINVAL; + + return VERITY_create_or_verify_hash(cd, 0, + verity_hdr->version, + verity_hdr->hash_name, + hash_device, + data_device, + verity_hdr->hash_block_size, + verity_hdr->data_block_size, + verity_hdr->data_size, + VERITY_hash_offset_block(verity_hdr), + root_hash, + root_hash_size, + verity_hdr->salt, + verity_hdr->salt_size); +} diff --git a/src/Makefile.am b/src/Makefile.am index a1cf3dae..c35a5d17 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,14 +39,12 @@ endif # veritysetup if VERITYSETUP -INCLUDES += -I$(top_srcdir)/lib/crypto_backend veritysetup_SOURCES = \ veritysetup.c veritysetup_LDADD = \ - $(top_builddir)/lib/crypto_backend/libcrypto_backend.la \ - @CRYPTO_LIBS@ \ + $(top_builddir)/lib/libcryptsetup.la \ @POPT_LIBS@ veritysetup_CFLAGS = $(cryptsetup_CFLAGS) @@ -59,6 +57,8 @@ veritysetup_static_SOURCES = $(veritysetup_SOURCES) veritysetup_static_CFLAGS = $(veritysetup_CFLAGS) veritysetup_static_LDFLAGS = -all-static veritysetup_static_LDADD = $(veritysetup_LDADD) \ - @CRYPTO_STATIC_LIBS@ + @CRYPTO_STATIC_LIBS@ \ + @DEVMAPPER_STATIC_LIBS@ \ + @UUID_LIBS@ endif endif diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 3bc24c19..dde6aecf 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -32,7 +32,6 @@ #include #include #include -#include #include #include "cryptsetup.h" diff --git a/src/cryptsetup.h b/src/cryptsetup.h index 5e4ae90c..784c521b 100644 --- a/src/cryptsetup.h +++ b/src/cryptsetup.h @@ -29,6 +29,8 @@ #include "lib/utils_loop.h" #include "lib/utils_fips.h" +#include "libcryptsetup.h" + #define DEFAULT_CIPHER(type) (DEFAULT_##type##_CIPHER "-" DEFAULT_##type##_MODE) #define log_dbg(x...) clogger(NULL, CRYPT_LOG_DEBUG, __FILE__, __LINE__, x) diff --git a/src/veritysetup.c b/src/veritysetup.c index 81196b1a..0abf5cc2 100644 --- a/src/veritysetup.c +++ b/src/veritysetup.c @@ -17,651 +17,311 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#define _FILE_OFFSET_BITS 64 - -#include "config.h" +/* TODO: + * - detect dm-verity (devmapper) + * - init_by_name() + * - unify units / uint64 etc + * - check translations + * - support device without superblock + * - audit alloc errors / error path + * - change command names (cryptsetup style) + * - extend superblock (UUID) + * - warn if block_size > PAGE_SIZE + */ #include #include -#include #include -#include #include #include -#include -#include -#include -#include -#include #include -#include "crypto_backend.h" +#include "cryptsetup.h" -#define DEFAULT_BLOCK_SIZE 4096 -#define DM_VERITY_MAX_LEVELS 63 - -#define DEFAULT_SALT_SIZE 32 -#define MAX_SALT_SIZE 384 +#define PACKAGE_VERITY "veritysetup" #define MODE_VERIFY 0 #define MODE_CREATE 1 #define MODE_ACTIVATE 2 - -#define MAX_FORMAT_VERSION 1 +#define MODE_DUMP 3 static int mode = -1; -static int use_superblock = 1; +static int use_superblock = 1; /* FIXME: no superblock not supported */ -static const char *dm_device; -static const char *data_device; -static const char *hash_device; +static const char *dm_device = NULL; +static const char *data_device = NULL; +static const char *hash_device = NULL; static const char *hash_algorithm = NULL; -static const char *root_hash; +static const char *root_hash = NULL; -static int version = -1; -static int data_block_size = 0; -static int hash_block_size = 0; +static int version = 1; +static int data_block_size = 4096; +static int hash_block_size = 4096; static char *data_blocks_string = NULL; static long long data_blocks = 0; static char *hash_start_string = NULL; -static long long hash_start = 0; static const char *salt_string = NULL; -static FILE *data_file; -static FILE *hash_file; +static unsigned salt_size = 32; -static off_t data_file_blocks; -static off_t hash_file_blocks; -static off_t used_hash_blocks; - -static char *root_hash_bytes; -static char *calculated_digest; - -static char *salt_bytes; -static unsigned salt_size; - -static unsigned digest_size; -static unsigned char digest_size_bits; -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 off_t superblock_position; - -static int retval = 0; +static off_t superblock_position = 0; +static int opt_verbose = 0; static int opt_debug = 0; +static int opt_version_mode = 0; -struct superblock { - uint8_t signature[8]; - uint8_t version; - uint8_t data_block_bits; - uint8_t hash_block_bits; - uint8_t pad1[1]; - uint16_t salt_size; - uint8_t pad2[2]; - uint32_t data_blocks_hi; - uint32_t data_blocks_lo; - uint8_t algorithm[16]; - uint8_t salt[MAX_SALT_SIZE]; - uint8_t pad3[88]; -}; +static int hex_to_bytes(const char *hex, char *result) +{ + char buf[3] = "xx\0", *endp; + int i, len; -#define DM_VERITY_SIGNATURE "verity\0\0" -#define DM_VERITY_VERSION 0 + len = strlen(hex) / 2; + for (i = 0; i < len; i++) { + memcpy(buf, &hex[i * 2], 2); + result[i] = strtoul(buf, &endp, 16); + if (endp != &buf[2]) + return -EINVAL; + } + return i; +} __attribute__((format(printf, 5, 6))) -void logger(struct crypt_device *cd, int level, const char *file, - int line, const char *format, ...) +static void clogger(struct crypt_device *cd, int level, const char *file, + int line, const char *format, ...) { - va_list argp; - char *target = NULL; + va_list argp; + char *target = NULL; - va_start(argp, format); + va_start(argp, format); - if (vasprintf(&target, format, argp) > 0) { - if (level >= 0) { - printf("%s\n", target); - } else if (opt_debug) - printf("# %s\n", target); - } + if (vasprintf(&target, format, argp) > 0) { + if (level >= 0) { + crypt_log(cd, level, target); +#ifdef CRYPT_DEBUG + } else if (opt_debug) + printf("# %s:%d %s\n", file ?: "?", line, target); +#else + } else if (opt_debug) + printf("# %s\n", target); +#endif + } - va_end(argp); - free(target); + va_end(argp); + free(target); +} + +static void _log(int level, const char *msg, void *usrptr __attribute__((unused))) +{ + switch(level) { + + case CRYPT_LOG_NORMAL: + fputs(msg, stdout); + break; + case CRYPT_LOG_VERBOSE: + if (opt_verbose) + fputs(msg, stdout); + break; + case CRYPT_LOG_ERROR: + fputs(msg, stderr); + break; + case CRYPT_LOG_DEBUG: + if (opt_debug) + printf("# %s\n", msg); + break; + default: + fprintf(stderr, "Internal error on logging class for msg: %s", msg); + break; + } +} + +static int action_dump(void) +{ + struct crypt_device *cd = NULL; + struct crypt_params_verity params = {}; + int r; + + if ((r = crypt_init(&cd, hash_device))) + return r; + + params.hash_area_offset = superblock_position; + r = crypt_load(cd, CRYPT_VERITY, ¶ms); + if (!r) + crypt_dump(cd); + crypt_free(cd); + return r; +} + +static int action_activate(int verify) +{ + struct crypt_device *cd = NULL; + struct crypt_params_verity params = {}; + uint32_t activate_flags = CRYPT_ACTIVATE_READONLY; + char root_hash_bytes[128]; + int r; + + if ((r = crypt_init(&cd, hash_device))) + goto out; + + if (verify) + params.flags |= CRYPT_VERITY_CHECK_HASH; + + if (use_superblock) { + params.hash_area_offset = superblock_position; + r = crypt_load(cd, CRYPT_VERITY, ¶ms); + } else {/* + params.hash_name = hash_algorithm; + params.salt = salt_bytes; + params.salt_size = salt_size; + params.data_block_size = data_block_size; + params.hash_block_size = hash_block_size; + + params.data_size = data_blocks * data_block_size / 512; + params.version = version; + params.flags |= CRYPT_VERITY_NO_HEADER; + r = crypt_load(cd, CRYPT_VERITY, ¶ms); + crypt_format(); */ + r = -EINVAL; + goto out; + } + if (r < 0) + goto out; + r = crypt_set_data_device(cd, data_device); + if (r < 0) + goto out; + + if (hex_to_bytes(root_hash, root_hash_bytes) != + crypt_get_volume_key_size(cd)) { + r = -EINVAL; + goto out; + } + r = crypt_activate_by_volume_key(cd, dm_device, root_hash_bytes, + crypt_get_volume_key_size(cd), + activate_flags); +out: + if (!r) + crypt_dump(cd); + crypt_free(cd); + return r; +} + +static int action_create(void) +{ + struct crypt_device *cd = NULL; + struct crypt_params_verity params = {}; + char salt_bytes[512]; + int r; + + if ((r = crypt_init(&cd, hash_device))) + goto out; + + params.hash_name = hash_algorithm; + params.data_device = data_device; + + if (salt_string) { + if (hex_to_bytes(salt_string, salt_bytes) != salt_size) { + r = -EINVAL; + goto out; + } + params.salt = salt_bytes; + } + + params.salt_size = salt_size; + params.data_block_size = data_block_size; + params.hash_block_size = hash_block_size; + params.data_size = data_blocks; + params.hash_area_offset = superblock_position; + params.version = version; + params.flags = CRYPT_VERITY_CREATE_HASH; + if (!use_superblock) + params.flags |= CRYPT_VERITY_NO_HEADER; + + r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, NULL, NULL, 0, ¶ms); + if (!r) + crypt_dump(cd); +out: + crypt_free(cd); + return r; +} + +static __attribute__ ((noreturn)) void usage(poptContext popt_context, + int exitcode, const char *error, + const char *more) +{ + poptPrintUsage(popt_context, stderr, 0); + if (error) + log_err("%s: %s\n", more, error); + poptFreeContext(popt_context); + exit(exitcode); } -__attribute__((__noreturn__)) static void help(poptContext popt_context, - enum poptCallbackReason reason, + enum poptCallbackReason reason __attribute__((unused)), struct poptOption *key, - const char *arg, - void *data) + const char *arg __attribute__((unused)), + void *data __attribute__((unused))) { - if (!strcmp(key->longName, "help")) { + if (key->shortName == '?') { + log_std("%s %s\n", PACKAGE_VERITY, PACKAGE_VERSION); poptPrintHelp(popt_context, stdout, 0); - } else { - printf("veritysetup"); - printf("\n"); - } - exit(0); + exit(EXIT_SUCCESS); + } else + usage(popt_context, EXIT_SUCCESS, NULL, NULL); } -static struct poptOption popt_help_options[] = { - { NULL, 0, POPT_ARG_CALLBACK, help, 0, NULL, NULL }, - { "help", 'h', POPT_ARG_NONE, NULL, 0, "Show help", NULL }, - { "version", 0, POPT_ARG_NONE, NULL, 0, "Show version", NULL }, - POPT_TABLEEND -}; - -static struct poptOption popt_options[] = { - { NULL, '\0', 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 }, - { "activate", 'a', POPT_ARG_VAL, &mode, MODE_ACTIVATE, "Activate the device", NULL }, - { "no-superblock", 0, POPT_ARG_VAL, &use_superblock, 0, "Do not create/use superblock" }, - { "format", 0, POPT_ARG_INT, &version, 0, "Format version (1 - normal format, 0 - original Chromium OS format)", "number" }, - { "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" }, - { "data-blocks", 0, POPT_ARG_STRING, &data_blocks_string, 0, "The number of blocks in the data file", "blocks" }, - { "hash-start", 0, POPT_ARG_STRING, &hash_start_string, 0, "Starting block on the hash device", "512-byte sectors" }, - { "algorithm", 0, POPT_ARG_STRING, &hash_algorithm, 0, "Hash algorithm (default sha256)", "string" }, - { "salt", 0, POPT_ARG_STRING, &salt_string, 0, "Salt", "hex string" }, - POPT_TABLEEND -}; - -__attribute__((__format__(__printf__, 1, 2), __noreturn__)) -static void exit_err(const char *msg, ...) -{ - va_list args; - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - fputc('\n', stderr); - exit(2); -} - -__attribute__((__noreturn__)) -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 ? 1 : s); - if (!ptr) exit_err("out of memory"); - return ptr; -} - -static char *xstrdup(const char *str) -{ - return strcpy(xmalloc(strlen(str) + 1), str); -} - -static char *xprint(unsigned long long num) -{ - size_t s = snprintf(NULL, 0, "%llu", num); - char *p = xmalloc(s + 1); - snprintf(p, s + 1, "%llu", num); - return p; -} - -static char *xhexprint(char *bytes, size_t len) -{ - size_t i; - char *p = xmalloc(len * 2 + 1); - p[0] = 0; - for (i = 0; i < len; i++) - snprintf(p + i * 2, 3, "%02x", (unsigned char)bytes[i]); - return p; -} - -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; - - digest_size_bits = 0; - while (1 << digest_size_bits < digest_size) - digest_size_bits++; - 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_zero(FILE *wr, char *left_block, unsigned left_bytes) -{ - if (left_bytes) { - if (mode != MODE_CREATE) { - unsigned x; - 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\n", (long long)ftello(wr) - left_bytes); - } - } else { - if (fwrite(left_block, left_bytes, 1, wr) != 1) - stream_err(wr, "write"); - } - } -} - -static void create_or_verify_stream(FILE *rd, FILE *wr, int block_size, off_t blocks) -{ - char *left_block = xmalloc(hash_block_size); - char *data_buffer = xmalloc(block_size); - char *read_digest = mode != MODE_CREATE ? xmalloc(digest_size) : NULL; - off_t blocks_to_write = (blocks >> hash_per_block_bits) + - !!(blocks & ((1 << hash_per_block_bits) - 1)); - struct crypt_hash *ctx = NULL; - - if (crypt_hash_init(&ctx, hash_algorithm)) - exit_err("hash_init failed"); - - memset(left_block, 0, hash_block_size); - while (blocks_to_write--) { - unsigned x; - unsigned left_bytes = hash_block_size; - 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 (version >= 1) { - if (crypt_hash_write(ctx, salt_bytes, salt_size)) - exit_err("hash_write failed"); - } - if (crypt_hash_write(ctx, data_buffer, block_size)) - exit_err("hash_write failed"); - if (!version) { - if (crypt_hash_write(ctx, salt_bytes, salt_size)) - exit_err("hash_write failed"); - } - if (crypt_hash_final(ctx, calculated_digest, digest_size)) - exit_err("hash_final failed"); - if (!wr) - break; - if (mode != MODE_CREATE) { - 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\n", (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"); - } - if (!version) { - left_bytes -= digest_size; - } else { - create_or_verify_zero(wr, left_block, (1 << digest_size_bits) - digest_size); - left_bytes -= 1 << digest_size_bits; - } - } - if (wr) - create_or_verify_zero(wr, left_block, left_bytes); - } - if (mode == MODE_CREATE && wr) { - if (fflush(wr)) { - perror("fflush"); - exit(1); - } - if (ferror(wr)) { - stream_err(wr, "write"); - } - } - crypt_hash_destroy(ctx); - free(left_block); - free(data_buffer); - if (mode != MODE_CREATE) - free(read_digest); -} - -static char **make_target_line(void) -{ - const int line_elements = 14; - char **line = xmalloc(line_elements * sizeof(char *)); - int i = 0; - char *algorithm_copy = xstrdup(hash_algorithm); - /* transform ripemdXXX to rmdXXX */ - if (!strncmp(algorithm_copy, "ripemd", 6)) - memmove(algorithm_copy + 1, algorithm_copy + 4, strlen(algorithm_copy + 4) + 1); - if (!strcmp(algorithm_copy, "whirlpool")) - strcpy(algorithm_copy, "wp512"); - line[i++] = xstrdup("0"); - line[i++] = xprint((unsigned long long)data_file_blocks * data_block_size / 512); - line[i++] = xstrdup("verity"); - line[i++] = xprint(version); - line[i++] = xstrdup(data_device); - line[i++] = xstrdup(hash_device); - line[i++] = xprint(data_block_size); - line[i++] = xprint(hash_block_size); - line[i++] = xprint(data_file_blocks); - line[i++] = xprint(hash_start * 512 / hash_block_size); - line[i++] = algorithm_copy; - line[i++] = xhexprint(calculated_digest, digest_size); - line[i++] = !salt_size ? xstrdup("-") : xhexprint(salt_bytes, salt_size); - line[i++] = NULL; - if (i > line_elements) - exit_err("INTERNAL ERROR: insufficient array size"); - return line; -} - -static void free_target_line(char **line) -{ - int i; - for (i = 0; line[i]; i++) - free(line[i]); - free(line); -} - -static void create_or_verify(void) +static void _dbg_version_and_cmd(int argc, const char **argv) { int i; - memset(calculated_digest, 0, digest_size); - if (mode != MODE_ACTIVATE) - 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_CREATE) { - if (memcmp(calculated_digest, root_hash_bytes, digest_size)) { - fprintf(stderr, "verification failed in the root block\n"); - retval = 1; - } - if (!retval && mode == MODE_VERIFY) - fprintf(stderr, "hash successfully verified\n"); - } else { - char **target_line; - char *p; - if (fsync(fileno(hash_file))) { - perror("fsync"); - exit(1); - } - printf("hash device size: %llu\n", (unsigned long long)used_hash_blocks * hash_block_size); - printf("data block size %u, hash block size %u, %u tree levels\n", data_block_size, hash_block_size, levels); - if (salt_size) - p = xhexprint(salt_bytes, salt_size); - else - p = xstrdup("-"); - printf("salt: %s\n", p); - free(p); - p = xhexprint(calculated_digest, digest_size); - printf("root hash: %s\n", p); - free(p); - printf("target line:"); - target_line = make_target_line(); - for (i = 0; target_line[i]; i++) - printf(" %s", target_line[i]); - free_target_line(target_line); - printf("\n"); - } -} - -__attribute__((__noreturn__)) -static void activate(void) -{ - int i; - size_t len = 1; - char *table_arg; - char **target_line = make_target_line(); - for (i = 0; target_line[i]; i++) { + log_std("# %s %s processing \"", PACKAGE_VERITY, PACKAGE_VERSION); + for (i = 0; i < argc; i++) { if (i) - len++; - len += strlen(target_line[i]); + log_std(" "); + log_std("%s", argv[i]); } - table_arg = xmalloc(len); - table_arg[0] = 0; - for (i = 0; target_line[i]; i++) { - if (i) - strcat(table_arg, " "); - strcat(table_arg, target_line[i]); - } - free_target_line(target_line); - execlp("dmsetup", "dmsetup", "-r", "create", dm_device, "--table", table_arg, NULL); - perror("dmsetup"); - exit(2); -} - -static void get_hex(const char *string, 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); - } -} - -static struct superblock superblock; - -static void load_superblock(void) -{ - long long sb_data_blocks; - - block_fseek(hash_file, superblock_position, 1); - if (fread(&superblock, sizeof(struct superblock), 1, hash_file) != 1) - stream_err(hash_file, "read"); - if (memcmp(superblock.signature, DM_VERITY_SIGNATURE, sizeof(superblock.signature))) - exit_err("superblock not found on the hash device"); - if (superblock.version > MAX_FORMAT_VERSION) - exit_err("unknown version"); - if (superblock.data_block_bits < 9 || superblock.data_block_bits >= 31) - exit_err("invalid data_block_bits in the superblock"); - if (superblock.hash_block_bits < 9 || superblock.hash_block_bits >= 31) - exit_err("invalid data_block_bits in the superblock"); - sb_data_blocks = ((unsigned long long)ntohl(superblock.data_blocks_hi) << 31 << 1) | ntohl(superblock.data_blocks_lo); - if (sb_data_blocks < 0 || (off_t)sb_data_blocks < 0 || (off_t)sb_data_blocks != sb_data_blocks) - exit_err("invalid data blocks in the superblock"); - if (!memchr(superblock.algorithm, 0, sizeof(superblock.algorithm))) - exit_err("invalid hash algorithm in the superblock"); - if (ntohs(superblock.salt_size) > MAX_SALT_SIZE) - exit_err("invalid salt_size in the superblock"); - - if (version == -1) { - version = superblock.version; - } else { - if (version != superblock.version) - exit_err("version (%d) does not match superblock value (%d)", version, superblock.version); - } - - if (!data_block_size) { - data_block_size = 1 << superblock.data_block_bits; - } else { - if (data_block_size != 1 << superblock.data_block_bits) - exit_err("data block size (%d) does not match superblock value (%d)", data_block_size, 1 << superblock.data_block_bits); - } - - if (!hash_block_size) { - hash_block_size = 1 << superblock.hash_block_bits; - } else { - if (hash_block_size != 1 << superblock.hash_block_bits) - exit_err("hash block size (%d) does not match superblock value (%d)", hash_block_size, 1 << superblock.hash_block_bits); - } - - if (!data_blocks_string) { - data_blocks = sb_data_blocks; - data_blocks_string = (char *)""; - } else { - if (data_blocks != sb_data_blocks) - exit_err("data blocks (%lld) does not match superblock value (%lld)", data_blocks, sb_data_blocks); - } - - if (!hash_algorithm) { - hash_algorithm = (char *)superblock.algorithm; - } else { - if (strcmp(hash_algorithm, (char *)superblock.algorithm)) - exit_err("hash algorithm (%s) does not match superblock value (%s)", hash_algorithm, superblock.algorithm); - } - - if (!salt_bytes) { - salt_size = ntohs(superblock.salt_size); - salt_bytes = xmalloc(salt_size); - memcpy(salt_bytes, superblock.salt, salt_size); - } else { - if (salt_size != ntohs(superblock.salt_size) || - memcmp(salt_bytes, superblock.salt, salt_size)) - exit_err("salt does not match superblock value"); - } -} - -static void save_superblock(void) -{ - memset(&superblock, 0, sizeof(struct superblock)); - - memcpy(&superblock.signature, DM_VERITY_SIGNATURE, sizeof(superblock.signature)); - superblock.version = version; - superblock.data_block_bits = ffs(data_block_size) - 1; - superblock.hash_block_bits = ffs(hash_block_size) - 1; - superblock.salt_size = htons(salt_size); - superblock.data_blocks_hi = htonl(data_file_blocks >> 31 >> 1); - superblock.data_blocks_lo = htonl(data_file_blocks & 0xFFFFFFFF); - strncpy((char *)superblock.algorithm, hash_algorithm, sizeof superblock.algorithm); - memcpy(superblock.salt, salt_bytes, salt_size); - - block_fseek(hash_file, superblock_position, 1); - if (fwrite(&superblock, sizeof(struct superblock), 1, hash_file) != 1) - stream_err(hash_file, "write"); + log_std("\"\n"); } int main(int argc, const char **argv) { + static struct poptOption popt_help_options[] = { + { NULL, '\0', POPT_ARG_CALLBACK, help, 0, NULL, NULL }, + { "help", '?', POPT_ARG_NONE, NULL, 0, N_("Show this help message"), NULL }, + { "usage", '\0', POPT_ARG_NONE, NULL, 0, N_("Display brief usage"), NULL }, + POPT_TABLEEND + }; + static struct poptOption popt_options[] = { + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, N_("Help options:"), NULL }, + { "version", '\0', POPT_ARG_NONE, &opt_version_mode, 0, N_("Print package version"), NULL }, + { "verbose", 0 /*v*/, POPT_ARG_NONE, &opt_verbose, 0, N_("Shows more detailed error messages"), NULL }, + { "debug", '\0', POPT_ARG_NONE, &opt_debug, 0, N_("Show debug messages"), NULL }, + { "create", 'c', POPT_ARG_VAL, &mode, MODE_CREATE, "Create hash", NULL }, + { "verify", 'v', POPT_ARG_VAL, &mode, MODE_VERIFY, "Verify integrity", NULL }, + { "activate", 'a', POPT_ARG_VAL, &mode, MODE_ACTIVATE, "Activate the device", NULL }, + { "dump", 'd', POPT_ARG_VAL, &mode, MODE_DUMP, "Dump the device", NULL }, + { "no-superblock", 0, POPT_ARG_VAL, &use_superblock, 0, "Do not create/use superblock" }, + { "format", 0, POPT_ARG_INT, &version, 0, "Format version (1 - normal format, 0 - original Chromium OS format)", "number" }, + { "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" }, + { "data-blocks", 0, POPT_ARG_STRING, &data_blocks_string, 0, "The number of blocks in the data file", "blocks" }, + { "hash-start", 0, POPT_ARG_STRING, &hash_start_string, 0, "Starting block on the hash device", "512-byte sectors" }, + { "algorithm", 0, POPT_ARG_STRING, &hash_algorithm, 0, "Hash algorithm (default sha256)", "string" }, + { "salt", 0, POPT_ARG_STRING, &salt_string, 0, "Salt", "hex string" }, + POPT_TABLEEND + }; poptContext popt_context; int r; - const char *s; char *end; + static long long hash_start = 0; - if (sizeof(struct superblock) != 512) - exit_err("INTERNAL ERROR: bad superblock size %ld", (long)sizeof(struct superblock)); + crypt_set_log_callback(NULL, _log, NULL); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); popt_context = poptGetContext("verity", argc, argv, popt_options, 0); - poptSetOtherOptionHelp(popt_context, "[-c | -v | -a] [ if activating] [ if activating or verifying] [OPTION...]"); + poptSetOtherOptionHelp(popt_context, "[-c|-v|-a|-d] [ if activating] [ if activating or verifying] [OPTION...]"); if (argc <= 1) { poptPrintHelp(popt_context, stdout, 0); @@ -670,162 +330,113 @@ int main(int argc, const char **argv) r = poptGetNextOpt(popt_context); if (r < -1) - exit_err("bad option %s", poptBadOption(popt_context, 0)); + usage(popt_context, EXIT_FAILURE, poptStrerror(r), + poptBadOption(popt_context, POPT_BADOPTION_NOALIAS)); + if (opt_version_mode) { + log_std("%s %s\n", PACKAGE_VERITY, PACKAGE_VERSION); + poptFreeContext(popt_context); + exit(EXIT_SUCCESS); + } if (mode < 0) - exit_err("verify, create or activate mode not specified"); + usage(popt_context, EXIT_FAILURE, _("Unknown action."), + poptGetInvocationName(popt_context)); if (mode == MODE_ACTIVATE) { dm_device = poptGetArg(popt_context); - if (!dm_device) - exit_err("device name is missing"); - if (!*dm_device || strchr(dm_device, '/')) - exit_err("invalid device name to activate"); + if (!dm_device || !*dm_device) + usage(popt_context, EXIT_FAILURE, + _("Missing activation device name."), + poptGetInvocationName(popt_context)); } data_device = poptGetArg(popt_context); if (!data_device) - exit_err("data device is missing"); + usage(popt_context, EXIT_FAILURE, _("Missing data device name."), + poptGetInvocationName(popt_context)); hash_device = poptGetArg(popt_context); if (!hash_device) - exit_err("metadata device is missing"); + usage(popt_context, EXIT_FAILURE, _("Missing hash device name."), + poptGetInvocationName(popt_context)); - if (mode != MODE_CREATE) { + if (mode == MODE_ACTIVATE || 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_CREATE ? "r" : "r+"); - if (!hash_file && errno == ENOENT && mode == MODE_CREATE) - hash_file = fopen(hash_device, "w+"); - if (!hash_file) { - perror(hash_device); - exit(2); + usage(popt_context, EXIT_FAILURE, _("Root hash not specified."), + poptGetInvocationName(popt_context)); } if (data_blocks_string) { data_blocks = strtoll(data_blocks_string, &end, 10); if (!*data_blocks_string || *end) - exit_err("invalid number of data blocks"); + usage(popt_context, EXIT_FAILURE, + _("Invalid number of data blocks."), + poptGetInvocationName(popt_context)); } + /* hash start */ if (hash_start_string) { hash_start = strtoll(hash_start_string, &end, 10); if (!*hash_start_string || *end) - exit_err("invalid hash start"); + usage(popt_context, EXIT_FAILURE, + _("Invalid hash device offset."), + poptGetInvocationName(popt_context)); } if (hash_start < 0 || (unsigned long long)hash_start * 512 / 512 != hash_start || (off_t)(hash_start * 512) < 0 || - (off_t)(hash_start * 512) != hash_start * 512) exit_err("invalid hash start"); + (off_t)(hash_start * 512) != hash_start * 512) + usage(popt_context, EXIT_FAILURE, + _("Invalid hash device offset."), + poptGetInvocationName(popt_context)); + + if (use_superblock) + superblock_position = hash_start * 512; if (salt_string || !use_superblock) { if (!salt_string || !strcmp(salt_string, "-")) salt_string = ""; salt_size = strlen(salt_string) / 2; - if (salt_size > MAX_SALT_SIZE) - exit_err("too long salt (max %d bytes)", MAX_SALT_SIZE); - get_hex(salt_string, &salt_bytes, salt_size, "salt"); } - if (use_superblock) { - superblock_position = hash_start * 512; - if (mode != MODE_CREATE) - load_superblock(); - } - - if (version == -1) - version = MAX_FORMAT_VERSION; - if (version < 0 || version > MAX_FORMAT_VERSION) - exit_err("invalid format version"); - - if (!data_block_size) - data_block_size = DEFAULT_BLOCK_SIZE; - if (!hash_block_size) - hash_block_size = data_block_size; - if (data_block_size < 512 || (data_block_size & (data_block_size - 1)) || data_block_size >= 1U << 31) - exit_err("invalid data block size"); + usage(popt_context, EXIT_FAILURE, _("Invalid data block size."), + poptGetInvocationName(popt_context)); if (hash_block_size < 512 || (hash_block_size & (hash_block_size - 1)) || hash_block_size >= 1U << 31) - exit_err("invalid hash block size"); + usage(popt_context, EXIT_FAILURE, _("Invalid hash block size."), + poptGetInvocationName(popt_context)); if (data_blocks < 0 || (off_t)data_blocks < 0 || (off_t)data_blocks != data_blocks) - exit_err("invalid number of data blocks"); - - 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 (data_file_blocks < data_blocks) - exit_err("data file is too small"); - if (data_blocks_string) - data_file_blocks = data_blocks; - - if (use_superblock) { - hash_start = hash_start + (sizeof(struct superblock) + 511) / 512; - hash_start = (hash_start + (hash_block_size / 512 - 1)) & ~(long long)(hash_block_size / 512 - 1); - } - - if ((unsigned long long)hash_start * 512 % hash_block_size) - exit_err("hash start not aligned on block size"); + usage(popt_context, EXIT_FAILURE, _("Invalid number of data blocks."), + poptGetInvocationName(popt_context)); if (!hash_algorithm) hash_algorithm = "sha256"; - if (strlen(hash_algorithm) >= sizeof(superblock.algorithm) && use_superblock) - exit_err("hash algorithm name is too long"); - if (crypt_backend_init(NULL)) - exit_err("cannot initialize crypto backend"); - - digest_size = crypt_hash_size(hash_algorithm); - if (!digest_size) exit_err("hash algorithm %s not found", hash_algorithm); - - if (!salt_bytes) { - salt_size = DEFAULT_SALT_SIZE; - salt_bytes = xmalloc(salt_size); - if (crypt_backend_rng(salt_bytes, salt_size, CRYPT_RND_SALT, 0)) - exit_err("rng failed"); + if (opt_debug) { + opt_verbose = 1; + crypt_set_debug_level(-1); + _dbg_version_and_cmd(argc, argv); } - calculated_digest = xmalloc(digest_size); - - if (mode != MODE_CREATE) { - get_hex(root_hash, &root_hash_bytes, digest_size, "root_hash"); + switch (mode) { + case MODE_ACTIVATE: + r = action_activate(0); + break; + case MODE_VERIFY: + r = action_activate(1); + break; + case MODE_CREATE: + r = action_create(); + break; + case MODE_DUMP: + r = action_dump(); + break; } - calculate_positions(); - - create_or_verify(); - - if (use_superblock) { - if (mode == MODE_CREATE) - save_superblock(); - } - - fclose(data_file); - fclose(hash_file); - - if (mode == MODE_ACTIVATE && !retval) - activate(); - - free(salt_bytes); - free(calculated_digest); - if (mode != MODE_CREATE) - free(root_hash_bytes); poptFreeContext(popt_context); - - return retval; + return r; } diff --git a/tests/verity-compat-test b/tests/verity-compat-test index 79cc9408..3dcfec39 100755 --- a/tests/verity-compat-test +++ b/tests/verity-compat-test @@ -21,8 +21,9 @@ function remove_mapping() function fail() { [ -n "$1" ] && echo "$1" - remove_mapping echo "FAILED" + [ -f $DEV_OUT ] && cat $DEV_OUT + remove_mapping exit 2 } @@ -52,6 +53,7 @@ function wipe() { dd if=/dev/zero of=$LOOPDEV1 bs=256k >/dev/null 2>&1 dd if=/dev/zero of=$LOOPDEV2 bs=256k >/dev/null 2>&1 + rm -f $DEV_OUT >/dev/null 2>&1 } function check_exists() @@ -61,17 +63,17 @@ function check_exists() function compare_out() # $1 what, $2 expected { - OPT=$(grep "$1" $DEV_OUT | sed -e s/.*\:\ //) + OPT=$(grep -v "^#" $DEV_OUT | grep -i "$1" | sed -e s/.*\:\ //) [ -z "$OPT" ] && fail [ $OPT != $2 ] && fail "$1 differs ($OPT)" } -function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, [$5 offset] +function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 offset] { if [ -z "$LOOPDEV2" ] ; then - BLOCKS=$(($5 * 512 / $1)) + BLOCKS=$(($6 * 512 / $1)) DEV_PARAMS="$LOOPDEV1 $LOOPDEV1 \ - --hash-start $5 \ + --hash-start $6 \ --data-blocks=$BLOCKS" else DEV_PARAMS="$LOOPDEV1 $LOOPDEV2" @@ -79,20 +81,20 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, [$5 offset] for fail in data hash; do wipe - echo -n "V$4 block size $1: " + echo -n "V$4 $5 block size $1: " $VERITYSETUP -c $DEV_PARAMS --format=$4 \ --data-block-size=$1 --hash-block-size=$1 \ - --algorithm=sha256 --salt=$3 \ + --algorithm=$5 --salt=$3 \ >$DEV_OUT || fail echo -n "[root hash]" compare_out "root hash" $2 - compare_out "salt" "$3" + compare_out "alt" "$3" - $VERITYSETUP -v $DEV_PARAMS $2 >/dev/null 2>&1 || fail + $VERITYSETUP -v $DEV_PARAMS $2 >>$DEV_OUT 2>&1 || fail echo -n "[verify]" - $VERITYSETUP -a $DEV_NAME $DEV_PARAMS $2 >/dev/null 2>&1 || fail + $VERITYSETUP -a $DEV_NAME $DEV_PARAMS $2 >>$DEV_OUT 2>&1 || fail check_exists echo -n "[activate]" @@ -117,9 +119,9 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, [$5 offset] ;; esac - $VERITYSETUP -v $DEV_PARAMS $2 >/dev/null 2>&1 && \ + $VERITYSETUP -v $DEV_PARAMS $2 >>$DEV_OUT 2>&1 && \ fail "userspace check for $TXT corruption" - $VERITYSETUP -a $DEV_NAME $DEV_PARAMS $2 >/dev/null 2>&1 || \ + $VERITYSETUP -a $DEV_NAME $DEV_PARAMS $2 >>$DEV_OUT 2>&1 || \ fail "activation" dd if=/dev/mapper/$DEV_NAME of=/dev/null bs=$1 2>/dev/null dmsetup status $DEV_NAME | grep "verity V" >/dev/null && \ @@ -152,21 +154,25 @@ SALT=e48da609055204e89ae53b655ca2216dd983cf3cb829f34f63a297d106d53e2d echo "Verity tests [separate devices]" prepare 8192 1024 -check_root_hash 512 9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 $SALT 1 -check_root_hash 1024 54d92778750495d1f80832b486ebd007617d746271511bbf0e295e143da2b3df $SALT 1 -check_root_hash 4096 e522df0f97da4febb882ac40f30b37dc0b444bf6df418929463fa25280f09d5c $SALT 1 -check_root_hash 8192 7fbc02e9ffd56d0b3686c4fe8cbf20c72552df29317ea3b09a5e39a46a92d2f5 $SALT 1 +check_root_hash 512 9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 $SALT 1 sha256 +check_root_hash 1024 54d92778750495d1f80832b486ebd007617d746271511bbf0e295e143da2b3df $SALT 1 sha256 +check_root_hash 4096 e522df0f97da4febb882ac40f30b37dc0b444bf6df418929463fa25280f09d5c $SALT 1 sha256 # version 0 -check_root_hash 4096 cbbf4ebd004ef65e29b935bb635a39cf754d677f3fa10b0126da725bbdf10f7d $SALT 0 +check_root_hash 4096 cbbf4ebd004ef65e29b935bb635a39cf754d677f3fa10b0126da725bbdf10f7d $SALT 0 sha256 +# sha1 +check_root_hash 1024 d0e9163ca8844aaa2e88fe5265a8c5d9ee494a99 $SALT 1 sha1 +check_root_hash 1024 73509e8e868be6b8ac939817a98a3d35121413b2 dadada 1 sha1 echo "Verity tests [one device offset]" prepare $((8192 + 1024)) -check_root_hash 512 9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 $SALT 1 16384 -check_root_hash 1024 54d92778750495d1f80832b486ebd007617d746271511bbf0e295e143da2b3df $SALT 1 16384 -check_root_hash 4096 e522df0f97da4febb882ac40f30b37dc0b444bf6df418929463fa25280f09d5c $SALT 1 16384 -check_root_hash 8192 7fbc02e9ffd56d0b3686c4fe8cbf20c72552df29317ea3b09a5e39a46a92d2f5 $SALT 1 16384 +check_root_hash 512 9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 $SALT 1 sha256 16384 +check_root_hash 1024 54d92778750495d1f80832b486ebd007617d746271511bbf0e295e143da2b3df $SALT 1 sha256 16384 +check_root_hash 4096 e522df0f97da4febb882ac40f30b37dc0b444bf6df418929463fa25280f09d5c $SALT 1 sha256 16384 # version 0 -check_root_hash 4096 cbbf4ebd004ef65e29b935bb635a39cf754d677f3fa10b0126da725bbdf10f7d $SALT 0 16384 +check_root_hash 4096 cbbf4ebd004ef65e29b935bb635a39cf754d677f3fa10b0126da725bbdf10f7d $SALT 0 sha256 16384 +# sha1 +check_root_hash 1024 d0e9163ca8844aaa2e88fe5265a8c5d9ee494a99 $SALT 1 sha1 16384 +check_root_hash 1024 73509e8e868be6b8ac939817a98a3d35121413b2 dadada 1 sha1 16384 remove_mapping exit 0