From: Ana Bobillo <[email protected]>
On a part with secure boot fused closed, the i.MX boot ROM's serial
download mode only runs an image it has authenticated, and NXP treats a
ROM that reads out memory there as a vulnerability (CVE-2022-45163).
The SDP gadget in SPL takes the ROM's place in that chain but does none
of this, so a host on the USB port of a closed part can run any code it
sends:
- SDP_JUMP_ADDRESS first calls sdp_jump_imxheader(), which calls the
entry of any IVT it finds, unchecked, before the FIT, container and
legacy image paths that authenticate;
- SDP_WRITE_FILE writes anywhere, the running loader's own BSS, heap,
stack and page tables included, so that what it wrote runs before any
image is checked;
- SDP_READ_REGISTER reads any address.
When sdp_is_closed() says the part is closed:
- skip the raw IVT jump, and take an image only by a path that
authenticates it: a FIT with IMX_HAB (spl_load_simple_fit_fix_load()),
a container with AHAB_BOOT (spl_load_imx_container()), a legacy image
with IMX_HAB whose header starts the range the i.MX jump_to_image()
authenticates (else the entry point comes from an unsigned header).
A container on a HAB part, a FIT on an AHAB part and a script in
U-Boot proper are refused. Where SPL searches for a FIT or container
header and finds none, an IVT image on a closed part included, the
jump ends in the existing panic;
- receive and drop a WRITE_FILE that wraps, starts below DRAM
(CFG_SYS_SDRAM_BASE: the ROM, the peripherals, the on-chip RAM where
SPL runs) or touches this phase's BSS, malloc areas, device tree, page
tables or stack (once relocated: the stack up to ram_top);
- refuse READ_REGISTER.
Each refusal is answered with a failure in report 4, and the gadget
waits for the next command. Read-back of a download through
READ_REGISTER, as imx_usb_loader's verify option does, no longer works
on a closed part. An open part, and every other platform, behaves as
before: these checks only apply when sdp_is_closed() returns true.
Fixes: 5661f08a7144 ("usb: gadget: add SDP driver")
Fixes: 2c72ead73874 ("usb: gadget: f_sdp: Allow SPL to load and boot FIT via
SDP")
Signed-off-by: Ana Bobillo <[email protected]>
---
drivers/usb/gadget/f_sdp.c | 125 ++++++++++++++++++++++++++++++++++++-
1 file changed, 122 insertions(+), 3 deletions(-)
diff --git a/drivers/usb/gadget/f_sdp.c b/drivers/usb/gadget/f_sdp.c
index 8bd8f239766..7d12f0b8444 100644
--- a/drivers/usb/gadget/f_sdp.c
+++ b/drivers/usb/gadget/f_sdp.c
@@ -22,12 +22,16 @@
#include <log.h>
#include <malloc.h>
#include <linux/printk.h>
+#include <linux/sizes.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/composite.h>
+#include <config.h>
#include <asm/io.h>
+#include <asm/global_data.h>
+#include <asm/sections.h>
#include <g_dnl.h>
#include <sdp.h>
#include <spl.h>
@@ -36,6 +40,8 @@
#include <imx_container.h>
#include <watchdog.h>
+DECLARE_GLOBAL_DATA_PTR;
+
#define HID_REPORT_ID_MASK 0x000000ff
/*
@@ -69,6 +75,8 @@ struct hid_report {
#define SDP_WRITE_REGISTER_COMPLETE 0x128A8A12
#define SDP_SKIP_DCD_HEADER_COMPLETE 0x900DD009
#define SDP_ERROR_IMXHEADER 0x000a0533
+/* Status of a command refused on a closed part (HAB_FAILURE is 0x33) */
+#define SDP_ERROR_CLOSED 0x33333333
#define SDP_COMMAND_LEN 16
@@ -113,6 +121,7 @@ struct f_sdp {
u32 dnl_bytes;
u32 dnl_bytes_remaining;
u32 jmp_address;
+ bool dnl_discard;
bool always_send_status;
u32 error_status;
@@ -293,6 +302,61 @@ __weak bool sdp_is_closed(void)
return false;
}
+/*
+ * On a closed part the image is authenticated when the host jumps to it, so
+ * its download must not change what runs before then: it has to land in
+ * DRAM, clear of this phase's BSS, heaps, stack, device tree and page tables.
+ * SPL runs from on-chip RAM below DRAM; U-Boot proper runs from the top of
+ * DRAM, which the stack range covers there.
+ */
+static bool sdp_write_allowed(u32 addr, u32 cnt)
+{
+ const gd_t *g = gd;
+ ulong start = addr, end = start + cnt;
+ ulong sp = (ulong)&start;
+ ulong top = (ulong)g + sizeof(*g);
+ const void *fdt = g->fdt_blob;
+ int i;
+
+ if (!sdp_is_closed())
+ return true;
+
+#ifdef CFG_SYS_SDRAM_BASE
+ if (start < CFG_SYS_SDRAM_BASE || cnt > U32_MAX - addr)
+ return false;
+#else
+ return false;
+#endif
+
+ if (!IS_ENABLED(CONFIG_XPL_BUILD)) {
+ /* ram_top may be 4 GiB, which a 32-bit ulong holds as 0 */
+ top = (ulong)g->ram_top;
+ if (!top)
+ top = ULONG_MAX;
+ }
+
+ /* Stack (up to ram_top once relocated), BSS, heaps, DT, page tables */
+ ulong busy[][2] = {
+ { sp > SZ_64K ? sp - SZ_64K : 0, top },
+ { (ulong)__bss_start, (ulong)__bss_end },
+ { mem_malloc_start, mem_malloc_end },
+ { (ulong)fdt, fdt ? (ulong)fdt + fdt_totalsize(fdt) : 0 },
+#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
+ { g->malloc_base, g->malloc_base + g->malloc_limit },
+#endif
+#if defined(CONFIG_ARM) && \
+ !(CONFIG_IS_ENABLED(SYS_ICACHE_OFF) &&
CONFIG_IS_ENABLED(SYS_DCACHE_OFF))
+ { g->arch.tlb_addr, g->arch.tlb_addr + g->arch.tlb_size },
+#endif
+ };
+
+ for (i = 0; i < ARRAY_SIZE(busy); i++)
+ if (start < busy[i][1] && busy[i][0] < end)
+ return false;
+
+ return true;
+}
+
static void sdp_rx_command_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_sdp *sdp = req->context;
@@ -326,6 +390,14 @@ static void sdp_rx_command_complete(struct usb_ep *ep,
struct usb_request *req)
sdp->dnl_address = addr;
sdp->dnl_bytes_remaining = cnt;
sdp->next_state = SDP_STATE_TX_REGISTER;
+
+ /* Nothing is read out of a closed part */
+ if (sdp_is_closed()) {
+ sdp->error_status = SDP_ERROR_CLOSED;
+ sdp->next_state = SDP_STATE_IDLE;
+ break;
+ }
+
printf("Reading %d registers at 0x%08x... ",
sdp->dnl_bytes_remaining, sdp->dnl_address);
break;
@@ -339,6 +411,12 @@ static void sdp_rx_command_complete(struct usb_ep *ep,
struct usb_request *req)
sdp->dnl_bytes = sdp->dnl_bytes_remaining;
sdp->next_state = SDP_STATE_IDLE;
+ /* A refused download is received and dropped, then reported */
+ sdp->dnl_discard = !sdp_write_allowed(sdp->dnl_address,
+ sdp->dnl_bytes);
+ if (sdp->dnl_discard)
+ sdp->error_status = SDP_ERROR_CLOSED;
+
printf("Downloading file of size %d to 0x%08x... ",
sdp->dnl_bytes_remaining, sdp->dnl_address);
@@ -414,7 +492,7 @@ static void sdp_rx_data_complete(struct usb_ep *ep, struct
usb_request *req)
}
sdp->dnl_bytes_remaining -= datalen;
- if (sdp->state == SDP_STATE_RX_FILE_DATA_BUSY) {
+ if (sdp->state == SDP_STATE_RX_FILE_DATA_BUSY && !sdp->dnl_discard) {
memcpy(sdp_ptr(sdp->dnl_address), req->buf + 1, datalen);
sdp->dnl_address += datalen;
}
@@ -757,6 +835,23 @@ static u32 sdp_jump_imxheader(void *address)
return 0;
}
+/*
+ * On a closed part the ROM only runs an image it has authenticated: take an
+ * image only by a load path that authenticates it, else report the failure.
+ */
+static bool sdp_jump_refused(bool authenticated)
+{
+ if (authenticated || !sdp_is_closed())
+ return false;
+
+ debug("%s: refused on a closed part\n", __func__);
+ sdp_func->error_status = SDP_ERROR_CLOSED;
+ sdp_func->next_state = SDP_STATE_IDLE;
+ sdp_func->state = SDP_STATE_TX_STATUS;
+
+ return true;
+}
+
#ifdef CONFIG_XPL_BUILD
static ulong sdp_load_read(struct spl_load_info *load, ulong sector,
ulong count, void *buf)
@@ -842,7 +937,10 @@ static int sdp_handle_in_ep(struct spl_image_info
*spl_image,
break;
case SDP_STATE_JUMP:
printf("Jumping to header at 0x%08x\n", sdp_func->jmp_address);
- status = sdp_jump_imxheader(sdp_ptr(sdp_func->jmp_address));
+ /* An IVT entry is called as it is, unauthenticated */
+ status = SDP_ERROR_IMXHEADER;
+ if (!sdp_is_closed())
+ status =
sdp_jump_imxheader(sdp_ptr(sdp_func->jmp_address));
/* If imx header fails, try some U-Boot specific headers */
if (status) {
@@ -864,6 +962,10 @@ static int sdp_handle_in_ep(struct spl_image_info
*spl_image,
int ret;
debug("Found FIT\n");
+ /* spl_load_simple_fit_fix_load() authenticates
it */
+ if
(sdp_jump_refused(IS_ENABLED(CONFIG_IMX_HAB)))
+ return 0;
+
spl_load_init(&load, sdp_load_read, header, 1);
ret = spl_load_simple_fit(spl_image, &load, 0,
header);
@@ -878,6 +980,10 @@ static int sdp_handle_in_ep(struct spl_image_info
*spl_image,
struct spl_load_info load;
int ret;
+ /* spl_load_imx_container() authenticates it */
+ if
(sdp_jump_refused(IS_ENABLED(CONFIG_AHAB_BOOT)))
+ return 0;
+
spl_load_init(&load, sdp_load_read, header, 1);
ret = spl_load_imx_container(spl_image, &load,
0);
if (ret)
@@ -889,10 +995,23 @@ static int sdp_handle_in_ep(struct spl_image_info
*spl_image,
/* In SPL, allow jumps to U-Boot images */
struct spl_image_info spl_image = {};
struct spl_boot_device bootdev = {};
- spl_parse_image_header(&spl_image, &bootdev, header);
+ int err = spl_parse_image_header(&spl_image, &bootdev,
+ header);
+
+ /*
+ * The i.MX jump_to_image() authenticates from load_addr
+ * on, so the header is signed only if it lies there
+ */
+ if (sdp_jump_refused(IS_ENABLED(CONFIG_IMX_HAB) && !err
&&
+ spl_image.load_addr ==
sdp_func->jmp_address))
+ return 0;
spl_board_prepare_for_boot();
jump_to_image(&spl_image);
#else
+ /* Nothing authenticates a script */
+ if (sdp_jump_refused(false))
+ return 0;
+
/* In U-Boot, allow jumps to scripts */
cmd_source_script(sdp_func->jmp_address, NULL, NULL);
#endif