Repository navigation
Write only changed token objects to storage - #257
Conversation
Store ids are list positions, and C_CreateObject and C_DestroyObject rewrote every token object. With a flash backed custom store, such as wolfBoot's, each write got slower as the token grew. A create now writes the new object and the token record. A destroy moves the newest object into the freed position, so at most that one object is rewritten. C_SetAttributeValue writes the changed token object, as the next create or destroy no longer does.
There was a problem hiding this comment.
🟡 Changes recommended
Storage pauses can cause inconsistent reloads, and persistence failures leave failed attribute updates applied.
2 open findings
What changed in this PR
Reduces token-storage writes by persisting only affected objects, while retaining full writes for token-wide operations.
Changes:
- Adds selective storage passes for object creation and deletion.
- Persists token-object attribute changes immediately.
- Adds regression coverage for unchanged records and reload behavior.
| File | Description |
|---|---|
| wolfpkcs11/internal.h | Declares the object persistence helper. |
| tests/token_partial_store_test.c | Tests selective writes, deletion, and reloads. |
| tests/include.am | Registers and links the new test. |
| src/internal.c | Implements selective persistence and object relocation. |
| src/crypto.c | Persists successful attribute updates. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
With only changed objects written, a store pass that was skipped, because WOLFPKCS11_NO_STORE was set, or that failed was no longer repaired by the next create or destroy rewriting everything. Mark the token when that happens so its next store pass writes all objects. C_SetAttributeValue no longer fails when its write fails. The change is already live, and the marked token has it written by the next store pass or C_Finalize, as happened before attribute changes were written here. Also fix the test build with GCC format truncation checks, and test changes made while storage is turned off.
Frauschi
left a comment
There was a problem hiding this comment.
Nice speed-up, and moving the head into the freed slot keeps the format unchanged. I traced the relink by hand and the moved object lands on the freed id in every case, and the full test passes locally with --enable-debug. One durability point on destroy (pre-existing, but the description says otherwise) and a few smaller things inline.
A destroy removed the object's records before writing the token, so a pass that failed left the token record pointing at a missing id and the next C_Initialize failed. Write the moved object and the token record first, and only then remove what is no longer used: the destroyed head's records, or the moved object's records under its old id and the destroyed object's key record when its store type differs from the moved object's. Also log a failed write of a changed object in debug builds, update the slot store doc blocks, and test destroys and attribute changes whose store fails.


A token object's store id is its position in the token's object list, and both
WP11_Session_AddObjectandWP11_Session_RemoveObjectByHandlecalledwp11_Slot_Store, which writes the token record and every object (two records each).WP11_STORE_BATCHonly covers the built-in file store, so with a custom store every record is a separate write. With wolfBoot's TrustZone flash store each record rewrite costs several sector erase/program cycles, so every create got slower as the token filled up.wp11_Token_StorePasstakes aWP11_STORE_PASS_*selector: all objects (as before), the token record plus one object, or one object only.wp11_Token_Storeandwp11_Slot_Storekeep their full-store behavior for C_InitToken, PIN changes, re-keying and C_Finalize.C_SetAttributeValueon a token object now writes that object (WP11_Object_Persist). Until now attribute changes reached storage only at the next full rewrite or C_Finalize. If the write fails the call still succeeds, since the change is already live, and the marked token (below) has it written by the next store pass or C_Finalize.WOLFPKCS11_NO_STOREset at run time) or fails marks the token (storeDirty), so its next store pass writes the token record and every object. The full rewrites used to repair this on the next create or destroy. A destroy also keeps the moved object's old records when its new copy was not written.Behavior changes worth reviewing:
C_FindObjectscan return objects in a different order (the moved object changes place). PKCS#11 does not define the order.unbound) are now rebound when that object is next written, not on any create or destroy.New test
tests/token_partial_store_test.c(file store). It checks that a create leaves the other object records untouched (same inode). It copies the store directory before C_Finalize, after creates, destroys (oldest, middle, newest) and a label change, then reloads from that copy. It checks the values, the renamed label, that destroyed objects are gone, and that no records are left behind. It fails on master at the first check. A second phase destroys, renames and creates objects whileWOLFPKCS11_NO_STOREis set, creates one more after unsetting it, and reloads from storage before C_Finalize. It fails withoutstoreDirty(the rename and the moved object are lost). In debug builds a third phase usesWP11_Test_StoreCloseFailAfterto fail the store of a destroy, a rename and aCKA_ENCRYPTchange on the AES key. Each reloads unchanged from a copy taken before C_Finalize, and is caught up by the next create. The destroy case fails with the previous ordering (C_Initialize returns CKR_FUNCTION_FAILED).Testing:
make checkpasses in the default,--enable-nssand--enable-debugconfigs; the pre-commit hookmake testpasses. Merged with current master,--enable-debugmake checkpasses too.WOLFPKCS11_TPM_STORE. The TPM NV reservation of the token record still runs on every pass that writes the token record.ZD 21908