Skip to content

Fenrir fixes 2026 06 11#795

Merged
mattia-moffa merged 23 commits into
wolfSSL:masterfrom
danielinux:fenrir-fixes-2026-06-11
Jun 11, 2026
Merged

Fenrir fixes 2026 06 11#795
mattia-moffa merged 23 commits into
wolfSSL:masterfrom
danielinux:fenrir-fixes-2026-06-11

Conversation

@danielinux

@danielinux danielinux commented Jun 11, 2026

Copy link
Copy Markdown
Member

Fix remaining low/info level findings.

809b98d F-3734: add unit tests for magic check and item_count boundary in flash_otp_keystore
405a142 F-3740: add roundtrip test for shrinking firmware delta (size_a > size_b)
333055d F-3967: propagate HAL_Spi_Transmit errors in FRAM_Write and FRAM_Init
e9b70fe F-3969: warn when WOLFBOOT_UDS_UID_FALLBACK_FORTEST=1 weakens UDS to public device UID
f600fe9 F-4968: fix wc_FreeRsaKey missing on early-return paths in wolfHSM RSA verify
524397d F-5675: fix signed-overflow UB from uint8_t<<24 in serve_update version assembly
5cec621 F-3735: fix EndingAddress uint32_t* arithmetic in x86_64_efi_do_boot
2365e45 F-3736: fix pA_ver/pB_ver int/uint32_t mismatch in update_disk wolfBoot_start
ddf05a9 F-3738: fix policySz uint16_t underflow in wolfBoot_unseal_blob for short policy
a10a993 F-3741: zeroize digest and hash in uds_from_uid for stm32l5/stm32h5
ef85a1e F-3962: fix hal_flash_erase loop bound in stm32u3.c skipping final partial page
4b95dc2 F-3963: fix hal_flash_erase loop bound in stm32c0.c skipping final partial page
2f8f0c6 F-3964: fix hal_flash_erase loop bound in stm32g0.c skipping final partial page
15e4b81 F-3965: fix hal_flash_erase loop bound in stm32l0.c skipping final partial page
2f53d0d F-3966: fix hal_flash_erase loop bound in stm32wb.c skipping final partial page
889788d F-3968: emit build-time warning when ALLOW_DOWNGRADE=1 is set
1690522 F-3970: serialise custom TLV integer values through header_store_u64_le
61dbd25 F-3972: add direction-sensitive unit tests for wolfBoot_dualboot_candidate_addr
108a33c F-3973: validate in_vec[N].len before dereferencing typed PS IPC pointers
e81f262 F-3974: zero ENCRYPT_CACHE stack buffer before return in hal_set_key
1016c4d F-4410: fix uint32_t->int cast bypassing anti-rollback in update_ram wolfBoot_start
2ee4108 F-4411: fix uint32_t->int cast bypassing anti-rollback in hwswap wolfBoot_start

…Boot_start

update_flash_hwswap.c stored the firmware versions into int locals via
(int)wolfBoot_current_firmware_version() / (int)wolfBoot_update_firmware_version().
Under WOLFBOOT_FIXED_PARTITIONS those macros resolve to wolfBoot_get_image_version(),
which returns uint32_t. Versions >= 0x80000000 became negative when cast to int,
failed the ">= 0" clamp, and left boot_v/update_v at 0. With both partition
versions above INT_MAX, max_v collapsed to 0 and the rollback guard
"(max_v > 0U) && (active_v < max_v)" was silently skipped, allowing a rolled-back
image to be staged. This was a regression introduced in 90670fc; the original
code used uint32_t directly.

wolfBoot_get_blob_version() returns uint32_t with 0 as its only "invalid" value,
so the negative-clamp logic was both unnecessary and harmful. Restore the direct
uint32_t reads.

Add tools/unit-tests/unit-update-flash-hwswap.c which drives wolfBoot_start with
BOOT (v0x80000002, unbootable) and UPDATE (v0x80000001, valid): the boot path
falls back to UPDATE and must deny the downgrade. The test fails before the fix
(do_boot reached) and passes after.
…wolfBoot_start

update_ram.c stored the firmware versions into int locals via
(int)wolfBoot_current_firmware_version() / (int)wolfBoot_update_firmware_version().
Under WOLFBOOT_FIXED_PARTITIONS those macros resolve to wolfBoot_get_image_version(),
which returns uint32_t. Versions >= 0x80000000 became negative when cast to int,
failed the ">= 0" clamp, and left boot_v/update_v at 0. With both partition
versions above INT_MAX, max_v collapsed to 0 and the rollback guard
"(max_v > 0U) && (active_v < max_v)" was silently skipped, allowing a rolled-back
image to be staged after a fallback. Same fundamental pattern as F-4411 (hwswap)
and the documented 3736 (update_disk).

wolfBoot_get_image_version() returns uint32_t with 0 as its only "invalid" value,
so the negative-clamp logic was both unnecessary and harmful. Use the direct
uint32_t reads.

