The SDP gadget lets SPL, and the sdp command, act as the i.MX boot
ROM's serial download mode: a host on the USB port writes files to
memory and jumps to them. On a part whose secure boot is fused closed,
the ROM only runs an image it has authenticated. The gadget does not
ask: SDP_JUMP_ADDRESS first calls the entry of any IVT it finds at the
given address, before the FIT, container and legacy paths that
authenticate, and SDP_WRITE_FILE and SDP_READ_REGISTER take any
address. A closed part that boots SPL with SPL_USB_SDP_SUPPORT will
therefore run unsigned code from the USB host, past the chain of trust
that HAB or AHAB set up. Separately, two ep0 requests take the length
of their data stage from the host's wLength alone, against a 4 KiB
buffer.

Patch 1 bounds every control request's data stage to the ep0 buffer in
composite_setup(), as Linux commit 36dfdf11af49 ("USB: gadget: detect
too-big endpoint 0 requests") and barebox do: an OUT request that is
too long is stalled, an IN request is shortened. This covers the SDP
report 2 SET_REPORT and the OS descriptor vendor request, which clears
and returns wLength bytes of the 4 KiB buffer.

Patches 2 and 3 fix the SDP data path on every part: a zero-length
report 2 packet made the copy length -1, a report 2 sent outside a
download was copied to wherever the last one ended, and the padding of
the last packet was copied past the announced length.

Patch 4 converts the command's address and count once instead of in
each command, with no change in behaviour. It makes room in SPL for
the checks of patch 6 on imx6dl_mamoj, whose SPL is close to its size
limit.

Patch 5 adds sdp_is_closed(), a weak hook (false by default) with
implementations for HAB parts (imx_hab_is_enabled()) and ELE AHAB parts
(OEM closed or locked lifecycle), and reports SDP_SECURITY_CLOSED as
the ROM does. Patch 6 then, on a closed part only, skips the raw IVT
jump, takes an image only by a path that authenticates it, refuses
READ_REGISTER, and receives and drops a WRITE_FILE that lies below
DRAM or touches the loader's own BSS, heaps, device tree, page tables
or stack. A legacy image is taken only when its header starts the
range that jump_to_image() authenticates, since its entry point comes
from that header. Refusals are answered with a failure in report 4 and
the gadget waits for the next command; where SPL searches for a FIT or
container header and finds none, the jump ends in the existing panic.
On an open part, and on every other platform, the SDP flows of
imx_usb_loader and uuu are unchanged: every new check sits behind
sdp_is_closed().

There is no sandbox UDC, and the closed paths need fused hardware, so
the series was checked by building the SPL SDP boards before and after
it with the toolchain CI uses (gcc 14.2), with and without the part's
secure boot option:

  defconfig        secure boot   SPL bytes before -> after
  imx6dl_mamoj     IMX_HAB (*)          58800 -> 59088
  apalis_imx6      -                    46928 -> 47224
  apalis_imx6      IMX_HAB              49864 -> 50208
  colibri_imx6     -                    43280 -> 43576
  colibri_imx6     IMX_HAB              46248 -> 46560
  imx8mm_evk       -                   152448 -> 152400
  imx8mm_evk       IMX_HAB             155224 -> 155704
  verdin-imx8mm    -                   156336 -> 156288
  verdin-imx8mm    IMX_HAB             159112 -> 159576
  imx952_evk       -                   125248 -> 125696
  imx952_evk       AHAB_BOOT           129088 -> 129520
  qcom_sdm845_spl  -                   242032 -> 242192

  (*) in its defconfig

All build without new warnings, and imx6dl_mamoj, apalis_imx6,
imx8mm_evk and imx952_evk build at patches 4 and 5 too. The SPL links
the weak sdp_is_closed() without the option and the i.MX one with it;
on the imx8mm boards, where LTO inlines it, the SDP code calls
imx_hab_is_enabled() only with IMX_HAB. The tightest are imx6dl_mamoj,
whose u-boot-spl.bin grows from 61017 to 61305 bytes against its limit
of 61344, and verdin-imx8mm with IMX_HAB, which keeps 168 bytes of its
SPL's on-chip RAM, from 632; this is also why the refusals print
nothing.

checkpatch flags the two #if in patch 6: they guard gd members that
only exist under those options, so IS_ENABLED() cannot replace them.

CI: the series passes all 91 jobs of U-Boot's Azure pipeline, in build
13725 of my CI-only pull request, on main e43c38e0021.
PR:    https://github.com/u-boot/u-boot/pull/1064
Build: https://dev.azure.com/u-boot/u-boot/_build/results?buildId=13725

Ana Bobillo (6):
  usb: gadget: composite: bound ep0 requests to the request buffer
  usb: gadget: f_sdp: drop an empty data packet
  usb: gadget: f_sdp: write report 2 data only within a download
  usb: gadget: f_sdp: decode the command's address and count once
  usb: gadget: f_sdp: report a closed part as the boot ROM does
  usb: gadget: f_sdp: refuse unauthenticated loads on a closed part

 arch/arm/mach-imx/ele_ahab.c   |  13 +++
 arch/arm/mach-imx/hab.c        |   7 ++
 drivers/usb/gadget/composite.c |  16 +++
 drivers/usb/gadget/f_sdp.c     | 175 +++++++++++++++++++++++++++++----
 include/sdp.h                  |  19 ++++
 5 files changed, 212 insertions(+), 18 deletions(-)


base-commit: e43c38e00218eae1a7b4a683c0fef8973d838fa3

Reply via email to