Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain in uSDHC initialization and selection, FIT memory bounds, and DT fixups.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds an i.MX95 Cortex-A55 BL33 target that verifies signed Linux FIT images from SD and boots them at EL2.
Changes:
- Adds i.MX95 HAL, linker layout, configuration, documentation, and build integration.
- Adds a polled uSDHC SD-card driver with controller selection.
- Adds AArch64 handoff, cache/MMU teardown, DTB fixups, and crash diagnostics.
File summaries
| File | Summary and final findings |
|---|---|
src/boot_aarch64.c |
Invokes the fused EL2 handoff. |
src/boot_aarch64_start.S |
Implements startup, cache teardown, and crash vectors. Nit (1): Correct the reversed x0/x1 ABI comment. |
options.mk |
Adds selectable disk-controller configuration. |
Makefile |
Configures the target artifact build. |
hal/imx95_usdhc.c |
Implements uSDHC SD reads. Critical (3): CMD0 uses an incorrect response type. Critical (1): Required controller clocks are not enabled safely. Moderate (2): eMMC selects the SD-slot controller. Critical (1): Startup clocks are not emitted after reset. Moderate (1): 64-bit offsets are narrowed without overflow validation. |
hal/imx95_a55.ld |
Defines the BL33 memory layout and stack. |
hal/imx95_a55.h |
Defines i.MX95 hardware addresses and constants. |
hal/imx95_a55.c |
Provides platform HAL, FIT loading, cache handling, and DTB fixups. Moderate (3): Kernel and ramdisk cache-clean ranges do not cover permitted destinations and sizes. Moderate (2): /chosen lookup must be restricted to the root child. Moderate (3): DT fixup errors must abort the handoff. Moderate (1): /memory lookup must be restricted to the root child. |
docs/Targets.md |
Documents the new target. |
config/examples/imx95-a55.config |
Configures FIT boot and memory addresses. Moderate (1): The kernel-to-ramdisk gap is smaller than the default kernel allowance and can permit overlap. |
arch.mk |
Integrates target flags and drivers. Moderate (1): The SD-only driver is selected for eMMC configurations. |
Review details
Suppressed comments (5)
arch.mk:244
DISK_EMMC=1selects this object, buthal/imx95_usdhc.cis explicitly an SD-only protocol implementation (CMD8/ACMD41/ACMD6); an eMMC uses CMD1 and has no SD-card initialization. This configuration will link the driver and fail atdisk_init; reject eMMC or only select this object forDISK_SDCARD.
ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
DISK_DRIVER=usdhc
OBJS+=hal/imx95_usdhc.o
config/examples/imx95-a55.config:72
- This layout leaves only 0x08000000 (128 MiB) between the kernel destination 0xB2000000 and ramdisk 0xBA000000, but
fit_load_image()accepts the default 256 MiB kernel output. A valid signed FIT with a kernel larger than 128 MiB will be overwritten when the ramdisk is loaded. Cap the kernel to the available gap or validate non-overlapping actual ranges before loading.
# Linux FIT boot: kernel at 0xB2000000, ramdisk at 0xBA000000 - both above
# the staging window so the copies cannot overlap the staged FIT.
FIT_RAMDISK?=1
WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0xBA000000
hal/imx95_a55.c:334
- This whole-tree
device_typesearch can select a nested memory node and update itsreg, leaving the root/memorynode absent or unchanged. Since this fixup is specifically for/memory, use the direct root-child lookup before creating it.
off = fdt_find_devtype(&ctx, -1, "memory");
hal/imx95_usdhc.c:424
disk_read()receives a 64-bit byte offset, butlbais narrowed touint32_tbefore being used as the SD command argument. A GPT partition at or above 2 TiB therefore wraps to a different sector instead of failing;src/sdhci.cexplicitly rejects this case. Keep the calculation 64-bit until validating the controller's 32-bit LBA limit, then return an error on overflow.
uint32_t lba, off, chunk, blocks;
src/boot_aarch64_start.S:1759
- The ABI comment says
x0 = dts, but the caller passesentryin x0 anddtsin x1; the implementation itself copies those values into x20/x21 in that order. Keeping the reversed documentation makes future callers liable to branch to the DTB or pass the kernel entry as x0.
/* el2_flush_disable_mmu_and_boot(entry, dts): fused flush + MMU-off + jump
* (x0 = dts, x1-x3 zeroed) touching NO memory after the disable. With the
* D-cache off, a stack reload of entry/dts between a flush call and a
* separate C jump can read stale DRAM (set/way misses a DSU system cache).
* The set/way loop clobbers x0-x11 only; x20/x21 carry the values. */
- Files reviewed: 11/11 changed files
- Comments generated: 8
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
793b63a to
869ae18
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The new uSDHC disk_read() truncates 64-bit byte offsets into 32-bit command arguments without bounds checks, which can wrap and read incorrect sectors on attacker-controlled partition tables.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 1
- Review effort level: Lite
30f6cee to
5a630fc
Compare
de4d669 to
1d594b4
Compare
7f04921 to
13b3bf5
Compare
c0af17e to
0a90e9f
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical destination-protection and BL31 handoff issues remain unresolved.
Review effort: Lite
Findings: 4
Open (5)
Image destinations can overwrite the live FIT staging buffer · New Compressed image decompression bypasses destination validation · New Destination checks omit the OP-TEE shared memory region · New BL31 is called without the required entrypoint parameters · New Fixed 1024-byte read rejects valid distant signature blocks · New
Resolved since last review (2)
2c03ae9 to
7a73c95
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
FIT bounds can be disabled unintentionally, ELE release failures are not fail-closed, and documented build options are not propagated.
Review effort: Balanced
Findings: 2
Open (5)
Preserve FIT destination checks when LINUX_BOOTARGS is omitted · New Abort boot when ELE authentication context release fails · New Pass IMX95_LOG_RING_BASE to the compiler · New Forward IMX95 stage1 passthrough and overlap build options · New Expand test inputs to exercise oversized read chunking · New
|
|
||
| #### What stage 1 authenticates | ||
|
|
||
| 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: |
There was a problem hiding this comment.
From the PR message:
The stage 1 booted the same way before the ELE authentication work, but has not been re-validated since and the enclave calls have not run on silicon
Could it be possible to test this?
| * | ||
| * wolfBoot is free software; you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation; either version 2 of the License, or |
| * | ||
| * wolfBoot is free software; you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation; either version 2 of the License, or |



