[PATCH] tests: FIT: Add "clone" image attack image test
Tom Rini
trini at konsulko.com
Wed Mar 18 18:02:33 CET 2026
Related to the problem resolved with commit 2092322b31cc ("boot: Add
fit_config_get_hash_list() to build signed node list"), add a testcase
for the problem as well.
Reported-by: Apple Security Engineering and Architecture (SEAR)
Signed-off-by: Tom Rini <trini at konsulko.com>
---
Apple has confirmed that we can include the test case now, rather than
waiting.
---
test/py/tests/test_vboot.py | 10 ++++++
test/py/tests/vboot_evil.py | 65 +++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py
index 19f3f981379e..55518bed07e9 100644
--- a/test/py/tests/test_vboot.py
+++ b/test/py/tests/test_vboot.py
@@ -372,6 +372,16 @@ def test_vboot(ubman, name, sha_algo, padding, sign_options, required,
msg = 'Signature checking prevents use of unit addresses (@) in nodes'
run_bootm(sha_algo, 'evil kernel@', msg, False, efit)
+ # Try doing a clone of the images
+ efit = '%stest.evilclone.fit' % tmpdir
+ shutil.copyfile(fit, efit)
+ vboot_evil.add_evil_node(fit, efit, evil_kernel, 'clone')
+
+ utils.run_and_log_expect_exception(
+ ubman, [fit_check_sign, '-f', efit, '-k', dtb],
+ 1, 'Failed to verify required signature')
+ run_bootm(sha_algo, 'evil clone', 'Bad Data Hash', False, efit)
+
# Create a new properly signed fit and replace header bytes
make_fit('sign-configs-%s%s.its' % (sha_algo, padding), ubman, mkimage, dtc_args, datadir, fit)
sign_fit(sha_algo, sign_options)
diff --git a/test/py/tests/vboot_evil.py b/test/py/tests/vboot_evil.py
index e2b0cd65468b..5720631ae522 100644
--- a/test/py/tests/vboot_evil.py
+++ b/test/py/tests/vboot_evil.py
@@ -14,6 +14,7 @@ FDT_END = 0x9
FAKE_ROOT_ATTACK = 0
KERNEL_AT = 1
+IMAGE_CLONE = 2
MAGIC = 0xd00dfeed
@@ -274,6 +275,66 @@ def get_prop_value(dt_struct, dt_strings, prop_path):
return tag_data
+def image_clone_attack(dt_struct, dt_strings, kernel_content, kernel_hash):
+ # retrieve the default configuration name
+ default_conf_name = get_prop_value(
+ dt_struct, dt_strings, '/configurations/default')
+ default_conf_name = str(default_conf_name[:-1], 'utf-8')
+
+ conf_path = '/configurations/' + default_conf_name
+
+ # fetch the loaded kernel name from the default configuration
+ loaded_kernel = get_prop_value(dt_struct, dt_strings, conf_path + '/kernel')
+
+ loaded_kernel = str(loaded_kernel[:-1], 'utf-8')
+
+ # since this is the last child in images!
+ loaded_fdt_name = get_prop_value(dt_struct, dt_strings, conf_path + '/fdt')
+
+ loaded_fdt_name = str(loaded_fdt_name[:-1], 'utf-8')
+
+ # determine boundaries of the images
+ (img_node_start, img_node_end) = (determine_offset(
+ dt_struct, dt_strings, '/images'))
+ if img_node_start is None and img_node_end is None:
+ print('Fatal error, unable to find images node')
+ sys.exit()
+
+ # copy the images node
+ img_node_copy = dt_struct[img_node_start:img_node_end]
+
+ # create an additional empty node
+ empty_node = struct.pack('>I', FDT_BEGIN_NODE) + b"EMPTYNO\0" + struct.pack('>I', FDT_END_NODE)
+ # right before the end, we add it!
+ img_node_copy = img_node_copy[:-4] + empty_node + img_node_copy[-4:]
+
+ # insert the copy inside the tree
+ dt_struct = dt_struct[:img_node_end-4] + \
+ img_node_copy + empty_node + dt_struct[img_node_end-4:]
+
+ # change the content of the kernel being loaded
+ dt_struct = change_property_value(
+ dt_struct, dt_strings, '/images/' + loaded_kernel + '/data', kernel_content)
+
+ # change the content of the kernel being loaded
+ dt_struct = change_property_value(
+ dt_struct, dt_strings, '/images/' + loaded_kernel + '/hash-1/value', kernel_hash)
+
+ # finally, the main bug: change the hashed nodes to use the images clone instead!
+ hashed_nodes: bytes = get_prop_value(dt_struct, dt_strings, conf_path + '/signature/hashed-nodes')
+ print(f"got hashed nodes: {hashed_nodes}")
+ nodes = hashed_nodes.split(b"\0")
+ patched_nodes = []
+ for node in nodes:
+ new_node = node
+ if node.startswith(b"/images/"):
+ # reparent the node
+ new_node = b"/images" + node
+ patched_nodes.append(new_node)
+ hashed_nodes = b"\0".join(patched_nodes)
+ dt_struct = change_property_value(
+ dt_struct, dt_strings, conf_path + '/signature/hashed-nodes', hashed_nodes)
+ return dt_struct
def kernel_at_attack(dt_struct, dt_strings, kernel_content, kernel_hash):
"""Conduct the kernel@ attack
@@ -419,6 +480,8 @@ def add_evil_node(in_fname, out_fname, kernel_fname, attack):
attack = FAKE_ROOT_ATTACK
elif attack == 'kernel@':
attack = KERNEL_AT
+ elif attack == 'clone':
+ attack = IMAGE_CLONE
else:
raise ValueError('Unknown attack name!')
@@ -455,6 +518,8 @@ def add_evil_node(in_fname, out_fname, kernel_fname, attack):
elif attack == KERNEL_AT:
dt_struct = kernel_at_attack(dt_struct, dt_strings, kernel_content,
hash_digest)
+ elif attack == IMAGE_CLONE:
+ dt_struct = image_clone_attack(dt_struct, dt_strings, kernel_content, hash_digest)
# now rebuild the new file
size_dt_strings = len(dt_strings)
--
2.43.0
More information about the U-Boot
mailing list