@@ -36,6 +36,60 @@
# dd if=/dev/random bs=1k count=1 | sha256sum
#
# and assign it to the parameter VERITY_SALT.
+#
+# Forward Error Correction (FEC) can optionally be generated by setting
+# VERITY_FEC to "1". dm-verity then transparently recovers a limited number of
+# corrupted blocks instead of only detecting them.
+#
+# The number of Reed-Solomon roots is set via VERITY_FEC_ROOTS (default 2, max
+# 24). Higher number of roots means higher error resilience but also more
+# overhead in terms of space.
+#
+# The FEC data is either appended after the hash tree or put in its own file,
+# using VERITY_IMAGE_FECDEV_SUFFIX.
+#
+# When FEC is enabled, additional values are appended to the parameter file:
+#
+# VERITY_FEC_RS_ROOTS - number of Reed-Solomon roots
+# VERITY_FEC_BLOCKS - number of parity blocks generated on the FEC device.
+# This is the FEC overhead and is NOT the kernel table's
+# "fec_blocks" argument (see the example below).
+# VERITY_FEC_START - offset (in VERITY_DATA_BLOCK_SIZE blocks) of the FEC
+# data on the FEC device.
+#
+# extending the dmsetup example above (with <fec_dev> as the FEC blockdevice) to
+#
+# . <IMAGE_LINK_NAME>.verity-params
+# dmsetup create <dm_dev_name> --readonly --table "0 $VERITY_DATA_SECTORS \
+# verity 1 <dev> <hash_dev> \
+# $VERITY_DATA_BLOCK_SIZE $VERITY_HASH_BLOCK_SIZE \
+# $VERITY_DATA_BLOCKS $VERITY_DATA_BLOCKS \
+# $VERITY_HASH_ALGORITHM $VERITY_ROOT_HASH $VERITY_SALT \
+# 9 ignore_zero_blocks use_fec_from_device <fec_dev> \
+# fec_roots $VERITY_FEC_RS_ROOTS \
+# fec_blocks $((VERITY_DATA_BLOCKS + VERITY_HASH_BLOCKS)) \
+# fec_start $VERITY_FEC_START"
+#
+# Note that the kernel's "fec_blocks" is the number of blocks covered by FEC,
+# i.e. the data and hash blocks together (VERITY_DATA_BLOCKS +
+# VERITY_HASH_BLOCKS), and differs from VERITY_FEC_BLOCKS, which counts the
+# generated parity blocks.
+#
+# The same device as above can be configured with veritysetup, using
+#
+# . <IMAGE_LINK_NAME>.verity-params
+# veritysetup open --no-superblock <dev> <dm_dev_name> <dev> $VERITY_ROOT_HASH \
+# --data-block-size=$VERITY_DATA_BLOCK_SIZE \
+# --hash-block-size=$VERITY_HASH_BLOCK_SIZE \
+# --data-blocks=$VERITY_DATA_BLOCKS \
+# --hash-offset=$((VERITY_DATA_BLOCKS * VERITY_DATA_BLOCK_SIZE)) \
+# --salt=$VERITY_SALT \
+# --fec-device=<dev> \
+# --fec-offset=$((VERITY_FEC_START * VERITY_DATA_BLOCK_SIZE)) \
+# --fec-roots=$VERITY_FEC_RS_ROOTS
+#
+# NOTE: To be able to use dm-verity with FEC, your kernel must have
+# CONFIG_DM_VERITY_FEC enabled.
inherit image-artifact-names
@@ -48,6 +102,9 @@ VERITY_IMAGE_FSTYPE ?= "ext4"
VERITY_IMAGE_SUFFIX ?= ".verity"
VERITY_IMAGE_HASHDEV_SUFFIX ?= "${VERITY_IMAGE_SUFFIX}"
VERITY_INPUT_IMAGE ?= "${IMGDEPLOYDIR}/${IMAGE_LINK_NAME}.${VERITY_IMAGE_FSTYPE}"
+VERITY_FEC ?= ""
+VERITY_FEC_ROOTS ?= "2"
+VERITY_IMAGE_FECDEV_SUFFIX ?= "${VERITY_IMAGE_HASHDEV_SUFFIX}"
IMAGE_TYPEDEP:verity = "${VERITY_IMAGE_FSTYPE}"
IMAGE_TYPES_MASKED += "verity"
@@ -75,6 +132,10 @@ python do_image_verity () {
verity_image_hashdev_suffix = d.getVar('VERITY_IMAGE_HASHDEV_SUFFIX')
verity_hashdev = '{}{}'.format(image, verity_image_hashdev_suffix)
+ fec = bb.utils.to_boolean(d.getVar('VERITY_FEC'))
+ verity_image_fecdev_suffix = d.getVar('VERITY_IMAGE_FECDEV_SUFFIX')
+ verity_fecdev = '{}{}'.format(image, verity_image_fecdev_suffix)
+
# For better readability the parameter VERITY_BLOCK_SIZE is specified in
# bytes. It must be a multiple of the logical sector size which is 512 bytes
# in Linux. Make sure that this is the case as otherwise the resulting
@@ -87,6 +148,11 @@ python do_image_verity () {
if salt == d.getVar('CLASS_VERITY_SALT'):
bb.warn("Please overwrite VERITY_SALT with an image specific one!")
+ if fec:
+ fec_roots = int(d.getVar('VERITY_FEC_ROOTS'))
+ if not 2 <= fec_roots <= 24:
+ bb.fatal("VERITY_FEC_ROOTS must be between 2 and 24 (got %d)!" % fec_roots)
+
shutil.copyfile(image, verity)
data_size_blocks, data_size_rest = divmod(os.stat(verity).st_size, block_size)
@@ -125,6 +191,26 @@ python do_image_verity () {
verityfile.seek(0, io.SEEK_END)
verityfile.write(b'\x00' * (block_size - data_size_rest))
+ # Optionally generate Reed-Solomon FEC data. If the FEC data shares a file
+ # with the hash tree, it must start past it. Do a first veritysetup pass to
+ # get the size of the hash tree and calculate the offset. With a dedicated
+ # FEC file the FEC data starts at offset 0 and a single pass suffices.
+ fec_offset = 0
+ if fec:
+ if verity_fecdev == verity_hashdev:
+ try:
+ subprocess.check_output(veritysetup_command)
+ except subprocess.CalledProcessError as err:
+ bb.fatal('%s returned with %s (%s)' % (err.cmd, err.returncode, err.output))
+ fec_offset = os.stat(verity_hashdev).st_size
+ if fec_offset % block_size != 0:
+ bb.fatal("hash tree end (%d) is not a multiple of the block size" % fec_offset)
+ veritysetup_command.append('--fec-offset={}'.format(fec_offset))
+ veritysetup_command += [
+ '--fec-device={}'.format(verity_fecdev),
+ '--fec-roots={}'.format(fec_roots),
+ ]
+
# Create verity image
try:
output = subprocess.check_output(veritysetup_command)
@@ -151,6 +237,13 @@ python do_image_verity () {
params.append('VERITY_DATA_SECTORS={}'.format(data_size//512))
+ if fec:
+ # Where the FEC data starts on its device, in blocks. It's not part
+ # of veritysetup's output, so emit it for `dmsetup create ...`.
+ # A byte offset, if needed (e.g. for `veritysetup open ...`), is
+ # VERITY_FEC_START * VERITY_DATA_BLOCK_SIZE.
+ params.append('VERITY_FEC_START={}'.format(fec_offset//block_size))
+
try:
with open(image + '.verity-params', 'w') as f:
f.write('\n'.join(params))
@@ -161,6 +254,8 @@ python do_image_verity () {
suffix_list = [ verity_image_suffix, '.verity-info', '.verity-params' ]
if verity != verity_hashdev:
suffix_list.append(verity_image_hashdev_suffix)
+ if fec and verity_fecdev not in (verity, verity_hashdev):
+ suffix_list.append(verity_image_fecdev_suffix)
for suffix in suffix_list:
try:
dm-verity can use Reed-Solomon forward error correction (FEC) data to transparently recover a limited number of corrupted blocks instead of only detecting them. This can be useful for mitigating error-prone storage. We generate this FEC data as an option, controlled by VERITY_FEC (default off, same behavior as before) and VERITY_FEC_ROOTS (number of Reed-Solomon roots, default 2). The FEC data is appended to the hash tree by default. If VERITY_IMAGE_FECDEV_SUFFIX is set, a separate file with the given suffix will be used. When enabled, the parameter file gains additional variables necessary for correct DM table generation. Recipes that do not set VERITY_FEC = "1" behave the same as before. Signed-off-by: Andreas Zdziarstek <andreas.zdziarstek@gmail.com> --- While testing this across the possible device layouts, I noticed that the class uses --no-superblock when there is no separate hash device (the default case). But the option is dropped and the superblock created if VERITY_IMAGE_HASHDEV_SUFFIX is set. As the HASHDEV addition was a separate patch, I wondered if this was intentional? As an idea: It would also be possible to make it fully configurable via a VERITY_SUPERBLOCK setting. meta-oe/classes/image_types_verity.bbclass | 95 ++++++++++++++++++++++ 1 file changed, 95 insertions(+)