From 14de7ef914167ed08af6c0fb283e91f25e68b60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20PORTAY?= Date: Fri, 25 Dec 2020 03:08:05 -0500 Subject: [PATCH 1/3] veritysetup: add support for hash-offset option The verity parameter hash_area_offset allows to locate the superblock in the hash device. It can be used to have a single device which contains both data and hashes. This adds the option hash-offset=BYTES (sixth argument) which is the equivalent of the option --hash-offset in the veritysetup world. See `veritysetup(8)` for more details. --- man/systemd-veritysetup-generator.xml | 3 ++- man/veritytab.xml | 7 +++++++ src/veritysetup/veritysetup.c | 19 +++++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/man/systemd-veritysetup-generator.xml b/man/systemd-veritysetup-generator.xml index 37ded91a93..6098895f55 100644 --- a/man/systemd-veritysetup-generator.xml +++ b/man/systemd-veritysetup-generator.xml @@ -85,7 +85,8 @@ systemd.verity_root_options= Takes a comma-separated list of dm-verity options. Expects the following options - , , , + , , + , , , and . See veritysetup8 for more diff --git a/man/veritytab.xml b/man/veritytab.xml index dc2f11c31e..ec5d0f45a1 100644 --- a/man/veritytab.xml +++ b/man/veritytab.xml @@ -60,6 +60,13 @@ This is based on crypttab(5). + + + + Offset of hash area/superblock on hash-device. (Multiples of 512 bytes.) + + + diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index ae497b02ee..e1b0e00e42 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -10,12 +10,14 @@ #include "hexdecoct.h" #include "log.h" #include "main-func.h" +#include "parse-util.h" #include "path-util.h" #include "pretty-print.h" #include "process-util.h" #include "string-util.h" #include "terminal-util.h" +static uint64_t arg_hash_offset = 0; static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY; static char *arg_root_hash_signature = NULL; @@ -104,7 +106,17 @@ static int parse_options(const char *options) { else if (streq(word, "panic-on-corruption")) arg_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION; #endif - else if ((val = startswith(word, "root-hash-signature="))) { + else if ((val = startswith(word, "hash-offset="))) { + uint64_t off; + + r = parse_size(val, 1024, &off); + if (r < 0) + return log_error_errno(r, "Failed to parse offset '%s': %m", word); + if (off % 512 != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "hash-offset= expects a 512-byte aligned value."); + + arg_hash_offset = off; + } else if ((val = startswith(word, "root-hash-signature="))) { r = save_roothashsig_option(val, /* strict= */ true); if (r < 0) return r; @@ -138,6 +150,7 @@ static int run(int argc, char *argv[]) { if (streq(verb, "attach")) { const char *volume, *data_device, *verity_device, *root_hash, *options; _cleanup_free_ void *m = NULL; + struct crypt_params_verity p = {}; crypt_status_info status; size_t l; @@ -173,9 +186,11 @@ static int run(int argc, char *argv[]) { r = parse_options(options); if (r < 0) return log_error_errno(r, "Failed to parse options: %m"); + + p.hash_area_offset = arg_hash_offset; } - r = crypt_load(cd, CRYPT_VERITY, NULL); + r = crypt_load(cd, CRYPT_VERITY, &p); if (r < 0) return log_error_errno(r, "Failed to load verity superblock: %m"); From 0bbf7a842a3feb4952415d82d2b469f5c5d91836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20PORTAY?= Date: Wed, 23 Dec 2020 10:10:15 -0500 Subject: [PATCH 2/3] veritysetup: add support for superblock and underlying options The verity parameter no_superblock allows to format/open an hash device without the superblock. However, the superblock data must be set to open the data-device. This adds the option superblocks (sixth argument) and all the underlying options which are implied to set the superblock manually if hash device has no superblock: - superblock=BOOL - format=NUMBER (hash version type, 0 for original ChromeOS, 1 for modern) - data-block-size=BYTES (max page-size, multiple of 512) - hash-block-size=BYTES (max page-size, multiple of 512) - data-blocks=BLOCKS (size of data-device in blocks) - salt=HEXSTR (salt used at format, max 256 bytes) - uuid=UUID - hash=STR (algorithm name for dm-verity used at format, default is sha256) See `veritysetup(8)` for more details. --- man/systemd-veritysetup-generator.xml | 13 ++- man/veritytab.xml | 56 +++++++++++ src/veritysetup/veritysetup.c | 138 ++++++++++++++++++++++++-- 3 files changed, 198 insertions(+), 9 deletions(-) diff --git a/man/systemd-veritysetup-generator.xml b/man/systemd-veritysetup-generator.xml index 6098895f55..c591fcb24f 100644 --- a/man/systemd-veritysetup-generator.xml +++ b/man/systemd-veritysetup-generator.xml @@ -85,9 +85,16 @@ systemd.verity_root_options= Takes a comma-separated list of dm-verity options. Expects the following options - , , - , , - , and + , + , + , + , + , + , + , , + , , , + , , + and . See veritysetup8 for more details. diff --git a/man/veritytab.xml b/man/veritytab.xml index ec5d0f45a1..b4f2be3e30 100644 --- a/man/veritytab.xml +++ b/man/veritytab.xml @@ -60,6 +60,40 @@ This is based on crypttab(5). + + + + Use dm-verity with or without permanent on-disk superblock. + + + + + + Specifies the hash version type. Format type 0 is original Chrome OS version. Format type 1 is + modern version. + + + + + + Used block size for the data device. (Note kernel supports only page-size as maximum + here; Multiples of 512 bytes.) + + + + + + Used block size for the hash device. (Note kernel supports only page-size as maximum + here; Multiples of 512 bytes.) + + + + + + Number of blocks of data device used in verification. If not specified, the whole device is + used. + + @@ -67,6 +101,21 @@ This is based on crypttab(5). + + + + Salt used for format or verification. Format is a hexadecimal string; 256 bytes long maximum; + -is the special value for empty. + + + + + + Use the provided UUID for format command instead of generating new one. The UUID must be + provided in standard UUID format, e.g. 12345678-1234-1234-1234-123456789abc. + + + @@ -101,6 +150,13 @@ This is based on crypttab(5). + + + + Hash algorithm for dm-verity. This should be the name of the algorithm, like "sha1". For default + see veritysetup --help. + + diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index e1b0e00e42..120d8b619a 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -17,10 +17,22 @@ #include "string-util.h" #include "terminal-util.h" +static char *arg_hash = NULL; +static bool arg_superblock = true; +static int arg_format = 1; +static uint64_t arg_data_block_size = 4096; +static uint64_t arg_hash_block_size = 4096; +static uint64_t arg_data_blocks = 0; static uint64_t arg_hash_offset = 0; +static void *arg_salt = NULL; +static uint64_t arg_salt_size = 32; +static char *arg_uuid = NULL; static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY; static char *arg_root_hash_signature = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); +STATIC_DESTRUCTOR_REGISTER(arg_salt, freep); +STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep); STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep); static int help(void) { @@ -64,6 +76,25 @@ static int save_roothashsig_option(const char *option, bool strict) { "base64 string encoding signature prefixed by base64:."); } +static int parse_block_size(const char *t, uint64_t *size) { + uint64_t u; + int r; + + r = parse_size(t, 1024, &u); + if (r < 0) + return r; + + if (u < 512 || u > (512 * 1024)) + return -ERANGE; + + if ((u % 512) != 0 || !ISPOWEROF2(u)) + return -EINVAL; + + *size = u; + + return 0; +} + static int parse_options(const char *options) { int r; @@ -106,7 +137,45 @@ static int parse_options(const char *options) { else if (streq(word, "panic-on-corruption")) arg_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION; #endif - else if ((val = startswith(word, "hash-offset="))) { + else if ((val = startswith(word, "superblock="))) { + + r = parse_boolean(val); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean '%s': %m", word); + + arg_superblock = r; + } else if ((val = startswith(word, "format="))) { + + if (!STR_IN_SET(val, "0", "1")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "format= expects either 0 (original Chrome OS version) or " + "1 (modern version)."); + + arg_format = val[0] - '0'; + } else if ((val = startswith(word, "data-block-size="))) { + uint64_t sz; + + r = parse_block_size(val, &sz); + if (r < 0) + return log_error_errno(r, "Failed to parse size '%s': %m", word); + + arg_data_block_size = sz; + } else if ((val = startswith(word, "hash-block-size="))) { + uint64_t sz; + + r = parse_block_size(val, &sz); + if (r < 0) + return log_error_errno(r, "Failed to parse size '%s': %m", word); + + arg_hash_block_size = sz; + } else if ((val = startswith(word, "data-blocks="))) { + uint64_t u; + + r = safe_atou64(val, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse number '%s': %m", word); + + arg_data_blocks = u; + } else if ((val = startswith(word, "hash-offset="))) { uint64_t off; r = parse_size(val, 1024, &off); @@ -116,6 +185,42 @@ static int parse_options(const char *options) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "hash-offset= expects a 512-byte aligned value."); arg_hash_offset = off; + } else if ((val = startswith(word, "salt="))) { + + if (!string_is_safe(val)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid."); + + if (isempty(val)) { + arg_salt = mfree(arg_salt); + arg_salt_size = 32; + } else if (streq(val, "-")) { + arg_salt = mfree(arg_salt); + arg_salt_size = 0; + } else { + size_t l; + void *m; + + r = unhexmem(val, strlen(val), &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse salt '%s': %m", word); + + free_and_replace(arg_salt, m); + arg_salt_size = l; + } + } else if ((val = startswith(word, "uuid="))) { + + r = sd_id128_from_string(val, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse UUID '%s': %m", word); + + r = free_and_strdup(&arg_uuid, val); + if (r < 0) + return log_oom(); + } else if ((val = startswith(word, "hash="))) { + + r = free_and_strdup(&arg_hash, val); + if (r < 0) + return log_oom(); } else if ((val = startswith(word, "root-hash-signature="))) { r = save_roothashsig_option(val, /* strict= */ true); if (r < 0) @@ -186,13 +291,34 @@ static int run(int argc, char *argv[]) { r = parse_options(options); if (r < 0) return log_error_errno(r, "Failed to parse options: %m"); - - p.hash_area_offset = arg_hash_offset; } - r = crypt_load(cd, CRYPT_VERITY, &p); - if (r < 0) - return log_error_errno(r, "Failed to load verity superblock: %m"); + if (arg_superblock) { + p = (struct crypt_params_verity) { + .hash_area_offset = arg_hash_offset, + }; + + r = crypt_load(cd, CRYPT_VERITY, &p); + if (r < 0) + return log_error_errno(r, "Failed to load verity superblock: %m"); + } else { + p = (struct crypt_params_verity) { + .hash_name = arg_hash, + .data_device = data_device, + .salt = arg_salt, + .salt_size = arg_salt_size, + .hash_type = arg_format, + .data_block_size = arg_data_block_size, + .hash_block_size = arg_hash_block_size, + .data_size = arg_data_blocks, + .hash_area_offset = arg_hash_offset, + .flags = CRYPT_VERITY_NO_HEADER, + }; + + r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); + if (r < 0) + return log_error_errno(r, "Failed to format verity superblock: %m"); + } r = crypt_set_data_device(cd, data_device); if (r < 0) From 21c60c76e10b23d47b97fdd88a56314fc0b3c66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20PORTAY?= Date: Sun, 27 Dec 2020 08:55:09 -0500 Subject: [PATCH 3/3] veritysetup: add support for fec options The verity fec_* parameters allows to use Forward Error Correction to recover from corruption if hash verification fails. This adds the options fec_device, fec_offset and fec_roots (sixth argument) which are the equivalent of the options --fec-device, --fec-offset and --fec-roots in the veritysetup world. - fec-device=FILE - fec-offset=BYTES - fec-roots=UINT64 See `veritysetup(8)` for more details. --- man/systemd-veritysetup-generator.xml | 3 +- man/veritytab.xml | 24 ++++++++++++++ src/veritysetup/veritysetup.c | 47 +++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/man/systemd-veritysetup-generator.xml b/man/systemd-veritysetup-generator.xml index c591fcb24f..71bc1fda64 100644 --- a/man/systemd-veritysetup-generator.xml +++ b/man/systemd-veritysetup-generator.xml @@ -94,7 +94,8 @@ , , , , , , , - and + , , + , and . See veritysetup8 for more details. diff --git a/man/veritytab.xml b/man/veritytab.xml index b4f2be3e30..557d13e1ed 100644 --- a/man/veritytab.xml +++ b/man/veritytab.xml @@ -157,6 +157,30 @@ This is based on crypttab(5). see veritysetup --help. + + + + Use forward error correction (FEC) to recover from corruption if hash verification fails. Use + encoding data from the specified device. The fec device argument can be block device or file image. For format, + if fec device path doesn't exist, it will be created as file. Note: block sizes for data and hash devices must + match. Also, if the verity data_device is encrypted the fec_device should be too. + + + + + + This is the offset, in bytes, from the start of the FEC device to the beginning of the encoding + data. (Aligned on 512 bytes.) + + + + + + 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). + + + diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index 120d8b619a..be95c1f206 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "cryptsetup-util.h" #include "fileio.h" +#include "fstab-util.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" @@ -28,11 +29,15 @@ static void *arg_salt = NULL; static uint64_t arg_salt_size = 32; static char *arg_uuid = NULL; static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY; +static char *arg_fec_what = NULL; +static uint64_t arg_fec_offset = 0; +static uint64_t arg_fec_roots = 2; static char *arg_root_hash_signature = NULL; STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); STATIC_DESTRUCTOR_REGISTER(arg_salt, freep); STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep); +STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep); STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep); static int help(void) { @@ -221,6 +226,42 @@ static int parse_options(const char *options) { r = free_and_strdup(&arg_hash, val); if (r < 0) return log_oom(); + } else if ((val = startswith(word, "fec-device="))) { + _cleanup_free_ char *what = NULL; + + what = fstab_node_to_udev_node(val); + if (!what) + return log_oom(); + + if (!path_is_absolute(what)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an absolute path."); + + if (!path_is_normalized(what)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an normalized path."); + + r = free_and_strdup(&arg_fec_what, what); + if (r < 0) + return log_oom(); + } else if ((val = startswith(word, "fec-offset="))) { + uint64_t off; + + r = parse_size(val, 1024, &off); + if (r < 0) + return log_error_errno(r, "Failed to parse offset '%s': %m", word); + if (off % 512 != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-offset= expects a 512-byte aligned value."); + + arg_fec_offset = off; + } else if ((val = startswith(word, "fec-roots="))) { + uint64_t u; + + r = safe_atou64(val, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse number '%s', ignoring: %m", word); + if (u < 2 || u > 24) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-rootfs= expects a value between 2 and 24 (including)."); + + arg_fec_roots = u; } else if ((val = startswith(word, "root-hash-signature="))) { r = save_roothashsig_option(val, /* strict= */ true); if (r < 0) @@ -295,7 +336,10 @@ static int run(int argc, char *argv[]) { if (arg_superblock) { p = (struct crypt_params_verity) { + .fec_device = arg_fec_what, .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, }; r = crypt_load(cd, CRYPT_VERITY, &p); @@ -305,6 +349,7 @@ static int run(int argc, char *argv[]) { p = (struct crypt_params_verity) { .hash_name = arg_hash, .data_device = data_device, + .fec_device = arg_fec_what, .salt = arg_salt, .salt_size = arg_salt_size, .hash_type = arg_format, @@ -312,6 +357,8 @@ static int run(int argc, char *argv[]) { .hash_block_size = arg_hash_block_size, .data_size = arg_data_blocks, .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, .flags = CRYPT_VERITY_NO_HEADER, };