From 4dd703ea6c49881ad11349f5a1799d8a3edda18a Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Wed, 4 May 2016 10:07:47 +0200 Subject: [PATCH] Support activation options for error handling modes in dm-verity. This patch adds veritysetup support for these Linux kernel dm-verity options: --ignore-corruption - dm-verity just logs detected corruption --restart-on-corruption - dm-verity restarts the kernel if corruption is detected If the options above are not specified, default behaviour for dm-verity remains. Default is that I/O operation fails with I/O error if corrupted block is detected. --ignore-zero-blocks - Instructs dm-verity to not verify blocks that are expected to contain zeroes and always return zeroes directly instead. NOTE that these options could have serious security or functional impacts, do not use them without assessing the risks! --- lib/libcryptsetup.h | 10 ++++- lib/libdevmapper.c | 86 ++++++++++++++++++++++++++++++++++------ lib/setup.c | 6 +-- lib/utils_dm.h | 5 ++- man/veritysetup.8 | 25 ++++++++++-- src/veritysetup.c | 35 +++++++++++++++- tests/verity-compat-test | 38 ++++++++++++++++++ 7 files changed, 181 insertions(+), 24 deletions(-) diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 15138d6c..80bbf5c8 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -3,8 +3,8 @@ * * Copyright (C) 2004, Jana Saout * Copyright (C) 2004-2007, Clemens Fruhwirth - * Copyright (C) 2009-2015, Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2015, Milan Broz + * Copyright (C) 2009-2016, Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2016, Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -667,6 +667,12 @@ int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot); #define CRYPT_ACTIVATE_SAME_CPU_CRYPT (1 << 6) /** use submit_from_crypt_cpus for dm-crypt */ #define CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS (1 << 7) +/** dm-verity: ignore_corruption flag - ignore corruption, log it only */ +#define CRYPT_ACTIVATE_IGNORE_CORRUPTION (1 << 8) +/** dm-verity: restart_on_corruption flag - restart kernel on corruption */ +#define CRYPT_ACTIVATE_RESTART_ON_CORRUPTION (1 << 9) +/** dm-verity: ignore_zero_blocks - do not verify zero blocks */ +#define CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS (1 << 10) /** diff --git a/lib/libdevmapper.c b/lib/libdevmapper.c index ca2084fc..b6cd3a7c 100644 --- a/lib/libdevmapper.c +++ b/lib/libdevmapper.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004, Jana Saout * Copyright (C) 2004-2007, Clemens Fruhwirth - * Copyright (C) 2009-2015, Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2015, Milan Broz + * Copyright (C) 2009-2016, Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2016, Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -159,6 +159,15 @@ static void _dm_set_verity_compat(const char *dm_version, unsigned verity_maj, { if (verity_maj > 0) _dm_crypt_flags |= DM_VERITY_SUPPORTED; + else + return; + /* + * ignore_corruption, restart_on corruption is available since 1.2 (kernel 4.1) + * ignore_zero_blocks since 1.3 (kernel 4.5) + * (but some dm-verity targets 1.2 don't support it) + */ + if (_dm_satisfies_version(1, 3, verity_maj, verity_min)) + _dm_crypt_flags |= DM_VERITY_ON_CORRUPTION_SUPPORTED; log_dbg("Detected dm-verity version %i.%i.%i.", verity_maj, verity_min, verity_patch); @@ -357,14 +366,35 @@ out: /* https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity */ static char *get_dm_verity_params(struct crypt_params_verity *vp, - struct crypt_dm_active_device *dmd) + struct crypt_dm_active_device *dmd, uint32_t flags) { - int max_size, r; + int max_size, r, num_options = 0; char *params = NULL, *hexroot = NULL, *hexsalt = NULL; + char features[256]; if (!vp || !dmd) return NULL; + /* These flags are not compatible */ + if ((flags & CRYPT_ACTIVATE_IGNORE_CORRUPTION) && + (flags & CRYPT_ACTIVATE_RESTART_ON_CORRUPTION)) + flags &= ~CRYPT_ACTIVATE_IGNORE_CORRUPTION; + + if (flags & CRYPT_ACTIVATE_IGNORE_CORRUPTION) + num_options++; + if (flags & CRYPT_ACTIVATE_RESTART_ON_CORRUPTION) + num_options++; + if (flags & CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS) + num_options++; + + if (num_options) + snprintf(features, sizeof(features)-1, " %d%s%s%s", num_options, + (flags & CRYPT_ACTIVATE_IGNORE_CORRUPTION) ? " ignore_corruption" : "", + (flags & CRYPT_ACTIVATE_RESTART_ON_CORRUPTION) ? " restart_on_corruption" : "", + (flags & CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS) ? " ignore_zero_blocks" : ""); + else + *features = '\0'; + hexroot = crypt_safe_alloc(dmd->u.verity.root_hash_size * 2 + 1); if (!hexroot) goto out; @@ -388,12 +418,12 @@ static char *get_dm_verity_params(struct crypt_params_verity *vp, goto out; r = snprintf(params, max_size, - "%u %s %s %u %u %" PRIu64 " %" PRIu64 " %s %s %s", + "%u %s %s %u %u %" PRIu64 " %" PRIu64 " %s %s %s %s", vp->hash_type, device_block_path(dmd->data_device), device_block_path(dmd->u.verity.hash_device), vp->data_block_size, vp->hash_block_size, vp->data_size, dmd->u.verity.hash_offset, - vp->hash_name, hexroot, hexsalt); + vp->hash_name, hexroot, hexsalt, features); if (r < 0 || r >= max_size) { crypt_safe_free(params); params = NULL; @@ -676,7 +706,7 @@ int dm_create_device(struct crypt_device *cd, const char *name, if (dmd->target == DM_CRYPT) table_params = get_dm_crypt_params(dmd, dmd_flags); else if (dmd->target == DM_VERITY) - table_params = get_dm_verity_params(dmd->u.verity.vp, dmd); + table_params = get_dm_verity_params(dmd->u.verity.vp, dmd, dmd_flags); r = _dm_create_device(name, type, dmd->data_device, dmd_flags, dmd->uuid, dmd->size, table_params, reload); @@ -696,7 +726,13 @@ int dm_create_device(struct crypt_device *cd, const char *name, if (r == -EINVAL && dmd_flags & (CRYPT_ACTIVATE_SAME_CPU_CRYPT|CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS) && !(dm_flags() & (DM_SAME_CPU_CRYPT_SUPPORTED|DM_SUBMIT_FROM_CRYPT_CPUS_SUPPORTED))) - log_err(cd, _("Requested dmcrypt performance options are not supported.\n")); + log_err(cd, _("Requested dm-crypt performance options are not supported.\n")); + + if (r == -EINVAL && dmd_flags & (CRYPT_ACTIVATE_IGNORE_CORRUPTION| + CRYPT_ACTIVATE_RESTART_ON_CORRUPTION| + CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS) && + !(dm_flags() & DM_VERITY_ON_CORRUPTION_SUPPORTED)) + log_err(cd, _("Requested dm-verity data corruption handling options are not supported.\n")); crypt_safe_free(table_params); dm_exit_context(); @@ -893,7 +929,7 @@ static int _dm_query_crypt(uint32_t get_flags, return -EINVAL; } - /* All parameters shold be processed */ + /* All parameters should be processed */ if (params) return -EINVAL; } @@ -936,7 +972,8 @@ static int _dm_query_verity(uint32_t get_flags, uint32_t val32; uint64_t val64; ssize_t len; - char *str, *str2; + char *str, *str2, *arg; + unsigned int i; int r; if (get_flags & DM_ACTIVE_VERITY_PARAMS) @@ -1032,8 +1069,6 @@ static int _dm_query_verity(uint32_t get_flags, /* salt */ str = strsep(¶ms, " "); - if (params) - return -EINVAL; if (vp) { if (!strcmp(str, "-")) { vp->salt_size = 0; @@ -1047,6 +1082,33 @@ static int _dm_query_verity(uint32_t get_flags, } } + /* Features section, available since verity target version 1.3 */ + if (params) { + /* Number of arguments */ + val64 = strtoull(params, ¶ms, 10); + if (*params != ' ') + return -EINVAL; + params++; + + for (i = 0; i < val64; i++) { + if (!params) + return -EINVAL; + arg = strsep(¶ms, " "); + if (!strcasecmp(arg, "ignore_corruption")) + dmd->flags |= CRYPT_ACTIVATE_IGNORE_CORRUPTION; + else if (!strcasecmp(arg, "restart_on_corruption")) + dmd->flags |= CRYPT_ACTIVATE_RESTART_ON_CORRUPTION; + else if (!strcasecmp(arg, "ignore_zero_blocks")) + dmd->flags |= CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS; + else /* unknown option */ + return -EINVAL; + } + + /* All parameters should be processed */ + if (params) + return -EINVAL; + } + return 0; } diff --git a/lib/setup.c b/lib/setup.c index 0bf5bcfe..307e15cc 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004, Jana Saout * Copyright (C) 2004-2007, Clemens Fruhwirth - * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2014, Milan Broz + * Copyright (C) 2009-2016, Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2016, Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -2009,7 +2009,7 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, } r = VERITY_activate(cd, name, volume_key, volume_key_size, - &cd->u.verity.hdr, CRYPT_ACTIVATE_READONLY); + &cd->u.verity.hdr, flags|CRYPT_ACTIVATE_READONLY); if (r == -EPERM) { free(cd->u.verity.root_hash); diff --git a/lib/utils_dm.h b/lib/utils_dm.h index cd8b6534..9d79c07b 100644 --- a/lib/utils_dm.h +++ b/lib/utils_dm.h @@ -3,8 +3,8 @@ * * Copyright (C) 2004, Jana Saout * Copyright (C) 2004-2007, Clemens Fruhwirth - * Copyright (C) 2009-2015, Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2015, Milan Broz + * Copyright (C) 2009-2016, Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2016, Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,6 +42,7 @@ struct device; #define DM_TCW_SUPPORTED (1 << 6) /* tcw (TCRYPT CBC with whitening) */ #define DM_SAME_CPU_CRYPT_SUPPORTED (1 << 7) /* same_cpu_crypt */ #define DM_SUBMIT_FROM_CRYPT_CPUS_SUPPORTED (1 << 8) /* submit_from_crypt_cpus */ +#define DM_VERITY_ON_CORRUPTION_SUPPORTED (1 << 9) /* ignore/restart_on_corruption, ignore_zero_block */ uint32_t dm_flags(void); diff --git a/man/veritysetup.8 b/man/veritysetup.8 index 15dd9630..4c5168ba 100644 --- a/man/veritysetup.8 +++ b/man/veritysetup.8 @@ -37,7 +37,8 @@ Creates a mapping with backed by device and using The is a hexadecimal string. -\fB\fR can be [\-\-hash-offset, \-\-no-superblock] +\fB\fR can be [\-\-hash-offset, \-\-no-superblock, +\-\-ignore-corruption or \-\-restart-on-corruption, \-\-ignore-zero-blocks] If option \-\-no-superblock is used, you have to use as the same options as in initial format operation. @@ -110,6 +111,24 @@ 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. .TP +.B "\-\-ignore-corruption", "\-\-restart-on-corruption" +Defines what to do if data integrity problem is detected (data corruption). + +Without these options kernel fails the IO operation with I/O error. +With \-\-ignore-corruption option the corruption is only logged. +With \-\-restart-on-corruption the kernel is restarted immediatelly. +(You have to provide way how to avoid restart loops.) + +\fBWARNING:\fR Use these options only for very specific cases. +These options are available since Linux kernel version 4.1. +.TP +.B "\-\-ignore-zero-blocks" +Instruct kernel to not verify blocks that are expected to contain zeroes +and always directly return zeroes instead. + +\fBWARNING:\fR Use this option only in very specific cases. +This option is available since Linux kernel version 4.5. +.TP .B "\-\-version" Show the program version. .SH RETURN CODES @@ -130,9 +149,9 @@ The first implementation of veritysetup was written by Chrome OS authors. This version is based on verification code written by Mikulas Patocka and rewritten for libcryptsetup by Milan Broz . .SH COPYRIGHT -Copyright \(co 2012-2013 Red Hat, Inc. +Copyright \(co 2012-2016 Red Hat, Inc. .br -Copyright \(co 2012-2014 Milan Broz +Copyright \(co 2012-2016 Milan Broz This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/src/veritysetup.c b/src/veritysetup.c index 8f45be4c..70eb0547 100644 --- a/src/veritysetup.c +++ b/src/veritysetup.c @@ -1,8 +1,8 @@ /* * veritysetup - setup cryptographic volumes for dm-verity * - * Copyright (C) 2012-2013, Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2013, Milan Broz + * Copyright (C) 2012-2016, Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2016, Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -33,6 +33,9 @@ static uint64_t data_blocks = 0; static const char *salt_string = NULL; static uint64_t hash_offset = 0; static const char *opt_uuid = NULL; +static int opt_restart_on_corruption = 0; +static int opt_ignore_corruption = 0; +static int opt_ignore_zero_blocks = 0; static int opt_version_mode = 0; @@ -127,6 +130,13 @@ static int _activate(const char *dm_device, if ((r = crypt_init(&cd, hash_device))) goto out; + if (opt_ignore_corruption) + activate_flags |= CRYPT_ACTIVATE_IGNORE_CORRUPTION; + if (opt_restart_on_corruption) + activate_flags |= CRYPT_ACTIVATE_RESTART_ON_CORRUPTION; + if (opt_ignore_zero_blocks) + activate_flags |= CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS; + if (use_superblock) { params.flags = flags; params.hash_area_offset = hash_offset; @@ -273,6 +283,14 @@ static int action_status(int arg) } log_std(" hash offset: %" PRIu64 " sectors\n", vp.hash_area_offset * vp.hash_block_size / 512); + + if (cad.flags & (CRYPT_ACTIVATE_IGNORE_CORRUPTION| + CRYPT_ACTIVATE_RESTART_ON_CORRUPTION| + CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS)) + log_std(" flags: %s%s%s\n", + (cad.flags & CRYPT_ACTIVATE_IGNORE_CORRUPTION) ? "ignore_corruption " : "", + (cad.flags & CRYPT_ACTIVATE_RESTART_ON_CORRUPTION) ? "restart_on_corruption " : "", + (cad.flags & CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS) ? "ignore_zero_blocks" : ""); } out: crypt_free(cd); @@ -383,6 +401,9 @@ int main(int argc, const char **argv) { "hash", 'h', POPT_ARG_STRING, &hash_algorithm, 0, N_("Hash algorithm"), N_("string") }, { "salt", 's', POPT_ARG_STRING, &salt_string, 0, N_("Salt"), N_("hex string") }, { "uuid", '\0', POPT_ARG_STRING, &opt_uuid, 0, N_("UUID for device to use."), NULL }, + { "restart-on-corruption", 0,POPT_ARG_NONE,&opt_restart_on_corruption, 0, N_("Restart kernel if corruption is detected"), NULL }, + { "ignore-corruption", 0, POPT_ARG_NONE, &opt_ignore_corruption, 0, N_("Ignore corruption, log it only"), NULL }, + { "ignore-zero-blocks", 0, POPT_ARG_NONE, &opt_ignore_zero_blocks, 0, N_("Do not verify zeroed blocks"), NULL }, POPT_TABLEEND }; @@ -468,6 +489,16 @@ int main(int argc, const char **argv) poptGetInvocationName(popt_context)); } + if ((opt_ignore_corruption || opt_restart_on_corruption || opt_ignore_zero_blocks) && strcmp(aname, "create")) + usage(popt_context, EXIT_FAILURE, + _("Option --ignore-corruption, --restart-on-corruption or --ignore-zero-blocks is allowed only for create operation.\n"), + poptGetInvocationName(popt_context)); + + if (opt_ignore_corruption && opt_restart_on_corruption) + usage(popt_context, EXIT_FAILURE, + _("Option --ignore-corruption and --restart-on-corruption cannot be used together.\n"), + poptGetInvocationName(popt_context)); + if (opt_debug) { opt_verbose = 1; crypt_set_debug_level(-1); diff --git a/tests/verity-compat-test b/tests/verity-compat-test index 56a8f0bf..cb90edaf 100755 --- a/tests/verity-compat-test +++ b/tests/verity-compat-test @@ -55,6 +55,18 @@ function check_exists() [ -b /dev/mapper/$DEV_NAME ] || fail } +function check_version() +{ + VER_STR=$(dmsetup targets | grep verity | cut -f 3 -dv) + VER_MAJ=$(echo $VER_STR | cut -f 1 -d.) + VER_MIN=$(echo $VER_STR | cut -f 2 -d.) + + # option supported in 1.3 + test $VER_MAJ -gt 1 && return 0 + test $VER_MIN -ge 3 && return 0 + return 1 +} + function compare_out() # $1 what, $2 expected { OPT=$(grep -v "^#" $DEV_OUT | grep -i "$1" | sed -e s/.*\:\ // ) @@ -132,6 +144,21 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 done } +function check_option() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, $6 CLI option, $7 status option +{ + DEV_PARAMS="$LOOPDEV1 $LOOPDEV2" + FORMAT_PARAMS="--format=$4 --data-block-size=$1 --hash-block-size=$1 --hash=$5 --salt=$3" + + echo -n "Option $6 " + $VERITYSETUP format $DEV_PARAMS $FORMAT_PARAMS >/dev/null 2>&1 || fail + $VERITYSETUP create $DEV_NAME $DEV_PARAMS $2 $6 >/dev/null 2>&1 || fail + check_exists + $VERITYSETUP status $DEV_NAME 2>/dev/null | grep flags | grep -q $7 || fail + dmsetup table $DEV_NAME 2>/dev/null | grep -q $7 || fail + $VERITYSETUP remove $DEV_NAME >/dev/null 2>&1 || fail + echo "[OK]" +} + function valgrind_setup() { which valgrind >/dev/null 2>&1 || fail "Cannot find valgrind." @@ -181,5 +208,16 @@ check_root_hash 4096 ef29c902d87350f1da4bfa536e16cebc162a909bf89abe448b81ec500d4 check_root_hash 1024 d0e9163ca8844aaa2e88fe5265a8c5d9ee494a99 $SALT 1 sha1 8388608 check_root_hash 1024 73509e8e868be6b8ac939817a98a3d35121413b2 dadada 1 sha1 8388608 +if check_version ; then + echo "Verity data corruption options test." + SALT=e48da609055204e89ae53b655ca2216dd983cf3cb829f34f63a297d106d53e2d + HASH=9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 + prepare 8192 1024 + check_option 512 $HASH $SALT 1 sha256 "--ignore-corruption" "ignore_corruption" + check_option 512 $HASH $SALT 1 sha256 "--restart-on-corruption" "restart_on_corruption" + check_option 512 $HASH $SALT 1 sha256 "--ignore-zero-blocks" "ignore_zero_blocks" + check_option 512 $HASH $SALT 1 sha256 "--ignore-corruption --ignore-zero-blocks" "ignore_corruption" +fi + remove_mapping exit 0