summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/boot/Makefile6
-rw-r--r--test/boot/bootdev.c23
-rw-r--r--test/boot/bootflow.c47
-rw-r--r--test/boot/bootstd_common.h5
-rw-r--r--test/boot/fit_verity.c306
-rw-r--r--test/boot/image_fdt.c83
-rw-r--r--test/boot/vbe_read_fit.c224
-rw-r--r--test/cmd/Makefile1
-rw-r--r--test/cmd/bdinfo.c7
-rw-r--r--test/cmd/config.c28
-rw-r--r--test/cmd/part.c2
-rw-r--r--test/cmd_ut.c4
-rw-r--r--test/dm/Makefile2
-rw-r--r--test/dm/acpi.c2
-rw-r--r--test/dm/net_defrag.c82
-rw-r--r--test/dm/pmbus.c242
-rw-r--r--test/dm/regulator.c38
-rw-r--r--test/dm/reset.c107
-rw-r--r--test/dm/sysinfo.c16
-rw-r--r--test/image/spl_load.c49
-rw-r--r--test/image/spl_load_os.c11
-rw-r--r--test/lib/string.c58
-rw-r--r--test/py/tests/fs_helper.py2
-rwxr-xr-xtest/py/tests/test_fit.py178
-rw-r--r--test/py/tests/test_fit_mkimage_validate.py57
-rw-r--r--test/py/tests/test_fit_verity.py175
-rw-r--r--test/py/tests/test_fs/test_erofs.py16
-rw-r--r--test/py/tests/test_fs/test_squashfs/sqfs_common.py22
-rw-r--r--test/py/tests/test_source.py31
-rw-r--r--test/py/tests/test_suite.py2
-rw-r--r--test/py/tests/test_ut.py26
-rw-r--r--test/py/tests/test_vboot.py191
-rw-r--r--test/py/utils.py13
-rwxr-xr-xtest/run1
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]('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]('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]('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]('cmd_echo')
[email protected]('cmd_source')
[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]('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]('fit_signature')
+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."""
diff --git a/test/run b/test/run
index 768b22577c4..7c0263713c6 100755
--- a/test/run
+++ b/test/run
@@ -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}