Skip to content

Commit 5df9e95

Browse files
committed
imx95_a55: have stage 1 authenticate the container set it loads
1 parent 5854428 commit 5df9e95

9 files changed

Lines changed: 842 additions & 74 deletions

File tree

‎config/examples/imx95-a55.config‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
# ROM -> ELE -> SM -> OEI -> SPL [c1] -> BL31 -> OP-TEE -> wolfBoot -> Linux
33
# wolfBoot takes U-Boot's slot in AHAB container 2 at 0x90200000; the other
44
# container images are the vendor's, reused verbatim.
5+
#
6+
# `make stage1` from this same config also replaces SPL, and then the chain is
7+
# ROM -> ELE -> SM -> OEI -> wolfBoot [c1] -> BL31 -> OP-TEE -> wolfBoot
8+
# with stage 1 making the ELE call that authenticates container 2 that SPL used
9+
# to make (IMX95_AHAB_AUTH, on by default). See docs/Targets.md.
510

611
ARCH?=AARCH64
712
TARGET?=imx95_a55

‎docs/Targets.md‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9859,7 +9859,14 @@ Both caches are enabled by `hal_init()`, which matters because verifying an imag
98599859

98609860
## NXP i.MX95 Cortex-A55 (BL33)
98619861

9862-
wolfBoot replaces U-Boot proper on the i.MX95's Cortex-A55 cluster: it is the third image in AHAB container 2 (after BL31 and OP-TEE), entered by BL31 at `0x90200000` in NS-EL2, where it verifies a Linux FIT (kernel + DTB + initramfs) with ML-DSA-87 and boots it at EL2. SPL/ELE authenticate the container itself (AHAB), so the chain is ROM -> ELE -> SPL -> BL31 -> wolfBoot -> Linux once SRK fuses are programmed. NXP also documents an OEM PQC SRK hybrid (ML-DSA) AHAB flow, so the container half of the chain can be post-quantum as well.
9862+
wolfBoot replaces U-Boot proper on the i.MX95's Cortex-A55 cluster: it is the third image in AHAB container 2 (after BL31 and OP-TEE), entered by BL31 at `0x90200000` in NS-EL2, where it verifies a Linux FIT (kernel + DTB + initramfs) with ML-DSA-87 and boots it at EL2. NXP also documents an OEM PQC SRK hybrid (ML-DSA) AHAB flow, so the container half of the chain can be post-quantum as well.
9863+
9864+
Which component authenticates the containers depends on which slot wolfBoot occupies, and the two cases are not the same:
9865+
9866+
- **wolfBoot as BL33 only** (this section). U-Boot SPL is still in container 0 and still calls the ELE to authenticate container 2 before BL31 runs, so the chain is ROM -> ELE -> SPL -> BL31 -> wolfBoot -> Linux once SRK fuses are programmed.
9867+
- **wolfBoot as stage 1 as well** (next section). SPL is gone, so wolfBoot performs that ELE call itself. `IMX95_AHAB_AUTH` is what does it, and it is on by default; the chain becomes ROM -> ELE -> wolfBoot stage 1 -> BL31 -> wolfBoot BL33 -> Linux.
9868+
9869+
In both cases the ROM and the ELE authenticate container 0 before any A55 code runs, so whichever image sits in the SPL slot is itself verified.
98639870

98649871
Validated on a Toradex SMARC iMX95 with `TARGET=imx95_a55` (`config/examples/imx95-a55.config`): full boot to Linux userspace with the FIT on the carrier SD (uSDHC2, `hal/imx95_usdhc.c` - i.MX uSDHC, not SDHCI-compatible).
98659872

@@ -9884,6 +9891,23 @@ DDR is up before stage 1 runs, because the OEI did it, so stage 1 only needs the
98849891

98859892
Two things differ from the BL33 build. The console is programmed rather than inherited, since nothing has configured LPUART1 yet. And on eMMC the containers live in a boot partition, not the user area: `imx95_emmc_boot_partition()` reads back the same `PARTITION_CONFIG` field that told the ROM which one to load from, and reads follow it.
98869893

