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

Reply via email to