Merge branch 'next'

This commit is contained in:
Tom Rini
2026-07-06 18:26:12 -06:00
1206 changed files with 40578 additions and 25980 deletions
+4
View File
@@ -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
View File
@@ -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;
}
+47
View File
@@ -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 */
+4 -1
View File
@@ -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;
+306
View File
@@ -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);
+83
View File
@@ -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
View File
@@ -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));
}
}
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+16
View File
@@ -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);
+11
View File
@@ -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);
+34
View File
@@ -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)
{
+178
View File
@@ -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}"
)
+175
View File
@@ -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')
+4 -12
View File
@@ -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'))
+31
View File
@@ -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')
+1 -1
View File
@@ -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',
]
+21 -5
View File
@@ -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:
+191
View File
@@ -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],
+13
View File
@@ -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."""