[PATCH v4 7/8] test: py: add mkimage dm-verity round-trip test
Daniel Golle
daniel at makrotopia.org
Wed May 13 04:09:47 CEST 2026
Add test/py/tests/test_fit_verity.py covering:
- mkimage writes correct dm-verity properties for matched and
mismatched block sizes (4096/4096 and 4096/1024);
- veritysetup verify re-checks the digest against the .itb's
external data section;
- mkimage rejects dm-verity images built without -E.
All tests are skipped if veritysetup is not installed on the host.
Signed-off-by: Daniel Golle <daniel at makrotopia.org>
Reviewed-by: Simon Glass <sjg at chromium.org>
---
v4:
* verify the computed digest with veritysetup verify against the
external data section
* parametrize test_mkimage_verity with matched and mismatched
block sizes to exercise hash-start-block != num-data-blocks
* use run_and_log_expect_exception() with the expected diagnostic
for the no-external-data case
v3: drop unused 'struct' import and the home-rolled have_veritysetup()
helper; use @pytest.mark.requiredtool('veritysetup') instead
v2: new patch
test/py/tests/test_fit_verity.py | 175 +++++++++++++++++++++++++++++++
1 file changed, 175 insertions(+)
create mode 100644 test/py/tests/test_fit_verity.py
diff --git a/test/py/tests/test_fit_verity.py b/test/py/tests/test_fit_verity.py
new file mode 100644
index 00000000000..5952c189858
--- /dev/null
+++ b/test/py/tests/test_fit_verity.py
@@ -0,0 +1,175 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2026 Daniel Golle <daniel at makrotopia.org>
+
+"""
+Test mkimage dm-verity Merkle-tree generation
+
+Build a minimal .its with a dm-verity subnode (user-provided properties only),
+run mkimage -E, and verify that the computed properties (digest, salt,
+num-data-blocks, hash-start-block) are written into the resulting FIT.
+The computed digest is then re-verified by running ``veritysetup verify``
+against the external data section of the .itb.
+
+This test does not run the sandbox. It only exercises the host tool 'mkimage'.
+Requires 'veritysetup' from the cryptsetup package on the build host.
+"""
+
+import os
+import struct
+import pytest
+import utils
+
+ITS_TEMPLATE = """\
+/dts-v1/;
+
+/ {
+ description = "dm-verity test";
+ #address-cells = <1>;
+
+ images {
+ rootfs {
+ description = "test filesystem";
+ data = /incbin/("./rootfs.bin");
+ type = "filesystem";
+ arch = "sandbox";
+ compression = "none";
+
+ dm-verity {
+ algo = "sha256";
+ data-block-size = <%d>;
+ hash-block-size = <%d>;
+ };
+ };
+ };
+
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "test config";
+ loadables = "rootfs";
+ };
+ };
+};
+"""
+
+def _fdt_totalsize(path):
+ """Read the totalsize field from an FDT header (offset 4, big-endian u32)."""
+ with open(path, 'rb') as f:
+ magic, totalsize = struct.unpack('>II', f.read(8))
+ assert magic == 0xd00dfeed, f'not an FDT: magic={magic:#x}'
+ return totalsize
+
+
+def _run_round_trip(ubman, tempdir, data_block_size, hash_block_size):
+ """Build a FIT with dm-verity, verify written properties, re-verify with veritysetup."""
+ mkimage = ubman.config.build_dir + '/tools/mkimage'
+
+ rootfs_file = os.path.join(tempdir, 'rootfs.bin')
+ its_file = os.path.join(tempdir, 'image.its')
+ fit_file = os.path.join(tempdir, 'image.itb')
+
+ # 64 data blocks of 0xa5
+ num_blocks = 64
+ data_size = data_block_size * num_blocks
+ with open(rootfs_file, 'wb') as f:
+ f.write(bytes([0xa5]) * data_size)
+
+ with open(its_file, 'w') as f:
+ f.write(ITS_TEMPLATE % (data_block_size, hash_block_size))
+
+ dtc_args = f'-I dts -O dtb -i {tempdir}'
+ utils.run_and_log(ubman,
+ [mkimage, '-E', '-D', dtc_args, '-f', its_file, fit_file])
+
+ def fdt_get(node, prop):
+ val = utils.run_and_log(ubman, f'fdtget {fit_file} {node} {prop}')
+ return val.strip()
+
+ def fdt_get_hex(node, prop):
+ val = utils.run_and_log(ubman, f'fdtget -tbx {fit_file} {node} {prop}')
+ return ''.join(b.zfill(2) for b in val.strip().split())
+
+ verity_path = '/images/rootfs/dm-verity'
+
+ assert fdt_get(verity_path, 'algo') == 'sha256'
+ assert int(fdt_get(verity_path, 'data-block-size')) == data_block_size
+ assert int(fdt_get(verity_path, 'hash-block-size')) == hash_block_size
+
+ nblk = int(fdt_get(verity_path, 'num-data-blocks'))
+ assert nblk == num_blocks, f'num-data-blocks {nblk} != {num_blocks}'
+
+ hblk = int(fdt_get(verity_path, 'hash-start-block'))
+ # With --no-superblock, hash-start-block = data_size / hash-block-size
+ assert hblk == data_size // hash_block_size, \
+ f'hash-start-block {hblk} != {data_size // hash_block_size}'
+
+ digest = fdt_get_hex(verity_path, 'digest')
+ assert len(digest) == 64 and digest != '0' * 64
+ salt = fdt_get_hex(verity_path, 'salt')
+ assert len(salt) == 64
+
+ # Re-verify the digest with veritysetup against the .itb's external data.
+ # With -E, image data sits after the FIT FDT at (fdt_totalsize + data-offset).
+ data_offset = int(fdt_get('/images/rootfs', 'data-offset'))
+ data_size_full = int(fdt_get('/images/rootfs', 'data-size'))
+ ext_pos = _fdt_totalsize(fit_file) + data_offset
+ expanded = os.path.join(tempdir, 'expanded.bin')
+ with open(fit_file, 'rb') as src, open(expanded, 'wb') as dst:
+ src.seek(ext_pos)
+ dst.write(src.read(data_size_full))
+
+ utils.run_and_log(ubman, [
+ 'veritysetup', 'verify', expanded, expanded, digest,
+ '--no-superblock',
+ f'--data-block-size={data_block_size}',
+ f'--hash-block-size={hash_block_size}',
+ f'--data-blocks={nblk}',
+ '--hash=sha256',
+ f'--salt={salt}',
+ f'--hash-offset={data_size}',
+ ])
+
+
+ at pytest.mark.requiredtool('dtc')
+ at pytest.mark.requiredtool('fdtget')
+ at pytest.mark.requiredtool('veritysetup')
+ at pytest.mark.parametrize('data_block_size,hash_block_size,subdir', [
+ (4096, 4096, 'verity-equal'),
+ (4096, 1024, 'verity-unequal'),
+])
+def test_mkimage_verity(ubman, data_block_size, hash_block_size, subdir):
+ """mkimage writes correct dm-verity properties and the digest verifies.
+
+ Run with matching and mismatched block sizes so the
+ ``hash-start-block != num-data-blocks`` path is exercised.
+ """
+ tempdir = os.path.join(ubman.config.result_dir, subdir)
+ os.makedirs(tempdir, exist_ok=True)
+ _run_round_trip(ubman, tempdir, data_block_size, hash_block_size)
+
+
+ at pytest.mark.requiredtool('dtc')
+ at pytest.mark.requiredtool('veritysetup')
+def test_mkimage_verity_requires_external(ubman):
+ """mkimage rejects dm-verity without -E with the expected diagnostic."""
+
+ mkimage = ubman.config.build_dir + '/tools/mkimage'
+ tempdir = os.path.join(ubman.config.result_dir, 'verity_no_ext')
+ os.makedirs(tempdir, exist_ok=True)
+
+ rootfs_file = os.path.join(tempdir, 'rootfs.bin')
+ its_file = os.path.join(tempdir, 'image.its')
+ fit_file = os.path.join(tempdir, 'image.itb')
+
+ with open(rootfs_file, 'wb') as f:
+ f.write(bytes([0xa5]) * 4096 * 8)
+
+ with open(its_file, 'w') as f:
+ f.write(ITS_TEMPLATE % (4096, 4096))
+
+ dtc_args = f'-I dts -O dtb -i {tempdir}'
+ utils.run_and_log_expect_exception(
+ ubman,
+ [mkimage, '-D', dtc_args, '-f', its_file, fit_file],
+ 1, 'dm-verity requires external data')
--
2.54.0
More information about the U-Boot
mailing list