Skip to content

Report failed flash commits from the PKCS#11 keyvault, check STM32U5 flash errors - #957

Open
LinuxJedi wants to merge 4 commits into
wolfSSL:masterfrom
LinuxJedi:pkcs11-store-flash-errors
Open

LinuxJedi wants to merge 4 commits into
wolfSSL:masterfrom
LinuxJedi:pkcs11-store-flash-errors

Conversation

@LinuxJedi

@LinuxJedi LinuxJedi commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

A flash erase or program that fails while the PKCS#11 keyvault (src/pkcs11_store.c) commits a record went unnoticed. The store ignored the return values of hal_flash_erase() and hal_flash_write(), wolfPKCS11's wolfPKCS11_Store_Close() returns void, and on STM32U5 the HAL returned 0 anyway. wolfPKCS11 therefore treated the record as stored and did not write it again (since wolfSSL/wolfPKCS11#257 it only rewrites everything after a store that failed).

pkcs11_store: erase and program failures in cache_commit_entry(), restore_backup() and check_vault() are counted. A store window records the count at wolfPKCS11_Store_Open(), and the new wolfPKCS11_Store_CloseCommit() returns BUFFER_E when it changed during the window. wolfPKCS11_Store_Remove() does the same. The sector cache is shared, so every window open during a failure reports it; a window that lost nothing only makes wolfPKCS11 write all records again. The commit sequence itself (backup sector, then destination) is unchanged. user_settings.h defines WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT, which wolfSSL/wolfPKCS11#265 adds: a wolfPKCS11 with it closes written records through wolfPKCS11_Store_CloseCommit() and writes the token again after a failure. The pinned wolfPKCS11 ignores the define and keeps calling wolfPKCS11_Store_Close(), so this can merge before or after it. wolfPKCS11_Store_CloseCommit(), which wolfPKCS11_Store_Close() calls, returns 0 for a NULL store: wolfPKCS11 2.2.0 and earlier close a data record even when opening it failed, and the close wrote through the handle (wolfSSL/wolfPKCS11#265 stops wolfPKCS11 doing that).

stm32u5: hal_flash_write() and hal_flash_erase() now check OPERR, PROGERR, WRPERR, PGAERR, SIZERR and PGSERR after each quad-word and each page, clear PG or PER and the flags, and return -1. The status is read before EOP is cleared, since clearing EOP writes the error flags back and clears them too. Both return -1 for a negative length or a span not inside the flash, where hal_flash_write() used to return 0 (TrustZone) or not check at all, and hal_flash_erase() returned 0 for pages past the top. docs/HAL.md notes that hal_flash_erase() returns non-zero on failure and that the keyvault depends on both functions checking the error flags.

Testing:

  • unit-stm32u5-write also extracts hal_flash_erase(). Its mock wait erases pages and can fail one operation with chosen flags. New tests cover each error flag for write and erase, flags that are not errors, and out-of-range spans. The error and range tests fail on the HAL without the checks.
  • unit-pkcs11_store (and -stalecache): new tests cover a failed write or erase in a window's close, a failure seen by every open window but not by a later one, wolfPKCS11_Store_Remove(), and failures while formatting the vault or restoring the header from the backup sector. They fail on the store without the tracking. The mock flash gains a one-shot hal_flash_erase_fail. Another test closes a NULL store, which faults without the check.
  • The whole unit suite, in wolfboot-ci-sim:v1.0, gives the same results on master and on this branch (223 pass; the same 7 tests fail on both for lack of tools or SDKs in that image).
  • In wolfboot-ci-m33mu:v1.25, the stm32h5-tz PKCS11_TESTAPP=1 run passes and stm32u5-wolfcrypt-tz.config builds, with the pinned wolfPKCS11 and with Let custom stores report records that did not commit wolfPKCS11#265 (which then calls wolfPKCS11_Store_CloseCommit()).
  • On an STM32U585 (wolfDemo board, TrustZone on, stm32u5-wolfcrypt-tz.config), these commits on top of v2.10.0 with stm32u5: accept secure alias addresses in hal_flash_write/erase #943 and stm32u5: skip programming quad-words that are erased and stay erased #944, and a 2.1.0 backport of Let custom stores report records that did not commit wolfPKCS11#265: the PKCS#11 store and HAL code is the same as here. Creating 8 token objects, reading them back and destroying them across reboots raised no flash errors, with the same commit counts and times as before. With the keyvault pages write-protected (WRP1A), the HAL failed the erases and writes, and each C_DestroyObject returned CKR_FUNCTION_FAILED instead of CKR_OK; once the protection was removed, the objects were intact and could be destroyed. A firmware update (swap) was not run on the board, so that use of the U5 HAL checks is covered by the unit tests only.

ZD 21908

hal_flash_write() never read the FLASH_SR error flags and returned 0
whatever happened. With TrustZone it also returned 0 for an address out
of range, and without TrustZone it did not check the address at all.
hal_flash_erase() failed only for a zero length or an address below the
flash, and returned 0 for pages past the top of the flash.

