Merge branch 'next'
This commit is contained in:
@@ -14,7 +14,11 @@ 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
|
||||
|
||||
+12
-11
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Tests for FIT dm-verity cmdline generation
|
||||
*
|
||||
* Copyright 2026 Daniel Golle <daniel@makrotopia.org>
|
||||
*/
|
||||
|
||||
#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);
|
||||
@@ -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);
|
||||
+3
-4
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+107
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('fdtget')
|
||||
@pytest.mark.parametrize('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}"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright 2026 Daniel Golle <daniel@makrotopia.org>
|
||||
|
||||
"""
|
||||
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}',
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('fdtget')
|
||||
@pytest.mark.requiredtool('veritysetup')
|
||||
@pytest.mark.parametrize('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)
|
||||
|
||||
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('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')
|
||||
@@ -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'))
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('cmd_source')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.buildconfigspec('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')
|
||||
|
||||
@pytest.mark.buildconfigspec('cmd_echo')
|
||||
@pytest.mark.buildconfigspec('cmd_source')
|
||||
@pytest.mark.buildconfigspec('fit')
|
||||
@pytest.mark.notbuildconfigspec('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')
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
@@ -631,7 +631,23 @@ def test_ut_dm_init_bootstd(ubman):
|
||||
ubman.restart_uboot()
|
||||
|
||||
|
||||
def test_ut(ubman, ut_subtest):
|
||||
@pytest.fixture(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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
@pytest.mark.buildconfigspec('fit_signature')
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('fdtput')
|
||||
@pytest.mark.requiredtool('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],
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user