The CTR counter block built on the stack is a full copy of the secret
disk-encryption nonce plus counter; both in-tree sibling IV helpers
already scrub their stack copy, this was the only one that did not.
The final image-size check re-derived the low-memory limit with a
uint32 subtraction and no ordering check, so an inverted tolum
wrapped into a near-2^32 limit and accepted any image. Compare the
tolum/load_address ordering in 32-bit (low-memory) form when
computing slot_max, and reuse that validated value in the check.
Add a unit test for the inverted-tolum case (fails closed, both
slots rejected).
The WOLFBOOT_FSP low-memory size check was the only per-slot rejection
in the boot retry loop that used a bare break, so an image whose
header-declared fw_size exceeded the tolum window aborted the boot
instead of trying the other slot. Every other rejection in the same
loop switches partitions and retries; match that.
pkcs11_crypto_deinit() - the only caller of pkcs11_pin_wipe() - was
invoked from the update_flash path alone (src/update_flash.c:1715).
On the RAMBOOT, hwswap and disk pre-handoff paths the PKCS#11 login
credential stayed in retained bootloader memory after handoff.
Add the same #ifdef ENCRYPT_PKCS11 deinit block after the WOLFHSM
cleanup in src/update_ram.c, src/update_flash_hwswap.c and
src/update_disk.c, in the same position as the existing update_flash
call (before hal_flash_protect/hal_prepare_boot). update_ram.c and
update_flash_hwswap.c did not include encrypt.h, where
pkcs11_crypto_deinit() is declared - add the include (update_flash.c
and update_disk.c already had it). The deinit is a no-op when crypto
was never initialized, so the calls are safe on every build.
Verification: full build with PKCS11 enabled (sim config +
CFLAGS_EXTRA: ENCRYPT_PKCS11, EXT_ENCRYPTED, EXT_FLASH,
WOLFCRYPT_SECURE_MODE, SECURE_PKCS11, WOLFPKCS11_USER_SETTINGS +
mechanism/sizes/PIN) compiles all sources cleanly; the link stops on
pre-existing externals (token library + secure-mode wolfssl objects
that a real target's link config supplies) - a control build of the
unpatched tree fails identically with the same undefined-symbol set.
sim and kontron_vx3060_s2 builds green (PKCS11 disabled, hunks
inactive). cstyle clean on the changed hunks.
The WOLFBOOT_DTS_MAX_SIZE/WOLFBOOT_DTS_MIN_SIZE pair was defined
separately in update_disk.c (F-7066) and update_ram.c (pre-existing), so
the two copies could drift. Move it to include/fdt.h, the FDT dialect
header both translation units already pull in via image.h; the hal
override (nxp_ppc.h, included before fdt.h in boot_ppc.c) keeps its
precedence. Also replace the 'bounded by the staging region' comment,
which claimed more than the code guarantees: the copy is clamped to
WOLFBOOT_DTS_MAX_SIZE, so the staging window at
WOLFBOOT_LOAD_DTS_ADDRESS must be at least that large (or the bound
overridden for the target), and the header comment now says so.
Skoll review finding 3, 2026-08-21 wolfboot review.
The FIT boot path relocated the flat-dt sub-image with a copy whose
length came from the FIT-declared data property length, never bounded
against the WOLFBOOT_LOAD_DTS_ADDRESS staging region - unlike the
sibling DTB paths, which all validate the parsed size against
WOLFBOOT_DTS_MIN_SIZE/WOLFBOOT_DTS_MAX_SIZE first. The length was also
harvested through a (int*)&dts_size cast of a uint32_t.
Relocate the parsed DTB size instead: validate it against the same
MIN/MAX bounds as the other DTB sources and copy that many bytes. An
out-of-range DTB is rejected (dts_addr stays NULL and the existing
fallback chain applies) rather than partially or oversize copied.
Applied to both call sites of the pattern: update_ram.c (memcpy) and
update_disk.c (wolfBoot_fit_memcpy), which also gains the DTS bounds
macros it was missing.
unit-update-disk-fit (drives the real update_disk.c wolfBoot_start)
gains two cases: a parsed size above WOLFBOOT_DTS_MAX_SIZE and one
below WOLFBOOT_DTS_MIN_SIZE are both rejected without a copy, while
the existing success/failure-copy cases keep passing. The staging
stand-in is grown so the pre-fix unbounded copy is observable as a
copy instead of a crash.
ForceZero() is only visible in libwolfboot.c, which pulls in misc.c
inline; update_disk.c called it without a declaration, so any config with
disk encryption failed to build. Use the exported wc_ForceZero() from
memory.o instead, which is always linked.
Add a zynqmp_sdcard ENCRYPT build job to CI, the only one that compiles
these paths.
dd0712ec added the disk_decrypted_header_clear()/disk_crypto_clear() pair
to the wolfBoot_start() panic paths that were missing it, but the FIT
flat-device-tree load failure was one more: when wolfBoot_fit_memcpy()
fails to relocate the DTS, wolfBoot_panic() is entered with
disk_encrypt_key/disk_encrypt_nonce still live in BSS, and that call never
returns on a real target.
Add unit-update-disk-fit, which drives wolfBoot_start() through the FIT
branch with DISK_ENCRYPT enabled and snapshots the module statics from the
WOLFBOOT_HOOK_PANIC hook.
- wolfBoot_fit_memcpy: return int so a failed PDMA copy propagates instead
of being swallowed. The weak default (memcpy) returns 0; the MPFS250 PDMA
override returns -1 if any chunk's mpfs_pdma_memcpy() fails. Callers now
fail closed: fit_load_image_inner returns NULL (kernel load then panics
via update_disk.c), the update_disk DTS copy panics, and hal_dts_fixup
returns an error on a failed L2->DDR copy-back.
- options.mk: gate -DSTACK_SIZE_PER_HART behind RISC-V arch (RISCV/RISCV64).
The macro is only consumed by the RISC-V startup asm and the mpfs250-m.ld
sed token, so it is no longer emitted for PPC/ARM/other targets. The
unconditional default (?= 0) is kept because the linker sed always needs
a value to substitute.
update_disk.c wolfBoot_start() already zeroizes disk_encrypt_key/nonce and
the decrypted header before most wolfBoot_panic() halts, but four paths
were missed: anti-rollback rejection, FIT FPGA load failure, FIT kernel
load failure, and flash-protect failure. Since wolfBoot_panic() halts
forever on real targets, these paths left the key live in BSS on a halted
device. Add the same disk_decrypted_header_clear()/disk_crypto_clear()
pair used on the other panic paths immediately before each.
Versions with the high bit set (>= 0x80000000) were stored as negative
int, causing the `> 0` guards to fail silently and leaving pA_ver_u /
pB_ver_u at 0. This broke partition selection and disabled the rollback
check for any image with a version number above INT_MAX.
Declare pA_ver and pB_ver as uint32_t (matching the return type of
wolfBoot_get_blob_version and get_decrypted_blob_version), update the
guards to != 0U, and drop the now-redundant (uint32_t) casts.
Add a unit test that places 0x80000001 in A and 0x80000002 in B and
asserts B is selected; before the fix the test failed with A chosen.
src/fdt.c, include/fdt.h
- Propagate fdt_fixup_initrd error in fit_load_ramdisk so a /chosen
patch failure no longer silently boots a kernel with no initrd.
- Add fit_load_image_to(): decompress (or memcpy) directly to a
caller-supplied destination buffer instead of going through the
FIT-declared `load` address. fit_load_ramdisk now uses this when
WOLFBOOT_LOAD_RAMDISK_ADDRESS is set, so the override is a real
safety bound for compressed ramdisks (previously the gzip stream
was still inflated to the FIT `load` and only memcpy'd afterward).
- Refactor fit_load_image_ex into a shared inner helper.
- Reword the WOLFBOOT_FIT_MAX_DECOMP comment: the cap is a sanity
ceiling, not a per-destination memory-safety bound. Authenticity
is provided by the outer wolfBoot signature; tighter bounds need
fit_load_image_ex / _to with an explicit out_max / dst_max.
- Add WOLFBOOT_FIT_MAX_RAMDISK (defaults to WOLFBOOT_FIT_MAX_DECOMP)
so targets can pin a tighter ramdisk decompression bound.
src/update_ram.c, src/update_disk.c
- Panic when fit_load_image() returns NULL for the kernel subimage
instead of letting load_address=NULL propagate into do_boot().
tools/unit-tests/unit-gzip.c
- Add deterministic stored / fixed-Huffman / dynamic-Huffman gzip
fixtures so the inflater's BTYPE 00/01/10 paths are exercised
independent of host gzip(1) heuristics.
- Add FEXTRA / FNAME / FCOMMENT / FHCRC and combined-flag fixtures
plus a truncated-FEXTRA negative case to cover the optional gzip
header parser.
tools/unit-tests/unit-fit-gzip.c (new), tools/unit-tests/Makefile
- New libcheck binary covering the FIT loader's compression
branches: gzip success, gzip stream corruption, unknown
compression, compression="none" baseline, and the no-load
fail-closed path. Built twice from the same source - once with
WOLFBOOT_GZIP for the success / runtime-failure paths, and once
without it so the compile-time fail-closed branch is also tested.
Wires the new wolfBoot_gunzip inflater into the FIT image-loading path
and adds initramfs (ramdisk) extraction with DTB /chosen fixup so a
single signed FIT can carry kernel, DTB, and rootfs.
GZIP path
---------
* fit_load_image_ex(out_max) added; fit_load_image kept as a wrapper.
* When a subimage carries compression="gzip", inflate straight to the
FIT-declared load address, then verify the FIT hash-1 subnode
(sha256 / sha384 if available) for defense in depth on top of the
outer wolfBoot signature. The compression property is now read
unconditionally so a build without WOLFBOOT_GZIP can warn and fail
closed instead of silently memcpy-ing compressed bytes as if they
were raw.
* fit_verify_hash propagates wc_InitSha256 / wc_Sha256Update /
wc_Sha256Final return codes (and the SHA-384 equivalents) - any
non-zero return is treated as a verification failure so a misbehaving
backend cannot silently degrade to a no-op.
* GZIP=1 is the new default in the FIT-using example configs (zynqmp,
zynqmp_sdcard, polarfire_mpfs250, polarfire_mpfs250_qspi,
versal_vmk180, versal_vmk180_sdcard); set GZIP=0 to opt out.
Ramdisk path
------------
* fit_find_images() gains a ramdisk out-arg and fdt_fixup_initrd()
writes /chosen/linux,initrd-{start,end} as 64-bit big-endian cells.
* update_disk.c and update_ram.c load the FIT ramdisk node (under
WOLFBOOT_FIT_RAMDISK) and patch the loaded DTB. Compressed (gzip)
ramdisks reuse the same fit_load_image_ex() decompress path.
* RAMDISK=1 build switch defines WOLFBOOT_FIT_RAMDISK;
WOLFBOOT_LOAD_RAMDISK_ADDRESS is plumbed through tools/config.mk ->
Makefile sed -> include/target.h.in. Defaults to 0; when 0 the
ramdisk stays at whatever fit_load_image returned.
* hal/zynq.c and hal/versal.c bump fdt_totalsize headroom from 512 to
768 bytes to fit the new linux,initrd-{start,end} entries.
* config/examples/zynqmp_sdcard.config gains a commented-out opt-in
block (RAMDISK=1, WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40000000, alt
LINUX_BOOTARGS) so a single config file covers both rootfs-on-disk
and FIT-bundled-initramfs flows.
Builds against the existing master configs are byte-identical when
GZIP=0 and RAMDISK is unset.
Adds support for running wolfBoot in **Machine Mode (M-mode)** on the PolarFire SoC MPFS250T, booting from eNVM and loading a signed application from **SC QSPI flash** into on-chip LIM (Loosely Integrated Memory). No HSS (Hart Software Services) or DDR is required. It also extends the existing PolarFire HAL with multi-hart support, L2 cache configuration, per-hart UART, and refactors shared code (SDHCI, SCB mailbox, linker/test-app) for both S-mode and M-mode builds.