Repository navigation
Keep object handles unique when object ids run out - #255
Merged
Merged
Conversation
An object handle is the object id with the onToken flag in bit 28, but the id counter was never bounded. Once it passed 0x0FFFFFFF the id spilled into the flag, so a session object could get the handle of a token object. Reading the session object then read the token object, C_DestroyObject on it deleted the token object and its store files, and C_CloseSession, C_CloseAllSessions and C_Finalize hit a heap-use-after-free: WP11_Session_RemoveObject chose the list from the handle, left the session object linked, and wp11_Session_Final freed it and looped back onto it. At 2^31 the counter also overflowed. WP11_Session_AddObject now fails with OBJ_COUNT_E (CKR_FUNCTION_FAILED) once the counter is past OBJ_ID_MAX. wp11_Token_Load rejects a stored counter that is negative or above OBJ_ID_MAX + 1. OBJ_ID_MAX + 1 is still accepted, because that is what a token that has used every id stores. Reaching the limit through the public API takes hundreds of millions of objects, so add a DEBUG_WOLFPKCS11-only hook, WP11_Test_SetNextObjId, and object_handle_limit_test. The test fills the ids, checks that handles stay distinct and that creation fails past the limit, that a key pair rolled back at the limit leaves no object behind, that a token which has used every id reloads, and, with file storage, that a stored counter past the limit is rejected.
Session and token objects took their ids from the one token counter, which is stored with the token and never goes back. Every session object a process created used up an id for good, so a busy server could run the token out of ids, after which no object of either kind could be created until the token was reinitialised, losing its keys. Session handles have no onToken flag, so they cannot clash with token handles and do not need ids from the same counter. Session objects now take ids from a counter in the slot, next to its session list. It starts at 1 in wp11_Slot_Init, so it starts again on each C_Initialize, when no session objects exist. The token counter now only moves for token objects. WP11_Test_SetNextObjId takes an onToken flag to pick the counter, and object_handle_limit_test now checks that running out of token ids does not stop session objects from being created and that session ids are available again after C_Initialize.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The regression test has a C++ linkage defect and can leave corrupted token storage behind in no-environment builds.
2 open findings
What changed in this PR
Prevents object-ID exhaustion from producing invalid or colliding PKCS#11 handles.
Changes:
- Bounds object IDs and validates persisted counters.
- Separates session IDs from persistent token IDs.
- Adds debug-only regression coverage for exhaustion, rollback, and reload behavior.
| File | Description |
|---|---|
| wolfpkcs11/internal.h | Declares the debug counter-setting hook. |
| tests/object_handle_limit_test.c | Tests handle limits and persistence behavior. |
| tests/include.am | Registers and links the regression test. |
| src/internal.c | Enforces ID limits and separates session allocation. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Rejecting a stored counter outside 1..OBJ_ID_MAX + 1 broke tokens written by v1.3.0. That version stored neither the token flags nor the counter. With file storage the read runs out of data and falls back, but a TPM NV index is fixed size, so the read returns whatever follows the token data and C_Initialize failed (storage-upgrade-test-tpm). Such a counter was never usable anyway. v1.3.0 gave token objects the ids 1, 2, ... and stored them in the objects, while the counter fell back to 1 (file storage) or took the bytes that followed (TPM storage), so new token objects could get the handles of existing ones. Treat a stored counter outside the range like a missing one, and raise the counter above the id of every token object loaded. The test now stores an out of range counter and checks that the reloaded token gives its next object the id after the remaining object's, then resets the token instead of leaving it corrupted in storage.
wolfpkcs11/internal.h declares WP11_Test_SetNextObjId inside an extern "C" block, but object_handle_limit_test declared it on its own. Built as C++ (CC=g++ with --enable-debug) that declaration asked for a mangled symbol the library does not export, so the test failed to link. Wrap it in the same extern "C" guard.
Frauschi
reviewed
Oct 19, 2026
Frauschi
left a comment
Member
There was a problem hiding this comment.
The cap, the separate session id counter and the counter recovery on load all look right to me. Two small doc comment nits inline.
WP11_Session_AddObject also returns OBJ_COUNT_E when the slot's session object ids run out, and WP11_Test_SetNextObjId describes the load as rejecting an out of range counter, which it now replaces.
Frauschi
approved these changes
Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


An object handle is the object id with the onToken flag in bit 28 (
OBJ_HANDLE(on, i)), but nothing bounded the id. Session and token objects shared one counter,token->nextObjId, which is stored with the token and never goes back. Once it passed0x0FFFFFFFthe id spilled into the flag. Reproduced on master 427a22c with a debug + ASAN build:OBJ_HANDLE(0, 0x10000001) == OBJ_HANDLE(1, 1)), soC_GetAttributeValueon it read the token object.C_DestroyObjecton that handle deleted the token object and its store files.CKR_OBJECT_HANDLE_INVALID.C_CloseSession,C_CloseAllSessionsandC_Finalizehit a heap-use-after-free.WP11_Session_RemoveObjectpicks the list from the handle (since Match the handle when removing an object by handle #252), so it missed the session object and left it linked, andwp11_Session_Finalfreed it and looped back onto it.nextObjId++was signed overflow.This takes about 268M objects. Because every session object used up a stored id for good, a long-running server creating keys per connection could get there.
Commits
Cap object ids so handles stay unique
OBJ_ID_MAX(0x0FFFFFFF).WP11_Session_AddObjectfails withOBJ_COUNT_E(CKR_FUNCTION_FAILED) once the counter is past it, for both token and session objects.wp11_Token_Loadrejects a stored counter that is negative or aboveOBJ_ID_MAX + 1(replaced by the third commit).OBJ_ID_MAX + 1itself is accepted, because that is what a token that has used every id stores.Give session objects their own id counter
slot->nextSessObjId, next to the slot's session list. It is guarded by the token lock and starts at 1 inwp11_Slot_Init, so it starts again on eachC_Initialize, when no session objects exist.Recover the token object counter from the loaded objects
storage-upgrade-test-tpm. v1.3.0 stored neither the token flags nor the counter. With file storage the read runs out of data and falls back, but a TPM NV index is fixed size, so the read returned whatever followed the token data andC_Initializefailed.1..OBJ_ID_MAX + 1is now treated like a missing one, and on load the counter is raised above the id of every token object.Give the test hook declaration C linkage
wolfpkcs11/internal.hdeclares the hook insideextern "C". The test's own declaration did not, so aCC=g++ --enable-debugbuild would fail to link (Copilot review).Behaviour changes
CKR_FUNCTION_FAILED. Token ids only come back throughC_InitToken. Session ids come back on the nextC_Initialize.C_Initializeinstead of continuing from the token's stored counter.Tests
Reaching the limit through the API takes hundreds of millions of objects, so this adds a
DEBUG_WOLFPKCS11-only hook,WP11_Test_SetNextObjId(slotId, onToken, nextObjId), andtests/object_handle_limit_test.c. The test:It runs in
--enable-debugbuilds (theunit-test.ymldebug and TPM debug jobs) and skips otherwise.Testing:
wp11_Session_Final. Putting session objects back on the shared counter fails 9 checks. Removing the counter recovery on load fails 2 checks: the new token object gets the remaining object's handle and shadows it.WOLFPKCS11_NO_STORE,WOLFPKCS11_TPM_STOREandWOLFPKCS11_NO_ENV.make testpasses on every commit (debug + ASAN and default configs). The TPM store path of the test and the v1.3.0 TPM upgrade have not been run locally. CI covers them.