wolfBoot runs on the i.MX95 Cortex-A55 in two positions. As BL33 it is entered by BL31 in NS-EL2, verifies a Linux FIT (kernel, DTB, initramfs) with ML-DSA-87 and boots it at EL2. As a stage 1 it takes the place of U-Boot SPL in AHAB container 0, where the boot ROM loads it into OCRAM; it brings up the boot device, walks the container set, has the EdgeLock Enclave authenticate the next container and loads BL31, OP-TEE and BL33 out of it. The ELE firmware, the M33 System Manager and the Optional Executable Image (OEI) that trains DDR are untouched and still the SoC's own.
What it adds
hal/imx95_a55.{c,h,ld}- HAL, memory map and linker script. Also carries the LPUART1 console, the SCMI-over-MU client for the clocks, pinmux and power domains the System Manager owns, and the uSDHC driver for the carrier SD and the on-module eMMC including its boot partitions; the i.MX uSDHC is not SDHCI-register-compatible, sosrc/sdhci.cdoes not apply. All three are A55-only and shared between the two positions, so the BL33-only HAL API is gated onBUILD_LOADER_STAGE1the way the PPC targets share one HALhal/imx95_ahab.{c,h}- AHAB container-set parser, and the destination bounds stage 1 applies to every image before it writes any of them. Unit tests intools/unit-tests/:unit-imx95-ahab.cfor the parser and the bounds,unit-imx95-ele.cfor the enclave mailbox transport andunit-imx95-disk.cfor the byte-addresseddisk_read(), each driving the shipped code against emulated hardware by extracting it rather than copying ithal/imx95_a55_stage1.{c,ld},hal/imx95_a55_stage1_start.S- the stage 1 and its OCRAM entry.IMX95_AHAB_AUTH, on by default, issues the container-authenticate, per-image verify and release calls to the ELE that SPL used to make;docs/Targets.mdcovers it and the bring-up switches that are refused alongside it. The mailbox wait is bounded by a loop count as well as by a deadline, because nothing below BL31 guarantees the system counter is running and a deadline it never reaches is not a bound, and the register counts the mailbox reports are range-checked before they index itconfig/examples/imx95-a55.configand CI entries for the target and both stage-1 variantsTouches shared code
src/boot_aarch64_start.S- identity translation tables and an EL2 MMU enable for this target, so DRAM is mapped Normal cacheable and the wolfCrypt ARM assembly is usable; the carveout shared with the Cortex-M7 is mapped Normal Non-Cacheable because a core outside this cluster's coherency reads itsrc/boot_aarch64_cache.S- the set/way data cache clean moves out ofboot_aarch64_start.Sinto its own translation unit, so stage 1 can call it rather than carry a second copy of it; stage 1 links none of the rest of that filesrc/boot_aarch64.c- a device tree whose fixup fails no longer boots unpatched, because Linux may then lack its memory node. Enforced only when a tree is actually present, so a payload that passes none is unaffected -update_ram.chandsdo_boot()a NULL there, and a target whosehal_dts_fixup()is real reports failure on itstage1/Makefile- build freestanding and without the C runtime startup files. Without-ffreestandingthe compiler recognizes the byte-scan loop insrc/string.cas thestrlenidiom and rewrites it into a call tostrlenitself, sostrlenbecomes a branch to itselfhal/imx95_m7.{c,h}- do not issue a cache clean by address while the D-cache is disabled. The Cortex-M7 performs maintenance by address regardless, and out of a cold reset the cache RAMs hold random tags and dirty bits, so a hit writes to an arbitrary address. Its microsecond timer accumulates DWT cycles rather than converting before the caller subtracts, so a counter wrap yields the elapsed interval instead of a nonsense onetools/unit-tests/Makefile- the extraction-rule guards exit on a missing entry point rather than returning false inside a loop, where only the last item in the list could fail the build, and they match the call syntax so a renamed function no longer satisfies a check for its old nameHardware / test status
Validated on a Toradex SMARC i.MX95 (LPUART1, 115200 8N1). As BL33 it boots cold to Torizon Linux userspace over repeated power cycles, with the FIT on the carrier SD and the boot containers in an eMMC boot partition. The uSDHC driver is also exercised against the state the boot ROM leaves behind, which differs from the state U-Boot leaves: the ROM reads the boot partition in the controller's fast-boot mode with HS400 tuning applied, and
SYS_CTRL_RSTAclears none of it. The stage 1 booted the same way before the ELE authentication work, but has not been re-validated since and the enclave calls have not run on silicon: the module's serial console is currently unreachable, so a stage that stops early reports nothing. What stands behind it instead is the container parser replayed against the set this board boots, and unit tests over the parser, the destination bounds and the mailbox transport.Scope
The uSDHC driver is PIO; ADMA2 is follow-on work. Authenticating the container set 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 set would authenticate happily. That wants
sw_version/fuse_versionpolicy or the AHAB monotonic counter. Secondary and fallback container sets are not handled either - if the ROM booted set B, stage 1 still walks set A.