9894+
#### What stage 1 authenticates
9895+
9896+
Taking SPL's slot means taking over the step SPL performed. The boot ROM and the ELE authenticate container 0, so stage 1 is verified code, but the container it goes on to load BL31, OP-TEE and BL33 out of is a separate one that nothing has checked yet. `IMX95_AHAB_AUTH=1` (the default) makes stage 1 ask the ELE to check it, the same three calls U-Boot's SPL makes:
9897+
9898+
1. The container's header, image table and signature block are staged into DDR at `0x90000000` - NXP's own `IMG_CONTAINER_BASE` for this SoC - because the ELE reads the address it is given over its own master port and requires DDR. Every value stage 1 then acts on comes from those staged bytes; the medium is not consulted for the table again.
9899+
2. `ELE_OEM_CNTN_AUTH_REQ` authenticates the header.
9900+
3. Each image is loaded to its destination and then checked in place with `ELE_VERIFY_IMAGE_REQ`, one at a time so a failure names the image. `ELE_RELEASE_CONTAINER_REQ` follows, before the jump.
9901+
9902+
Any failure stops the boot. `IMX95_AHAB_AUTH=0` builds without it, for bring-up on a part whose containers are not signed; it cannot be combined with `IMX95_STAGE1_PASSTHROUGH` or `IMX95_STAGE1_ALLOW_SELF_OVERLAP`, both of which would carry on past a refusal, and the build fails if you try.
9903+
9904+
Separately, and regardless of that flag, stage 1 bounds every destination *before* writing any of them. An image is streamed straight from the boot device into the address the container names, so a check made afterwards would come too late to matter: by then the bytes have landed. Destinations must lie inside DDR, must not overlap the staging window, this loader, the M7 carveout, the ELE shared-memory region or the VPU boot area, must not overlap each other once rounded up to the block size, and image 0 must be an A55 executable whose entry point lies inside its own bytes.
9905+
9906+
Two limits are worth stating plainly:
9907+
9908+
- **On an open part AHAB reports success without enforcing a signature.** That is why an unsigned container boots at all. What authentication buys there is an ELE-checked hash of each image against a table nobody signed - integrity, not authenticity. Stage 1 prints the lifecycle on every boot so the difference is visible rather than implied. Enforcement begins when the SRK hash is fused; see the i.MX8QuadMax AHAB section above for what that costs and why it is irreversible.
9909+
- **Authenticating the container does not prevent rollback.** The offset of the next container is derived by walking container 0, which stage 1 reads off the medium and nothing re-authenticates, so a different but validly signed older container set would authenticate happily. That needs `sw_version`/`fuse_version` policy or the AHAB monotonic counter, neither of which is implemented here.
9910+
98879911
## TI C2000 C28x (LAUNCHXL-F28P55X)
98889912

98899913
wolfBoot runs on the Texas Instruments C2000 C28x DSP (TMS320F28P550SJ, 150 MHz) as a secure execute-in-place (XIP) bootloader. The C28x is word-addressed with `CHAR_BIT == 16` (no 8-bit type -- each octet occupies one 16-bit cell), built with the TI `cl2000` toolchain against wolfSSL's wide-byte (`CHAR_BIT != 8`) support.

‎hal/imx95_a55.h‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@
5252
#define IMX95_DRAM_SIZE 0x70000000UL /* 1.75 GiB, bank 0 */
5353
#define IMX95_DRAM_END (IMX95_DRAM_BASE + IMX95_DRAM_SIZE)
5454

55+
/* All of DDR bank 0, including the low carveouts. IMX95_DRAM_BASE above is
56+
* where wolfBoot itself may allocate; container images legitimately land below
57+
* it - BL31 at 0x8A200000 and OP-TEE at 0x8C000000 both do - so the bound a
58+
* stage 1 applies to an image destination is this wider one. */
59+
#define IMX95_DDR_BASE 0x80000000UL
60+
#define IMX95_DDR_SIZE (IMX95_DRAM_END - IMX95_DDR_BASE)
61+
62+
/* Where the container is staged for the ELE to authenticate. The enclave reads
63+
* the address it is given over its own master port and it must be in DDR, which
64+
* is why the header cannot simply be authenticated where stage 1 parsed it in
65+
* OCRAM. This is NXP's own IMG_CONTAINER_BASE for this SoC (CFG_SYS_SDRAM_BASE),
66+
* so it is the window every stock SPL boot already uses, and it sits in the
67+
* 2 MiB hole below BL33. Nothing may be loaded over it while an authentication
68+
* is outstanding: the ELE keeps reading these bytes until the container is
69+
* released. */
70+
#define IMX95_AHAB_STAGE_BASE 0x90000000UL
71+
#define IMX95_AHAB_STAGE_SIZE 0x00010000UL /* 64 KiB, the header's max span */
72+
5573
/* Reserved windows inside bank 0 that staging must not overlap. */
5674
#define IMX95_M7_DDR_BASE 0x80000000UL
5775
#define IMX95_M7_DDR_SIZE 0x01000000UL /* 16 MiB */

0 commit comments

Comments
 (0)