Check OPERR, PROGERR, WRPERR, PGAERR, SIZERR and PGSERR after each
quad-word program and each page erase. On an error, clear PG or PER and
the flags, and return -1. The status is read once before EOP is
cleared, because clearing EOP writes the error flags back as well,
which clears them. Both functions now return -1 for a negative length
or a span that is not all inside the flash, before touching it.

The STM32U5 write test now extracts hal_flash_erase() as well. Its flash
is two 8 KB pages, and the mock hal_flash_wait_complete() erases the
page selected in FLASH_CR on STRT. It can also fail one program or
erase with chosen FLASH_SR flags, leaving the flash unchanged. The host
register file cannot clear write-1-to-clear flags, so the mock sets
FLASH_NS_SR to the result of each operation. New tests check that each
error flag stops a write on the unit that set it and an erase on the
page that set it, with PG or PER cleared, that other flags do not fail
a write, and that ranges outside the flash are refused before anything
changes. The error and range tests fail on the HAL without the checks.
The keyvault ignored the return values of hal_flash_erase() and
hal_flash_write(), and wolfPKCS11_Store_Close() returns void, so a
record that did not reach flash looked committed. wolfPKCS11 then had
no reason to write it again.

Count erase and program failures in cache_commit_entry(),
restore_backup() and check_vault(). A store window records the count at
wolfPKCS11_Store_Open(), and the new wolfPKCS11_Store_CloseCommit()
returns BUFFER_E when it changed during the window.
wolfPKCS11_Store_Remove() does the same. The sector cache is shared, so
every window open during a failure reports it; a window that lost
nothing only makes wolfPKCS11 write all records again. The commit
sequence itself is unchanged.

user_settings.h defines WOLFPKCS11_CUSTOM_STORE_CLOSE_COMMIT, so a
wolfPKCS11 that supports it closes written records with
wolfPKCS11_Store_CloseCommit() and marks the token for a full store when
it fails. The pinned wolfPKCS11 ignores the define and keeps calling
wolfPKCS11_Store_Close(). docs/HAL.md now says that hal_flash_erase()
returns non-zero on failure, and why the keyvault needs both functions
to check the flash error flags.
The mock flash gains hal_flash_erase_fail, a one-shot erase failure like
hal_flash_write_fail. New tests check that a failed write or erase while
a window commits fails its close and not the next window's, that a
failure is reported by every window open at the time but not by one
opened later, that wolfPKCS11_Store_Remove() reports a failure and
returns NOT_AVAILABLE_E only for a missing object, and that failures
while Store_Open formats a blank vault or restores the header from the
backup sector belong to that window. They fail on the store without
failure tracking.
@LinuxJedi

Copy link
Copy Markdown
Member Author

@claude review

@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 #957

Scan targets checked: wolfboot-src, wolfboot-bugs
Not reviewed (no scan target covers them): docs/HAL.md, hal/stm32u5.h, tools/unit-tests/Makefile
Coverage: 2 of 6 in-scope changed file(s) opened by the reviewer; not opened: include/user_settings.h, tools/unit-tests/unit-mock-flash.c, tools/unit-tests/unit-pkcs11_store.c, tools/unit-tests/unit-stm32u5-write.c

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Lite

@claude claude 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.

I reviewed this PR and didn't find any bugs. It changes how the STM32U5 flash HAL reports errors, and that HAL is used during boot and updates. You also note it hasn't been tested on hardware, so a human look and a check on a board would still be worthwhile.

What was reviewed:

  • hal_flash_write/hal_flash_erase in hal/stm32u5.c: the new range checks, and checking FLASH_SR_ERRORS after each quad-word or page. Write reads SR once before clearing EOP, so the error flags aren't lost. Erase picks the secure or non-secure sr to match cr for each bank.
  • src/pkcs11_store.c: the failure counter in cache_commit_entry, restore_backup and check_vault. The window baseline is taken before Open's own check_vault. In Store_Remove, STORE_FLASH_E is now checked before NOT_AVAILABLE_E, and the payload is still erased before the object is deleted.
  • Store_Close now wraps CloseCommit, so the pinned wolfPKCS11 behaves the same whether or not it knows the new define.
Extended reasoning...

The change touches the STM32U5 flash program/erase HAL in hal/stm32u5.c, which runs from RAM and has TrustZone secure and non-secure register paths. It also touches the secure PKCS#11 keyvault commit path in src/pkcs11_store.c, plus a new wolfPKCS11 integration define in user_settings.h. Both are security-sensitive: the HAL is used for firmware updates, and the keyvault holds key material. Write and erase now return -1 for errors and out-of-range spans where they used to return 0. Error flags set on TrustZone bank-1 writes will now fail operations that used to succeed. The author says the change hasn't been checked on hardware, so it should not be approved without a human review.

wolfPKCS11 2.2.0 and earlier close a data record even when opening it
failed, and pass NULL. wolfPKCS11_Store_CloseCommit(), which
wolfPKCS11_Store_Close() calls, wrote through the handle and faulted.
It now returns 0 for NULL. unit-pkcs11_store covers both closes.
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