Commit Graph

1238 Commits (f573e97e76af7ec0c61ce7bcb348bbe4b5784fb5)

Author SHA1 Message Date
Daniele Lacamera f573e97e76 F-11029: publish IPI fence completion only after the fence executes
sbi_ipi_irq() read-and-cleared the per-hart op word before executing
the requested fence.i/sfence.vma, and sbi_wait_ipi_done() treated a
zero op word as completion. The SBI remote-fence ecalls are
synchronous, so a requester could return while the target had not yet
run its fence (e.g. resume relying on a new page table before the
target flushed its TLB).

Split the protocol into pending work and completion state, per hart:
ipi_done[h] is incremented by the target only after it has executed
the fence ops it consumed; the requester snapshots it into
ipi_wait_gen[h] before posting (so a concurrent coalesced consume of
two requesters' ops still increments past both snapshots) and waits
until ipi_done[h] passes the snapshot. SSIP posts are fire-and-forget
and do not wait, as before.

Verification:
- Built: riscv-none-elf-gcc 15.2 -fsyntax-only -Wall with
  WOLFBOOT_RISCV_MMODE + WOLFBOOT_MMODE_SMODE_BOOT: clean.
- Tested: none (race condition; the contract skips failing-first tests
  for races, and the file is MPFS S-mode monitor code with no host or
  CI build target).
- Pitfalls: the shared DTIM struct gains two per-hart arrays; the
  struct is self-initialized under init_magic by this same code on
  every hart, so no cross-version ABI is broken. A target that never
  runs M-soft still hits the bounded spin timeout as before. The
  completion increment covers only fence ops, matching the only
  waiting call sites (both RFENCE paths).
- Style: cstyle-check.sh flag count unchanged from the pre-change
  file (3 pre-existing).
- Message: F-11029: prefix, no co-author trailers.
- Unverified: no multi-hart runtime execution (MPFS board only).
2026-08-25 08:51:09 +02:00
Daniele Lacamera e108d4e86c F-11028: panic when both RAM-boot images fail verification
The RAM boot loop switched to the other partition on a verification
failure (active ^= 1, continue) with a comment claiming the failing
image was invalidated, but nothing was invalidated: wolfBoot_fallback_is_possible()
only sees that both partitions carry nonzero versions. Two present
but invalid images therefore alternated indefinitely, re-verifying
forever. (The flash path does not have this hole because it erases
the failing partition, which zeroes its version and makes the second
fallback check fail.)

Track the candidates attempted this boot: once both partitions have
failed, panic with a clear message. Same-version images are used in
the regression test so the anti-rollback guard cannot mask the
alternation.

unit-update-ram gains test_both_images_corrupted_panics: both
partitions carry valid-version images with corrupted digests; the
boot must panic after exactly two attempts. Pre-fix the loop ran
unbounded (process had to be killed; ~60k partition switches in 30 s).

Verification:
- Built: unit-update-ram, unit-update-ram-enc, unit-update-ram-nofixed,
  unit-update-ram-uboot all compile.
- Tested: unit-update-ram 20/20, unit-update-ram-nofixed 3/3,
  unit-update-ram-uboot 5/5; the new test shows Boot fail, Update
  fail, panic.
- Pitfalls: attempt tracking is per boot session (local), no new
  persistent state; the flash path is untouched.
- Style: cstyle-check.sh flag count on both changed files is
  unchanged from the pre-change versions (pre-existing FMT/R1).
- Message: F-11028: prefix, no co-author trailers.
- Note: unit-update-ram.c defines 21 tests but wires 20 into the
  suite (test_forward_update_samesize_notrigger was never added);
  pre-existing, left as-is.
2026-08-25 08:51:09 +02:00
Daniele Lacamera e02a734e72 F-11027: abort the ELF load when an mmu_cb mapping fails
elf_load_image_mmu() skipped a segment (continue) when its mmu_cb
mapping failed, then kept loading the rest, published the ELF entry
point and returned success for a partially loaded image. The x86 FSP
payload path (boot_x86_fsp_payload.c) passes a real mmu_cb and only
panics on a non-zero return, so the buggy continue booted a payload
with a missing segment.

Return -6 with a fail-loud message, matching the program-header
clobber guard that aborts for the same reason: never silently drop a
a PT_LOAD segment.

unit-elf-mmu-fail fails the first segment's mapping and checks the
load is rejected with no entry point published; a second test checks
successful mappings still load the segments and publish the entry.

Verification:
- Built: unit test compiles elf.c (WOLFBOOT_ELF config); elf.c
  syntax-clean under the WOLFBOOT_FSP config (gcc -fsyntax-only).
- Tested: unit-elf-mmu-fail 2/2; pre-fix the failure-path test got
  ret == 0 (entry published for a partially loaded image).
- Pitfalls: no caller switches on the exact code (all check != 0);
  -6 is new and dedicated to the mapping failure.
- Style: cstyle-check.sh on src/elf.c flags pre-existing FMT/R1
  issues also present on the pre-change file; the new test trips the
  uncrustify pointer-alignment class the sibling unit tests trip and
  matches their local style.
- Message: F-11027: prefix, no co-author trailers.
2026-08-25 08:51:09 +02:00
Daniele Lacamera c9fcdddefb F-11024: pass full-width fields to wc_ecc_rs_raw_to_sig in the wolfHSM verify path
The wolfHSM verify path of wolfBoot_verify_signature_ecc() converts
the fixed-width raw R||S signature to DER with wc_ecc_rs_raw_to_sig().
It passed the minimal field sizes (mp_unsigned_bin_size) while leaving
the pointers at the start of each fixed-width field, so whenever R or
S had a leading zero byte the conversion encoded a zero-padded integer
with the low bytes truncated, and wc_ecc_verify_hash() rejected an
otherwise valid signature (each component has roughly a 1 in 256
chance of a leading zero).

Pass the full-width fields (point_sz for both): the raw signature is
fixed-width and left-zero-padded, and the conversion strips the
padding itself. The multiprecision sizing only existed to compute the
minimal lengths and is dropped with the fix.

unit-ecc-raw-der signs until a leading-zero signature shows up, then
checks that the minimal-size pattern rejects it while the full-width
pattern accepts it, and that both patterns agree for leading-zero-free
signatures.

Verification:
- Built: src/image.c syntax-clean with WOLFBOOT_ENABLE_WOLFHSM_CLIENT
  (gcc -fsyntax-only, __WOLFBOOT, ECC256/SHA256 config, partition
  stubs); normal library build via make test-lib.
- Tested: unit-ecc-raw-der 3/3: the minimal pattern rejects the
  leading-zero signature the full-width pattern accepts; both agree
  on leading-zero-free signatures.
- Pitfalls: no key material touched; the dropped mp values had no
  matching mp_clear before the fix either (stack variables).
- Style: cstyle-check.sh on src/image.c flags two pre-existing
  violations (L1869 anonymous union, L2079 C99 declaration) outside
  this change; the new test trips the same uncrustify
  pointer-alignment class the unit-stm32l5/u5-write twins trip and
  matches their local style.
- Message: F-11024: prefix, no co-author trailers.
- Unverified: no wolfHSM target builds in CI; the HSM branch was
  checked by syntax-only compile, not a full target build.
2026-08-25 08:50:42 +02:00
David Garske 527c63007e elf: read the ELF entry point before loading segments over it 2026-08-25 08:46:40 +02:00
David Garske 25cdd5a0d7 c2000: add wolfBoot TI C28x (TMS320F28P550SJ) secure-boot XIP port 2026-08-25 00:01:33 +02:00
David Garske a5c94502d4
Merge pull request #868 from danielinux/fenrir-fixes-2026-08-21
Fenrir fixes 2026 08 21
2026-08-24 13:34:37 -04:00
David Garske 8015a1fa4f Peer review fixes (thanks Daniele) 2026-08-24 12:47:34 +02:00
David Garske 8ec8443345 Fixes from skoll review findings 2026-08-24 12:47:34 +02:00
David Garske 88984858d5 Peer review fixes (thanks Jack) 2026-08-24 12:47:34 +02:00
David Garske 2df97e448e cm4: define weak hal_get_boot_dts in update_disk.c so ARM32/RISC-V disk targets link 2026-08-24 12:47:34 +02:00
David Garske 67c819bc0b cm4: add RAUC A/B slot selection via U-Boot env (wolfBoot replaces U-Boot) 2026-08-24 12:47:34 +02:00
David Garske 56651bde95 cm4: add Linux kernel FIT boot via firmware-provided DTB with injected bootargs 2026-08-24 12:47:34 +02:00
David Garske 9a4f800546 cm4: hardware-validate eMMC A/B disk boot and FIPS on CM4 (arm64 header, 0x200000 load, mini-UART console) 2026-08-24 12:47:34 +02:00
David Garske 71b855f7b5 cm4: add wolfCrypt FIPS 140-3 support + CI, SDHCI unit test, review fixes 2026-08-24 12:47:34 +02:00
David Garske eb7ad83ca0 cm4: add Raspberry Pi Compute Module 4 (BCM2711 Cortex-A72) target with authenticated boot 2026-08-24 12:47:34 +02:00
Daniele Lacamera daf2248fc4 remove anonymous code blocks 2026-08-24 12:37:53 +02:00
Daniele Lacamera ca6b60a1e6 scrub the RMW scratch buffers in ext_flash_encrypt_write
Since F-9750 the head and tail read-modify-write paths decrypt the
stored neighbour block into block/enc_block before splicing the
caller's bytes, so the two stack buffers transiently hold plaintext the
caller never supplied, and several exits returned without scrubbing
them (stale head plaintext also outlived into the tail path). Funnel
every exit after the partition switch through a single cleanup that
ForceZero()s both buffers, matching the zeroization posture of the rest
of the campaign (F-7396 header cache, F-7966/F-7971 keys, aes_set_iv
IV). Defense-in-depth: the buffers are stack-local, but this is the most
long-lived plaintext in the write path.

Skoll review finding 4, 2026-08-21 wolfboot review.
2026-08-21 09:11:44 +02:00
Daniele Lacamera 9b1a48554a share the DTS size bounds in fdt.h and state the window assumption
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.
2026-08-21 09:08:26 +02:00
Daniele Lacamera 72c1ec234a bound the ELF paddr range check by the destination pointer width
The F-9756 validation rejected seg_start > UINT64_MAX - filesz, but the
very next line truncates: load_addr = (uintptr_t)seg_start. On 32-bit
targets a paddr that fits in 64 bits but not in the 32-bit address space
(e.g. 0x1_0000_0000) passed every check and the flash hash walk read the
wrapped (possibly unmapped) address - the same fault class the check was
written to prevent. Bound the range by UINTPTR_MAX, the width the cast
actually uses, and pin the 32-bit-only case with a guard test.

Skoll review finding 1, 2026-08-21 wolfboot review.
2026-08-21 08:57:46 +02:00
Daniele Lacamera 04531cbf2d F-9751: Add positive E2E encrypted-update test, fix what it exposes
Add unit-update-flash-enc-full, the full end-to-end suite (forward
updates, rollback, empty boot, diffbase) against the encrypted
swap, plus a byte-for-byte fallback-IV roundtrip test.

The suite exposes two product defects:

- ext_flash_encrypt_write() partial-block re-syncs re-anchored the
  keystream at the standard-IV position once the one-shot fallback
  IV offset had been consumed by the initial set_iv, corrupting the
  tail of fallback-IV images. Capture the IV offset in effect at
  entry and re-apply it on every re-sync.

- wolfBoot_final_swap() called wolfBoot_set_encrypt_key() with the
  internal flash unlocked, but the backend expects the flash locked
  (it manages the unlock/lock around the key write itself) and ends
  with the flash locked. Lock before the call and drop the now
  redundant lock on the failure path.

Test plumbing for the encrypted target: update-partition writes in
the tests now go through the encryption-aware writer, as the update
tool does; the hand-rolled TLV headers use the sign tool's dense
layout (padding gaps are ciphertext in encrypted builds); and the
testing-flag sites anchor on the state trailer, which sits ahead of
the key/nonce region in encrypted builds.

Verified: unit-update-flash-enc-full 35/35, unit-update-flash-enc
8/8, unit-extflash + AES128/256/ChaCha20 variants 8/8 each, full
unit suite green, stm32wb + AES256 cross-build green.
2026-08-21 08:52:59 +02:00
Daniele Lacamera 5caac27eb9 F-9750: decrypt the stored block before the encrypted RMW patch
Both partial-block read-modify-write paths in ext_flash_encrypt_write
read the stored block (ciphertext), spliced the new plaintext in, and
re-encrypted the whole block. The untouched bytes were therefore
XOR'd with the keystream a second time: stored ciphertext came back
as plaintext in flash, and the next read of those bytes returned raw
ciphertext instead of the original data. Any encrypted update whose
first or last block partially overlaps a block with previous content
- e.g. a retry over a previously written update image - silently
corrupted the neighbouring bytes.

Decrypt the stored block before splicing (into the scratch buffer, so
no backend has to handle in-place decrypt) and re-encrypt the merged
plaintext. Erased (0xFF) bytes round-trip unchanged because the
decrypt/encrypt pair is the identity on the stored value.

The re-encryption re-syncs the stream to the block index first: the
decrypt step consumes keystream, and on the ChaCha/PKCS#11 backends
encrypt and decrypt share a single stream state, while on the AES
backends the decrypt context had not advanced with the full-block
writes. The tail path also syncs the decrypt context, which on the
AES backends sits at the first block's index after the aligned
writes. Fallback-IV offset handling mirrors ext_flash_decrypt_read.

New unit-extflash tests (run under the plain, AES-128, AES-256 and
ChaCha20 variants): a mid-block patch must leave the untouched bytes
of a previously written block intact, a trailing partial block must
leave the rest of a previously written block intact, and a stream
written in small unaligned chunks must round-trip byte for byte. All
three fail on the pre-fix code with every cipher.
2026-08-21 08:52:59 +02:00
Daniele Lacamera 00bc8b5823 F-6762: compare the TLV field budget in a 32-bit domain
wolfBoot_find_header() and the sign tool's re-parser checked each
field's 4+len against (uint16_t)(header_size - IMAGE_HEADER_OFFSET).
For any header of 64 KiB or more the cast wraps (0x10000 -> 0), so the
guard rejects every field and an image the tool signs cannot be parsed
by the bootloader - a pack/parse roundtrip break, fail-safe but fatal
for large TLVs (post-quantum signatures, big cert chains).

Compare in the uint32_t domain in both walkers. No shipped config
reaches this size yet (largest example is 12288), so this pins the
roundtrip for future large-header configs.

unit-parser-large-header (new) builds the walker with
IMAGE_HEADER_SIZE = 0x10008 - exactly the wrap boundary - and asserts
a 300-byte TLV and a 4-byte version field are located (both were
rejected pre-fix, proven against the pre-fix walker in a scratch
build).
2026-08-21 08:52:59 +02:00
Daniele Lacamera e9ee83796f F-7396: scrub the decrypted-header cache after field extraction
Under EXT_ENCRYPTED + MMU, decrypt_header() decrypts the firmware
manifest into the file-scope dec_hdr buffer, which the blob-field
lookups and wolfBoot_ram_decrypt() consume - but never clear, so a
plaintext manifest of an image whose confidentiality is the point of
EXT_ENCRYPTED sat in .bss through do_boot(). The disk-boot twin
(update_disk.c) wipes its equivalent on every exit.

Add dec_hdr_clear() and invoke it once the field of interest has been
extracted: in wolfBoot_get_blob_version/type/diffbase_version (the
tails now extract into a local and return it, identical values in all
builds) and in wolfBoot_ram_decrypt right after the length field is
taken - the only field read from the manifest, the copy loop that
follows uses its own block buffer.
2026-08-21 08:52:59 +02:00
Daniele Lacamera 495b80b9b0 F-7966: propagate the CTR counter carry without branching on the nonce
aes_set_iv() and pkcs11_crypto_set_iv() added the block counter to the
nonce-derived counter block and propagated the carry with a loop that
only runs - and whose trip count depends - on the nonce words: the
overflow branch reveals that the high word was within one count of
wrapping, and the inner loop's exit point reveals how many of the low
words are 0xFFFFFFFF. crypto_set_iv() runs once per encrypted block,
so a timing attacker gets one measurement per block.

Replace both with an unconditional branch-free four-word carry
(standard carry-out flags, three iterations regardless of content).
Arithmetic is identical: verified old-vs-new expression equality over
5M random counter/nonce inputs plus the full-carry, zero-counter and
max-counter boundary cases; unit-aes128/unit-aes256 encrypted
roundtrips pass with the new code, and the ENCRYPT_PKCS11
CKM_AES_CTR path compiles clean.
2026-08-21 08:52:59 +02:00
Daniele Lacamera f3098cbf6d F-9766: scrub the nonce copies in the IV-derivation helpers
wolfBoot_crypto_set_iv() copies the firmware encryption nonce onto the
stack (local_nonce) for the AES/PKCS#11 backends and aes_set_iv() derives
iv_buf from it, and both returned without scrubbing the copies. The
rest of the codebase pairs key scrubs with nonce scrubs (e.g.
update_disk.c); these helpers are called once per encrypted block,
leaving a nonce copy on the stack at the end of every encrypted I/O
sequence, including the one preceding do_boot().

ForceZero both buffers after the derived IV is consumed. The PKCS#11
set_iv helper is intentionally left as-is: it writes the counter into
the persistent pkcs11_params CTR state, which the token updates
in-place and which must survive the call.
2026-08-21 08:52:59 +02:00
Daniele Lacamera 58390cbf27 F-7071: scrub the staging buffer on the swap resume early-return
wolfBoot_swap_and_final_erase reads the staging-sector trailer into
tmpBuffer (which also stages the firmware key/nonce under EXT_ENCRYPTED)
and scrubs it on every exit except the resume early-return, which
returned -1 with the buffer still holding the bytes just read from
flash. Add the zeroize there so all four exits share the same
invariant.
2026-08-21 08:52:59 +02:00
Daniele Lacamera 8d39a5f9a4 F-9721: make ext_flash_encrypt_write doc match its signature
The Doxygen comment documented a 'forcedEnc' parameter that does not
exist (the function takes address, data, len) and named AES for a
routine whose encryption step is the configured cipher - ChaCha20,
AES-CTR, or a PKCS#11-backed cipher, per build configuration.
2026-08-21 08:52:59 +02:00
Daniele Lacamera f08c62a000 F-9756: validate PT_LOAD segments before the scatter flash hash walk
wolfBoot_check_flash_image_elf() fed every PT_LOAD entry's
paddr/BASE_OFF straight into update_hash_flash_addr() with the 64-bit
file_size truncated to the uint32_t the reader consumes, and never
bounded an intermediate segment's file layout against the manifest.
The read loop then memcpy's from (or drives the flash driver at)
whatever address the image declares - an unauthenticated partition
(e.g. WOLFBOOT_SKIP_BOOT_VERIFY builds) or a corrupt one could walk
the hash over unmapped memory.

Validate each loadable segment before hashing and fail the check
instead of continuing:
  - file_size must fit the uint32_t hash length,
  - offset + file_size must stay inside the manifest image
    (overflow-safe comparison; previously only the last segment was
    checked, after the loop),
  - paddr + BASE_OFF + file_size must not overflow the address space.

The mismatch log no longer prints the first 8 digest bytes.

Note: a full flash-region bound for paddr needs a configured
scatter-region size; no such knob exists in the target configuration
today (scattered segments are deliberately placed outside the
boot/update/swap partitions), so the region check is left as a
follow-up.

unit-image-elf-scatter gains three cases with a multi-segment
fixture: a 2^32 file_size and a segment layout extending past
fw_size (both verified OK pre-fix because the stored digest matched
the truncated/out-of-layout walk) are now rejected with -1, and a
paddr whose range overflows the address space is rejected before any
flash read (pre-fix: read at 0xfffffffffffffffb, segfault on host).
2026-08-21 08:52:59 +02:00
Daniele Lacamera d554bbb98b F-7066: bound the FIT DTS relocation copy to the staging size
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.
2026-08-21 08:52:59 +02:00
Daniele Lacamera fe1dcb080d F-9757: length-bound the FIT name/compression string properties
fit_find_images() took the FIT configuration's image names
(kernel/fdt/ramdisk/fpga) and the configuration name (default)
straight from fdt_getprop() and passed them on to
fdt_find_node_offset(), which strlen()s them; fit_load_image_inner()
strcmp()'d the compression property after only checking it was
non-empty. A property value not NUL-terminated within its declared
length makes those calls scan past the property - and past the end
of the blob for a property at the tail.

Add fit_getprop_string(), which returns the property value only
when it is NUL-terminated within its declared length, and use it
for the five name properties (a malformed value is rejected and the
type-based search still applies). Compare compression within the
declared length: the value must be exactly "gzip" or "none";
any other shape fails closed with the existing
unsupported-compression path instead of being strcmp()'d past the
property.

unit-fdt gains a FIT whose configuration kernel property is
unterminated (the valid default is still honored, the image name
is rejected); unit-fit-gzip gains a truncated compression="none"
value, which used to pass the subimage through as raw and now
fails closed. Both build variants (gzip enabled/disabled) run it.
2026-08-21 08:52:59 +02:00
Daniele Lacamera 19d8a9e346 F-9758: validate FDT layout in fdt_check_header, use it in fdt_get_string
fdt_get_string() bounded stroffset against size_dt_strings but
formed the string-table pointer from off_dt_strings without ever
validating either header field against totalsize; a DTB declaring a
large off_dt_strings with a small size_dt_strings made every
property lookup (fdt_getprop -> fdt_get_string) scan far outside
the blob.

Validate the structural layout in fdt_check_header() for finalized
(FDT_MAGIC) blobs: the reservation map, structure block and string
table must sit inside the blob and not overlap, checked in 64-bit
so the size fields cannot wrap. fdt_get_string() now requires a
valid header before forming the pointer. The SW_MAGIC (in-progress
edit) state keeps its existing check, since its layout is different.

Test fixtures are adjusted to the validated layout: the two
pre-existing fdt_get_string fixtures now set the header fields the
lookup relies on, the compatible-test builder sets the magic word
and points the reservation map at the canonical empty list right
after the header (it pointed into the string table before).
2026-08-21 08:52:59 +02:00
Daniele Lacamera 08903c252e F-7990: bound the compatible string walk to declared lengths
fdt_node_offset_by_compatible() compared each entry of a
multi-string compatible value with memcmp(compatible, prop,
complen+1) before locating the entry's NUL terminator. When the
declared property length equals the search length (no room for a
NUL), the comparison reads one byte past the property data and
accepts the entry when that byte happens to be zero (e.g. the
4-byte alignment padding).

Locate each entry's NUL terminator within the declared length
first and only compare entries whose length equals the search
length: nothing is read past the property, and an unterminated
trailing entry can no longer match.

unit-fdt gains four cases with a minimal single-node FDT builder:
an unterminated exact-length entry does not match (it did before
the fix), a terminated exact-length entry matches, multi-string
lists still match on later entries, and a longer entry that starts
with the search string does not match.
2026-08-21 08:52:59 +02:00
Daniele Lacamera c5ae368021 F-7974: declare hdr_cpy_done as int in both translation units
libwolfboot.c defined the external-flash header-cache flag as
uint32_t while image.c declared it extern int - the same object with
incompatible types in two translation units is undefined behaviour. It
is benign on every supported target (both are 32-bit) but a latent
portability defect; the flag is written from both files. Make the
definition int, matching both declarations.
2026-08-21 08:52:59 +02:00
Daniele Lacamera d722c0f2bc F-7971: free the ECC key on the wolfHSM setup error paths
In the wolfHSM branch of wolfBoot_verify_signature_ecc(), both failure
paths after wc_ecc_init_ex() - the HSM key-ID setup and
wc_ecc_import_unsigned() - did a bare return that skipped the
wc_ecc_free(&ecc) at the end of the function, leaking the key (and its
heap-allocated mp_ints in non-SP-math builds). Free the key before
returning on both paths, matching the plain wolfCrypt path.

The wc_ecc_import_unsigned() path sits in the server-only, non-cert-chain
configuration, which does not compile today (it references pubkey/
point_sz, declared only for the software and client builds) - see the
F-7995 note; the free is added there for consistency so the path is
correct if that configuration is ever made buildable.
2026-08-21 08:52:59 +02:00
Daniele Lacamera ce5f20c74d F-7970: free the LMS key on the parameter/import error paths
wolfBoot_verify_signature_lms() returned without calling wc_LmsKey_Free()
when wc_LmsKey_SetParameters() or wc_LmsKey_ImportPubRaw() failed after a
successful wc_LmsKey_Init(), leaking the key (and whatever heap state the
LMS backend allocated for it). Free the key on both error paths, matching
the success path.
2026-08-21 08:52:59 +02:00
Brett Nicholas 25aa151f0b add asymmetric partition sizing for monolithic updates 2026-08-19 17:47:48 +02:00
David Garske d42109c843
Merge pull request #862 from danielinux/fenrir-fixes-2026-08-18
Fenrir fixes 2026 08 18
2026-08-18 12:12:50 -07:00
Daniele Lacamera 502a69e9df Condense the verbose comments
The test file headers ran to 20-40 lines of prose before the licence,
restating the whole finding and the harness design. Cut them to a short
paragraph on what broke and a short one on how the test reaches the
code. Same for the long inline blocks in the HAL and libwolfboot
changes.

Comments only; no functional change.
2026-08-18 20:12:06 +02:00
Daniele Lacamera 57db8948d1 Remove tracker ids from code, condense comments
Drop the internal report numbers from source, test and Makefile
comments: they mean nothing outside the tracker and do not belong in
the tree. Condense the long inline comment blocks the review flagged,
and note in the raspi3 encrypted example and its docs that CI builds
but does not boot it, so the end-to-end path has no automated
coverage.

Comments and docs only; no functional change.
2026-08-18 19:48:12 +02:00
David Garske 27edd0f5e7 tegra234: bare-metal BL33 target with verified EL2->EL1 + device-tree boot 2026-08-18 11:01:45 +02:00
Daniele Lacamera 52a1b085e0 libwolfboot: scrub NVM_CACHE without pulling in wolfCrypt
Widening the misc.c include guard with NVM_FLASH_WRITEONCE made this
file include <wolfssl/wolfcrypt/types.h> and <wolfcrypt/src/misc.c> in
every such build, including the two that cannot resolve them:
tools/check_config has no wolfSSL include path, and the STM32Cube
test-app has no stm32wbxx_hal_conf.h. Both fail to compile.

Scrub with a local volatile byte loop instead and put the guard back.
That also suits the RAMFUNCTION callers better, since ForceZero() lives
in flash and must not be called while flash is being programmed.
2026-08-18 10:16:44 +02:00
Daniele Lacamera 88b637130c sdhci: service the watchdog inside the busy waits
sdhci_wait_busy() runs at the top of every transfer and can now spin
for the full 30 s budget. Neither loop petted the watchdog, so on a
platform with one a stuck card produced a reset instead of the clean
I/O error the timeout exists to give. The shared deadline between the
two loops is deliberate and now says so.

The weak sdhci_platform_wdt_pet() default moves out of the
SDHCI_BLOCK_VIA_PDMA guard, since the busy waits call it on every
build. The test drops its SDHCI_WAIT_BUSY_TIMEOUT_MS=50 override and
steps its timer 1 ms per read instead, so the shipped default is what
is tested, and it counts watchdog services.
2026-08-18 09:56:20 +02:00
Daniele Lacamera c7f277f495 libwolfboot: scrub the key cache on the static-buffer builds
The exit_lock scrub was guarded to the case where ENCRYPT_CACHE is a
stack local, skipping both static ones -- NVM_FLASH_WRITEONCE aliases
it to NVM_CACHE, WOLFBOOT_SMALL_STACK gives it its own array. Those
are exactly the buffers where the plaintext key and nonce would stay
resident for the rest of the boot.
2026-08-18 09:19:13 +02:00
Daniele Lacamera 597283ad8d sdhci: roll back the UHS one-shot with the registers
sdhci_uhs_recover_rollback() restored 3.3V but left g_uhs_recovered
set, leaving the host in a state the base code could not reach: back
at 3.3V with the recovery spent, so a genuinely UHS-I card could never
be retried for the rest of the boot.
2026-08-18 09:19:13 +02:00
Daniele Lacamera 7d46d3f3df sdhci: compare the busy deadline in microseconds
The poll loops divided a 64-bit elapsed count by 1000 on every
iteration, which is a libgcc helper call on 32-bit targets. Compute
the timeout in microseconds once instead.
2026-08-18 08:41:31 +02:00
Daniele Lacamera 685888a8ef F-9765: make ForceZero visible in the NVM_FLASH_WRITEONCE test-app build
The trailer scrubbing added for F-9765 calls ForceZero() from
trailer_write()/partition_magic_write(), which compile under
NVM_FLASH_WRITEONCE in the test-app build of libwolfboot.c too. That
build defines neither __WOLFBOOT nor EXT_ENCRYPTED, so the file's
wolfcrypt/src/misc.c inline include (the only place ForceZero becomes
visible, and the reason the test-app wolfcrypt link set carries no
misc.c) was compiled out and -Werror rejected the implicit
declaration.

List NVM_FLASH_WRITEONCE in the include gate, for the same reason
EXT_ENCRYPTED is already listed there.

Verified with the exact CI test-app compile commands for the two
failing presets (stm32h5, stm32u5) on arm-none-eabi-gcc: HEAD
reproduces the implicit-declaration error, the fix compiles clean.
Unit suite 123/123.
2026-08-18 08:19:24 +02:00
Daniele Lacamera 5053a4d1f0 F-9765: scrub NVM_CACHE after the trailer read-modify-write helpers
Under NVM_FLASH_WRITEONCE the partition-trailer helpers stage a full
flash sector into the file-scope NVM_CACHE before rewriting it. In
EXT_ENCRYPTED builds that same sector is where the firmware key/nonce
live - ENCRYPT_CACHE is a macro alias of NVM_CACHE in this
configuration, and hal_set_key() stages the key there - so after a
normal boot's wolfBoot_set_partition_state(PART_BOOT,
IMG_STATE_TESTING) the plaintext key/nonce sat in .bss at a fixed
address across the handoff to the application. The file's only
ForceZero of the buffer is compiled out precisely for
NVM_FLASH_WRITEONCE, and neither trailer_write() nor
partition_magic_write() scrubbed the buffer on return.

Scrub the whole staged sector (ForceZero, NVM_CACHE_SIZE) before each
return of both helpers, on success and on the failure path (the key
is already staged by the time the flash write fails).

Test: tools/unit-tests/unit-nvm-cache-scrub.c extracts the real
trailer_write() and partition_magic_write() and runs them over a
staged sector carrying a key/nonce pattern; pre-fix the pattern
remained in NVM_CACHE after all three scenarios.
2026-08-18 05:57:21 +02:00
Daniele Lacamera 3962967ddb F-9251: qualify wolfBoot_erase_encrypt_key() erasure pattern per build path
The doc comment claimed the key/nonce were always reset to 0xFF, but
only the flash-backed path does that (FLASH_BYTE_ERASED via
hal_set_key). The MMU path zeroizes the in-RAM copy with ForceZero()
(all-zero bytes) and the TSIP path has nothing to erase. State the
actual per-path behavior so a verification routine or self-test does
not assert the wrong erased pattern on MMU targets.

Comment-only change; no code or behavior changes.
2026-08-18 05:00:56 +02:00
Daniele Lacamera 0cbd17cf00 F-7984: bound the SDHCI busy waits with a finite timeout
sdhci_wait_busy() had no timeout in either its DATA0 polling loop or
its repeated CMD13 loop (the in-code TODO acknowledged it). A removed
card, a controller fault, or a card stuck in the programming state
left wolfBoot spinning forever instead of returning an I/O error.

Give both loops a shared, finite deadline (30 s worst-case erase
programming time, configurable for tests via
SDHCI_WAIT_BUSY_TIMEOUT_MS) and return -1 when it expires. Callers
already propagate a nonzero status as a failure.

Test: tools/unit-tests/unit-sdhci-wait-busy.c compiles the real
src/sdhci.c and scripts the controller through the host register file
(DATA0 held low; CMD13 responses with READY_FOR_DATA clear), with the
emulated timer advancing 1us per read and the deadline built to 50 ms,
so both stuck cases hit the deadline in wall milliseconds. Pre-fix the
DATA0 case hung until the test framework's timeout killed it.
2026-08-18 04:53:27 +02:00