veritysetup: add --root-hash-file option

Allow to pass the root hash via a file, rather than verbatim on
the command line, for the open/verify/format actions.
It is much more convenient when using veritysetup in scripts.

[some modifications by mbroz:]
- Add additional syntax and option description to man page.
- Fix a segfault with non-existing path.
- Do not read full file.
- Small refactor for argc handling and option processing.
This commit is contained in:
Luca Boccassi
2021-07-11 14:49:37 +01:00
committed by Milan Broz
parent 06f132066b
commit cc374ee10d
6 changed files with 143 additions and 21 deletions

View File

@@ -28,10 +28,15 @@ If hash device path doesn't exist, it will be created as file.
\fB<options>\fR can be [\-\-hash, \-\-no-superblock, \-\-format, \fB<options>\fR can be [\-\-hash, \-\-no-superblock, \-\-format,
\-\-data-block-size, \-\-hash-block-size, \-\-data-blocks, \-\-hash-offset, \-\-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 <path>.
.PP .PP
\fIopen\fR <data_device> <name> <hash_device> <root_hash> \fIopen\fR <data_device> <name> <hash_device> <root_hash>
.br .br
\fIopen\fR <data_device> <name> <hash_device> \-\-root-hash-file <path>
.br
\fIcreate\fR <name> <data_device> <hash_device> <root_hash> (\fBOBSOLETE syntax\fR) \fIcreate\fR <name> <data_device> <hash_device> <root_hash> (\fBOBSOLETE syntax\fR)
.IP .IP
Creates a mapping with <name> backed by device <data_device> and using Creates a mapping with <name> backed by device <data_device> and using
@@ -41,12 +46,19 @@ The <root_hash> is a hexadecimal string.
\fB<options>\fR can be [\-\-hash-offset, \-\-no-superblock, \fB<options>\fR can be [\-\-hash-offset, \-\-no-superblock,
\-\-ignore-corruption or \-\-restart-on-corruption, \-\-panic-on-corruption, \-\-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 <path> 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 If option \-\-no-superblock is used, you have to use as the same options
as in initial format operation. as in initial format operation.
.PP .PP
\fIverify\fR <data_device> <hash_device> <root_hash> \fIverify\fR <data_device> <hash_device> <root_hash>
.br
\fIverify\fR <data_device> <hash_device> \-\-root-hash-file <path>
.IP .IP
Verifies data on data_device with use of hash blocks stored on hash_device. 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 <root_hash> is a hexadecimal string. The <root_hash> is a hexadecimal string.
\fB<options>\fR can be [\-\-hash-offset, \-\-no-superblock] If option \-\-root-hash-file is used, the root hash is read from <path> instead
of from the command line parameter. Expects hex-encoded text, without terminating
newline.
\fB<options>\fR can be [\-\-hash-offset, \-\-no-superblock, \-\-root-hash-file]
If option \-\-no-superblock is used, you have to use as the same options If option \-\-no-superblock is used, you have to use as the same options
as in initial format operation. 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. 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). 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 .TP
.B "\-\-root-hash-file=FILE"
Path to file with stored root hash in hex-encoded text.
.TP
.B "\-\-root-hash-signature=FILE" .B "\-\-root-hash-signature=FILE"
Path to roothash signature file used to verify the root hash (in kernel). Path to roothash signature file used to verify the root hash (in kernel).
This feature requires Linux kernel version 5.4 or more recent. 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). 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). If hash_device does not exist, it is created (as file image).
.B "veritysetup format <data_device> <hash_device>" .B "veritysetup format --root-hash-file <path> <data_device> <hash_device>"
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 <path>.
.B "veritysetup \-\-data-blocks=256 \-\-hash-offset=1052672 format <device> <device>" .B "veritysetup \-\-data-blocks=256 \-\-hash-offset=1052672 format <device> <device>"
@@ -225,6 +245,10 @@ as in the format command. The <root_hash> was calculated in format command.
Verifies device without activation (in userspace). Verifies device without activation (in userspace).
.B "veritysetup \-\-data-blocks=256 \-\-hash-offset=1052672 --root-hash-file <path> verify <data_device> <hash_device>"
Verifies device without activation (in userspace). Root hash passed via a file rather than inline.
.B "veritysetup \-\-fec-device=<fec_device> \-\-fec-roots=10 format <data_device> <hash_device>" .B "veritysetup \-\-fec-device=<fec_device> \-\-fec-roots=10 format <data_device> <hash_device>"
Calculates and stores verification and encoding data for data_device. Calculates and stores verification and encoding data for data_device.

View File

@@ -126,6 +126,7 @@
#define OPT_RESILIENCE_HASH "resilience-hash" #define OPT_RESILIENCE_HASH "resilience-hash"
#define OPT_RESTART_ON_CORRUPTION "restart-on-corruption" #define OPT_RESTART_ON_CORRUPTION "restart-on-corruption"
#define OPT_RESUME_ONLY "resume-only" #define OPT_RESUME_ONLY "resume-only"
#define OPT_ROOT_HASH_FILE "root-hash-file"
#define OPT_ROOT_HASH_SIGNATURE "root-hash-signature" #define OPT_ROOT_HASH_SIGNATURE "root-hash-signature"
#define OPT_SALT "salt" #define OPT_SALT "salt"
#define OPT_SECTOR_SIZE "sector-size" #define OPT_SECTOR_SIZE "sector-size"

View File

@@ -77,7 +77,9 @@ static int action_format(void)
struct crypt_device *cd = NULL; struct crypt_device *cd = NULL;
struct crypt_params_verity params = {}; struct crypt_params_verity params = {};
uint32_t flags = CRYPT_VERITY_CREATE_HASH; 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 */ /* Try to create hash image if doesn't exist */
r = open(action_argv[1], O_WRONLY | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR); 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; goto out;
r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, ARG_STR(OPT_UUID_ID), NULL, 0, &params); r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, ARG_STR(OPT_UUID_ID), NULL, 0, &params);
if (!r) if (r < 0)
crypt_dump(cd); 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: out:
crypt_free(cd); crypt_free(cd);
free(CONST_CAST(char*)params.salt); free(CONST_CAST(char*)params.salt);
free(root_hash_bytes);
if (root_hash_fd != -1)
close(root_hash_fd);
return r; return r;
} }
@@ -128,11 +165,11 @@ static int _activate(const char *dm_device,
struct crypt_device *cd = NULL; struct crypt_device *cd = NULL;
struct crypt_params_verity params = {}; struct crypt_params_verity params = {};
uint32_t activate_flags = CRYPT_ACTIVATE_READONLY; uint32_t activate_flags = CRYPT_ACTIVATE_READONLY;
char *root_hash_bytes = NULL; char *root_hash_bytes = NULL, *root_hash_from_file = NULL;
ssize_t hash_size; ssize_t hash_size, hash_size_hex;
struct stat st; struct stat st;
char *signature = NULL; 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))) if ((r = crypt_init_data_device(&cd, hash_device, data_device)))
goto out; goto out;
@@ -165,6 +202,36 @@ static int _activate(const char *dm_device,
goto out; goto out;
hash_size = crypt_get_volume_key_size(cd); 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) { if (crypt_hex_to_bytes(root_hash, &root_hash_bytes, 0) != hash_size) {
log_err(_("Invalid root hash string specified.")); log_err(_("Invalid root hash string specified."));
r = -EINVAL; r = -EINVAL;
@@ -193,26 +260,39 @@ static int _activate(const char *dm_device,
out: out:
crypt_safe_free(signature); crypt_safe_free(signature);
crypt_free(cd); crypt_free(cd);
free(root_hash_from_file);
free(root_hash_bytes); free(root_hash_bytes);
free(CONST_CAST(char*)params.salt); free(CONST_CAST(char*)params.salt);
if (root_hash_fd != -1)
close(root_hash_fd);
return r; return r;
} }
static int action_open(void) static int action_open(void)
{ {
if (action_argc < 4 && !ARG_SET(OPT_ROOT_HASH_FILE_ID)) {
log_err(_("Command requires <root_hash> or --root-hash-file option as argument."));
return -EINVAL;
}
return _activate(action_argv[1], return _activate(action_argv[1],
action_argv[0], action_argv[0],
action_argv[2], 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); ARG_SET(OPT_ROOT_HASH_SIGNATURE_ID) ? CRYPT_VERITY_ROOT_HASH_SIGNATURE : 0);
} }
static int action_verify(void) static int action_verify(void)
{ {
if (action_argc < 3 && !ARG_SET(OPT_ROOT_HASH_FILE_ID)) {
log_err(_("Command requires <root_hash> or --root-hash-file option as argument."));
return -EINVAL;
}
return _activate(NULL, return _activate(NULL,
action_argv[0], action_argv[0],
action_argv[1], action_argv[1],
action_argv[2], ARG_SET(OPT_ROOT_HASH_FILE_ID) ? NULL : action_argv[2],
CRYPT_VERITY_CHECK_HASH); CRYPT_VERITY_CHECK_HASH);
} }
@@ -396,8 +476,8 @@ static struct action_type {
const char *desc; const char *desc;
} action_types[] = { } action_types[] = {
{ "format", action_format, 2, N_("<data_device> <hash_device>"),N_("format device") }, { "format", action_format, 2, N_("<data_device> <hash_device>"),N_("format device") },
{ "verify", action_verify, 3, N_("<data_device> <hash_device> <root_hash>"),N_("verify device") }, { "verify", action_verify, 2, N_("<data_device> <hash_device> [<root_hash>]"),N_("verify device") },
{ "open", action_open, 4, N_("<data_device> <name> <hash_device> <root_hash>"),N_("open device as <name>") }, { "open", action_open, 3, N_("<data_device> <name> <hash_device> [<root_hash>]"),N_("open device as <name>") },
{ "close", action_close, 1, N_("<name>"),N_("close device (remove mapping)") }, { "close", action_close, 1, N_("<name>"),N_("close device (remove mapping)") },
{ "status", action_status, 1, N_("<name>"),N_("show active device status") }, { "status", action_status, 1, N_("<name>"),N_("show active device status") },
{ "dump", action_dump, 1, N_("<hash_device>"),N_("show on-disk information") }, { "dump", action_dump, 1, N_("<hash_device>"),N_("show on-disk information") },

View File

@@ -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_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_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, {}, {}) ARG(OPT_SALT, 's', POPT_ARG_STRING, N_("Salt"), N_("hex string"), CRYPT_ARG_STRING, {}, {})

View File

@@ -37,6 +37,7 @@
#define OPT_IGNORE_ZERO_BLOCKS_ACTIONS { OPEN_ACTION } #define OPT_IGNORE_ZERO_BLOCKS_ACTIONS { OPEN_ACTION }
#define OPT_RESTART_ON_CORRUPTION_ACTIONS { OPEN_ACTION } #define OPT_RESTART_ON_CORRUPTION_ACTIONS { OPEN_ACTION }
#define OPT_PANIC_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 } #define OPT_ROOT_HASH_SIGNATURE_ACTIONS { OPEN_ACTION }
enum { enum {

View File

@@ -21,7 +21,7 @@ function remove_mapping()
[ -b /dev/mapper/$DEV_NAME2 ] && dmsetup remove $DEV_NAME2 >/dev/null 2>&1 [ -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 [ -b /dev/mapper/$DEV_NAME ] && dmsetup remove $DEV_NAME >/dev/null 2>&1
[ ! -z "$LOOPDEV1" ] && losetup -d $LOOPDEV1 >/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="" LOOPDEV1=""
LOOPDEV2="" 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] 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 if [ -z "$LOOPDEV2" ] ; then
BLOCKS=$(($6 / $1)) BLOCKS=$(($6 / $1))
DEV_PARAMS="$LOOPDEV1 $LOOPDEV1 \ 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" DEV_PARAMS="$LOOPDEV1 $LOOPDEV2"
fi fi
for root_hash_as_file in yes no; do
for sb 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" FORMAT_PARAMS="--format=$4 --data-block-size=$1 --hash-block-size=$1 --hash=$5 --salt=$3"
if [ $sb == yes ] ; then 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" FORMAT_PARAMS="$FORMAT_PARAMS --no-superblock"
VERIFY_PARAMS=$FORMAT_PARAMS VERIFY_PARAMS=$FORMAT_PARAMS
fi 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 for fail in data hash; do
wipe 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 $VERITYSETUP format $DEV_PARAMS $FORMAT_PARAMS >$DEV_OUT || fail
echo -n "[root hash]" echo -n "[root hash]"
compare_out "root hash" $2 compare_out "root hash" $2
compare_out "salt" "$3" 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]" 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 check_exists
echo -n "[activate]" echo -n "[activate]"
@@ -167,9 +180,9 @@ function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6
;; ;;
esac 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" 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" fail "activation"
dd if=/dev/mapper/$DEV_NAME of=/dev/null bs=$1 2>/dev/null dd if=/dev/mapper/$DEV_NAME of=/dev/null bs=$1 2>/dev/null
dmsetup status $DEV_NAME | grep "verity V" >/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]" echo "[$TXT corruption]"
done done
done done
done
} }
function corrupt_device() # $1 device, $2 device_size(in bytes), $3 #{corrupted_bytes} function corrupt_device() # $1 device, $2 device_size(in bytes), $3 #{corrupted_bytes}