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 <[email protected]>
Reviewed-by: Simon Glass <[email protected]>
---
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 <[email protected]>
+
+"""
+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}',
+    ])
+
+
[email protected]('dtc')
[email protected]('fdtget')
[email protected]('veritysetup')
[email protected]('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)
+
+
[email protected]('dtc')
[email protected]('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

Reply via email to