diff options
| -rw-r--r-- | test/boot/Makefile | 2 | ||||
| -rw-r--r-- | test/boot/vbe_read_fit.c | 224 |
2 files changed, 225 insertions, 1 deletions
diff --git a/test/boot/Makefile b/test/boot/Makefile index 89538d4f0a6..b8e5f64ac82 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -18,7 +18,7 @@ endif obj-$(CONFIG_MEASURED_BOOT) += measurement.o ifdef CONFIG_OF_LIVE -obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o +obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o vbe_read_fit.o endif obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o diff --git a/test/boot/vbe_read_fit.c b/test/boot/vbe_read_fit.c new file mode 100644 index 00000000000..f67de0e7165 --- /dev/null +++ b/test/boot/vbe_read_fit.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bounds-check tests for vbe_read_fit() + * + * vbe_read_fit() pulls a firmware-phase FIT from a trusted firmware area + * on a block device. The external-data location and size carried in the + * FIT image node are attacker-controllable when the firmware area is on + * mutable boot media, so vbe_read_fit() must reject FITs whose external + * data extends past @area_size before issuing the follow-up blk_read(). + * + * These tests build small synthetic FITs with deliberately out-of-range + * values and confirm vbe_read_fit() returns -E2BIG for each. + * + * Copyright 2026 Canonical Ltd. + * Written by Aristo Chen <[email protected]> + */ + +#include <blk.h> +#include <dm.h> +#include <image.h> +#include <memalign.h> +#include <mmc.h> +#include <test/test.h> +#include <test/ut.h> +#include <linux/libfdt.h> +#include "bootstd_common.h" +#include "../../boot/vbe_common.h" + +/* + * The synthetic FIT is written to mmc1 starting at block TEST_FIT_BLK. + * bootstd_setup_for_tests() uses blocks 4 and 6 (see bootstd_common.h); + * block 16 leaves a comfortable gap. + */ +#define TEST_FIT_BLK 16 +#define TEST_FIT_OFF ((ulong)TEST_FIT_BLK * MMC_MAX_BLOCK_LEN) +#define TEST_AREA_SIZE 0x1000 + +/** + * build_fit() - Build a minimal external-data FIT for vbe_read_fit() + * + * The FIT advertises a single firmware image whose @data-position and + * @data-size are passed in directly. Both values are attacker-controlled + * in the real threat model. + * + * @buf: Destination buffer (must be at least 512 bytes) + * @buf_size: Size of @buf + * @data_position: Value written to the image's data-position property + * @data_size: Value written to the image's data-size property + * Returns: 0 on success, libfdt error otherwise + */ +static int build_fit(void *buf, size_t buf_size, u32 data_position, + u32 data_size) +{ + int ret; + + ret = fdt_create(buf, buf_size); + if (ret) + return ret; + ret = fdt_finish_reservemap(buf); + if (ret) + return ret; + + ret = fdt_begin_node(buf, ""); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_DESC_PROP, "vbe-read-fit test"); + if (ret) + return ret; + ret = fdt_property_u32(buf, FIT_TIMESTAMP_PROP, 0); + if (ret) + return ret; + + ret = fdt_begin_node(buf, "images"); + if (ret) + return ret; + ret = fdt_begin_node(buf, "u-boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_DESC_PROP, "U-Boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_TYPE_PROP, "firmware"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_ARCH_PROP, "sandbox"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_OS_PROP, "u-boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_PHASE_PROP, "u-boot"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_COMP_PROP, "none"); + if (ret) + return ret; + ret = fdt_property_u32(buf, FIT_DATA_POSITION_PROP, data_position); + if (ret) + return ret; + ret = fdt_property_u32(buf, FIT_DATA_SIZE_PROP, data_size); + if (ret) + return ret; + ret = fdt_end_node(buf); /* u-boot */ + if (ret) + return ret; + ret = fdt_end_node(buf); /* images */ + if (ret) + return ret; + + ret = fdt_begin_node(buf, "configurations"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_DEFAULT_PROP, "conf-1"); + if (ret) + return ret; + ret = fdt_begin_node(buf, "conf-1"); + if (ret) + return ret; + ret = fdt_property_string(buf, "compatible", "sandbox"); + if (ret) + return ret; + ret = fdt_property_string(buf, FIT_FIRMWARE_PROP, "u-boot"); + if (ret) + return ret; + ret = fdt_end_node(buf); /* conf-1 */ + if (ret) + return ret; + ret = fdt_end_node(buf); /* configurations */ + if (ret) + return ret; + + ret = fdt_end_node(buf); /* root */ + if (ret) + return ret; + + return fdt_finish(buf); +} + +/** + * place_fit_on_mmc() - Write a synthetic FIT to mmc1 and return its blk dev + * + * @uts: Unit test state + * @fit: FIT image to write + * @blkp: On success, receives the block udevice for mmc1 + * Returns: 0 on success, -ve on error + */ +static int place_fit_on_mmc(struct unit_test_state *uts, const void *fit, + struct udevice **blkp) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, blkbuf, MMC_MAX_BLOCK_LEN); + struct udevice *mmc; + struct blk_desc *desc; + size_t fit_size = fdt_totalsize(fit); + size_t pos; + int blknum = TEST_FIT_BLK; + + ut_assertok(uclass_get_device(UCLASS_MMC, 1, &mmc)); + desc = blk_get_by_device(mmc); + if (!desc) + return log_msg_ret("desc", -ENODEV); + + for (pos = 0; pos < fit_size; pos += MMC_MAX_BLOCK_LEN, blknum++) { + size_t this_blk = min(fit_size - pos, + (size_t)MMC_MAX_BLOCK_LEN); + + memset(blkbuf, '\0', MMC_MAX_BLOCK_LEN); + memcpy(blkbuf, (const u8 *)fit + pos, this_blk); + if (blk_dwrite(desc, blknum, 1, blkbuf) != 1) + return log_msg_ret("wr", -EIO); + } + *blkp = desc->bdev; + + return 0; +} + +/* + * data-position points past area_size: vbe_read_fit() must reject the + * FIT with -E2BIG before issuing the external-data blk_read(). + */ +static int vbe_read_fit_oob_position(struct unit_test_state *uts) +{ + u8 fit[1024] __aligned(8); + struct udevice *blk; + ulong load_addr = 0, len = 0; + char *name = NULL; + int ret; + + ut_assertok(build_fit(fit, sizeof(fit), + TEST_AREA_SIZE + 0x10, 0x40)); + ut_assertok(place_fit_on_mmc(uts, fit, &blk)); + + ret = vbe_read_fit(blk, TEST_FIT_OFF, TEST_AREA_SIZE, + NULL, &load_addr, &len, &name); + ut_asserteq(-E2BIG, ret); + + return 0; +} + +BOOTSTD_TEST(vbe_read_fit_oob_position, UTF_DM | UTF_SCAN_FDT); + +/* + * data-position is inside the area but data-size pushes the end past + * area_size: vbe_read_fit() must reject the FIT with -E2BIG. + */ +static int vbe_read_fit_oversize_data(struct unit_test_state *uts) +{ + u8 fit[1024] __aligned(8); + struct udevice *blk; + ulong load_addr = 0, len = 0; + char *name = NULL; + int ret; + + ut_assertok(build_fit(fit, sizeof(fit), + 0x400, TEST_AREA_SIZE)); + ut_assertok(place_fit_on_mmc(uts, fit, &blk)); + + ret = vbe_read_fit(blk, TEST_FIT_OFF, TEST_AREA_SIZE, + NULL, &load_addr, &len, &name); + ut_asserteq(-E2BIG, ret); + + return 0; +} + +BOOTSTD_TEST(vbe_read_fit_oversize_data, UTF_DM | UTF_SCAN_FDT); |
