1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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}',
])
@pytest.mark.requiredtool('dtc')
@pytest.mark.requiredtool('fdtget')
@pytest.mark.requiredtool('veritysetup')
@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)
@pytest.mark.requiredtool('dtc')
@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')
|