image_types_verity.bbclass: add FEC support

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>
Signed-off-by: Khem Raj <khem.raj@oss.qualcomm.com>
This commit is contained in:
Andreas Zdziarstek
2026-06-14 22:05:24 +02:00
committed by Khem Raj
parent 549ad825ea
commit 3755354fb2
@@ -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: