spi_flash_read only rejected address > FLASH_DEVICE_SIZE (off-by-one,
admits address == FLASH_DEVICE_SIZE) and never validated address+len
against the device size, so an in-bounds start with an out-of-range
extent was not rejected. spi_flash_write had no bounds check at all.
Add an inclusive/full-extent check to both.
The fw_size > (WOLFBOOT_PARTITION_SIZE - IMAGE_HEADER_SIZE) guard was only
tested on the reject side (oversize + 1); no test asserted that a firmware
payload exactly filling the partition payload budget is accepted. A >/>=
mutation at image.c:1402 or image.c:1618 would silently reject a
maximally-sized valid image and survive the existing suite. Add positive
boundary assertions (via wolfBoot_open_image()/wolfBoot_open_image_address()
and wolfBoot_open_self_address()) paired with the existing reject-side
checks in test_open_image.
In the else branch of hal_flash_write, off was computed once per call from
the original (call-time) "address" instead of the current position
"address + i". The word index dst_idx advanced with i, but the fill loop
kept starting at the stale off, so once destination address and source
buffer had different alignment mod 4, every word after the first was
filled at the wrong byte offset, dropping and misplacing data. Derive off
and dst from address + i so each word's offset tracks the current
position, mirroring the fix already applied to mcxa.c for the same bug
class (F-5963).
SIGN=NONE disables firmware signature authenticity verification
entirely (wolfBoot_verify_authenticity() becomes a stub that always
confirms), leaving only a hash check an attacker can satisfy. Every
other fail-safe-weakening option in this file (ALLOW_DOWNGRADE,
DISABLE_BACKUP, WOLFBOOT_UDS_UID_FALLBACK_FORTEST, FPGA_NONFATAL)
emits a $(warning ...) so the tradeoff is visible at build time;
SIGN=NONE was missing one despite being the most security-critical.
The scattered-ELF flash integrity check's final image_CT_compare()
rejection branch had zero unit test coverage, so a weakened comparison
(e.g. != 0 -> == 0, or a dropped return -2) would silently pass. Add a
positive case (correctly-hashed scattered image verifies OK) and a
negative case (single corrupted byte in a scattered segment's
flash-resident payload is rejected via the digest-mismatch branch).
arg2num() parsed --custom-tlv values with signed strtoll(), which
saturates to LLONG_MAX (0x7FFFFFFFFFFFFFFF) on positive overflow. For
LEN==8 no masking is applied afterwards (unlike LEN 1/2/4), so any
value >= 2^63 silently encoded as 0x7FFFFFFFFFFFFFFF instead of the
value the user supplied, breaking the TLV encode/decode roundtrip.
Switch to strtoull() and reject (exit 16) when it reports ERANGE for
an 8-byte value, mirroring the existing fw_version range check.
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.
bmpatch mmaps `base` sized to the source file (len1) then memcpy's the
reconstructed output into base+len3, which runs past the mapping once the
destination image is larger than the source (a common case for delta
updates). This causes writes into the zero-fill slack of the last mapped
page to be silently dropped (never synced to the file even after the
trailing ftruncate) or, for larger overruns, a SIGBUS. Reproduced with the
existing `delta-test` fixtures: patching 1.txt back to the (larger) 0.txt
already corrupts the tail of the output today.
Write reconstructed blocks with pwrite(fd1, ...) instead of memcpy into the
undersized mapping; regular file writes grow the file naturally instead of
faulting/silently discarding data past the mapped region. The mmap of
`base` is only used for reads, so this is a minimal, in-place-preserving
fix. Verified `make -C tools/delta delta-test` now passes both directions
(shrinking 0->1 and growing 1->0).
hal_flash_write() and hal_flash_erase() aligned the invalidation start
address down to a 32-byte cache line but rounded the length up from
"len" alone, omitting the (address - aligned_address) offset. Whenever
(address % 32) + (len % 32) > 32, the invalidated range fell short of
address + len, leaving the last cache line stale after a write/erase.
Extract the range computation into hal_flash_cache_align_range()
(hal/imx_rt.h, dependency-free so it's unit-testable without the
NXP SDK) and include the down-alignment offset before rounding the
length up, so the invalidated range always covers [address, address+len).
FPGA_NONFATAL downgrades a failed PL bitstream load from fatal (panic)
to a logged non-fatal warning, letting boot continue without the
programmable logic. Every other fail-safe-weakening option in this
file (ALLOW_DOWNGRADE, DISABLE_BACKUP,
WOLFBOOT_UDS_UID_FALLBACK_FORTEST) emits a $(warning ...) so the
tradeoff is visible at build time; FPGA_NONFATAL was missing one.
test_hwswap_highversion_rollback_denied was the only test for
update_flash_hwswap.c's wolfBoot_start and always ends in boot_panic,
so the entire successful-boot path (IMG_STATE_UPDATING->TESTING
transitions before and after the HW dualbank swap, and the same-version
boundary of the anti-rollback check) was never exercised and mutations
there would go undetected. Add three tests that reach do_boot() and pin
each of those code paths.
otp-keystore-gen.c reads the device root UDS into the stack buffer
`uds` and copies it into the heap buffer `otp_buf` at OTP_UDS_OFFSET,
then on every exit path calls free(otp_buf) without wiping it first,
and never clears `uds`. Both copies of the highest-value device secret
remain in the host process's freed heap chunk and stack frame.
Add a local secure_zero() helper (no wolfSSL dependency, matching this
standalone host tool's existing bare-gcc build) and call it on the
success path and on the write-failure/short-UDS-read error paths,
before free()/exit(), mirroring the zeroize-before-release pattern
already used elsewhere in this tree (src/x86/ata.c,
src/x86/ahci.c). Paths that exit before `uds` is populated are left
untouched since there is no secret to wipe yet.
In the unaligned/partial-word path, address/len were advanced by the full
flash-word-relative loop index "i" (which starts at start_off), instead of
by the number of data bytes actually consumed (i - start_off). On a write
spanning more than one flash word this drops start_off bytes of input data
and misdirects the following word write. Mirrors the already-correct form
in hal/kinetis_kl26.c.
A matched block's source offset is encoded as off[0..2] right after the
ESC (0x7f) header marker. When the match offset falls in
[0x7f0000, 0x7fffff], off[0] is 0x7f, so the header begins with ESC ESC
-- the same two bytes wb_patch uses to decode an escaped literal 0x7f.
The decoder then emits a single literal byte, consumes only 2 of the 6
header bytes, and desyncs the rest of the stream, breaking the
wb_patch(wb_diff(A,B)) == B roundtrip for base images >= ~8MB (a
supported MMU/Linux delta-update configuration).
Make wb_diff skip any candidate match whose offset's most-significant
byte equals ESC, in both the forward (base-image) and backward
(previously-patched-image) search paths, so the ambiguous header is
never produced; the position falls back to literal encoding instead.
security_command_passphrase() copies the plaintext disk-unlock secret
into the file-static DMA buffer `buffer` at ATA_SECURITY_PASSWORD_OFFSET
but never wipes it. On the slot<0 early return no ATA command is even
dispatched, and on failures of ata_security_set_password()/
ata_security_unlock_device() (which sata_unlock_disk() turns into a
panic()) no later IDENTIFY DMA ever overwrites it, so the secret is
left resident in BSS for as long as the device stays powered.
Zeroize the password field on the slot<0 path and after synchronous
command completion. The async path (used only by the currently
unreachable ata_security_erase_unit()) is left untouched because the
HBA may still be DMAing out of the buffer when exec_cmd_slot_ex()
returns ATA_ERR_BUSY; wiping it there would race the transfer.
sata_unlock_disk() calls panic() directly when the post-unlock ATA
security state doesn't match the expected SEC5/SEC6, bypassing the
cleanup: label that zeroizes the plaintext secret[] stack buffer. On
x86, panic() is an infinite hlt() loop, so the plaintext disk-unlock
secret (TPM-unsealed key or password) remains readable in DRAM for as
long as the device stays halted. Zeroize secret[] before panic() on
this path, mirroring the existing cleanup zeroization.
Add tests asserting rejection on a mismatched pubkey hint and on a
short digestSz from wolfTPM2_NVReadAuth, so a weakened comparison
(e.g. == 0 -> >= 0, or a dropped clause) is caught.
cache_commit() is the single choke point through which every psa_store
helper (create_object, delete_object, wolfPSA_Store_Write's sector loop,
update_store_size, erase_object_payload, check_vault) flushes the static
cached_sector staging buffer to flash. The buffer was never cleared, so
key-object plaintext staged there could remain resident in static SRAM
after the store operation returned. Wipe cached_sector with wc_ForceZero
right after the flash write completes, mirroring the existing pattern in
src/wolfhsm_flash_hal.c.
The sbom target fed `cc -dM -E` an empty /dev/null with only
WOLFSSL_USER_SETTINGS, so gen-sbom's build properties collapsed to host
compiler builtins (all filtered out as noise) and the SBOM did not reflect
the selected configuration. Harvest the actual -D config macros from CFLAGS
(filtered in the shell so defines whose values contain spaces, e.g.
BOOTLOADER_PARTITION_SIZE=$((...)), survive intact) so the SBOM captures the
real TARGET/SIGN/HASH/feature set.
Also:
- add preflight checks for empty WOLFBOOT_VERSION and missing GEN_SBOM
- pass sources via --srcs-file to avoid ARG_MAX on large configs
- include .S sources in the source set, not just .c
- make HOSTCC and GEN_SBOM overridable
- README: soften "CRA Compliance" wording, use wolfboot-<version>.* names,
fix dangling wolfssl/doc/CRA.md link to point at the wolfSSL CRA Kit
Signed-off-by: Sameeh Jubran <sameeh.j@gmail.com>
Adds sbom target that produces CycloneDX and SPDX
SBOM files for a specific TARGET+SIGN combination.
Sources extracted from OBJS variable via wildcard.
wolfcrypt sources compiled into wolfBoot are listed
as wolfBoot's own sources (not a separate component).
Requires GEN_SBOM or WOLFBOOT_LIB_WOLFSSL set.