diff options
Diffstat (limited to 'test')
34 files changed, 1998 insertions, 59 deletions
diff --git a/test/boot/Makefile b/test/boot/Makefile index 89538d4f0a6..59a87028704 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -14,11 +14,15 @@ endif ifdef CONFIG_SANDBOX obj-$(CONFIG_$(PHASE_)CMDLINE) += bootm.o +ifdef CONFIG_UT_DM +obj-$(CONFIG_$(PHASE_)OF_LIBFDT) += image_fdt.o endif +endif +obj-$(CONFIG_$(PHASE_)FIT_VERITY) += fit_verity.o obj-$(CONFIG_MEASURED_BOOT) += measurement.o ifdef CONFIG_OF_LIVE -obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o +obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o vbe_read_fit.o endif obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o diff --git a/test/boot/bootdev.c b/test/boot/bootdev.c index 0820bf10ee0..c2eaf0b2c55 100644 --- a/test/boot/bootdev.c +++ b/test/boot/bootdev.c @@ -384,19 +384,19 @@ static int bootdev_test_hunter(struct unit_test_state *uts) ut_assert_nextline(" 2 mmc mmc_bootdev"); ut_assert_nextline(" 4 nvme nvme_bootdev"); ut_assert_nextline(" 4 qfw qfw_bootdev"); + ut_assert_nextline(" 4 host sandbox_bootdev"); ut_assert_nextline(" 4 scsi scsi_bootdev"); ut_assert_nextline(" 4 spi_flash sf_bootdev"); ut_assert_nextline(" 5 usb usb_bootdev"); ut_assert_nextline(" 4 virtio virtio_bootdev"); - ut_assert_nextline("(total hunters: 9)"); + ut_assert_nextline("(total hunters: 10)"); ut_assert_console_end(); ut_assertok(bootdev_hunt("usb1", false)); ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found"); ut_assert_console_end(); - /* USB is 8th in the list, so bit 7 */ - ut_asserteq(BIT(7), std->hunters_used); + ut_asserteq(BIT(USB_HUNTER), std->hunters_used); return 0; } @@ -417,7 +417,7 @@ static int bootdev_test_cmd_hunt(struct unit_test_state *uts) ut_assert_nextline("Prio Used Uclass Hunter"); ut_assert_nextlinen("----"); ut_assert_nextline(" 6 ethernet eth_bootdev"); - ut_assert_skip_to_line("(total hunters: 9)"); + ut_assert_skip_to_line("(total hunters: 10)"); ut_assert_console_end(); /* Use the MMC hunter and see that it updates */ @@ -425,7 +425,7 @@ static int bootdev_test_cmd_hunt(struct unit_test_state *uts) ut_assertok(run_command("bootdev hunt -l", 0)); ut_assert_skip_to_line(" 5 ide ide_bootdev"); ut_assert_nextline(" 2 * mmc mmc_bootdev"); - ut_assert_skip_to_line("(total hunters: 9)"); + ut_assert_skip_to_line("(total hunters: 10)"); ut_assert_console_end(); /* Scan all hunters */ @@ -441,6 +441,7 @@ static int bootdev_test_cmd_hunt(struct unit_test_state *uts) ut_assert_nextline("Hunting with: nvme"); ut_assert_nextline("Hunting with: qfw"); + ut_assert_nextline("Hunting with: host"); ut_assert_nextline("Hunting with: scsi"); ut_assert_nextline("scanning bus for devices..."); ut_assert_skip_to_line("Hunting with: spi_flash"); @@ -458,11 +459,12 @@ static int bootdev_test_cmd_hunt(struct unit_test_state *uts) ut_assert_nextline(" 2 * mmc mmc_bootdev"); ut_assert_nextline(" 4 * nvme nvme_bootdev"); ut_assert_nextline(" 4 * qfw qfw_bootdev"); + ut_assert_nextline(" 4 * host sandbox_bootdev"); ut_assert_nextline(" 4 * scsi scsi_bootdev"); ut_assert_nextline(" 4 * spi_flash sf_bootdev"); ut_assert_nextline(" 5 * usb usb_bootdev"); ut_assert_nextline(" 4 * virtio virtio_bootdev"); - ut_assert_nextline("(total hunters: 9)"); + ut_assert_nextline("(total hunters: 10)"); ut_assert_console_end(); ut_asserteq(GENMASK(MAX_HUNTER, 0), std->hunters_used); @@ -646,8 +648,7 @@ static int bootdev_test_next_label(struct unit_test_state *uts) ut_asserteq_str("scsi.id0lun0.bootdev", dev->name); ut_asserteq(BOOTFLOW_METHF_SINGLE_UCLASS, mflags); - /* SCSI is 6th in the list, so bit 5 */ - ut_asserteq(BIT(MMC_HUNTER) | BIT(5), std->hunters_used); + ut_asserteq(BIT(MMC_HUNTER) | BIT(SCSI_HUNTER), std->hunters_used); ut_assertok(bootdev_next_label(&iter, &dev, &mflags)); ut_assert_console_end(); @@ -657,7 +658,7 @@ static int bootdev_test_next_label(struct unit_test_state *uts) mflags); /* dhcp: Ethernet is first so bit 0 */ - ut_asserteq(BIT(MMC_HUNTER) | BIT(5) | BIT(0), std->hunters_used); + ut_asserteq(BIT(MMC_HUNTER) | BIT(SCSI_HUNTER) | BIT(0), std->hunters_used); ut_assertok(bootdev_next_label(&iter, &dev, &mflags)); ut_assert_console_end(); @@ -667,7 +668,7 @@ static int bootdev_test_next_label(struct unit_test_state *uts) mflags); /* pxe: Ethernet is first so bit 0 */ - ut_asserteq(BIT(MMC_HUNTER) | BIT(5) | BIT(0), std->hunters_used); + ut_asserteq(BIT(MMC_HUNTER) | BIT(SCSI_HUNTER) | BIT(0), std->hunters_used); mflags = 123; ut_asserteq(-ENODEV, bootdev_next_label(&iter, &dev, &mflags)); @@ -675,7 +676,7 @@ static int bootdev_test_next_label(struct unit_test_state *uts) ut_assert_console_end(); /* no change */ - ut_asserteq(BIT(MMC_HUNTER) | BIT(5) | BIT(0), std->hunters_used); + ut_asserteq(BIT(MMC_HUNTER) | BIT(SCSI_HUNTER) | BIT(0), std->hunters_used); return 0; } diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index 56ee1952357..1cc137c9700 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -19,6 +19,8 @@ #include <mapmem.h> #ifdef CONFIG_SANDBOX #include <asm/test.h> +#include <sandbox_host.h> +#include <os.h> #endif #include <dm/device-internal.h> #include <dm/lists.h> @@ -1532,3 +1534,48 @@ static int bootstd_images(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootstd_images, UTF_CONSOLE); + +#if defined(CONFIG_SANDBOX) && defined(CONFIG_BOOTMETH_GLOBAL) +/* + * Check that bootdev scanning does not stop if higher-priority bootdevs + * are failed to be hunted. + */ +static int bootdev_hunt_fallthrough(struct unit_test_state *uts) +{ + struct bootstd_priv *std; + struct udevice *dev; + + ut_assertok(bootstd_get_priv(&std)); + bootstd_test_drop_bootdev_order(uts); + test_set_skip_delays(true); + bootstd_reset_usb(); + console_record_reset_enable(); + + /* + * Create a sandbox block device (BOOTDEVP_4_SCAN_FAST) and mark it as + * broken so that bootdev_hunt_prio() returns an error. + */ + ut_asserteq(0, uclass_id_count(UCLASS_HOST)); + ut_assertok(host_create_device("test", true, DEFAULT_BLKSZ, &dev)); + ut_assertok(host_set_flags_by_label("test", HOST_FLAG_BROKEN)); + ut_asserteq(1, uclass_id_count(UCLASS_HOST)); + + /* + * Scan with hunting. + * The sandbox hunter at priority 4 must fail, but the USB hunter at + * priority 5 must still be reached. + */ + ut_assertok(run_command("bootflow scan -l", 0)); + + ut_assert(!(std->hunters_used & BIT(HOST_HUNTER))); + ut_assert_skip_to_line("Hunting with: host"); + + /* USB was hunted despite the sandbox hunter failure */ + ut_assert(std->hunters_used & BIT(USB_HUNTER)); + ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found"); + + return 0; +} +BOOTSTD_TEST(bootdev_hunt_fallthrough, + UTF_DM | UTF_SCAN_FDT | UTF_SF_BOOTDEV | UTF_CONSOLE); +#endif /* CONFIG_SANDBOX */ diff --git a/test/boot/bootstd_common.h b/test/boot/bootstd_common.h index dd769313a84..672917454a3 100644 --- a/test/boot/bootstd_common.h +++ b/test/boot/bootstd_common.h @@ -21,8 +21,11 @@ #define TEST_VERNUM 0x00010002 enum { - MAX_HUNTER = 8, MMC_HUNTER = 2, /* ID of MMC hunter */ + HOST_HUNTER = 5, + SCSI_HUNTER = 6, + USB_HUNTER = 8, + MAX_HUNTER = 9, }; struct unit_test_state; diff --git a/test/boot/fit_verity.c b/test/boot/fit_verity.c new file mode 100644 index 00000000000..7459a9d6f81 --- /dev/null +++ b/test/boot/fit_verity.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for FIT dm-verity cmdline generation + * + * Copyright 2026 Daniel Golle <[email protected]> + */ + +#include <image.h> +#include <test/test.h> +#include <test/ut.h> + +#define FIT_VERITY_TEST(_name, _flags) UNIT_TEST(_name, _flags, fit_verity) + +/* FIT blob buffer size — generous to avoid FDT_ERR_NOSPACE */ +#define FIT_BUF_SIZE 4096 + +/* Test digest (32 bytes = sha256) */ +static const u8 test_digest[32] = { + 0x8e, 0x67, 0x91, 0x63, 0x7f, 0x93, 0xcb, 0xb8, + 0x1f, 0xc4, 0x52, 0x99, 0xe2, 0x03, 0xcb, 0xe8, + 0x5c, 0xa2, 0xe4, 0x7a, 0x38, 0xf5, 0x05, 0x1b, + 0xdd, 0xee, 0xce, 0x92, 0xd7, 0xb1, 0xc9, 0xf9, +}; + +/* Test salt (32 bytes) */ +static const u8 test_salt[32] = { + 0xaa, 0x7b, 0x11, 0xf8, 0xdb, 0x8f, 0xe2, 0xe5, + 0xbf, 0xd4, 0xec, 0xa1, 0xd1, 0x8a, 0x22, 0xb5, + 0xde, 0x7e, 0xa3, 0x9d, 0x2e, 0x1b, 0x93, 0xbb, + 0x72, 0x72, 0xce, 0x0c, 0x6c, 0xa3, 0xcc, 0x8e, +}; + +/** + * build_verity_fit() - construct a minimal FIT blob with dm-verity metadata + * @buf: output buffer (at least FIT_BUF_SIZE bytes) + * @num_loadables: number of filesystem loadables to create (1 or 2) + * + * Builds a FIT blob containing: + * - /images/rootfsN with type="filesystem" and a dm-verity subnode + * - /configurations/conf-1 referencing the loadable(s) + * + * Return: configuration node offset, or -ve on error + */ +static int build_verity_fit(void *buf, int num_loadables) +{ + int images_node, conf_node, confs_node, img_node, verity_node; + fdt32_t val; + int ret, i; + char name[32]; + /* + * Build the loadables string list. FDT stringlists are concatenated + * NUL-terminated strings. E.g. "rootfs0\0rootfs1\0" + */ + char loadables[128]; + int loadables_len = 0; + + ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE); + if (ret) + return ret; + + /* /images */ + images_node = fdt_add_subnode(buf, 0, "images"); + if (images_node < 0) + return images_node; + + for (i = 0; i < num_loadables; i++) { + snprintf(name, sizeof(name), "rootfs%d", i); + + img_node = fdt_add_subnode(buf, images_node, name); + if (img_node < 0) + return img_node; + + ret = fdt_setprop_string(buf, img_node, FIT_TYPE_PROP, + "filesystem"); + 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; + + val = cpu_to_fdt32(100); + 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; + + /* Append to loadables stringlist */ + loadables_len += snprintf(loadables + loadables_len, + sizeof(loadables) - loadables_len, + "%s", name) + 1; + } + + /* /configurations/conf-1 */ + 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(buf, conf_node, FIT_LOADABLE_PROP, + loadables, loadables_len); + if (ret) + return ret; + + return conf_node; +} + +/* Test: single dm-verity loadable produces correct cmdline fragments */ +static int fit_verity_test_single(struct unit_test_state *uts) +{ + char buf[FIT_BUF_SIZE]; + struct bootm_headers images; + int conf_noffset; + + conf_noffset = build_verity_fit(buf, 1); + ut_assert(conf_noffset >= 0); + + memset(&images, 0, sizeof(images)); + ut_assertok(fit_verity_build_cmdline(buf, conf_noffset, &images)); + + /* dm_mod_create should contain the target spec for rootfs0 */ + ut_assertnonnull(images.dm_mod_create); + ut_assert(strstr(images.dm_mod_create, "rootfs0,,,")); + ut_assert(strstr(images.dm_mod_create, "verity 1")); + ut_assert(strstr(images.dm_mod_create, "/dev/fit0")); + ut_assert(strstr(images.dm_mod_create, "4096 4096 100 100")); + ut_assert(strstr(images.dm_mod_create, "sha256")); + /* Check hex-encoded digest prefix */ + ut_assert(strstr(images.dm_mod_create, "8e6791637f93cbb8")); + /* Check hex-encoded salt prefix */ + ut_assert(strstr(images.dm_mod_create, "aa7b11f8db8fe2e5")); + + /* dm_mod_waitfor should reference /dev/fit0 */ + ut_assertnonnull(images.dm_mod_waitfor); + ut_asserteq_str("/dev/fit0", images.dm_mod_waitfor); + + fit_verity_free(&images); + ut_assertnull(images.dm_mod_create); + ut_assertnull(images.dm_mod_waitfor); + + return 0; +} +FIT_VERITY_TEST(fit_verity_test_single, 0); + +/* Test: FIT with no dm-verity subnode returns 0, pointers stay NULL */ +static int fit_verity_test_no_verity(struct unit_test_state *uts) +{ + char buf[FIT_BUF_SIZE]; + struct bootm_headers images; + int conf_node, images_node, img_node, confs_node; + int ret; + + ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE); + ut_assertok(ret); + + images_node = fdt_add_subnode(buf, 0, "images"); + ut_assert(images_node >= 0); + + img_node = fdt_add_subnode(buf, images_node, "rootfs"); + ut_assert(img_node >= 0); + ut_assertok(fdt_setprop_string(buf, img_node, FIT_TYPE_PROP, + "filesystem")); + /* No dm-verity subnode */ + + confs_node = fdt_add_subnode(buf, 0, "configurations"); + ut_assert(confs_node >= 0); + conf_node = fdt_add_subnode(buf, confs_node, "conf-1"); + ut_assert(conf_node >= 0); + ut_assertok(fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP, + "rootfs")); + + memset(&images, 0, sizeof(images)); + ut_asserteq(0, fit_verity_build_cmdline(buf, conf_node, &images)); + ut_assertnull(images.dm_mod_create); + ut_assertnull(images.dm_mod_waitfor); + + return 0; +} +FIT_VERITY_TEST(fit_verity_test_no_verity, 0); + +/* Test: two dm-verity loadables produce combined cmdline */ +static int fit_verity_test_two_loadables(struct unit_test_state *uts) +{ + char buf[FIT_BUF_SIZE]; + struct bootm_headers images; + int conf_noffset; + + conf_noffset = build_verity_fit(buf, 2); + ut_assert(conf_noffset >= 0); + + memset(&images, 0, sizeof(images)); + ut_assertok(fit_verity_build_cmdline(buf, conf_noffset, &images)); + + /* Both targets should appear, separated by ";" */ + ut_assertnonnull(images.dm_mod_create); + ut_assert(strstr(images.dm_mod_create, "rootfs0,,,")); + ut_assert(strstr(images.dm_mod_create, ";rootfs1,,,")); + ut_assert(strstr(images.dm_mod_create, "/dev/fit0")); + ut_assert(strstr(images.dm_mod_create, "/dev/fit1")); + + /* dm_mod_waitfor should list both devices */ + ut_assertnonnull(images.dm_mod_waitfor); + ut_assert(strstr(images.dm_mod_waitfor, "/dev/fit0")); + ut_assert(strstr(images.dm_mod_waitfor, "/dev/fit1")); + + fit_verity_free(&images); + return 0; +} +FIT_VERITY_TEST(fit_verity_test_two_loadables, 0); + +/* Test: invalid block size (not power of two) returns -EINVAL */ +static int fit_verity_test_bad_blocksize(struct unit_test_state *uts) +{ + char buf[FIT_BUF_SIZE]; + struct bootm_headers images; + int images_node, conf_node, confs_node, img_node, verity_node; + fdt32_t val; + int ret; + + ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE); + ut_assertok(ret); + + images_node = fdt_add_subnode(buf, 0, "images"); + ut_assert(images_node >= 0); + + img_node = fdt_add_subnode(buf, images_node, "rootfs"); + ut_assert(img_node >= 0); + ut_assertok(fdt_setprop_string(buf, img_node, FIT_TYPE_PROP, + "filesystem")); + + verity_node = fdt_add_subnode(buf, img_node, FIT_VERITY_NODENAME); + ut_assert(verity_node >= 0); + + ut_assertok(fdt_setprop_string(buf, verity_node, + FIT_VERITY_ALGO_PROP, "sha256")); + + /* 3000 is not a power of two */ + val = cpu_to_fdt32(3000); + ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_DBS_PROP, + &val, sizeof(val))); + val = cpu_to_fdt32(4096); + ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_HBS_PROP, + &val, sizeof(val))); + + val = cpu_to_fdt32(100); + ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_NBLK_PROP, + &val, sizeof(val))); + ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_HBLK_PROP, + &val, sizeof(val))); + + ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_DIGEST_PROP, + test_digest, sizeof(test_digest))); + ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_SALT_PROP, + test_salt, sizeof(test_salt))); + + confs_node = fdt_add_subnode(buf, 0, "configurations"); + ut_assert(confs_node >= 0); + conf_node = fdt_add_subnode(buf, confs_node, "conf-1"); + ut_assert(conf_node >= 0); + ut_assertok(fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP, + "rootfs")); + + memset(&images, 0, sizeof(images)); + ut_asserteq(-EINVAL, fit_verity_build_cmdline(buf, conf_node, &images)); + ut_assertnull(images.dm_mod_create); + ut_assertnull(images.dm_mod_waitfor); + + return 0; +} +FIT_VERITY_TEST(fit_verity_test_bad_blocksize, 0); diff --git a/test/boot/image_fdt.c b/test/boot/image_fdt.c new file mode 100644 index 00000000000..5417689a683 --- /dev/null +++ b/test/boot/image_fdt.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#include <config.h> + +#include <fdt_support.h> +#include <image.h> +#include <lmb.h> +#include <malloc.h> + +#include <asm/global_data.h> + +#include <test/test.h> +#include <test/ut.h> + +#define IMAGE_FDT_TEST(_name, _flags) UNIT_TEST(_name, _flags, image_fdt) + +DECLARE_GLOBAL_DATA_PTR; + +/** + * test_boot_fdt_add_mem_rsv_regions - Make sure dt reservations are created and + * destroyed correctly + * @uts: Test state + * + * This test depends on the UT_DM device tree and ensures the following + * statements hold true: The default reservation in test.dtb exists. + * Re-reserving that region will result in an error. Loading a new device tree + * will remove old reservations. + */ +static int test_boot_fdt_add_mem_rsv_regions(struct unit_test_state *uts) +{ + phys_addr_t start = CFG_SYS_SDRAM_BASE + 0x100000; + const void *old_blob = gd->fdt_blob; + int ret = CMD_RET_FAILURE; + ulong fdt_sz; + int nodeoffset; + void *new_blob; + + /* Default reservation should exist */ + ut_asserteq(1, lmb_is_reserved_flags(start, LMB_NOMAP)); + + /* Attempting to re-reserve should warn the user */ + boot_fdt_add_mem_rsv_regions(gd->fdt_blob); + ut_assert_nextlinen("ERROR: reserving"); + ut_assert_console_end(); + + /* Loading a new_blob device tree should be allowed */ + fdt_sz = fdt_totalsize(gd->fdt_blob); + new_blob = malloc(fdt_sz); + ut_assertnonnull(new_blob); + memcpy(new_blob, gd->fdt_blob, fdt_sz); + + nodeoffset = fdt_path_offset(new_blob, "/reserved-memory"); + if (nodeoffset < 0) + goto free_blob; + + if (fdt_del_node(new_blob, nodeoffset)) + goto free_blob; + + boot_fdt_add_mem_rsv_regions(new_blob); + gd->fdt_blob = new_blob; + + if (ut_check_console_end(uts)) { + ut_failf(uts, __FILE__, __LINE__, __func__, "console", + "Expected no more output, got '%s'", uts->actual_str); + goto switch_fdt; + } + + /* Reservation should not exist now */ + if (!lmb_is_reserved_flags(start, LMB_NOMAP)) + ret = 0; + + /* Cleanup */ +switch_fdt: + boot_fdt_add_mem_rsv_regions(old_blob); + gd->fdt_blob = old_blob; +free_blob: + free(new_blob); + return ret; +} +IMAGE_FDT_TEST(test_boot_fdt_add_mem_rsv_regions, UTF_CONSOLE); diff --git a/test/boot/vbe_read_fit.c b/test/boot/vbe_read_fit.c new file mode 100644 index 00000000000..f67de0e7165 --- /dev/null +++ b/test/boot/vbe_read_fit.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bounds-check tests for vbe_read_fit() + * + * vbe_read_fit() pulls a firmware-phase FIT from a trusted firmware area + * on a block device. The external-data location and size carried in the + * FIT image node are attacker-controllable when the firmware area is on + * mutable boot media, so vbe_read_fit() must reject FITs whose external + * data extends past @area_size before issuing the follow-up blk_read(). + * + * These tests build small synthetic FITs with deliberately out-of-range + * values and confirm vbe_read_fit() returns -E2BIG for each. + * + * Copyright 2026 Canonical Ltd. + * Written by Aristo Chen <[email protected]> + */ + +#include <blk.h> +#include <dm.h> +#include <image.h> +#include <memalign.h> +#include <mmc.h> +#include <test/test.h> +#include <test/ut.h> +#include <linux/libfdt.h> +#include "bootstd_common.h" +#include "../../boot/vbe_common.h" + +/* + * The synthetic FIT is written to mmc1 starting at block TEST_FIT_BLK. + * bootstd_setup_for_tests() uses blocks 4 and 6 (see bootstd_common.h); + * block 16 leaves a comfortable gap. + */ +#define TEST_FIT_BLK 16 +#define TEST_FIT_OFF ((ulong)TEST_FIT_BLK * MMC_MAX_BLOCK_LEN) +#define TEST_AREA_SIZE 0x1000 + +/** + * build_fit() - Build a minimal external-data FIT for vbe_read_fit() + * + * The FIT advertises a single firmware image whose @data-position and + * @data-size are passed in directly. Both values are attacker-controlled + * in the real threat model. + * + * @buf: Destination buffer (must be at least 512 bytes) + * @buf_size: Size of @buf + * @data_position: Value written to the image's data-position property + * @data_size: Value written to the image's data-size property + * Returns: 0 on success, libfdt error otherwise + */ +static int build_fit(void *buf, size_t buf_size, u32 data_position, + u32 data_size) +{ + int ret; + + ret = fdt_create(buf, buf_size); + if (ret) + return ret; + ret = fdt_finish_reservemap(buf); + if (ret) + return ret; + + ret = fdt_begin_node(buf, ""); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_DESC_PROP, "vbe-read-fit test"); + if (ret) + return ret; + ret = fdt_property_u32(buf, FIT_TIMESTAMP_PROP, 0); + if (ret) + return ret; + + ret = fdt_begin_node(buf, "images"); + if (ret) + return ret; + ret = fdt_begin_node(buf, "u-boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_DESC_PROP, "U-Boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_TYPE_PROP, "firmware"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_ARCH_PROP, "sandbox"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_OS_PROP, "u-boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_PHASE_PROP, "u-boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_COMP_PROP, "none"); + if (ret) + return ret; + ret = fdt_property_u32(buf, FIT_DATA_POSITION_PROP, data_position); + if (ret) + return ret; + ret = fdt_property_u32(buf, FIT_DATA_SIZE_PROP, data_size); + if (ret) + return ret; + ret = fdt_end_node(buf); /* u-boot */ + if (ret) + return ret; + ret = fdt_end_node(buf); /* images */ + if (ret) + return ret; + + ret = fdt_begin_node(buf, "configurations"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_DEFAULT_PROP, "conf-1"); + if (ret) + return ret; + ret = fdt_begin_node(buf, "conf-1"); + if (ret) + return ret; + ret = fdt_property_string(buf, "compatible", "sandbox"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_FIRMWARE_PROP, "u-boot"); + if (ret) + return ret; + ret = fdt_end_node(buf); /* conf-1 */ + if (ret) + return ret; + ret = fdt_end_node(buf); /* configurations */ + if (ret) + return ret; + + ret = fdt_end_node(buf); /* root */ + if (ret) + return ret; + + return fdt_finish(buf); +} + +/** + * place_fit_on_mmc() - Write a synthetic FIT to mmc1 and return its blk dev + * + * @uts: Unit test state + * @fit: FIT image to write + * @blkp: On success, receives the block udevice for mmc1 + * Returns: 0 on success, -ve on error + */ +static int place_fit_on_mmc(struct unit_test_state *uts, const void *fit, + struct udevice **blkp) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, blkbuf, MMC_MAX_BLOCK_LEN); + struct udevice *mmc; + struct blk_desc *desc; + size_t fit_size = fdt_totalsize(fit); + size_t pos; + int blknum = TEST_FIT_BLK; + + ut_assertok(uclass_get_device(UCLASS_MMC, 1, &mmc)); + desc = blk_get_by_device(mmc); + if (!desc) + return log_msg_ret("desc", -ENODEV); + + for (pos = 0; pos < fit_size; pos += MMC_MAX_BLOCK_LEN, blknum++) { + size_t this_blk = min(fit_size - pos, + (size_t)MMC_MAX_BLOCK_LEN); + + memset(blkbuf, '\0', MMC_MAX_BLOCK_LEN); + memcpy(blkbuf, (const u8 *)fit + pos, this_blk); + if (blk_dwrite(desc, blknum, 1, blkbuf) != 1) + return log_msg_ret("wr", -EIO); + } + *blkp = desc->bdev; + + return 0; +} + +/* + * data-position points past area_size: vbe_read_fit() must reject the + * FIT with -E2BIG before issuing the external-data blk_read(). + */ +static int vbe_read_fit_oob_position(struct unit_test_state *uts) +{ + u8 fit[1024] __aligned(8); + struct udevice *blk; + ulong load_addr = 0, len = 0; + char *name = NULL; + int ret; + + ut_assertok(build_fit(fit, sizeof(fit), + TEST_AREA_SIZE + 0x10, 0x40)); + ut_assertok(place_fit_on_mmc(uts, fit, &blk)); + + ret = vbe_read_fit(blk, TEST_FIT_OFF, TEST_AREA_SIZE, + NULL, &load_addr, &len, &name); + ut_asserteq(-E2BIG, ret); + + return 0; +} + +BOOTSTD_TEST(vbe_read_fit_oob_position, UTF_DM | UTF_SCAN_FDT); + +/* + * data-position is inside the area but data-size pushes the end past + * area_size: vbe_read_fit() must reject the FIT with -E2BIG. + */ +static int vbe_read_fit_oversize_data(struct unit_test_state *uts) +{ + u8 fit[1024] __aligned(8); + struct udevice *blk; + ulong load_addr = 0, len = 0; + char *name = NULL; + int ret; + + ut_assertok(build_fit(fit, sizeof(fit), + 0x400, TEST_AREA_SIZE)); + ut_assertok(place_fit_on_mmc(uts, fit, &blk)); + + ret = vbe_read_fit(blk, TEST_FIT_OFF, TEST_AREA_SIZE, + NULL, &load_addr, &len, &name); + ut_asserteq(-E2BIG, ret); + + return 0; +} + +BOOTSTD_TEST(vbe_read_fit_oversize_data, UTF_DM | UTF_SCAN_FDT); diff --git a/test/cmd/Makefile b/test/cmd/Makefile index 8d6932f1176..8d36463879d 100644 --- a/test/cmd/Makefile +++ b/test/cmd/Makefile @@ -17,6 +17,7 @@ ifdef CONFIG_CONSOLE_RECORD obj-$(CONFIG_CMD_ACPI) += acpi.o endif obj-$(CONFIG_CMD_BDI) += bdinfo.o +obj-$(CONFIG_CMD_CONFIG) += config.o obj-$(CONFIG_COREBOOT_SYSINFO) += coreboot.o obj-$(CONFIG_CMD_FDT) += fdt.o obj-$(CONFIG_CMD_HASH) += hash.o diff --git a/test/cmd/bdinfo.c b/test/cmd/bdinfo.c index 7f4f1868c6a..7b7fb0894dd 100644 --- a/test/cmd/bdinfo.c +++ b/test/cmd/bdinfo.c @@ -138,16 +138,15 @@ static int lmb_test_dump_all(struct unit_test_state *uts) static int bdinfo_check_mem(struct unit_test_state *uts) { - struct bd_info *bd = gd->bd; int i; for (i = 0; i < CONFIG_NR_DRAM_BANKS; ++i) { - if (bd->bi_dram[i].size) { + if (gd->dram[i].size) { ut_assertok(test_num_l(uts, "DRAM bank", i)); ut_assertok(test_num_ll(uts, "-> start", - bd->bi_dram[i].start)); + gd->dram[i].start)); ut_assertok(test_num_ll(uts, "-> size", - bd->bi_dram[i].size)); + gd->dram[i].size)); } } diff --git a/test/cmd/config.c b/test/cmd/config.c new file mode 100644 index 00000000000..5a48060801f --- /dev/null +++ b/test/cmd/config.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for config command + */ + +#include <console.h> +#include <test/cmd.h> +#include <test/ut.h> + +static int cmd_test_config(struct unit_test_state *uts) +{ + ut_assertok(run_command("config", 0)); + ut_assert_skip_to_line("# Automatically generated file; DO NOT EDIT."); + ut_assert_skip_to_linen("# Compiler:"); + ut_assert_skip_to_line("CONFIG_CMD_CONFIG=y"); + + console_record_reset_enable(); + + ut_assertok(run_command("config cmd_config=y", 0)); + ut_assert_nextline("CONFIG_CMD_CONFIG=y"); + ut_assert_console_end(); + + ut_assertok(run_command("config 'this string never appears in .config'", 0)); + ut_assert_console_end(); + + return 0; +} +CMD_TEST(cmd_test_config, UTF_CONSOLE); diff --git a/test/cmd/part.c b/test/cmd/part.c index e0149011476..b01bc286723 100644 --- a/test/cmd/part.c +++ b/test/cmd/part.c @@ -50,7 +50,7 @@ static int setup_gpt_partitions(struct unit_test_state *uts, if (ret == -ENODEV) return -EAGAIN; - ut_asserteq(mmc_dev_num, ret); + ut_assert(ret >= 0 && ret == mmc_dev_num); if (CONFIG_IS_ENABLED(RANDOM_UUID)) { for (i = 0; i < ARRAY_SIZE(gpt_parts); i++) diff --git a/test/cmd_ut.c b/test/cmd_ut.c index 44e5fdfdaa6..4328670d0d6 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -59,8 +59,10 @@ SUITE_DECL(env); SUITE_DECL(exit); SUITE_DECL(fdt); SUITE_DECL(fdt_overlay); +SUITE_DECL(fit_verity); SUITE_DECL(font); SUITE_DECL(hush); +SUITE_DECL(image_fdt); SUITE_DECL(lib); SUITE_DECL(loadm); SUITE_DECL(log); @@ -86,8 +88,10 @@ static struct suite suites[] = { SUITE(exit, "shell exit and variables"), SUITE(fdt, "fdt command"), SUITE(fdt_overlay, "device tree overlays"), + SUITE(fit_verity, "FIT dm-verity cmdline generation"), SUITE(font, "font command"), SUITE(hush, "hush behaviour"), + SUITE(image_fdt, "image fdt parsing"), SUITE(lib, "library functions"), SUITE(loadm, "loadm command parameters and loading memory blob"), SUITE(log, "logging functions"), diff --git a/test/dm/Makefile b/test/dm/Makefile index d69b0e08d66..76aa1fff9ba 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_MULTIPLEXER) += mux-emul.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o obj-y += fdtdec.o obj-$(CONFIG_MTD_RAW_NAND) += nand.o +obj-$(CONFIG_IP_DEFRAG) += net_defrag.o obj-$(CONFIG_UT_DM) += nop.o obj-y += ofnode.o obj-y += ofread.o @@ -94,6 +95,7 @@ obj-$(CONFIG_PINCONF) += pinmux.o endif obj-$(CONFIG_POWER_DOMAIN) += power-domain.o obj-$(CONFIG_ACPI_PMC) += pmc.o +obj-$(CONFIG_CMD_PMBUS) += pmbus.o obj-$(CONFIG_DM_PMIC) += pmic.o obj-$(CONFIG_DM_PWM) += pwm.o obj-$(CONFIG_ARM_FFA_TRANSPORT) += ffa.o diff --git a/test/dm/acpi.c b/test/dm/acpi.c index 2de7983f9ae..293ea0274b5 100644 --- a/test/dm/acpi.c +++ b/test/dm/acpi.c @@ -136,7 +136,7 @@ static int testacpi_inject_dsdt(const struct udevice *dev, struct acpi_ctx *ctx) return 0; } -struct acpi_ops testacpi_ops = { +static const struct acpi_ops testacpi_ops = { .get_name = testacpi_get_name, .write_tables = testacpi_write_tables, .fill_madt = testacpi_fill_madt, diff --git a/test/dm/net_defrag.c b/test/dm/net_defrag.c new file mode 100644 index 00000000000..3fd40de90cd --- /dev/null +++ b/test/dm/net_defrag.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Regression test for IP fragment reassembly. + * + * The test drives the real RX path via net_process_received_packet(). Final IP + * fragment (MF=0) is duplicated, crafted payload triggers redelivery of the datagram, + * which fails the test for the unfixed code. + */ + +#include <net.h> +#include <string.h> +#include <test/ut.h> +#include <dm/test.h> + +#define FRAG_LEN (8) +#define PAYLOAD_OFFSET (ETHER_HDR_SIZE + IP_HDR_SIZE) +#define FRAME_LEN (PAYLOAD_OFFSET + FRAG_LEN) + +static int udp_rx_count; + +static void defrag_udp_handler(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + udp_rx_count++; +} + +static int build_frag(uchar *buf, u16 off_flags, const u16 *payload) +{ + struct ethernet_hdr *et = (struct ethernet_hdr *)buf; + struct ip_udp_hdr *ip = (struct ip_udp_hdr *)(buf + ETHER_HDR_SIZE); + + memset(buf, 0, FRAME_LEN); + et->et_protlen = htons(PROT_IP); + + ip->ip_hl_v = 0x45; + ip->ip_len = htons(IP_HDR_SIZE + FRAG_LEN); + ip->ip_id = htons(0x4321); + ip->ip_off = htons(off_flags); + ip->ip_ttl = 64; + ip->ip_p = IPPROTO_UDP; + /* Broadcast destination is accepted regardless of net_ip. */ + ip->ip_dst.s_addr = 0xffffffff; + ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE); + + memcpy(buf + PAYLOAD_OFFSET, payload, FRAG_LEN); + + return FRAME_LEN; +} + +static int dm_test_net_ip_defrag_dup_last(struct unit_test_state *uts) +{ + rxhand_f *saved_handler = net_get_udp_handler(); + uchar frame[FRAME_LEN]; + /* UDP header, carried by first fragment. */ + u16 udp_hdr[4] = { htons(5000), htons(5001), + htons(UDP_HDR_SIZE + FRAG_LEN), 0 }; + /* + * Second fragment's payload doubles as a fake hole + * {last_byte >= FRAG_LEN, next_hole = 0, prev_hole = 0}, so that the + * buggy code re-reading it on a duplicate re-delivers the datagram. + */ + u16 frag_b[4] = { 2 * FRAG_LEN, 0, 0, 0 }; + + udp_rx_count = 0; + net_set_udp_handler(defrag_udp_handler); + + /* UDP header, offset 0, MF=1; then data, offset 1, MF=0 */ + net_process_received_packet(frame, build_frag(frame, IP_FLAGS_MFRAG, udp_hdr)); + net_process_received_packet(frame, build_frag(frame, 1, frag_b)); + ut_asserteq(1, udp_rx_count); + + /* Duplicate the final fragment: UDP datagram must not be delivered again. */ + net_process_received_packet(frame, build_frag(frame, 1, frag_b)); + ut_asserteq(1, udp_rx_count); + + net_set_udp_handler(saved_handler); + + return 0; +} + +DM_TEST(dm_test_net_ip_defrag_dup_last, 0); diff --git a/test/dm/pmbus.c b/test/dm/pmbus.c new file mode 100644 index 00000000000..0184b201829 --- /dev/null +++ b/test/dm/pmbus.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 Free Mobile - Vincent Jardin + * + * Unit tests for the PMBus 1.x framework, the generic + * PMBus regulator and the pmbus CLI command. + */ + +#include <dm.h> +#include <i2c.h> +#include <pmbus.h> +#include <dm/test.h> +#include <test/test.h> +#include <test/ut.h> + +/* The line pmbus dev prints when a chip is selected */ +#define PMBUS_ACTIVE_LINE \ + "pmbus: active i2c0:0x70 rail=\"sandbox-pmbus-vout\" " \ + "MFR_ID=\"SANDBOX\" MODEL=\"PMBUS-EMUL\" vendor=(generic)" + +/* The line pmbus list prints for the bound chip */ +#define PMBUS_LIST_LINE \ + " i2c0:0x70 rail=\"sandbox-pmbus-vout\" node=pmbus@70 " \ + "driver=pmbus_generic_regulator" + +/* Select the emulated chip and check the resulting banner line */ +static int pmbus_select(struct unit_test_state *uts) +{ + ut_assertok(run_command("pmbus dev 0:70", 0)); + ut_assert_nextline(PMBUS_ACTIVE_LINE); + return 0; +} + +/* The chip is reachable via UCLASS_REGULATOR (compatible = "pmbus") */ +static int dm_test_pmbus_bind(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(uclass_get_device_by_name(UCLASS_REGULATOR, "pmbus@70", + &dev)); + ut_asserteq_str("pmbus_generic_regulator", dev->driver->name); + ut_asserteq(UCLASS_I2C, device_get_uclass_id(dev_get_parent(dev))); + + return 0; +} + +DM_TEST(dm_test_pmbus_bind, UTF_SCAN_FDT); + +/* pmbus dev by <bus>:<addr> and by regulator-name select the chip */ +static int dm_test_pmbus_dev(struct unit_test_state *uts) +{ + ut_assertok(run_command("pmbus dev 0:70", 0)); + ut_assert_nextline(PMBUS_ACTIVE_LINE); + ut_assert_console_end(); + + /* Selecting by DT regulator-name resolves to the same chip */ + ut_assertok(run_command("pmbus dev sandbox-pmbus-vout", 0)); + ut_assert_nextline(PMBUS_ACTIVE_LINE); + ut_assert_console_end(); + + /* pmbus dev with no argument reprints the active chip */ + ut_assertok(run_command("pmbus dev", 0)); + ut_assert_nextline(PMBUS_ACTIVE_LINE); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_dev, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus list enumerates the bound chip among the UCLASS_REGULATOR devices */ +static int dm_test_pmbus_list(struct unit_test_state *uts) +{ + ut_assertok(run_command("pmbus list", 0)); + ut_assert_skip_to_line(PMBUS_LIST_LINE); + + return 0; +} + +DM_TEST(dm_test_pmbus_list, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus info decodes identification + the detected driver_info */ +static int dm_test_pmbus_info(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + ut_assertok(run_command("pmbus info", 0)); + ut_assert_nextline("pmbus device i2c0:0x70"); + ut_assert_nextline(" regulator-name: \"sandbox-pmbus-vout\""); + ut_assert_nextline(" MFR_ID : \"SANDBOX\""); + ut_assert_nextline(" MFR_MODEL : \"PMBUS-EMUL\""); + ut_assert_nextline(" MFR_REVISION : \"1.0\" raw=0x312e30"); + ut_assert_nextline(" PMBUS_REVISION: 0x33 (PMBus 1.3)"); + ut_assert_nextline(" vendor : (none)"); + ut_assert_nextline(" driver_info : pages=1"); + ut_assert_nextline(" [VOLTAGE_IN ] format=LINEAR"); + ut_assert_nextline(" [VOLTAGE_OUT ] format=LINEAR"); + ut_assert_nextline(" [CURRENT_IN ] format=LINEAR"); + ut_assert_nextline(" [CURRENT_OUT ] format=LINEAR"); + ut_assert_nextline(" [POWER ] format=LINEAR"); + ut_assert_nextline(" [TEMPERATURE ] format=LINEAR"); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_info, UTF_SCAN_FDT | UTF_CONSOLE); + +/* + * pmbus telemetry decodes the implemented sensors and prints + * "(not supported)" for the commands the emulator NAKs (READ_IIN, + * READ_POUT). LINEAR11 is used for VIN/IOUT/TEMP, LINEAR16 (with the + * VOUT_MODE 2^-8 exponent) for VOUT. + */ +static int dm_test_pmbus_telemetry(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + ut_assertok(run_command("pmbus telemetry", 0)); + ut_assert_nextline("pmbus telemetry @ i2c0:0x70"); + ut_assert_nextline(" VIN : raw=0x0abc 1400.000V"); + ut_assert_nextline(" VOUT : raw=0x0200 2.000V"); + ut_assert_nextline(" IIN : (not supported)"); + ut_assert_nextline(" IOUT : raw=0x0123 291.000A"); + ut_assert_nextline(" POUT : (not supported)"); + ut_assert_nextline(" TEMP : raw=0x0019 25.000C"); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_telemetry, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus status decodes every STATUS_* register; the emulator is clean */ +static int dm_test_pmbus_status(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + ut_assertok(run_command("pmbus status", 0)); + ut_assert_nextline("pmbus status @ i2c0:0x70"); + ut_assert_nextline(" STATUS_WORD (79h) = 0x0000 [clean]"); + ut_assert_nextline(" STATUS_VOUT (7Ah) = 0x00 [clean]"); + ut_assert_nextline(" STATUS_IOUT (7Bh) = 0x00 [clean]"); + ut_assert_nextline(" STATUS_INPUT (7Ch) = 0x00 [clean]"); + ut_assert_nextline(" STATUS_TEMP (7Dh) = 0x00 [clean]"); + ut_assert_nextline(" STATUS_CML (7Eh) = 0x00 [clean]"); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_status, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus read/pmbus write raw register access (byte, word, string) */ +static int dm_test_pmbus_read_write(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + /* Symbolic and numeric register names both resolve */ + ut_assertok(run_command("pmbus read VOUT_MODE", 0)); + ut_assert_nextline(" 20h VOUT_MODE b=0x18"); + ut_assertok(run_command("pmbus read 8b w", 0)); + ut_assert_nextline(" 8bh READ_VOUT w=0x0200"); + ut_assertok(run_command("pmbus read MFR_ID s", 0)); + ut_assert_nextline(" 99h MFR_ID s=\"SANDBOX\""); + + /* A word write is observable on the next read-back */ + ut_assertok(run_command("pmbus write VOUT_COMMAND 123 w", 0)); + ut_assert_nextline("pmbus: wrote 0x123 to 21h (VOUT_COMMAND)"); + ut_assertok(run_command("pmbus read VOUT_COMMAND w", 0)); + ut_assert_nextline(" 21h VOUT_COMMAND w=0x0123"); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_read_write, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus vout reads back VOUT via the active driver_info decoder */ +static int dm_test_pmbus_vout(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + ut_assertok(run_command("pmbus vout", 0)); + ut_assert_nextline("pmbus VOUT @ i2c0:0x70 raw=0x0200 2.000V"); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_vout, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus dump walks every standard register; spot-check one line */ +static int dm_test_pmbus_dump(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + ut_assertok(run_command("pmbus dump", 0)); + ut_assert_nextline("pmbus dump @ i2c0:0x70 (registers known to <pmbus.h>)"); + ut_assert_skip_to_line(" 20h VOUT_MODE b=0x18"); + + return 0; +} + +DM_TEST(dm_test_pmbus_dump, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus clear [faults] issues CLEAR_FAULTS (03h) */ +static int dm_test_pmbus_clear(struct unit_test_state *uts) +{ + ut_assertok(pmbus_select(uts)); + + ut_assertok(run_command("pmbus clear faults", 0)); + ut_assert_nextline("pmbus: CLEAR_FAULTS (03h) issued (RAM sticky STATUS_* cleared)"); + ut_assert_console_end(); + + return 0; +} + +DM_TEST(dm_test_pmbus_clear, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus scan finds the emulated chip by its MFR_ID block read */ +static int dm_test_pmbus_scan(struct unit_test_state *uts) +{ + ut_assertok(run_command("pmbus scan 0", 0)); + ut_assert_skip_to_line(" i2c0:0x70 MFR_ID=\"SANDBOX\""); + + return 0; +} + +DM_TEST(dm_test_pmbus_scan, UTF_SCAN_FDT | UTF_CONSOLE); + +/* pmbus help lists vendor extensions; none are registered here */ +static int dm_test_pmbus_help(struct unit_test_state *uts) +{ + ut_assertok(run_command("pmbus help", 0)); + ut_assert_nextline("pmbus: no vendor extensions registered."); + ut_assert_skip_to_line(" board hook (boot snapshot) and re run 'pmbus help'."); + + return 0; +} + +DM_TEST(dm_test_pmbus_help, UTF_SCAN_FDT | UTF_CONSOLE); diff --git a/test/dm/regulator.c b/test/dm/regulator.c index 449748ad52f..51007d4079d 100644 --- a/test/dm/regulator.c +++ b/test/dm/regulator.c @@ -28,6 +28,7 @@ enum { BUCK3, LDO1, LDO2, + LDO3, OUTPUT_COUNT, }; @@ -44,6 +45,7 @@ static const char *regulator_names[OUTPUT_COUNT][OUTPUT_NAME_COUNT] = { { SANDBOX_BUCK3_DEVNAME, SANDBOX_BUCK3_PLATNAME }, { SANDBOX_LDO1_DEVNAME, SANDBOX_LDO1_PLATNAME}, { SANDBOX_LDO2_DEVNAME, SANDBOX_LDO2_PLATNAME}, + { SANDBOX_LDO3_DEVNAME, SANDBOX_LDO3_PLATNAME}, }; /* Test regulator get method */ @@ -118,6 +120,42 @@ static int dm_test_power_regulator_set_get_voltage(struct unit_test_state *uts) } DM_TEST(dm_test_power_regulator_set_get_voltage, UTF_SCAN_FDT); +/* Test regulator set Voltage clamp method */ +static int dm_test_power_regulator_set_value_clamp(struct unit_test_state *uts) +{ + struct udevice *dev; + const char *platname; + + /* LDO3 have 'min' 1.8V and 'max' 3.3V */ + platname = regulator_names[LDO3][PLATNAME]; + ut_assertok(regulator_get_by_platname(platname, &dev)); + + /* 'target' in 'min'/'max' range - should not clamp voltage */ + ut_assertok(regulator_set_value_clamp(dev, 1700000, 1800000, 1950000)); + ut_asserteq(1800000, regulator_get_value(dev)); + ut_assertok(regulator_set_value_clamp(dev, 2700000, 3300000, 3600000)); + ut_asserteq(3300000, regulator_get_value(dev)); + + /* 'target' out of 'min'/'max' range - should clamp voltage */ + ut_assertok(regulator_set_value_clamp(dev, 1700000, 1700000, 1950000)); + ut_asserteq(1800000, regulator_get_value(dev)); + ut_assertok(regulator_set_value_clamp(dev, 2700000, 3400000, 3600000)); + ut_asserteq(3300000, regulator_get_value(dev)); + + /* 'min'/'max' out of range - should return -EINVAL */ + ut_asserteq(-EINVAL, + regulator_set_value_clamp(dev, 1200000, 1500000, 1700000)); + ut_asserteq(-EINVAL, + regulator_set_value_clamp(dev, 3500000, 4000000, 5000000)); + + /* 'min' higher than 'max' - should return -EINVAL */ + ut_asserteq(-EINVAL, + regulator_set_value_clamp(dev, 3100000, 3000000, 2900000)); + + return 0; +} +DM_TEST(dm_test_power_regulator_set_value_clamp, UTF_SCAN_FDT); + /* Test regulator set and get Current method */ static int dm_test_power_regulator_set_get_current(struct unit_test_state *uts) { diff --git a/test/dm/reset.c b/test/dm/reset.c index dceb6a1dad3..91fa7ff723b 100644 --- a/test/dm/reset.c +++ b/test/dm/reset.c @@ -19,6 +19,9 @@ /* This is the other reset phandle specifier handled by bulk */ #define OTHER_RESET_ID 2 +/* Line on reset-ctl-fallback (sandbox,reset-ctl-fallback-only); see test.dts */ +#define FALLBACK_RESET_ID 5 + /* Base test of the reset uclass */ static int dm_test_reset_base(struct unit_test_state *uts) { @@ -120,6 +123,110 @@ static int dm_test_reset_devm(struct unit_test_state *uts) } DM_TEST(dm_test_reset_devm, UTF_SCAN_FDT); +static int dm_test_reset_reset(struct unit_test_state *uts) +{ + struct udevice *dev_reset; + struct udevice *dev_test; + + ut_assertok(uclass_get_device_by_name(UCLASS_RESET, "reset-ctl", + &dev_reset)); + ut_asserteq(0, sandbox_reset_query(dev_reset, TEST_RESET_ID)); + + ut_assertok(uclass_get_device_by_name(UCLASS_MISC, "reset-ctl-test", + &dev_test)); + ut_assertok(sandbox_reset_test_get(dev_test)); + + /* Verify reset_count starts at 0 */ + ut_asserteq(0, sandbox_reset_get_count(dev_reset, TEST_RESET_ID)); + + ut_assertok(sandbox_reset_test_assert(dev_test)); + ut_asserteq(1, sandbox_reset_query(dev_reset, TEST_RESET_ID)); + + ut_assertok(sandbox_reset_test_reset(dev_test)); + + /* Verify reset was pulsed (count incremented) */ + ut_asserteq(1, sandbox_reset_get_count(dev_reset, TEST_RESET_ID)); + ut_asserteq(0, sandbox_reset_query(dev_reset, TEST_RESET_ID)); + + ut_assertok(sandbox_reset_test_free(dev_test)); + + return 0; +} +DM_TEST(dm_test_reset_reset, UTF_SCAN_FDT); + +/* + * reset_reset() fallback path: controller has no rst_reset op, so the + * core does assert -> udelay -> deassert. rst_reset-only accounting + * (reset_count) stays zero. Leave the line asserted before reset_reset() + * so we verify the fallback actually pulses it back to deasserted. + */ +static int dm_test_reset_reset_fallback_path(struct unit_test_state *uts) +{ + struct udevice *dev_reset_fb; + struct udevice *dev_test; + struct reset_ctl ctl; + + ut_assertok(uclass_get_device_by_name(UCLASS_RESET, "reset-ctl-fallback", + &dev_reset_fb)); + ut_asserteq(0, sandbox_reset_query(dev_reset_fb, FALLBACK_RESET_ID)); + ut_asserteq(0, sandbox_reset_get_count(dev_reset_fb, FALLBACK_RESET_ID)); + + ut_assertok(uclass_get_device_by_name(UCLASS_MISC, "reset-ctl-test", + &dev_test)); + ut_assertok(reset_get_by_name(dev_test, "fallback", &ctl)); + ut_asserteq_ptr(ctl.dev, dev_reset_fb); + ut_asserteq(FALLBACK_RESET_ID, ctl.id); + + ut_assertok(reset_assert(&ctl)); + ut_asserteq(1, sandbox_reset_query(dev_reset_fb, FALLBACK_RESET_ID)); + ut_asserteq(0, sandbox_reset_get_count(dev_reset_fb, FALLBACK_RESET_ID)); + + ut_assertok(reset_reset(&ctl, 1)); + ut_asserteq(0, sandbox_reset_get_count(dev_reset_fb, FALLBACK_RESET_ID)); + ut_asserteq(0, sandbox_reset_query(dev_reset_fb, FALLBACK_RESET_ID)); + + ut_assertok(reset_free(&ctl)); + + return 0; +} +DM_TEST(dm_test_reset_reset_fallback_path, UTF_SCAN_FDT); + +static int dm_test_reset_reset_bulk(struct unit_test_state *uts) +{ + struct udevice *dev_reset; + struct udevice *dev_test; + + ut_assertok(uclass_get_device_by_name(UCLASS_RESET, "reset-ctl", + &dev_reset)); + ut_asserteq(0, sandbox_reset_query(dev_reset, TEST_RESET_ID)); + ut_asserteq(0, sandbox_reset_query(dev_reset, OTHER_RESET_ID)); + + ut_assertok(uclass_get_device_by_name(UCLASS_MISC, "reset-ctl-test", + &dev_test)); + ut_assertok(sandbox_reset_test_get_bulk(dev_test)); + + /* Verify reset_count starts at 0 */ + ut_asserteq(0, sandbox_reset_get_count(dev_reset, TEST_RESET_ID)); + ut_asserteq(0, sandbox_reset_get_count(dev_reset, OTHER_RESET_ID)); + + ut_assertok(sandbox_reset_test_assert_bulk(dev_test)); + ut_asserteq(1, sandbox_reset_query(dev_reset, TEST_RESET_ID)); + ut_asserteq(1, sandbox_reset_query(dev_reset, OTHER_RESET_ID)); + + ut_assertok(sandbox_reset_test_reset_bulk(dev_test)); + + /* Verify resets were pulsed (counts incremented) */ + ut_asserteq(1, sandbox_reset_get_count(dev_reset, TEST_RESET_ID)); + ut_asserteq(1, sandbox_reset_get_count(dev_reset, OTHER_RESET_ID)); + ut_asserteq(0, sandbox_reset_query(dev_reset, TEST_RESET_ID)); + ut_asserteq(0, sandbox_reset_query(dev_reset, OTHER_RESET_ID)); + + ut_assertok(sandbox_reset_test_release_bulk(dev_test)); + + return 0; +} +DM_TEST(dm_test_reset_reset_bulk, UTF_SCAN_FDT); + static int dm_test_reset_bulk(struct unit_test_state *uts) { struct udevice *dev_reset; diff --git a/test/dm/sysinfo.c b/test/dm/sysinfo.c index 14ebe6b42e7..611f2e98d14 100644 --- a/test/dm/sysinfo.c +++ b/test/dm/sysinfo.c @@ -66,3 +66,19 @@ static int dm_test_sysinfo(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_sysinfo, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +static int dm_test_sysinfo_get_and_detect(struct unit_test_state *uts) +{ + struct udevice *sysinfo; + bool called_detect = false; + + ut_assertok(sysinfo_get_and_detect(&sysinfo)); + ut_assert(sysinfo); + + ut_assertok(sysinfo_get_bool(sysinfo, BOOL_CALLED_DETECT, + &called_detect)); + ut_assert(called_detect); + + return 0; +} +DM_TEST(dm_test_sysinfo_get_and_detect, UTF_SCAN_PDATA | UTF_SCAN_FDT); diff --git a/test/image/spl_load.c b/test/image/spl_load.c index 3b6206955d3..c43c977f784 100644 --- a/test/image/spl_load.c +++ b/test/image/spl_load.c @@ -368,6 +368,55 @@ SPL_IMG_TEST(spl_test_image, FIT_INTERNAL, 0); SPL_IMG_TEST(spl_test_image, FIT_EXTERNAL, 0); /* + * A FIT image's data-size property is not covered by the configuration + * signature, so it is untrusted input. load_simple_fit() must reject a + * data-size larger than the destination rather than overrun it, because the + * device read happens before the image hash is verified. + */ +static int spl_test_fit_external_oversize(struct unit_test_state *uts) +{ + size_t img_size, img_data, data_size = SPL_TEST_DATA_SIZE; + struct spl_image_info info_write = { + .name = "oversize", + .size = data_size, + }, info_read = { }; + struct spl_load_info load; + void *img; + int node; + + if (!image_supported(FIT_EXTERNAL)) + return -EAGAIN; + + img_size = create_image(NULL, FIT_EXTERNAL, &info_write, &img_data); + ut_assert(img_size); + img = calloc(img_size, 1); + ut_assertnonnull(img); + + generate_data(img + img_data, data_size, "oversize"); + ut_asserteq(img_size, create_image(img, FIT_EXTERNAL, &info_write, + NULL)); + + /* + * Inflate data-size far beyond the image buffer and any plausible + * load region. Without a bounds check, load_simple_fit() reads this + * many bytes off the "device" before the hash is checked. + */ + node = fdt_path_offset(img, FIT_IMAGES_PATH); + ut_assert(node >= 0); + node = fdt_first_subnode(img, node); + ut_assert(node >= 0); + ut_assertok(fdt_setprop_inplace_u32(img, node, FIT_DATA_SIZE_PROP, + 0x40000000)); + + spl_load_init(&load, spl_test_read, img, 1); + ut_asserteq(-EFBIG, spl_load_simple_fit(&info_read, &load, 0, img)); + + free(img); + return 0; +} +SPL_TEST(spl_test_fit_external_oversize, 0); + +/* * LZMA is too complex to generate on the fly, so let's use some data I put in * the oven^H^H^H^H compressed earlier */ diff --git a/test/image/spl_load_os.c b/test/image/spl_load_os.c index d17cf116a0e..ba9d7979a09 100644 --- a/test/image/spl_load_os.c +++ b/test/image/spl_load_os.c @@ -21,3 +21,14 @@ static int spl_test_load(struct unit_test_state *uts) } SPL_TEST(spl_test_load, 0); +static int spl_test_load_fit_full(struct unit_test_state *uts) +{ + struct spl_image_info image; + char fname[256]; + + ut_assertok(sandbox_spl_load_fit_full(fname, sizeof(fname), &image)); + + return 0; +} +SPL_TEST(spl_test_load_fit_full, 0); + diff --git a/test/lib/string.c b/test/lib/string.c index f56c2e4c946..d418a40c4d4 100644 --- a/test/lib/string.c +++ b/test/lib/string.c @@ -223,6 +223,40 @@ static int lib_memdup(struct unit_test_state *uts) } LIB_TEST(lib_memdup, 0); +/** lib_memdup_nul() - unit test for memdup_nul() */ +static int lib_memdup_nul(struct unit_test_state *uts) +{ + char buf[BUFLEN]; + size_t len; + char *p, *q; + + /* Zero size should return a buffer containing a single nul byte */ + p = memdup_nul(NULL, 0); + ut_assertnonnull(p); + ut_assert(p[0] == '\0'); + free(p); + + p = memdup_nul(buf, 0); + ut_assertnonnull(p); + ut_assert(p[0] == '\0'); + free(p); + + strcpy(buf, TEST_STR); + len = sizeof(TEST_STR); + p = memdup_nul(buf, len); + ut_asserteq_mem(p, buf, len); + ut_assert(p[len] == '\0'); + + q = memdup_nul(p, len); + ut_asserteq_mem(q, buf, len); + ut_assert(q[len] == '\0'); + free(q); + free(p); + + return 0; +} +LIB_TEST(lib_memdup_nul, 0); + /** lib_strnstr() - unit test for strnstr() */ static int lib_strnstr(struct unit_test_state *uts) { @@ -250,18 +284,36 @@ static int lib_strstr(struct unit_test_state *uts) { const char *s1 = "Itsy Bitsy Teenie Weenie"; const char *s2 = "eenie"; - const char *s3 = "easy"; + const char *s3 = "bits"; ut_asserteq_ptr(&s1[12], strstr(s1, s2)); ut_asserteq_ptr(&s1[13], strstr(&s1[3], &s2[1])); ut_assertnull(strstr(s1, s3)); - ut_asserteq_ptr(&s1[2], strstr(s1, &s3[2])); - ut_asserteq_ptr(&s1[8], strstr(&s1[5], &s3[2])); + ut_asserteq_ptr(&s1[1], strstr(s1, &s3[2])); + ut_asserteq_ptr(&s1[7], strstr(&s1[5], &s3[2])); return 0; } LIB_TEST(lib_strstr, 0); +/** lib_strcasestr() - unit test for strcasestr() */ +static int lib_strcasestr(struct unit_test_state *uts) +{ + const char *s1 = "Itsy Bitsy Teenie Weenie"; + const char *s2 = "eenie"; + const char *s3 = "bits"; + + ut_asserteq_ptr(&s1[12], strcasestr(s1, s2)); + ut_asserteq_ptr(&s1[13], strcasestr(&s1[3], &s2[1])); + ut_asserteq_ptr(&s1[5], strcasestr(s1, s3)); + ut_asserteq_ptr(&s1[1], strcasestr(s1, &s3[2])); + ut_asserteq_ptr(&s1[7], strcasestr(&s1[5], &s3[2])); + ut_assertnull(strcasestr(&s1[6], s3)); + + return 0; +} +LIB_TEST(lib_strcasestr, 0); + static int lib_strim(struct unit_test_state *uts) { char buf[BUFLEN], *p; diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index ee779474ce6..e3824b2c1fd 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -87,7 +87,7 @@ class FsHelper: """Remove created image""" if self.tmpdir: self.tmpdir.cleanup() - if self._do_cleanup: + if self._do_cleanup and self.fs_img: os.remove(self.fs_img) def __enter__(self): diff --git a/test/py/tests/test_fit.py b/test/py/tests/test_fit.py index 4f56a1421e1..76adb98e2c5 100755 --- a/test/py/tests/test_fit.py +++ b/test/py/tests/test_fit.py @@ -117,6 +117,36 @@ host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x ''' +# A minimal ITS for a compressed 'kernel_noload' kernel. bootm allocates a +# per-image decompression buffer for this image type, sized as a multiple of +# the compressed length; see the test_fit_kernel_noload_decomp_* tests. +NOLOAD_ITS = ''' +/dts-v1/; + +/ { + description = "FIT with a compressed kernel_noload image"; + #address-cells = <1>; + + images { + kernel-1 { + data = /incbin/("%(kernel)s"); + type = "kernel_noload"; + arch = "sandbox"; + os = "linux"; + compression = "gzip"; + load = <0>; + entry = <0>; + }; + }; + configurations { + default = "conf-1"; + conf-1 { + kernel = "kernel-1"; + }; + }; +}; +''' + @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('fit') @pytest.mark.requiredtool('dtc') @@ -426,3 +456,151 @@ class TestFitImage: output = ubman.run_command_list(cmds) assert "can't get kernel image!" in '\n'.join(output) + + def test_fit_iminfo_configs_first(self, ubman, fsetup): + """Regression: iminfo prints "Default Configuration" even when + /configurations is defined before /images in the source. + + fit_print_contents() in boot/image-fit.c used to read the default + configuration name from whatever offset libfdt happened to return + after iterating /images children. With /images defined first that + offset accidentally landed on /configurations; with /configurations + defined first the read returned NULL and the line silently went + missing. Fixed in commit "boot/fit: read default-config property + from the configurations node". + """ + configs_first_its = ''' +/dts-v1/; + +/ { + description = "FIT with /configurations before /images"; + #address-cells = <1>; + + configurations { + default = "conf-1"; + conf-1 { + description = "first config"; + kernel = "kernel-1"; + }; + }; + + images { + kernel-1 { + description = "first image"; + data = /incbin/("%(kernel)s"); + type = "kernel"; + arch = "sandbox"; + os = "linux"; + compression = "none"; + load = <0x40000>; + entry = <0x40000>; + }; + }; +}; +''' + fit = fit_util.make_fit(ubman, fsetup['mkimage'], configs_first_its, + fsetup, basename='configs-first.fit') + cmds = [ + 'host load hostfs 0 %#x %s' % (fsetup['fit_addr'], fit), + 'iminfo %#x' % fsetup['fit_addr'], + ] + output = '\n'.join(ubman.run_command_list(cmds)) + assert "Default Configuration: 'conf-1'" in output, ( + 'iminfo output is missing the "Default Configuration" line for a ' + 'FIT whose /configurations node precedes /images. Output was:\n' + + output) + + @pytest.mark.buildconfigspec('gzip') + def test_fit_kernel_noload_decomp_overflow(self, ubman, fsetup): + """Test that an over-large compressed kernel_noload image is rejected + + For a compressed 'kernel_noload' kernel, bootm_load_os() allocates a + decompression buffer of ALIGN(image_len * 8, SZ_1M) and must bound the + decompressor by that buffer. A kernel that decompresses to far more + than eight times its compressed size must therefore fail with a + decompression error instead of overflowing the buffer. + """ + sz_1m = 1 << 20 + + # CONFIG_SYS_BOOTM_LEN is the global decompression limit. Keep the + # uncompressed size below it, so the failure is forced by the smaller + # per-image kernel_noload buffer rather than by that global limit. + bootm_len = int(ubman.config.buildconfig['config_sys_bootm_len'], 0) + + # 4MB of zeros compresses to a few KB, so the decompression buffer + # (ALIGN(image_len * 8, SZ_1M), i.e. 1MB here) ends up far smaller + # than the uncompressed image. + decomp_size = 4 * sz_1m + kernel = fit_util.make_fname(ubman, 'test-noload-kernel.bin') + with open(kernel, 'wb') as fd: + fd.write(b'\0' * decomp_size) + kernel_gz = self.make_compressed(ubman, kernel) + + image_len = self.filesize(kernel_gz) + req_size = (image_len * 8 + sz_1m - 1) // sz_1m * sz_1m + assert req_size < decomp_size <= bootm_len, ( + 'Test setup error: need decomp buffer (%#x) < image (%#x) <= ' + 'CONFIG_SYS_BOOTM_LEN (%#x)' % (req_size, decomp_size, bootm_len)) + + fit = fit_util.make_fit(ubman, fsetup['mkimage'], NOLOAD_ITS, + {'kernel': kernel_gz}) + fit_addr = fsetup['fit_addr'] + + ubman.run_command_list([ + 'host load hostfs 0 %x %s' % (fit_addr, fit), + 'bootm start %x' % fit_addr, + ]) + + # 'bootm loados' decompresses the kernel. Decompression must stop at + # the buffer boundary and report 'Image too large'; it must not run + # past the buffer and return to the prompt. + ubman.run_command('bootm loados', wait_for_prompt=False) + try: + ubman.wait_for('Image too large') + finally: + # The decompression failure resets the board; bring up a fresh + # instance so later tests start from a clean console. + ubman.restart_uboot() + + @pytest.mark.buildconfigspec('gzip') + def test_fit_kernel_noload_decomp_boundary(self, ubman, fsetup): + """Test that decompression succeeds exactly at the buffer limit + + For a compressed 'kernel_noload' kernel, bootm_load_os() allocates a + decompression buffer of ALIGN(image_len * 8, SZ_1M). A kernel whose + decompressed size equals that buffer exactly must succeed, guarding + against an off-by-one rejection at the buffer limit. + """ + sz_1m = 1 << 20 + + # 1MiB of zeros compresses to a few KB, so image_len * 8 rounds up to + # exactly 1MiB. Picking decomp_size = 1MiB makes the decompressed size + # match the buffer exactly. + decomp_size = sz_1m + kernel = fit_util.make_fname(ubman, 'test-noload-kernel-boundary.bin') + with open(kernel, 'wb') as fd: + fd.write(b'\0' * decomp_size) + kernel_gz = self.make_compressed(ubman, kernel) + + image_len = self.filesize(kernel_gz) + req_size = (image_len * 8 + sz_1m - 1) // sz_1m * sz_1m + assert decomp_size == req_size, ( + 'Test setup error: need decomp_size (%#x) == req_size (%#x)' + % (decomp_size, req_size)) + + fit = fit_util.make_fit(ubman, fsetup['mkimage'], NOLOAD_ITS, + {'kernel': kernel_gz}, + basename='test-noload-boundary.fit') + fit_addr = fsetup['fit_addr'] + + # Decompression at the buffer limit must succeed, returning to the + # prompt cleanly and never printing 'Image too large'. + output = ubman.run_command_list([ + 'host load hostfs 0 %x %s' % (fit_addr, fit), + 'bootm start %x' % fit_addr, + 'bootm loados', + ]) + text = '\n'.join(output) + assert 'Image too large' not in text, ( + "'bootm loados' rejected a kernel_noload image whose decompressed " + 'size matches its buffer exactly: %s' % text) diff --git a/test/py/tests/test_fit_mkimage_validate.py b/test/py/tests/test_fit_mkimage_validate.py index 170b2a8cbbb..5922f071dd8 100644 --- a/test/py/tests/test_fit_mkimage_validate.py +++ b/test/py/tests/test_fit_mkimage_validate.py @@ -7,6 +7,7 @@ import os import subprocess import pytest import fit_util +import utils import re @pytest.mark.boardspec('sandbox') @@ -103,3 +104,59 @@ def test_fit_invalid_default_config(ubman): assert result.returncode != 0, "mkimage should fail due to missing default config" assert re.search(r"Default configuration '.*' not found under /configurations", result.stderr) + [email protected]('sandbox') [email protected]('dtc') [email protected]('fdtget') [email protected]('dtb_relpath,expected_desc', [ + # Crash triggers: last '.' precedes last '/', or leaf has no extension. + ('./mydt', 'mydt'), + ('./sub.d/leaf', 'leaf'), + ('./a.b/c', 'c'), + # Control case: extension lives in the leaf, no dotted directory. + ('./mydt.dtb', 'mydt'), +]) +def test_fit_auto_basename_dotted_directory(ubman, dtb_relpath, expected_desc): + """Regression test: mkimage -f auto must not crash when a -b path has a + '.' in its directory portion. + + Before the fix, get_basename() in tools/fit_image.c searched the whole + path for both the last '/' and the last '.'. When the '.' fell before + the '/', the computed length went negative and was passed unchanged to + memcpy(), which segfaulted. This test exercises three crashing paths + plus one control input. + """ + build_dir = ubman.config.build_dir + kernel = fit_util.make_kernel(ubman, 'kernel.bin', 'kernel') + itb_fname = fit_util.make_fname(ubman, 'auto_basename.itb') + + # Materialize the dtb at the requested relative path inside build_dir. + dtb_abs = os.path.join(build_dir, dtb_relpath) + os.makedirs(os.path.dirname(dtb_abs), exist_ok=True) + with open(dtb_abs, 'wb') as f: + f.write(b'dummy') + + cmd = ['./tools/mkimage', '-f', 'auto', + '-A', 'arm', '-O', 'linux', '-T', 'kernel', '-C', 'none', + '-a', '0x80000000', '-e', '0x80000000', '-n', 'test', + '-d', kernel, + '-b', dtb_relpath, + itb_fname] + # Run with cwd=build_dir so both ./tools/mkimage and the relative -b + # path resolve the same way the bug originally reproduced. + result = subprocess.run(cmd, capture_output=True, text=True, + cwd=build_dir) + + assert result.returncode == 0, ( + f"mkimage crashed or failed on -b {dtb_relpath!r}: " + f"rc={result.returncode}\nstdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" + ) + # The fdt sub-image description is set from get_basename(). Read it back + # from the produced FIT (a device tree) rather than parsing mkimage's + # console output. + desc = utils.run_and_log( + ubman, ['fdtget', itb_fname, '/images/fdt-1', 'description']).strip() + assert desc == expected_desc, ( + f"Expected /images/fdt-1 description {expected_desc!r}, got {desc!r}" + ) diff --git a/test/py/tests/test_fit_verity.py b/test/py/tests/test_fit_verity.py new file mode 100644 index 00000000000..f1b6262ed0e --- /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') diff --git a/test/py/tests/test_fs/test_erofs.py b/test/py/tests/test_fs/test_erofs.py index a2bb6b505f2..cec803256ac 100644 --- a/test/py/tests/test_fs/test_erofs.py +++ b/test/py/tests/test_fs/test_erofs.py @@ -6,19 +6,11 @@ import os import pytest import shutil import subprocess +import utils EROFS_SRC_DIR = 'erofs_src_dir' EROFS_IMAGE_NAME = 'erofs.img' -def generate_file(name, size): - """ - Generates a file filled with 'x'. - """ - content = 'x' * size - file = open(name, 'w') - file.write(content) - file.close() - def make_erofs_image(build_dir): """ Makes the EROFS images used for the test. @@ -36,15 +28,15 @@ def make_erofs_image(build_dir): os.makedirs(root) # 4096: uncompressed file - generate_file(os.path.join(root, 'f4096'), 4096) + utils.generate_file(os.path.join(root, 'f4096'), 4096) # 7812: Compressed file - generate_file(os.path.join(root, 'f7812'), 7812) + utils.generate_file(os.path.join(root, 'f7812'), 7812) # sub-directory with a single file inside subdir_path = os.path.join(root, 'subdir') os.makedirs(subdir_path) - generate_file(os.path.join(subdir_path, 'subdir-file'), 100) + utils.generate_file(os.path.join(subdir_path, 'subdir-file'), 100) # symlink os.symlink('subdir', os.path.join(root, 'symdir')) diff --git a/test/py/tests/test_fs/test_squashfs/sqfs_common.py b/test/py/tests/test_fs/test_squashfs/sqfs_common.py index d1621dcce3a..b366bde5f49 100644 --- a/test/py/tests/test_fs/test_squashfs/sqfs_common.py +++ b/test/py/tests/test_fs/test_squashfs/sqfs_common.py @@ -5,6 +5,7 @@ import os import shutil import subprocess +import utils """ standard test images table: Each table item is a key:value pair representing the output image name and its respective mksquashfs options. @@ -66,19 +67,6 @@ def init_standard_table(): for key, value in zip(STANDARD_TABLE.keys(), opts_list): STANDARD_TABLE[key] = value -def generate_file(file_name, file_size): - """ Generates a file filled with 'x'. - - Args: - file_name: the file's name. - file_size: the content's length and therefore the file size. - """ - content = 'x' * file_size - - file = open(file_name, 'w') - file.write(content) - file.close() - def generate_sqfs_src_dir(build_dir): """ Generates the source directory used to make the SquashFS images. @@ -107,20 +95,20 @@ def generate_sqfs_src_dir(build_dir): # 4096: minimum block size file_name = 'f4096' - generate_file(os.path.join(root, file_name), 4096) + utils.generate_file(os.path.join(root, file_name), 4096) # 5096: minimum block size + 1000 chars (fragment) file_name = 'f5096' - generate_file(os.path.join(root, file_name), 5096) + utils.generate_file(os.path.join(root, file_name), 5096) # 1000: less than minimum block size (fragment only) file_name = 'f1000' - generate_file(os.path.join(root, file_name), 1000) + utils.generate_file(os.path.join(root, file_name), 1000) # sub-directory with a single file inside subdir_path = os.path.join(root, 'subdir') os.makedirs(subdir_path) - generate_file(os.path.join(subdir_path, 'subdir-file'), 100) + utils.generate_file(os.path.join(subdir_path, 'subdir-file'), 100) # symlink (target: sub-directory) os.symlink('subdir', os.path.join(root, 'sym')) diff --git a/test/py/tests/test_source.py b/test/py/tests/test_source.py index 970d8c79869..29ab804f81b 100644 --- a/test/py/tests/test_source.py +++ b/test/py/tests/test_source.py @@ -34,3 +34,34 @@ def test_source(ubman): ubman.run_command('fdt rm /images default') assert 'Fail' in ubman.run_command('source || echo Fail') assert 'Fail' in ubman.run_command('source \\# || echo Fail') + [email protected]('sandbox') [email protected]('cmd_echo') [email protected]('cmd_source') [email protected]('fit') [email protected]('control_dtb_as_fit') +def test_source_control_dtb(ubman): + output = ubman.run_command('source ${fdtcontroladdr}') + assert '* default script' in output + + output = ubman.run_command('source ${fdtcontroladdr}:boot') + assert '* default script' in output + + output = ubman.run_command('source ${fdtcontroladdr}:outer') + assert '* outer 1' in output + assert '* inner' in output + assert '* outer 2' in output + + output = ubman.run_command('source ${fdtcontroladdr}:inner') + assert '* outer' not in output + assert '* inner' in output + + assert 'Fail' in ubman.run_command('source ${fdtcontroladdr}:no-such-script || echo Fail') + [email protected]('cmd_echo') [email protected]('cmd_source') [email protected]('fit') [email protected]('control_dtb_as_fit') +def test_source_reject_control_dtb(ubman): + assert 'Fail' in ubman.run_command('source ${fdtcontroladdr} || echo Fail') + assert 'Fail' in ubman.run_command('source ${fdtcontroladdr}:boot || echo Fail') diff --git a/test/py/tests/test_suite.py b/test/py/tests/test_suite.py index 7fe9a90dfd3..08285f12a5f 100644 --- a/test/py/tests/test_suite.py +++ b/test/py/tests/test_suite.py @@ -8,7 +8,7 @@ import re EXPECTED_SUITES = [ 'addrmap', 'bdinfo', 'bloblist', 'bootm', 'bootstd', 'cmd', 'common', 'dm', 'env', 'exit', 'fdt_overlay', - 'fdt', 'font', 'hush', 'lib', + 'fdt', 'font', 'hush', 'image_fdt', 'lib', 'loadm', 'log', 'mbr', 'measurement', 'mem', 'pci_mps', 'setexpr', 'upl', ] diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index dce5a37dd35..fa50c8008a5 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -631,7 +631,23 @@ def test_ut_dm_init_bootstd(ubman): ubman.restart_uboot() -def test_ut(ubman, ut_subtest): [email protected](name="ut_ubman") +def ut_ubman_fixture(ubman, ut_subtest): + """Fixture to restart the sandbox after known problematic tests. + + Args: + ubman (ConsoleBase): U-Boot console + ut_subtest (str): test to be executed via command ut, e.g 'foo bar' to + execute command 'ut foo bar' + """ + + yield ubman + + if ut_subtest in ("bootstd bootflow_cmd_boot", "bootstd bootflow_scan_boot"): + ubman.restart_uboot() + + +def test_ut(ut_ubman, ut_subtest): """Execute a "ut" subtest. The subtests are collected in function generate_ut_subtest() from linker @@ -644,18 +660,18 @@ def test_ut(ubman, ut_subtest): implemented in C function foo_test_bar(). Args: - ubman (ConsoleBase): U-Boot console + ut_ubman (ConsoleBase): U-Boot console ut_subtest (str): test to be executed via command ut, e.g 'foo bar' to execute command 'ut foo bar' """ if ut_subtest == 'hush hush_test_simple_dollar': # ut hush hush_test_simple_dollar prints "Unknown command" on purpose. - with ubman.disable_check('unknown_command'): - output = ubman.run_command('ut ' + ut_subtest) + with ut_ubman.disable_check('unknown_command'): + output = ut_ubman.run_command('ut ' + ut_subtest) assert 'Unknown command \'quux\' - try \'help\'' in output else: - output = ubman.run_command('ut ' + ut_subtest) + output = ut_ubman.run_command('ut ' + ut_subtest) assert output.endswith('failures: 0') lastline = output.splitlines()[-1] if "skipped: 0," not in lastline: diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py index 55518bed07e..4b6707caf70 100644 --- a/test/py/tests/test_vboot.py +++ b/test/py/tests/test_vboot.py @@ -415,6 +415,32 @@ def test_vboot(ubman, name, sha_algo, padding, sign_options, required, ubman, [fit_check_sign, '-f', fit, '-k', dtb], 1, 'Failed to verify required signature') + # Create a new properly signed fit and replace hashed-strings + # size property + make_fit('sign-configs-%s%s.its' % (sha_algo, padding), ubman, mkimage, dtc_args, datadir, fit) + sign_fit(sha_algo, sign_options) + utils.run_and_log(ubman, 'fdtput -t x %s %s hashed-strings 0' % + (fit, sig_node)) + run_bootm(sha_algo, 'Signed config with truncated hashed-strings', + 'Invalid hashed-strings property', False) + ubman.log.action('%s: Check truncated hashed-strings property' % sha_algo) + + # size_dt_strings is at offset 32 in the FDT header + with open(fit, 'rb') as handle: + handle.seek(32) + size_dt_strings = struct.unpack(">I", handle.read(4))[0] + utils.run_and_log(ubman, 'fdtput -t x %s %s hashed-strings 0 %#x' % + (fit, sig_node, size_dt_strings + 1)) + run_bootm(sha_algo, 'Signed config with overflowed hashed-strings size', + 'Strings region is out of bounds', False) + ubman.log.action('%s: Check overflowed hashed-strings size' % sha_algo) + + utils.run_and_log(ubman, 'fdtput -t x %s %s hashed-strings 0 %#x' % + (fit, sig_node, size_dt_strings)) + run_bootm(sha_algo, 'Signed config with in-bounds hashed-strings size', + 'Bad Data Hash', False) + ubman.log.action('%s: Check in-bounds hashed-strings size' % sha_algo) + def test_required_key(sha_algo, padding, sign_options): """Test verified boot with the given hash algorithm. @@ -563,6 +589,171 @@ def test_vboot(ubman, name, sha_algo, padding, sign_options, required, ubman.restart_uboot() [email protected]('sandbox') [email protected]('fit_signature') [email protected]('dtc') [email protected]('fdtput') [email protected]('openssl') +def test_vboot_ext_data_bounds(ubman): + """Test that malformed external-data properties are rejected. + + A signed FIT with external data exposes 'data-position', 'data-offset' and + 'data-size' properties. U-Boot must validate these before hashing the image + components, otherwise a crafted FIT could trigger an out-of-bounds access + during signature verification. + + These checks are independent of the hashing algorithm, so a single signing + configuration is enough. + + This works using sandbox only as it needs to update the device tree used + by U-Boot to hold public keys from the signing process. + """ + sha_algo = 'sha256' + + def run_bootm(test_type, expect_string): + """Run a 'bootm' command in U-Boot and expect it to fail. + + This always starts a fresh U-Boot instance since the device tree may + contain a new public key. + + Args: + test_type: A string identifying the test type. + expect_string: A string which is expected in the output. + """ + ubman.restart_uboot() + with ubman.log.section('Verified boot %s %s' % (sha_algo, test_type)): + output = ubman.run_command_list( + ['host load hostfs - 100 %s' % fit, + 'fdt addr 100', + 'bootm 100']) + assert expect_string in ''.join(output) + assert 'sandbox: continuing, as we cannot run' not in ''.join(output) + + def sign_fit(options): + """Sign the FIT + + Signs the FIT and writes the signature into it. It also writes the + public key into the dtb. + + Args: + options: Options to provide to mkimage. + """ + args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit] + if options: + args += options.split(' ') + ubman.log.action('%s: Sign images' % sha_algo) + utils.run_and_log(ubman, args) + + def create_rsa_pair(name): + """Generate a new RSA key pair and certificate. + + Args: + name: Name of the key (e.g. 'dev') + """ + public_exponent = 65537 + utils.run_and_log(ubman, 'openssl genpkey -algorithm RSA -out %s%s.key ' + '-pkeyopt rsa_keygen_bits:2048 ' + '-pkeyopt rsa_keygen_pubexp:%d' % + (tmpdir, name, public_exponent)) + + # Create a certificate containing the public key + utils.run_and_log(ubman, 'openssl req -batch -new -x509 -key %s%s.key ' + '-out %s%s.crt' % (tmpdir, name, tmpdir, name)) + + def set_external_data(prop, value): + """Set an external-data property of the kernel image. + + Args: + prop: Property name + value: The new value of the property + """ + utils.run_and_log( + ubman, 'fdtput -t x %s /images/kernel %s %#x' % (fit, prop, value) + ) + + def make_signed_fit(): + """Build a fresh signed FIT with external data. + + sign_fit() overwrites the FIT, so a new one is built before each test + case mutates its external-data properties. + """ + make_fit('sign-configs-%s.its' % sha_algo, ubman, mkimage, dtc_args, + datadir, fit) + sign_fit('-E') + + tmpdir = os.path.join(ubman.config.result_dir, 'ext-data-bounds') + '/' + if not os.path.exists(tmpdir): + os.mkdir(tmpdir) + datadir = ubman.config.source_dir + '/test/py/tests/vboot/' + fit = '%stest.fit' % tmpdir + mkimage = ubman.config.build_dir + '/tools/mkimage' + dtc_args = '-I dts -O dtb -i %s' % tmpdir + dtb = '%ssandbox-u-boot.dtb' % tmpdir + + bcfg = ubman.config.buildconfig + max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0) + + create_rsa_pair('dev') + + # Create a kernel image filled with zeroes + with open('%stest-kernel.bin' % tmpdir, 'wb') as fd: + fd.write(500 * b'\0') + + testcases = [ + ('negative data-position', + {'data-position': 0xffffffff}, 'Invalid external data position'), + ('negative data-offset', + {'data-offset': 0xffffffff}, 'Invalid external data offset'), + ('negative data-size', + {'data-size': 0xffffffff}, 'Invalid external data size'), + ('off-bounds data-position', + {'data-position': 0x7fffffff}, 'FIT external data is out of bounds'), + ('off-bounds data-offset', + {'data-offset': 0x10000000}, 'FIT external data is out of bounds'), + ('oversized data-size', + {'data-size': 0x7fffffff}, 'FIT external data is out of bounds'), + ('off-bounds data-position', + {'data-position': max_size + 1, 'data-size': 0}, + 'FIT external data is out of bounds'), + ('off-bounds data-offset', + {'data-offset': max_size + 1, 'data-size': 0}, + 'FIT external data is out of bounds'), + ('oversized data-size', + {'data-position': 0x0, 'data-size': max_size + 1}, + 'FIT external data is out of bounds'), + ('in-bounds data-position', + {'data-position': max_size, 'data-size': 0}, 'Bad Data Hash'), + ('in-bounds data-offset', + {'data-offset': max_size, 'data-size': 0}, 'Bad Data Hash'), + ('in-bounds data-size', + {'data-position': 0x0, 'data-size': max_size}, 'Bad Data Hash'), + ] + + # We need to use our own device tree file. Remember to restore it + # afterwards. + old_dtb = ubman.config.dtb + try: + ubman.config.dtb = dtb + + # Compile our device tree files for kernel and U-Boot. These are + # regenerated here since mkimage will modify them (by adding a + # public key) below. + dtc('sandbox-kernel.dts', ubman, dtc_args, datadir, tmpdir, dtb) + dtc('sandbox-u-boot.dts', ubman, dtc_args, datadir, tmpdir, dtb) + + ubman.log.action( + '%s: Test signed FIT with malformed external-data properties' % sha_algo) + for desc, props, expect_string in testcases: + make_signed_fit() + for prop, value in props.items(): + set_external_data(prop, value) + run_bootm('Signed config with %s' % desc, expect_string) + finally: + # Go back to the original U-Boot with the correct dtb. + ubman.config.dtb = old_dtb + ubman.restart_uboot() + + TESTDATA_IN = [ ['sha1-basic', 'sha1', '', None, False], ['sha1-pad', 'sha1', '', '-E -p 0x10000', False], diff --git a/test/py/utils.py b/test/py/utils.py index ca80e4b0b0a..e8971502509 100644 --- a/test/py/utils.py +++ b/test/py/utils.py @@ -51,6 +51,19 @@ def md5sum_file(fn, max_length=None): data = fh.read(*params) return md5sum_data(data) +def generate_file(file_name, file_size): + """ Generates a file filled with 'x'. + + Args: + file_name: the file's name. + file_size: the content's length and therefore the file size. + """ + content = 'x' * file_size + + file = open(file_name, 'w') + file.write(content) + file.close() + class PersistentRandomFile: """Generate and store information about a persistent file containing random data.""" @@ -80,7 +80,6 @@ export DTC=${DTC_DIR}/dtc TOOLS_DIR=build-sandbox_spl/tools run_test "binman" ./tools/binman/binman --toolpath ${TOOLS_DIR} test -run_test "patman" ./tools/patman/patman test run_test "u_boot_pylib" ./tools/u_boot_pylib/u_boot_pylib run_test "buildman" ./tools/buildman/buildman -t ${skip} |
