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 <[email protected]> --- 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(+) diff --git a/meta-oe/classes/image_types_verity.bbclass b/meta-oe/classes/image_types_verity.bbclass index 24462277af..efe94a2987 100644 --- a/meta-oe/classes/image_types_verity.bbclass +++ b/meta-oe/classes/image_types_verity.bbclass @@ -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: -- 2.53.0
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#127578): https://lists.openembedded.org/g/openembedded-devel/message/127578 Mute This Topic: https://lists.openembedded.org/mt/119805958/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
