diff --git a/man/veritysetup.8 b/man/veritysetup.8 index ecbff4ba..0013b966 100644 --- a/man/veritysetup.8 +++ b/man/veritysetup.8 @@ -28,10 +28,15 @@ If hash device path doesn't exist, it will be created as file. \fB\fR can be [\-\-hash, \-\-no-superblock, \-\-format, \-\-data-block-size, \-\-hash-block-size, \-\-data-blocks, \-\-hash-offset, -\-\-salt, \-\-uuid] +\-\-salt, \-\-uuid, \-\-root-hash-file] + +If option \-\-root-hash-file is used, the root hash is stored in hex-encoded text +format in . .PP \fIopen\fR .br +\fIopen\fR \-\-root-hash-file +.br \fIcreate\fR (\fBOBSOLETE syntax\fR) .IP Creates a mapping with backed by device and using @@ -41,12 +46,19 @@ The is a hexadecimal string. \fB\fR can be [\-\-hash-offset, \-\-no-superblock, \-\-ignore-corruption or \-\-restart-on-corruption, \-\-panic-on-corruption, -\-\-ignore-zero-blocks, \-\-check-at-most-once, \-\-root-hash-signature] +\-\-ignore-zero-blocks, \-\-check-at-most-once, \-\-root-hash-signature, +\-\-root-hash-file] + +If option \-\-root-hash-file is used, the root hash is read from instead +of from the command line parameter. Expects hex-encoded text, without terminating +newline. If option \-\-no-superblock is used, you have to use as the same options as in initial format operation. .PP \fIverify\fR +.br +\fIverify\fR \-\-root-hash-file .IP Verifies data on data_device with use of hash blocks stored on hash_device. @@ -54,7 +66,11 @@ This command performs userspace verification, no kernel device is created. The is a hexadecimal string. -\fB\fR can be [\-\-hash-offset, \-\-no-superblock] +If option \-\-root-hash-file is used, the root hash is read from instead +of from the command line parameter. Expects hex-encoded text, without terminating +newline. + +\fB\fR can be [\-\-hash-offset, \-\-no-superblock, \-\-root-hash-file] If option \-\-no-superblock is used, you have to use as the same options as in initial format operation. @@ -181,6 +197,9 @@ This is the offset, in bytes, from the start of the FEC device to the beginning Number of generator roots. This equals to the number of parity bytes in the encoding data. In RS(M, N) encoding, the number of roots is M-N. M is 255 and M-N is between 2 and 24 (including). .TP +.B "\-\-root-hash-file=FILE" +Path to file with stored root hash in hex-encoded text. +.TP .B "\-\-root-hash-signature=FILE" Path to roothash signature file used to verify the root hash (in kernel). This feature requires Linux kernel version 5.4 or more recent. @@ -207,9 +226,10 @@ Error codes are: Calculates and stores verification data on hash_device for the first 256 blocks (of block-size). If hash_device does not exist, it is created (as file image). -.B "veritysetup format " +.B "veritysetup format --root-hash-file " -Calculates and stores verification data on hash_device for the whole data_device. +Calculates and stores verification data on hash_device for the whole data_device, and store the +root hash as hex-encoded text in . .B "veritysetup \-\-data-blocks=256 \-\-hash-offset=1052672 format " @@ -225,6 +245,10 @@ as in the format command. The was calculated in format command. Verifies device without activation (in userspace). +.B "veritysetup \-\-data-blocks=256 \-\-hash-offset=1052672 --root-hash-file verify " + +Verifies device without activation (in userspace). Root hash passed via a file rather than inline. + .B "veritysetup \-\-fec-device= \-\-fec-roots=10 format " Calculates and stores verification and encoding data for data_device. diff --git a/src/utils_arg_names.h b/src/utils_arg_names.h index 57ab2a3c..b6865c06 100644 --- a/src/utils_arg_names.h +++ b/src/utils_arg_names.h @@ -126,6 +126,7 @@ #define OPT_RESILIENCE_HASH "resilience-hash" #define OPT_RESTART_ON_CORRUPTION "restart-on-corruption" #define OPT_RESUME_ONLY "resume-only" +#define OPT_ROOT_HASH_FILE "root-hash-file" #define OPT_ROOT_HASH_SIGNATURE "root-hash-signature" #define OPT_SALT "salt" #define OPT_SECTOR_SIZE "sector-size" diff --git a/src/veritysetup.c b/src/veritysetup.c index ec986793..04fd996d 100644 --- a/src/veritysetup.c +++ b/src/veritysetup.c @@ -77,7 +77,9 @@ static int action_format(void) struct crypt_device *cd = NULL; struct crypt_params_verity params = {}; uint32_t flags = CRYPT_VERITY_CREATE_HASH; - int r; + char *root_hash_bytes = NULL; + size_t root_hash_size; + int root_hash_fd = -1, i, r; /* Try to create hash image if doesn't exist */ r = open(action_argv[1], O_WRONLY | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR); @@ -111,11 +113,46 @@ static int action_format(void) goto out; r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, ARG_STR(OPT_UUID_ID), NULL, 0, ¶ms); - if (!r) - crypt_dump(cd); + if (r < 0) + goto out; + + crypt_dump(cd); + + /* Create or overwrite the root hash file */ + if (ARG_SET(OPT_ROOT_HASH_FILE_ID)) { + root_hash_size = crypt_get_volume_key_size(cd); + root_hash_bytes = malloc(root_hash_size); + if (!root_hash_bytes) { + r = -ENOMEM; + goto out; + } + + r = crypt_volume_key_get(cd, CRYPT_ANY_SLOT, root_hash_bytes, &root_hash_size, NULL, 0); + if (r < 0) + goto out; + + root_hash_fd = open(ARG_STR(OPT_ROOT_HASH_FILE_ID), O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); + if (root_hash_fd == -1) { + log_err(_("Cannot create root hash file %s for writing."), ARG_STR(OPT_ROOT_HASH_FILE_ID)); + r = -EINVAL; + goto out; + } + + for (i = 0; i < (int)root_hash_size; i++) + if (dprintf(root_hash_fd, "%02hhx", root_hash_bytes[i]) != 2) { + log_err(_("Cannot write to root hash file %s."), ARG_STR(OPT_ROOT_HASH_FILE_ID)); + r = -EIO; + goto out; + } + + log_dbg("Created root hash file %s.", ARG_STR(OPT_ROOT_HASH_FILE_ID)); + } out: crypt_free(cd); free(CONST_CAST(char*)params.salt); + free(root_hash_bytes); + if (root_hash_fd != -1) + close(root_hash_fd); return r; } @@ -128,11 +165,11 @@ static int _activate(const char *dm_device, struct crypt_device *cd = NULL; struct crypt_params_verity params = {}; uint32_t activate_flags = CRYPT_ACTIVATE_READONLY; - char *root_hash_bytes = NULL; - ssize_t hash_size; + char *root_hash_bytes = NULL, *root_hash_from_file = NULL; + ssize_t hash_size, hash_size_hex; struct stat st; char *signature = NULL; - int signature_size = 0, r; + int signature_size = 0, root_hash_fd = -1, r; if ((r = crypt_init_data_device(&cd, hash_device, data_device))) goto out; @@ -165,6 +202,36 @@ static int _activate(const char *dm_device, goto out; hash_size = crypt_get_volume_key_size(cd); + hash_size_hex = 2 * hash_size; + + if (!root_hash) { + root_hash_fd = open(ARG_STR(OPT_ROOT_HASH_FILE_ID), O_RDONLY); + if (root_hash_fd == -1) { + log_err(_("Cannot read root hash file %s."), ARG_STR(OPT_ROOT_HASH_FILE_ID)); + goto out; + } + + if (fstat(root_hash_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < hash_size_hex) { + log_err(_("Invalid root hash file %s."), ARG_STR(OPT_ROOT_HASH_FILE_ID)); + r = -EINVAL; + goto out; + } + + root_hash_from_file = malloc(hash_size_hex + 1); + if (!root_hash_from_file) { + r = -ENOMEM; + goto out; + } + + if (read_buffer(root_hash_fd, root_hash_from_file, hash_size_hex) != hash_size_hex) { + log_err(_("Cannot read root hash file %s."), root_hash_from_file); + goto out; + } + + root_hash_from_file[hash_size_hex] = '\0'; + root_hash = root_hash_from_file; + } + if (crypt_hex_to_bytes(root_hash, &root_hash_bytes, 0) != hash_size) { log_err(_("Invalid root hash string specified.")); r = -EINVAL; @@ -193,26 +260,39 @@ static int _activate(const char *dm_device, out: crypt_safe_free(signature); crypt_free(cd); + free(root_hash_from_file); free(root_hash_bytes); free(CONST_CAST(char*)params.salt); + if (root_hash_fd != -1) + close(root_hash_fd); return r; } static int action_open(void) { + if (action_argc < 4 && !ARG_SET(OPT_ROOT_HASH_FILE_ID)) { + log_err(_("Command requires or --root-hash-file option as argument.")); + return -EINVAL; + } + return _activate(action_argv[1], action_argv[0], action_argv[2], - action_argv[3], + ARG_SET(OPT_ROOT_HASH_FILE_ID) ? NULL : action_argv[3], ARG_SET(OPT_ROOT_HASH_SIGNATURE_ID) ? CRYPT_VERITY_ROOT_HASH_SIGNATURE : 0); } static int action_verify(void) { + if (action_argc < 3 && !ARG_SET(OPT_ROOT_HASH_FILE_ID)) { + log_err(_("Command requires or --root-hash-file option as argument.")); + return -EINVAL; + } + return _activate(NULL, action_argv[0], action_argv[1], - action_argv[2], + ARG_SET(OPT_ROOT_HASH_FILE_ID) ? NULL : action_argv[2], CRYPT_VERITY_CHECK_HASH); } @@ -396,8 +476,8 @@ static struct action_type { const char *desc; } action_types[] = { { "format", action_format, 2, N_(" "),N_("format device") }, - { "verify", action_verify, 3, N_(" "),N_("verify device") }, - { "open", action_open, 4, N_(" "),N_("open device as ") }, + { "verify", action_verify, 2, N_(" []"),N_("verify device") }, + { "open", action_open, 3, N_(" []"),N_("open device as ") }, { "close", action_close, 1, N_(""),N_("close device (remove mapping)") }, { "status", action_status, 1, N_(""),N_("show active device status") }, { "dump", action_dump, 1, N_(""),N_("show on-disk information") }, diff --git a/src/veritysetup_arg_list.h b/src/veritysetup_arg_list.h index 388565cd..40e136ba 100644 --- a/src/veritysetup_arg_list.h +++ b/src/veritysetup_arg_list.h @@ -57,6 +57,8 @@ ARG(OPT_PANIC_ON_CORRUPTION, '\0', POPT_ARG_NONE, N_("Panic kernel if corruption ARG(OPT_RESTART_ON_CORRUPTION, '\0', POPT_ARG_NONE, N_("Restart kernel if corruption is detected"), NULL, CRYPT_ARG_BOOL, {}, OPT_RESTART_ON_CORRUPTION_ACTIONS) +ARG(OPT_ROOT_HASH_FILE, '\0', POPT_ARG_STRING, N_("Path to root hash file"), NULL, CRYPT_ARG_STRING, {}, OPT_ROOT_HASH_FILE_ACTIONS) + ARG(OPT_ROOT_HASH_SIGNATURE, '\0', POPT_ARG_STRING, N_("Path to root hash signature file"), NULL, CRYPT_ARG_STRING, {}, OPT_ROOT_HASH_SIGNATURE_ACTIONS) ARG(OPT_SALT, 's', POPT_ARG_STRING, N_("Salt"), N_("hex string"), CRYPT_ARG_STRING, {}, {}) diff --git a/src/veritysetup_args.h b/src/veritysetup_args.h index 537c9076..2dd38130 100644 --- a/src/veritysetup_args.h +++ b/src/veritysetup_args.h @@ -37,6 +37,7 @@ #define OPT_IGNORE_ZERO_BLOCKS_ACTIONS { OPEN_ACTION } #define OPT_RESTART_ON_CORRUPTION_ACTIONS { OPEN_ACTION } #define OPT_PANIC_ON_CORRUPTION_ACTIONS { OPEN_ACTION } +#define OPT_ROOT_HASH_FILE_ACTIONS { FORMAT_ACTION, OPEN_ACTION, VERIFY_ACTION } #define OPT_ROOT_HASH_SIGNATURE_ACTIONS { OPEN_ACTION } enum { diff --git a/tests/verity-compat-test b/tests/verity-compat-test index 26df5805..5e17dc31 100755 --- a/tests/verity-compat-test +++ b/tests/verity-compat-test @@ -21,7 +21,7 @@ function remove_mapping() [ -b /dev/mapper/$DEV_NAME2 ] && dmsetup remove $DEV_NAME2 >/dev/null 2>&1 [ -b /dev/mapper/$DEV_NAME ] && dmsetup remove $DEV_NAME >/dev/null 2>&1 [ ! -z "$LOOPDEV1" ] && losetup -d $LOOPDEV1 >/dev/null 2>&1 - rm -f $IMG $IMG_HASH $DEV_OUT $FEC_DEV $IMG_TMP >/dev/null 2>&1 + rm -f $IMG $IMG.roothash $IMG_HASH $DEV_OUT $FEC_DEV $IMG_TMP >/dev/null 2>&1 LOOPDEV1="" LOOPDEV2="" } @@ -112,6 +112,10 @@ function check_root_hash_fail() function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 offset] { + local FORMAT_PARAMS + local VERIFY_PARAMS + local ROOT_HASH + if [ -z "$LOOPDEV2" ] ; then BLOCKS=$(($6 / $1)) DEV_PARAMS="$LOOPDEV1 $LOOPDEV1 \ @@ -121,6 +125,7 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 DEV_PARAMS="$LOOPDEV1 $LOOPDEV2" fi + for root_hash_as_file in yes no; do for sb in yes no; do FORMAT_PARAMS="--format=$4 --data-block-size=$1 --hash-block-size=$1 --hash=$5 --salt=$3" if [ $sb == yes ] ; then @@ -129,20 +134,28 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 FORMAT_PARAMS="$FORMAT_PARAMS --no-superblock" VERIFY_PARAMS=$FORMAT_PARAMS fi + if [ $root_hash_as_file == yes ] ; then + echo -n $2 > $IMG.roothash + FORMAT_PARAMS="$FORMAT_PARAMS --root-hash-file=$IMG.roothash" + VERIFY_PARAMS="$VERIFY_PARAMS --root-hash-file=$IMG.roothash" + ROOT_HASH="" + else + ROOT_HASH="$2" + fi for fail in data hash; do wipe - echo -n "V$4(sb=$sb) $5 block size $1: " + echo -n "V$4(sb=$sb root_hash_as_file=$root_hash_as_file) $5 block size $1: " $VERITYSETUP format $DEV_PARAMS $FORMAT_PARAMS >$DEV_OUT || fail echo -n "[root hash]" compare_out "root hash" $2 compare_out "salt" "$3" - $VERITYSETUP verify $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 || fail + $VERITYSETUP verify $DEV_PARAMS $VERIFY_PARAMS $ROOT_HASH >>$DEV_OUT 2>&1 || fail echo -n "[verify]" - $VERITYSETUP create $DEV_NAME $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 || fail + $VERITYSETUP create $DEV_NAME $DEV_PARAMS $VERIFY_PARAMS $ROOT_HASH >>$DEV_OUT 2>&1 || fail check_exists echo -n "[activate]" @@ -167,9 +180,9 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 ;; esac - $VERITYSETUP verify $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 && \ + $VERITYSETUP verify $DEV_PARAMS $VERIFY_PARAMS $ROOT_HASH >>$DEV_OUT 2>&1 && \ fail "userspace check for $TXT corruption" - $VERITYSETUP create $DEV_NAME $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 || \ + $VERITYSETUP create $DEV_NAME $DEV_PARAMS $VERIFY_PARAMS $ROOT_HASH >>$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 && \ @@ -178,6 +191,7 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 echo "[$TXT corruption]" done done + done } function corrupt_device() # $1 device, $2 device_size(in bytes), $3 #{corrupted_bytes}