Add tools/unit-tests/unit-update-ram-noramboot.c, a WOLFBOOT_NO_RAMBOOT build of
wolfBoot_start (the configuration that uses wolfBoot_open_image() and therefore
actually reaches the rollback guard). The rollback test drives BOOT
(v0x80000005, marked oversize so its open is rejected) and UPDATE (v0x80000003,
valid): the boot path falls back to UPDATE and must deny the downgrade. The test
fails before the fix (UPDATE staged, do_boot reached) and passes after. The
existing unit-update-ram target uses the RAMBOOT path, where wolfBoot_ramboot()
has its own separate int-cast of the version that rejects all high-version
images before the guard is reached; that path cannot exercise this guard and is
left unchanged.
The firmware encryption key and nonce were left in the NVM_CACHE_SIZE
stack buffer after hal_flash_write returned, until the frame was
naturally overwritten. Add ForceZero guarded by the same preprocessor
condition that declares the stack-local ENCRYPT_CACHE.
…ters

All four PS dispatch arms (PS_SET, PS_GET, PS_GET_INFO, PS_REMOVE) cast
in_vec[N].base to a typed pointer but only checked for NULL, not that
the caller-supplied .len covers the target type.  An NS caller could pass
len=0 (or len < sizeof) with a valid non-NULL base, causing the secure
side to read past the declared buffer.  Mirror the pattern already used
in wolfboot_crypto_dispatch (F-3541) and the attestation handler.
…idate_addr

Equal-version and newer-update cases were untested, allowing mutations of the
comparator or the addr assignment at libwolfboot.c:1446-1448 to survive. Add
two tests to unit-update-ram-nofixed.c that directly verify retval and *addr
for boot_v==update_v (must prefer primary) and update_v>boot_v (must prefer
update partition).
The --custom-tlv TAG LEN VAL path in make_header_ex() passed
&CMD.custom_tlv[i].val (a raw uint64_t pointer) directly to
header_append_tag(), which does a memcpy.  On big-endian build hosts the
first LEN bytes of the uint64_t are the high bytes, producing the wrong
LE encoding (e.g. 4-byte value 0x12345678 encodes as 00 00 00 00).  The
fix serialises through header_store_u64_le() before calling
header_append_tag(), matching the pattern already used by
header_append_tag_u16/u32/u64 for all system TLVs.

Add unit-sign-custom-tlv-le.py to verify the LE byte encoding of 4-byte
and 8-byte custom TLV values in the signed image header.
Mirror the DISABLE_BACKUP treatment: print a $(warning ...) so the
build operator gets an unmistakable signal that anti-rollback
enforcement has been disabled.
…rtial page

end_address = address + len - 1 (inclusive) combined with p < end_address
(strict less-than) skipped the last page when len % FLASH_PAGE_SIZE == 1.
Use the exclusive form end_address = address + len, mirroring the stm32h5
fix.  Add WOLFBOOT_UNIT_TEST_FLASH_ERASE guards and a new unit test that
proves the regression before the fix and passes after.
…rtial page

end_address was computed as address + len - 1 (inclusive) but paired with a
strict p < end_address guard, so the last page was skipped whenever len was
not a multiple of FLASH_PAGE_SIZE.  Change end_address to the exclusive form
address + len so the loop covers all pages that overlap the requested range.

Add unit-flash-erase-l0 regression test (mirrors the unit-flash-erase-wb
harness) with guards in hal/stm32l0.c allowing the HAL to be compiled on the
host; the unaligned-len test fails before the fix and passes after.
…rtial page

end_address was computed as address + len - 1 (inclusive) but the loop
used strict less-than, so the final page was skipped when len was not a
multiple of FLASH_PAGE_SIZE. Use an exclusive end (address + len) to
match the fix applied to stm32h5.c, stm32wb.c, and stm32l0.c.

Add WOLFBOOT_UNIT_TEST_FLASH_ERASE guard to hal/stm32g0.c and a new
unit test (unit-flash-erase-g0) that proves the regression and confirms
the fix.
…rtial page

end_address = address + len - 1 (inclusive) with p < end_address (strict) skips
the final page when len is not a multiple of FLASH_PAGE_SIZE.  Change to exclusive
end_address = address + len, matching the fix applied to stm32g0/l0/wb.  Add
WOLFBOOT_UNIT_TEST_FLASH_ERASE guards so the function can be included in isolation
on the host, and add unit-flash-erase-c0 to the test suite.
…rtial page

end_address was computed as address + len - 1 (inclusive last byte) but the
loop used strict p < end_address, so a non-page-aligned len caused the final
page to be skipped.  Change to end_address = address + len (exclusive) to
match stm32u5.c, the claimed reference implementation.

Add WOLFBOOT_UNIT_TEST_FLASH_ERASE guards to hal/stm32u3.c and hal/stm32u3.h
(DMB/ISB/DSB, FLASH_NS_CR/SR register macros, and all non-erase functions) so
that hal_flash_erase() can be compiled and tested in isolation on the host.
Add tools/unit-tests/unit-flash-erase-u3.c with four check-based tests; the
regression case (len = PAGE_SIZE + 1) failed before this fix.
Add hal_secret_zeroize calls for the digest buffer and SHA context
after memcpy in uds_from_uid, matching the existing pattern applied
to the OTP path in hal_uds_derive_key. Also moves/adds hal_secret_zeroize
definition before uds_from_uid so it is available at the call site.
…hort policy

