diff options
Diffstat (limited to 'test/boot')
| -rw-r--r-- | test/boot/fit_verity.c | 200 | ||||
| -rw-r--r-- | test/boot/image.c | 68 | ||||
| -rw-r--r-- | test/boot/measurement.c | 2 |
3 files changed, 269 insertions, 1 deletions
diff --git a/test/boot/fit_verity.c b/test/boot/fit_verity.c index 7459a9d6f81..4b5db839085 100644 --- a/test/boot/fit_verity.c +++ b/test/boot/fit_verity.c @@ -6,6 +6,11 @@ */ #include <image.h> +#include <fdt_region.h> +#include <malloc.h> +#include <linux/kernel.h> +#include <linux/libfdt.h> +#include <u-boot/hash-checksum.h> #include <test/test.h> #include <test/ut.h> @@ -304,3 +309,198 @@ static int fit_verity_test_bad_blocksize(struct unit_test_state *uts) return 0; } FIT_VERITY_TEST(fit_verity_test_bad_blocksize, 0); + +#if CONFIG_IS_ENABLED(FIT_SIGNATURE) +/** + * build_signed_verity_fit() - build a FIT with a signable verity config + * @buf: output buffer (at least FIT_BUF_SIZE bytes) + * + * Like build_verity_fit(), but the filesystem image also carries a hash + * subnode (required for a configuration to be signable) so the config's + * signed-region node list can be built with fit_config_get_signed_nodes(). + * + * Return: configuration node offset, or -ve on error + */ +static int build_signed_verity_fit(void *buf) +{ + int images_node, confs_node, conf_node, img_node, hash_node, verity_node; + fdt32_t val; + int ret; + + ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE); + if (ret) + return ret; + + images_node = fdt_add_subnode(buf, 0, "images"); + if (images_node < 0) + return images_node; + + img_node = fdt_add_subnode(buf, images_node, "rootfs"); + if (img_node < 0) + return img_node; + ret = fdt_setprop_string(buf, img_node, FIT_TYPE_PROP, "filesystem"); + if (ret) + return ret; + + hash_node = fdt_add_subnode(buf, img_node, "hash-1"); + if (hash_node < 0) + return hash_node; + ret = fdt_setprop_string(buf, hash_node, FIT_ALGO_PROP, "sha256"); + if (ret) + return ret; + ret = fdt_setprop(buf, hash_node, FIT_VALUE_PROP, test_digest, + sizeof(test_digest)); + if (ret) + return ret; + + verity_node = fdt_add_subnode(buf, img_node, FIT_VERITY_NODENAME); + if (verity_node < 0) + return verity_node; + ret = fdt_setprop_string(buf, verity_node, FIT_VERITY_ALGO_PROP, + "sha256"); + if (ret) + return ret; + val = cpu_to_fdt32(4096); + ret = fdt_setprop(buf, verity_node, FIT_VERITY_DBS_PROP, &val, + sizeof(val)); + if (ret) + return ret; + ret = fdt_setprop(buf, verity_node, FIT_VERITY_HBS_PROP, &val, + sizeof(val)); + if (ret) + return ret; + val = cpu_to_fdt32(100); + ret = fdt_setprop(buf, verity_node, FIT_VERITY_NBLK_PROP, &val, + sizeof(val)); + if (ret) + return ret; + ret = fdt_setprop(buf, verity_node, FIT_VERITY_HBLK_PROP, &val, + sizeof(val)); + if (ret) + return ret; + ret = fdt_setprop(buf, verity_node, FIT_VERITY_DIGEST_PROP, test_digest, + sizeof(test_digest)); + if (ret) + return ret; + ret = fdt_setprop(buf, verity_node, FIT_VERITY_SALT_PROP, test_salt, + sizeof(test_salt)); + if (ret) + return ret; + + confs_node = fdt_add_subnode(buf, 0, "configurations"); + if (confs_node < 0) + return confs_node; + conf_node = fdt_add_subnode(buf, confs_node, "conf-1"); + if (conf_node < 0) + return conf_node; + ret = fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP, "rootfs"); + if (ret) + return ret; + + return conf_node; +} + +/* + * Test: the dm-verity roothash and salt are inside the region covered by the + * configuration signature. + * + * A dm-verity filesystem image is not hashed by U-Boot; its integrity is + * delegated to the kernel, which trusts the roothash from the FIT dm-verity + * subnode. That roothash must therefore be part of the signed region, so that + * an attacker cannot swap both the filesystem and the roothash while keeping + * the configuration signature valid. + * + * This checks the property without a private key, so it also runs on real + * devices: it builds the exact node list the signature is computed over + * (fit_config_get_signed_nodes), turns it into hashed regions, and verifies both + * that the roothash bytes fall inside a region and that tampering them changes + * the hash. It uses the same hash path a device would (crypto accelerated where + * available). + */ +static int fit_verity_test_roothash_signed(struct unit_test_state *uts) +{ + char buf[FIT_BUF_SIZE]; + char *node_inc[32]; + char path_buf[256]; + char region_path[256]; + struct fdt_region fdt_regions[64]; + struct image_region *region = NULL; + int conf_node, verity_node; + int count, i, digest_len; + const void *digest; + ulong digest_off, region_off; + bool covered = false; + u8 hash_clean[32], hash_tampered[32], hash_control[32]; + + conf_node = build_signed_verity_fit(buf); + ut_assert(conf_node >= 0); + + verity_node = fdt_path_offset(buf, "/images/rootfs/dm-verity"); + ut_assert(verity_node >= 0); + + /* Build the node list the configuration signature is computed over. */ + count = fit_config_get_signed_nodes(buf, conf_node, node_inc, + ARRAY_SIZE(node_inc), path_buf, + sizeof(path_buf)); + ut_assert(count > 0); + + /* + * Turn the node list into hashed regions. No exclude list is needed: + * the excluded properties (data, data-size, data-offset, + * data-position) never include the dm-verity digest or salt, so the + * coverage answer is the same with or without it. + */ + count = fdt_find_regions(buf, node_inc, count, NULL, 0, fdt_regions, + ARRAY_SIZE(fdt_regions) - 1, region_path, + sizeof(region_path), 0); + ut_assert(count > 0); + /* Region array exhausted: mirror the bound fit_config_check_sig() enforces. */ + ut_assert(count < ARRAY_SIZE(fdt_regions) - 1); + + region = fit_region_make_list(buf, fdt_regions, count, NULL); + ut_assertnonnull(region); + + digest = fdt_getprop(buf, verity_node, FIT_VERITY_DIGEST_PROP, + &digest_len); + ut_assertnonnull(digest); + ut_assert(digest_len > 0); + digest_off = (ulong)((const char *)digest - (const char *)buf); + + /* + * Control: the hash covers a non-empty region and reacts to a change + * inside it. Flip a byte of the (signed) image hash value and confirm + * the computed hash differs, proving the region set and hash work. + */ + ut_assertok(hash_calculate("sha256", region, count, hash_clean)); + for (i = 0; i < count; i++) { + region_off = (ulong)((const char *)region[i].data - + (const char *)buf); + if (digest_off >= region_off && + digest_off + digest_len <= region_off + region[i].size) { + covered = true; + break; + } + } + + /* The roothash must be covered by the configuration signature. */ + ut_assert(covered); + + /* + * Tampering the roothash must change the signed hash. Only the digest + * is flipped here; salt sits in the same dm-verity node, so coverage + * of one implies coverage of the other. + */ + buf[digest_off] ^= 0xff; + ut_assertok(hash_calculate("sha256", region, count, hash_tampered)); + buf[digest_off] ^= 0xff; + ut_assert(memcmp(hash_clean, hash_tampered, sizeof(hash_clean)) != 0); + + /* Sanity: with the byte restored the hash matches the clean value. */ + ut_assertok(hash_calculate("sha256", region, count, hash_control)); + ut_asserteq_mem(hash_clean, hash_control, sizeof(hash_clean)); + + free(region); + return 0; +} +FIT_VERITY_TEST(fit_verity_test_roothash_signed, 0); +#endif /* FIT_SIGNATURE */ diff --git a/test/boot/image.c b/test/boot/image.c index 4df7b17ce88..2c6d9dcbc22 100644 --- a/test/boot/image.c +++ b/test/boot/image.c @@ -8,8 +8,76 @@ #include <image.h> #include <test/ut.h> +#include <linux/libfdt.h> #include "bootstd_common.h" +/* Test that the default configuration breaks best-match ties */ +static int test_fit_conf_find_compat(struct unit_test_state *uts) +{ + char fdt[256], fit[1024]; + int confs, images, node; + int ret; + + /* control devicetree with a two-entry compatible list */ + ut_assertok(fdt_create_empty_tree(fdt, sizeof(fdt))); + ut_assertok(fdt_appendprop_string(fdt, 0, "compatible", + "test,board-a")); + ut_assertok(fdt_appendprop_string(fdt, 0, "compatible", + "test,fallback")); + + /* FIT with two configurations matching the same compatible */ + ut_assertok(fdt_create_empty_tree(fit, sizeof(fit))); + images = fdt_add_subnode(fit, 0, "images"); + ut_assert(images >= 0); + confs = fdt_add_subnode(fit, 0, "configurations"); + ut_assert(confs >= 0); + ut_assertok(fdt_setprop_string(fit, confs, FIT_DEFAULT_PROP, "conf-2")); + /* + * fdt_add_subnode() inserts before existing subnodes: create conf-2 + * first so that conf-1 ends up listed first, like an .its compiled + * with the configurations in that order + */ + node = fdt_add_subnode(fit, confs, "conf-2"); + ut_assert(node >= 0); + ut_assertok(fdt_setprop_string(fit, node, "compatible", + "test,board-a")); + node = fdt_add_subnode(fit, confs, "conf-1"); + ut_assert(node >= 0); + ut_assertok(fdt_setprop_string(fit, node, "compatible", + "test,board-a")); + confs = fdt_path_offset(fit, "/configurations"); + node = fdt_first_subnode(fit, confs); + ut_asserteq_str("conf-1", fdt_get_name(fit, node, NULL)); + + /* on a tie, the default configuration wins */ + ret = fit_conf_find_compat(fit, fdt); + ut_assert(ret > 0); + ut_asserteq_str("conf-2", fdt_get_name(fit, ret, NULL)); + + /* without a default, the first listed configuration wins */ + confs = fdt_path_offset(fit, "/configurations"); + ut_assertok(fdt_delprop(fit, confs, FIT_DEFAULT_PROP)); + confs = fdt_path_offset(fit, "/configurations"); + ut_assertnull((void *)fdt_getprop(fit, confs, FIT_DEFAULT_PROP, NULL)); + ret = fit_conf_find_compat(fit, fdt); + ut_assert(ret > 0); + ut_asserteq_str("conf-1", fdt_get_name(fit, ret, NULL)); + + /* a strictly better match still beats the default */ + confs = fdt_path_offset(fit, "/configurations"); + ut_assertok(fdt_setprop_string(fit, confs, FIT_DEFAULT_PROP, "conf-2")); + confs = fdt_path_offset(fit, "/configurations"); + node = fdt_subnode_offset(fit, confs, "conf-2"); + ut_assertok(fdt_setprop_string(fit, node, "compatible", + "test,fallback")); + ret = fit_conf_find_compat(fit, fdt); + ut_assert(ret > 0); + ut_asserteq_str("conf-1", fdt_get_name(fit, ret, NULL)); + + return 0; +} +BOOTSTD_TEST(test_fit_conf_find_compat, 0); + /* Test of image phase */ static int test_image_phase(struct unit_test_state *uts) { diff --git a/test/boot/measurement.c b/test/boot/measurement.c index 71f503f1567..85a01f1fec8 100644 --- a/test/boot/measurement.c +++ b/test/boot/measurement.c @@ -9,9 +9,9 @@ #include <bootm.h> #include <env.h> #include <malloc.h> +#include <mapmem.h> #include <test/test.h> #include <test/ut.h> -#include <asm/io.h> #define MEASUREMENT_TEST(_name, _flags) \ UNIT_TEST(_name, _flags, measurement) |
