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,
\-\-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
\fIopen\fR <data_device> <name> <hash_device> <root_hash>
.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)
.IP
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,
\-\-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
as in initial format operation.
.PP
\fIverify\fR <data_device> <hash_device> <root_hash>
.br
\fIverify\fR <data_device> <hash_device> \-\-root-hash-file <path>
.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 <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
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 <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>"
@@ -225,6 +245,10 @@ as in the format command. The <root_hash> was calculated in format command.
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>"
Calculates and stores verification and encoding data for data_device.

View File

@@ -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"

View File

@@ -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, &params);
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 <root_hash> 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 <root_hash> 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_("<data_device> <hash_device>"),N_("format device") },
{ "verify", action_verify, 3, 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>") },
{ "verify", action_verify, 2, N_("<data_device> <hash_device> [<root_hash>]"),N_("verify device") },
{ "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)") },
{ "status", action_status, 1, N_("<name>"),N_("show active device status") },
{ "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_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, {}, {})

View File

@@ -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 {

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_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}