The guard `policySz <= 0` was dead for unsigned uint16_t, so values 1-3
passed. The subsequent `policySz -= sizeof(pcrMask)` (4) then wrapped to
65533-65535, passing a garbage length to wolfTPM2_VerifyHashTicket. Replace
the guard with `policySz < sizeof(pcrMask)` to reject policies too short to
hold a pcrMask.
…ot_start

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.
boot_addr is uint32_t*, so (boot_addr + *size) advanced *size * 4 bytes
instead of *size bytes, overstating the MEMMAP_DEVICE_PATH range by 4x
relative to the SourceSize passed to LoadImage. Cast to uint8_t* first
so the addition is byte-accurate and consistent with the SourceSize argument.

No unit test: compiling hal/x86_64_efi.c in a host test requires stubbing
the complete UEFI SDK type tree (EFI_FILE_IO_INTERFACE, EFI_LOADED_IMAGE,
EFI_GUID, CHAR16, LibFileInfo, FreePool, InitializeLib, …) throughout the
file — disproportionate scaffolding for a one-line arithmetic fix.
…on assembly

Shifting a uint8_t (int-promoted) left by 24 is undefined behaviour per C11
6.5.7p4 when the byte value is >= 0x80. Cast the MSB operand to uint32_t at
both sites (CMD_APP_VER line 391, CMD_HDR_VER line 412).

Note: the report's endian-mismatch claim is incorrect. CMD_APP_VER senders
(app_nrf52.c, app_stm32f4.c, app_stm32wb.c) all transmit version bytes
MSB-first; CMD_HDR_VER is sent LSB-first by uart_send_current_version. The
two decode expressions are intentionally asymmetric and both reconstruct the
correct version value.
…A verify

Under WOLFBOOT_ENABLE_WOLFHSM_CLIENT/SERVER, wc_InitRsaKey_ex at line 471
registers state with the wolfHSM subsystem, but four early-return paths in
wolfBoot_verify_signature_rsa_common bypassed the wc_FreeRsaKey at line 571:
RsaSetKeyId failure on the PUBKEY_ID path, KeyCache failure, RsaSetKeyId
failure on the non-PUBKEY_ID path, and KeyEvict failure post-verify.
Add wc_FreeRsaKey before each of these returns.
…public device UID

Add a $(warning ...) in options.mk matching the existing pattern used
for ALLOW_DOWNGRADE and DISABLE_BACKUP, so operators are alerted at
build time that UDS (and all derived DICE/PSA secrets) is being rooted
in the publicly-readable STM32 factory UID rather than OTP fuses.
WREN and address-phase return codes were silently overwritten by
subsequent assignments; a failed WREN would let the write proceed
with the WEL bit unset, causing a silently dropped write while
returning hal_status_ok. Add early-return checks after each transmit
phase in FRAM_Write and guard the subsequent calls in FRAM_Init.
…e_b)

Adds test_wb_patch_and_diff_shrinking_update to cover the previously
untested case where the target image is smaller than the base image
(3077 -> 2048 bytes), exercising distinct wb_diff loop paths when
off_b reaches size_b before size_a is exhausted.
…sh_otp_keystore

Cover the previously untested mutation points in keystore_num_pubkeys(): bad
magic returns 0, item_count > KEYSTORE_MAX_PUBKEYS returns 0, item_count ==
KEYSTORE_MAX_PUBKEYS is accepted (guards > vs >= mutation), and slot read
failure propagates through all keystore_get_* accessors.
  In hal/va416x0.c, FRAM_Write now explicitly aborts the split SPI write transaction on command-phase failure before returning, instead of leaving the bus in the half-open state introduced
  by the early return. I also added a host-side regression test in tools/unit-tests/unit-va416x0-fram.c that injects a command-phase HAL_Spi_Transmit(..., false) failure and verifies a later
  write still reaches the closing ...true phase.

  In src/arm_tee_psa_ipc.c, I extracted the protected-storage dispatch path into a small helper so it can be tested directly without changing runtime behavior. The new test in tools/unit-
  tests/unit-arm-tee-psa-ipc.c covers the short-vector invalid-argument branches for SET, GET, GET_INFO, and REMOVE, plus a full success path across those operations. The unit harness
  additions are wired up in tools/unit-tests/Makefile and use a tiny local CMSE stub in tools/unit-tests/arm_cmse.h.
@danielinux danielinux self-assigned this Jun 11, 2026

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #795

Scan targets checked: wolfboot-bugs, wolfboot-src

No new issues found in the changed files. ✅

@danielinux danielinux requested a review from mattia-moffa June 11, 2026 19:18
@danielinux danielinux removed their assignment Jun 11, 2026
@mattia-moffa mattia-moffa merged commit 76aba8e into wolfSSL:master Jun 11, 2026
383 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants