From 6ab4aac36c52a9f6ce1739b86d05da628c089355 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Fri, 16 Oct 2026 11:04:31 +0100 Subject: [PATCH 1/5] Cap object ids so handles stay unique 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. --- src/internal.c | 47 +++- tests/include.am | 7 + tests/object_handle_limit_test.c | 390 +++++++++++++++++++++++++++++++ wolfpkcs11/internal.h | 1 + 4 files changed, 443 insertions(+), 2 deletions(-) create mode 100644 tests/object_handle_limit_test.c diff --git a/src/internal.c b/src/internal.c index e34c7a07..ee08b65b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -157,12 +157,15 @@ /* Determine session id from session handle. */ #define SESS_HANDLE_SESS_ID(s) ((s) & 0xffff) +/* Largest object id. The id is the low 28 bits of an object handle and the + * onToken flag is bit 28, so a larger id would change the flag. */ +#define OBJ_ID_MAX 0x0fffffff /* Create an object handle from a onToken and object id. */ #define OBJ_HANDLE(on, i) (((on) << 28) | (i)) /* Determine whether object is onToken from object handle. */ #define OBJ_HANDLE_ON_TOKEN(h) ((int)((h) >> 28)) /* Determine object id from object handle. */ -#define OBJ_HANDLE_OBJ_ID(h) ((h) & 0xfffffff) +#define OBJ_HANDLE_OBJ_ID(h) ((h) & OBJ_ID_MAX) /* SINGLE_THREADED alters wolfSSL struct layouts; only wolfSSL sets it. */ #if defined(SINGLE_THREADED) || defined(WOLFPKCS11_SINGLE_THREADED) @@ -9034,6 +9037,12 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) token->nextObjId = 1; ret = 0; } + /* OBJ_ID_MAX + 1 is a token that has used every id. Beyond + * that, ids no longer fit in a handle. */ + else if (ret == 0 && (token->nextObjId < 0 || + token->nextObjId > OBJ_ID_MAX + 1)) { + ret = BUFFER_E; + } } } @@ -12599,7 +12608,9 @@ WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)) * @param session [in] Session object. * @param onToken [in] Whether to put object on token. * @param object [in] Key Object object. - * @return 0 on success. + * @return OBJ_COUNT_E when the object list is full or the token has used + * every object id. + * 0 on success. */ int WP11_Session_AddObject(WP11_Session* session, int onToken, WP11_Object* object) @@ -12645,6 +12656,9 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, if (token->objCnt >= WP11_TOKEN_OBJECT_CNT_MAX) ret = OBJ_COUNT_E; + /* Ids are not reused, so a token can run out of them. */ + if (token->nextObjId > OBJ_ID_MAX) + ret = OBJ_COUNT_E; #ifndef WOLFPKCS11_NO_STORE if (ret == 0) ret = wp11_Object_Encode(object, 0); @@ -12688,6 +12702,8 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, ret = OBJ_COUNT_E; } #endif + if (token->nextObjId > OBJ_ID_MAX) + ret = OBJ_COUNT_E; if (ret == 0) { session->objCnt++; /* Get next item in list after this object has been added. */ @@ -12959,6 +12975,33 @@ WP11_API int WP11_Test_RemoveObjectStale(CK_SESSION_HANDLE hSession, return ret; } + +/** + * Test hook: set the id the next object on a slot's token will get. + * + * Running out of object ids takes hundreds of millions of objects through the + * public API, so a white-box test sets the counter close to the limit instead. + * The value is not checked, so a test can also store an out of range value and + * check that loading the token rejects it. + * + * @param slotId [in] Slot id. + * @param nextObjId [in] Next object id. + * @return BAD_FUNC_ARG when the slot is not found. + * 0 on success. + */ +WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId) +{ + WP11_Slot* slot; + + if (WP11_Slot_Get(slotId, &slot) != 0) + return BAD_FUNC_ARG; + + WP11_Lock_LockRW(&slot->token.lock); + slot->token.nextObjId = nextObjId; + WP11_Lock_UnlockRW(&slot->token.lock); + + return 0; +} #endif /* DEBUG_WOLFPKCS11 */ /** diff --git a/tests/include.am b/tests/include.am index ff1565f6..0f3d5964 100644 --- a/tests/include.am +++ b/tests/include.am @@ -260,6 +260,11 @@ noinst_PROGRAMS += tests/destroy_stale_handle_test tests_destroy_stale_handle_test_SOURCES = tests/destroy_stale_handle_test.c tests_destroy_stale_handle_test_LDADD = +check_PROGRAMS += tests/object_handle_limit_test +noinst_PROGRAMS += tests/object_handle_limit_test +tests_object_handle_limit_test_SOURCES = tests/object_handle_limit_test.c +tests_object_handle_limit_test_LDADD = + check_PROGRAMS += tests/hbs_persistence_test noinst_PROGRAMS += tests/hbs_persistence_test tests_hbs_persistence_test_SOURCES = tests/hbs_persistence_test.c @@ -453,6 +458,7 @@ tests_logout_token_key_zero_test_LDADD += src/libwolfpkcs11.la tests_tpm_decode_bounds_test_LDADD += src/libwolfpkcs11.la tests_concurrent_destroy_object_test_LDADD += src/libwolfpkcs11.la tests_destroy_stale_handle_test_LDADD += src/libwolfpkcs11.la +tests_object_handle_limit_test_LDADD += src/libwolfpkcs11.la tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la tests_unique_id_test_LDADD += src/libwolfpkcs11.la @@ -526,6 +532,7 @@ tests_logout_token_key_zero_test_LDADD += src/libwolfpkcs11.la tests_tpm_decode_bounds_test_LDADD += src/libwolfpkcs11.la tests_concurrent_destroy_object_test_LDADD += src/libwolfpkcs11.la tests_destroy_stale_handle_test_LDADD += src/libwolfpkcs11.la +tests_object_handle_limit_test_LDADD += src/libwolfpkcs11.la tests_hbs_persistence_test_LDADD += src/libwolfpkcs11.la tests_hkdf_persistence_test_LDADD += src/libwolfpkcs11.la tests_unique_id_test_LDADD += src/libwolfpkcs11.la diff --git a/tests/object_handle_limit_test.c b/tests/object_handle_limit_test.c new file mode 100644 index 00000000..d6435574 --- /dev/null +++ b/tests/object_handle_limit_test.c @@ -0,0 +1,390 @@ +/* object_handle_limit_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPKCS11. + * + * wolfPKCS11 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPKCS11 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * Regression test for running out of object ids. + * + * An object handle is the object id with the onToken flag in bit 28. Ids come + * from one counter per token and are never reused. Once the counter passed + * 28 bits, the id spilled into the flag: a session object got the handle of a + * token object, so reading or destroying it acted on the token object, and + * closing the session freed an object that was still on the session list. + * Object creation must fail once the ids are used up instead, a token that has + * used every id must still load, and a stored counter past the limit must be + * rejected. + * + * Reaching the limit through the public API takes hundreds of millions of + * objects, so this test uses the DEBUG_WOLFPKCS11-only hook + * WP11_Test_SetNextObjId() to start the counter just below it. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include + +#ifndef WOLFPKCS11_USER_SETTINGS + #include +#endif +#include + +#ifndef HAVE_PKCS11_STATIC +#include +#endif + +#include "testdata.h" +#include "pkcs11_test_util.h" + +#define TEST_DIR "./store/object_handle_limit_test" +#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" + +#ifdef DEBUG_WOLFPKCS11 + +/* DEBUG_WOLFPKCS11-only test hook exported by libwolfpkcs11. */ +extern int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId); + +/* Mirror OBJ_ID_MAX and the onToken handle flag in src/internal.c. */ +#define OBJ_ID_MAX 0x0fffffffUL +#define HANDLE_ON_TOKEN 0x10000000UL + +/* A stored counter can only be corrupted and reloaded with file storage. */ +#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ + !defined(WOLFPKCS11_CUSTOM_STORE) + #define TEST_CORRUPT_COUNTER +#endif + +static CK_SLOT_ID slot = 0; +static const char* soPin = "password123456"; +static const char* userPin = "wolfpkcs11-test"; +static const char tokenLabel[] = "object-handle-limit"; + +static CK_RV token_setup(void) +{ + CK_C_INITIALIZE_ARGS args; + CK_SLOT_ID slotList[16]; + CK_ULONG slotCount = sizeof(slotList) / sizeof(slotList[0]); + CK_SESSION_HANDLE soSession = 0; + unsigned char label[32]; + CK_RV rv; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + if (rv != CKR_OK) + return rv; + rv = funcList->C_GetSlotList(CK_TRUE, slotList, &slotCount); + if (rv != CKR_OK) + return rv; + if (slotCount == 0) + return CKR_TOKEN_NOT_PRESENT; + slot = slotList[0]; + + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + if (rv != CKR_OK) + return rv; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, &soSession); + if (rv != CKR_OK) + return rv; + rv = funcList->C_Login(soSession, CKU_SO, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin)); + if (rv == CKR_OK) { + rv = funcList->C_InitPIN(soSession, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + funcList->C_Logout(soSession); + funcList->C_CloseSession(soSession); + return rv; +} + +static CK_RV open_user_session(CK_SESSION_HANDLE* session) +{ + CK_RV rv; + + rv = funcList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, + NULL, NULL, session); + if (rv == CKR_OK) { + rv = funcList->C_Login(*session, CKU_USER, (CK_UTF8CHAR_PTR)userPin, + (CK_ULONG)XSTRLEN(userPin)); + } + return rv; +} + +static CK_RV create_data(CK_SESSION_HANDLE session, CK_BBOOL onToken, + const char* label, CK_OBJECT_HANDLE* obj) +{ + CK_OBJECT_CLASS dataClass = CKO_DATA; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_TOKEN, &onToken, sizeof(onToken) }, + { CKA_PRIVATE, &ckFalse, sizeof(ckFalse) }, + { CKA_LABEL, (void*)label, (CK_ULONG)XSTRLEN(label) }, + }; + + *obj = CK_INVALID_HANDLE; + return funcList->C_CreateObject(session, tmpl, + sizeof(tmpl) / sizeof(*tmpl), obj); +} + +/* Check that a handle names the object with the expected label. */ +static void check_label(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, + const char* expect, const char* name) +{ + CK_RV rv; + char buf[64]; + CK_ATTRIBUTE attr = { CKA_LABEL, buf, sizeof(buf) }; + + rv = funcList->C_GetAttributeValue(session, obj, &attr, 1); + CHECK_RV(rv, name, CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(attr.ulValueLen == XSTRLEN(expect) && + XMEMCMP(buf, expect, attr.ulValueLen) == 0, name); + } +} + +static CK_ULONG count_objects(CK_SESSION_HANDLE session, CK_BBOOL onToken) +{ + CK_ATTRIBUTE tmpl = { CKA_TOKEN, &onToken, sizeof(onToken) }; + CK_OBJECT_HANDLE found[8]; + CK_ULONG cnt = 0; + + if (funcList->C_FindObjectsInit(session, &tmpl, 1) != CKR_OK) + return (CK_ULONG)-1; + if (funcList->C_FindObjects(session, found, + sizeof(found) / sizeof(*found), &cnt) != CKR_OK) { + cnt = (CK_ULONG)-1; + } + funcList->C_FindObjectsFinal(session); + return cnt; +} + +#ifdef HAVE_ECC +static CK_RV gen_keypair(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* pub, + CK_OBJECT_HANDLE* priv) +{ + CK_MECHANISM mech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + CK_BBOOL ckFalse = CK_FALSE; + CK_ATTRIBUTE pubTmpl[] = { + { CKA_EC_PARAMS, ecc_p256_params, sizeof(ecc_p256_params) }, + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + CK_ATTRIBUTE privTmpl[] = { + { CKA_TOKEN, &ckFalse, sizeof(ckFalse) }, + }; + + *pub = CK_INVALID_HANDLE; + *priv = CK_INVALID_HANDLE; + return funcList->C_GenerateKeyPair(session, &mech, + pubTmpl, sizeof(pubTmpl) / sizeof(*pubTmpl), + privTmpl, sizeof(privTmpl) / sizeof(*privTmpl), pub, priv); +} +#endif + +#ifndef WOLFPKCS11_NO_STORE +/* A token that has used every id must still load. */ +static void reload_test(CK_OBJECT_HANDLE tokFirst, CK_OBJECT_HANDLE tokLast) +{ + CK_RV rv; + CK_C_INITIALIZE_ARGS args; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE over; +#ifdef TEST_CORRUPT_COUNTER + char path[512]; +#else + unsigned char label[32]; +#endif + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + rv = funcList->C_Initialize(&args); + CHECK_RV(rv, "reload token with every id used", CKR_OK); + if (rv != CKR_OK) + return; + rv = open_user_session(&session); + CHECK_RV(rv, "open user session after reload", CKR_OK); + if (rv == CKR_OK) { + CHECK_TRUE(count_objects(session, CK_TRUE) == 2, + "token objects after reload"); + check_label(session, tokLast, "token-last", + "last token object label after reload"); + rv = create_data(session, CK_FALSE, "session-over", &over); + CHECK_RV(rv, "session object after reload fails", + CKR_FUNCTION_FAILED); + } + +#ifdef TEST_CORRUPT_COUNTER + /* Destroying a token object stores the token, here with the counter past + * the limit. Loading that token must fail. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, (int)OBJ_ID_MAX + 2) == 0, + "set next object id past the limit"); + rv = funcList->C_DestroyObject(session, tokFirst); + CHECK_RV(rv, "store token with counter past the limit", CKR_OK); + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + + rv = funcList->C_Initialize(&args); + CHECK_RV(rv, "load token with counter past the limit fails", + CKR_FUNCTION_FAILED); + if (rv == CKR_OK) + funcList->C_Finalize(NULL); + + snprintf(path, sizeof(path), "%s" PATH_SEP "%s", TEST_DIR, + WOLFPKCS11_TOKEN_FILENAME); + (void)remove(path); +#else + (void)tokFirst; + /* The token is not a file to delete, so reset it for later tests. */ + funcList->C_CloseSession(session); + XMEMSET(label, ' ', sizeof(label)); + XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); + rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, + (CK_ULONG)XSTRLEN(soPin), label); + CHECK_RV(rv, "reset token", CKR_OK); + funcList->C_Finalize(NULL); +#endif +} +#endif + +static void run_test(void) +{ + CK_RV rv; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE tokFirst = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE tokLast = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE sessLast = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE over = CK_INVALID_HANDLE; +#ifdef HAVE_ECC + CK_OBJECT_HANDLE pub; + CK_OBJECT_HANDLE priv; +#endif + + rv = pkcs11_load(); + CHECK_RV(rv, "load library", CKR_OK); + if (rv != CKR_OK) + return; + + rv = token_setup(); + CHECK_RV(rv, "token setup", CKR_OK); + if (rv == CKR_OK) { + rv = open_user_session(&session); + CHECK_RV(rv, "open user session", CKR_OK); + } + if (rv != CKR_OK) { + funcList->C_Finalize(NULL); + pkcs11_unload(); + return; + } + + rv = create_data(session, CK_TRUE, "token-first", &tokFirst); + CHECK_RV(rv, "create first token object", CKR_OK); + CHECK_TRUE((tokFirst & HANDLE_ON_TOKEN) != 0, + "first token object handle has the token flag"); +#ifdef HAVE_ECC + /* Control for the rollback check below. */ + rv = gen_keypair(session, &pub, &priv); + CHECK_RV(rv, "generate key pair", CKR_OK); + funcList->C_DestroyObject(session, pub); + funcList->C_DestroyObject(session, priv); +#endif + + /* Use up the ids. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, (int)OBJ_ID_MAX - 1) == 0, + "set next object id below the limit"); + rv = create_data(session, CK_FALSE, "session-last", &sessLast); + CHECK_RV(rv, "create session object", CKR_OK); + CHECK_TRUE(sessLast == OBJ_ID_MAX - 1, + "session object handle has no token flag"); + rv = create_data(session, CK_TRUE, "token-last", &tokLast); + CHECK_RV(rv, "create token object with the last id", CKR_OK); + CHECK_TRUE(tokLast == (HANDLE_ON_TOKEN | OBJ_ID_MAX), + "token object handle has the last id"); + + rv = create_data(session, CK_FALSE, "session-over", &over); + CHECK_RV(rv, "session object past the limit fails", CKR_FUNCTION_FAILED); + rv = create_data(session, CK_TRUE, "token-over", &over); + CHECK_RV(rv, "token object past the limit fails", CKR_FUNCTION_FAILED); + + check_label(session, tokFirst, "token-first", "first token object label"); + check_label(session, sessLast, "session-last", "session object label"); + check_label(session, tokLast, "token-last", "last token object label"); + CHECK_TRUE(count_objects(session, CK_TRUE) == 2, "two token objects"); + CHECK_TRUE(count_objects(session, CK_FALSE) == 1, "one session object"); + + rv = funcList->C_DestroyObject(session, sessLast); + CHECK_RV(rv, "destroy session object", CKR_OK); + CHECK_TRUE(count_objects(session, CK_TRUE) == 2, + "token objects kept after session object destroyed"); + +#ifdef HAVE_ECC + /* The public key takes the last id and the private key fails, so the + * public key has to be taken back off the session list. No session object + * has used this id, as session handles have no token flag. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, (int)OBJ_ID_MAX) == 0, + "set next object id to the last id"); + rv = gen_keypair(session, &pub, &priv); + CHECK_RV(rv, "key pair needing two ids fails", CKR_FUNCTION_FAILED); + CHECK_TRUE(count_objects(session, CK_FALSE) == 0, + "public key rolled back"); +#endif + + /* Closing the session frees its objects. */ + funcList->C_CloseSession(session); + funcList->C_Finalize(NULL); + +#ifndef WOLFPKCS11_NO_STORE + reload_test(tokFirst, tokLast); +#else + (void)tokLast; +#endif + pkcs11_unload(); +} + +#endif /* DEBUG_WOLFPKCS11 */ + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + +#ifndef DEBUG_WOLFPKCS11 + printf("Debug hook not available, skipping object handle limit test\n"); + return 77; /* automake: test skipped */ +#else +#ifndef WOLFPKCS11_NO_ENV + XSETENV("WOLFPKCS11_TOKEN_PATH", TEST_DIR, 1); +#endif + + printf("=== wolfPKCS11 object handle limit test ===\n"); + run_test(); + return pkcs11_test_summary(); +#endif +} diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index 41c2e763..bf9e1021 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -535,6 +535,7 @@ WP11_LOCAL void WP11_Session_RemoveObject(WP11_Session* session, #ifdef DEBUG_WOLFPKCS11 WP11_API int WP11_Test_RemoveObjectStale(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE liveHandle, CK_OBJECT_HANDLE staleHandle); +WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId); #endif WP11_LOCAL int WP11_Object_HandleOnToken(CK_OBJECT_HANDLE handle); WP11_LOCAL void WP11_Slot_ClearActiveObject(WP11_Slot* slot, From 1610b8c5f8dba6d13c6393d8a1d5843579676bbc Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Fri, 16 Oct 2026 11:08:43 +0100 Subject: [PATCH 2/5] Give session objects their own id counter 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. --- src/internal.c | 31 +++++++--- tests/object_handle_limit_test.c | 103 +++++++++++++++++++------------ wolfpkcs11/internal.h | 3 +- 3 files changed, 87 insertions(+), 50 deletions(-) diff --git a/src/internal.c b/src/internal.c index ee08b65b..5507402d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -565,7 +565,7 @@ typedef struct WP11_Token { WP11_Object* object; /* Linked list of token objects */ int objCnt; /* Count of objects on token */ int tokenFlags; /* Flags for token */ - int nextObjId; + int nextObjId; /* Id for the next token object */ byte userPinEmpty:2; /* Indicates user PIN is empty * 0 = not set * 1 = empty @@ -582,6 +582,8 @@ struct WP11_Slot { WP11_Token token; /* Token information for slot */ WP11_Session* session; /* Linked list of sessions */ WP11_Lock lock; /* Lock for access to slot info */ + int nextSessObjId; /* Id for the next session object, + * guarded by the token lock */ int devId; #ifdef WOLFPKCS11_TPM @@ -9405,6 +9407,9 @@ static int wp11_Slot_Init(WP11_Slot* slot, int id) XMEMSET(slot, 0, sizeof(*slot)); slot->id = id; + /* Session objects live and die with the slot's sessions, so their ids + * start again here, on each C_Initialize. */ + slot->nextSessObjId = 1; slot->token.state = WP11_TOKEN_STATE_UNKNOWN; slot->token.tokenFlags = 0; slot->calls[0].slot = slot; @@ -12702,14 +12707,18 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken, ret = OBJ_COUNT_E; } #endif - if (token->nextObjId > OBJ_ID_MAX) + /* Session objects have their own ids, so they do not use up the + * token's ids. Their handles have no onToken flag, so the two cannot + * clash. */ + if (session->slot->nextSessObjId > OBJ_ID_MAX) ret = OBJ_COUNT_E; if (ret == 0) { session->objCnt++; /* Get next item in list after this object has been added. */ next = session->object; /* Determine handle value */ - object->handle = OBJ_HANDLE(onToken, token->nextObjId++); + object->handle = OBJ_HANDLE(onToken, + session->slot->nextSessObjId++); object->next = next; session->object = object; object->session = session; @@ -12977,19 +12986,22 @@ WP11_API int WP11_Test_RemoveObjectStale(CK_SESSION_HANDLE hSession, } /** - * Test hook: set the id the next object on a slot's token will get. + * Test hook: set the id the next token or session object on a slot will get. * * Running out of object ids takes hundreds of millions of objects through the * public API, so a white-box test sets the counter close to the limit instead. - * The value is not checked, so a test can also store an out of range value and - * check that loading the token rejects it. + * The value is not checked, so a test can also store an out of range token + * counter and check that loading the token rejects it. * * @param slotId [in] Slot id. + * @param onToken [in] Non-zero for the token object counter, zero for + * the session object counter. * @param nextObjId [in] Next object id. * @return BAD_FUNC_ARG when the slot is not found. * 0 on success. */ -WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId) +WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int onToken, + int nextObjId) { WP11_Slot* slot; @@ -12997,7 +13009,10 @@ WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId) return BAD_FUNC_ARG; WP11_Lock_LockRW(&slot->token.lock); - slot->token.nextObjId = nextObjId; + if (onToken) + slot->token.nextObjId = nextObjId; + else + slot->nextSessObjId = nextObjId; WP11_Lock_UnlockRW(&slot->token.lock); return 0; diff --git a/tests/object_handle_limit_test.c b/tests/object_handle_limit_test.c index d6435574..8b2e601d 100644 --- a/tests/object_handle_limit_test.c +++ b/tests/object_handle_limit_test.c @@ -20,18 +20,22 @@ * * Regression test for running out of object ids. * - * An object handle is the object id with the onToken flag in bit 28. Ids come - * from one counter per token and are never reused. Once the counter passed - * 28 bits, the id spilled into the flag: a session object got the handle of a - * token object, so reading or destroying it acted on the token object, and - * closing the session freed an object that was still on the session list. - * Object creation must fail once the ids are used up instead, a token that has - * used every id must still load, and a stored counter past the limit must be - * rejected. + * An object handle is the object id with the onToken flag in bit 28. Ids are + * never reused. Once an id passed 28 bits it spilled into the flag: a session + * object got the handle of a token object, so reading or destroying it acted + * on the token object, and closing the session freed an object that was still + * on the session list. Object creation must fail once the ids are used up + * instead, a token that has used every id must still load, and a stored + * counter past the limit must be rejected. + * + * Token objects and session objects have separate counters. The token counter + * is stored with the token, while the session counter starts again on each + * C_Initialize. Running out of one must not stop objects of the other kind + * from being created. * * Reaching the limit through the public API takes hundreds of millions of * objects, so this test uses the DEBUG_WOLFPKCS11-only hook - * WP11_Test_SetNextObjId() to start the counter just below it. + * WP11_Test_SetNextObjId() to start the counters just below it. */ #ifdef HAVE_CONFIG_H @@ -65,7 +69,8 @@ #ifdef DEBUG_WOLFPKCS11 /* DEBUG_WOLFPKCS11-only test hook exported by libwolfpkcs11. */ -extern int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId); +extern int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int onToken, + int nextObjId); /* Mirror OBJ_ID_MAX and the onToken handle flag in src/internal.c. */ #define OBJ_ID_MAX 0x0fffffffUL @@ -210,13 +215,14 @@ static CK_RV gen_keypair(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* pub, #endif #ifndef WOLFPKCS11_NO_STORE -/* A token that has used every id must still load. */ +/* A token that has used every id must still load, with the session ids + * available again. */ static void reload_test(CK_OBJECT_HANDLE tokFirst, CK_OBJECT_HANDLE tokLast) { CK_RV rv; CK_C_INITIALIZE_ARGS args; CK_SESSION_HANDLE session = 0; - CK_OBJECT_HANDLE over; + CK_OBJECT_HANDLE obj; #ifdef TEST_CORRUPT_COUNTER char path[512]; #else @@ -226,7 +232,7 @@ static void reload_test(CK_OBJECT_HANDLE tokFirst, CK_OBJECT_HANDLE tokLast) XMEMSET(&args, 0, sizeof(args)); args.flags = CKF_OS_LOCKING_OK; rv = funcList->C_Initialize(&args); - CHECK_RV(rv, "reload token with every id used", CKR_OK); + CHECK_RV(rv, "reload token with every token id used", CKR_OK); if (rv != CKR_OK) return; rv = open_user_session(&session); @@ -236,16 +242,19 @@ static void reload_test(CK_OBJECT_HANDLE tokFirst, CK_OBJECT_HANDLE tokLast) "token objects after reload"); check_label(session, tokLast, "token-last", "last token object label after reload"); - rv = create_data(session, CK_FALSE, "session-over", &over); - CHECK_RV(rv, "session object after reload fails", - CKR_FUNCTION_FAILED); + rv = create_data(session, CK_TRUE, "token-over", &obj); + CHECK_RV(rv, "token object after reload fails", CKR_FUNCTION_FAILED); + rv = create_data(session, CK_FALSE, "session-reload", &obj); + CHECK_RV(rv, "session object after reload", CKR_OK); + CHECK_TRUE((obj & HANDLE_ON_TOKEN) == 0, + "session object after reload has no token flag"); } #ifdef TEST_CORRUPT_COUNTER /* Destroying a token object stores the token, here with the counter past * the limit. Loading that token must fail. */ - CHECK_TRUE(WP11_Test_SetNextObjId(slot, (int)OBJ_ID_MAX + 2) == 0, - "set next object id past the limit"); + CHECK_TRUE(WP11_Test_SetNextObjId(slot, 1, (int)OBJ_ID_MAX + 2) == 0, + "set next token object id past the limit"); rv = funcList->C_DestroyObject(session, tokFirst); CHECK_RV(rv, "store token with counter past the limit", CKR_OK); funcList->C_CloseSession(session); @@ -280,6 +289,7 @@ static void run_test(void) CK_SESSION_HANDLE session = 0; CK_OBJECT_HANDLE tokFirst = CK_INVALID_HANDLE; CK_OBJECT_HANDLE tokLast = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE sessFirst = CK_INVALID_HANDLE; CK_OBJECT_HANDLE sessLast = CK_INVALID_HANDLE; CK_OBJECT_HANDLE over = CK_INVALID_HANDLE; #ifdef HAVE_ECC @@ -316,46 +326,57 @@ static void run_test(void) funcList->C_DestroyObject(session, priv); #endif - /* Use up the ids. */ - CHECK_TRUE(WP11_Test_SetNextObjId(slot, (int)OBJ_ID_MAX - 1) == 0, - "set next object id below the limit"); - rv = create_data(session, CK_FALSE, "session-last", &sessLast); - CHECK_RV(rv, "create session object", CKR_OK); - CHECK_TRUE(sessLast == OBJ_ID_MAX - 1, - "session object handle has no token flag"); + /* Use up the token object ids. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, 1, (int)OBJ_ID_MAX) == 0, + "set next token object id to the last id"); rv = create_data(session, CK_TRUE, "token-last", &tokLast); CHECK_RV(rv, "create token object with the last id", CKR_OK); CHECK_TRUE(tokLast == (HANDLE_ON_TOKEN | OBJ_ID_MAX), "token object handle has the last id"); + rv = create_data(session, CK_TRUE, "token-over", &over); + CHECK_RV(rv, "token object past the limit fails", CKR_FUNCTION_FAILED); + rv = create_data(session, CK_FALSE, "session-first", &sessFirst); + CHECK_RV(rv, "session object with token ids used up", CKR_OK); + CHECK_TRUE((sessFirst & HANDLE_ON_TOKEN) == 0, + "session object handle has no token flag"); + + /* Use up the session object ids. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, 0, (int)OBJ_ID_MAX - 1) == 0, + "set next session object id below the limit"); + rv = create_data(session, CK_FALSE, "session-last", &sessLast); + CHECK_RV(rv, "create session object", CKR_OK); + CHECK_TRUE(sessLast == OBJ_ID_MAX - 1, + "session object handle has the id and no token flag"); +#ifdef HAVE_ECC + /* The public key takes the last id and the private key fails, so the + * public key has to be taken back off the session list. */ + rv = gen_keypair(session, &pub, &priv); + CHECK_RV(rv, "key pair needing two ids fails", CKR_FUNCTION_FAILED); + CHECK_TRUE(count_objects(session, CK_FALSE) == 2, + "public key rolled back"); +#else + /* Skip the last id, which the key pair would take. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, 0, (int)OBJ_ID_MAX + 1) == 0, + "use the last session object id"); +#endif rv = create_data(session, CK_FALSE, "session-over", &over); CHECK_RV(rv, "session object past the limit fails", CKR_FUNCTION_FAILED); - rv = create_data(session, CK_TRUE, "token-over", &over); - CHECK_RV(rv, "token object past the limit fails", CKR_FUNCTION_FAILED); check_label(session, tokFirst, "token-first", "first token object label"); - check_label(session, sessLast, "session-last", "session object label"); check_label(session, tokLast, "token-last", "last token object label"); + check_label(session, sessFirst, "session-first", + "first session object label"); + check_label(session, sessLast, "session-last", + "last session object label"); CHECK_TRUE(count_objects(session, CK_TRUE) == 2, "two token objects"); - CHECK_TRUE(count_objects(session, CK_FALSE) == 1, "one session object"); + CHECK_TRUE(count_objects(session, CK_FALSE) == 2, "two session objects"); rv = funcList->C_DestroyObject(session, sessLast); CHECK_RV(rv, "destroy session object", CKR_OK); CHECK_TRUE(count_objects(session, CK_TRUE) == 2, "token objects kept after session object destroyed"); -#ifdef HAVE_ECC - /* The public key takes the last id and the private key fails, so the - * public key has to be taken back off the session list. No session object - * has used this id, as session handles have no token flag. */ - CHECK_TRUE(WP11_Test_SetNextObjId(slot, (int)OBJ_ID_MAX) == 0, - "set next object id to the last id"); - rv = gen_keypair(session, &pub, &priv); - CHECK_RV(rv, "key pair needing two ids fails", CKR_FUNCTION_FAILED); - CHECK_TRUE(count_objects(session, CK_FALSE) == 0, - "public key rolled back"); -#endif - /* Closing the session frees its objects. */ funcList->C_CloseSession(session); funcList->C_Finalize(NULL); diff --git a/wolfpkcs11/internal.h b/wolfpkcs11/internal.h index bf9e1021..abea34e0 100644 --- a/wolfpkcs11/internal.h +++ b/wolfpkcs11/internal.h @@ -535,7 +535,8 @@ WP11_LOCAL void WP11_Session_RemoveObject(WP11_Session* session, #ifdef DEBUG_WOLFPKCS11 WP11_API int WP11_Test_RemoveObjectStale(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE liveHandle, CK_OBJECT_HANDLE staleHandle); -WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int nextObjId); +WP11_API int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int onToken, + int nextObjId); #endif WP11_LOCAL int WP11_Object_HandleOnToken(CK_OBJECT_HANDLE handle); WP11_LOCAL void WP11_Slot_ClearActiveObject(WP11_Slot* slot, From d47ddc500515ba1bf40a91abf37e51c6f830517e Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Fri, 16 Oct 2026 11:26:19 +0100 Subject: [PATCH 3/5] Recover the token object counter from the loaded objects 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. --- src/internal.c | 20 +++++--- tests/object_handle_limit_test.c | 80 ++++++++++++++++---------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/internal.c b/src/internal.c index 5507402d..2d7c3624 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9035,16 +9035,16 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) } else if (ret == 0) { ret = wp11_storage_read_int(storage, &token->nextObjId); - if (ret == BUFFER_E || (ret == 0 && token->nextObjId == 0)) { + /* OBJ_ID_MAX + 1 is a token that has used every id. Any other + * value outside 1..OBJ_ID_MAX is not a real counter. With TPM + * storage, a token written before the counter was stored + * reads back whatever follows its data. The loaded objects + * set a lower bound below either way. */ + if (ret == BUFFER_E || (ret == 0 && (token->nextObjId < 1 || + token->nextObjId > OBJ_ID_MAX + 1))) { token->nextObjId = 1; ret = 0; } - /* OBJ_ID_MAX + 1 is a token that has used every id. Beyond - * that, ids no longer fit in a handle. */ - else if (ret == 0 && (token->nextObjId < 0 || - token->nextObjId > OBJ_ID_MAX + 1)) { - ret = BUFFER_E; - } } } @@ -9054,6 +9054,12 @@ static int wp11_Token_Load(WP11_Slot* slot, int tokenId, WP11_Token* token) for (i = token->objCnt - 1; (ret == 0) && (i >= 0); i--) { /* Load the objects. */ ret = wp11_Object_Load(object, tokenId, i); + /* Keep new token object ids above the stored ones, in case the + * counter was not stored or was not usable. */ + if (ret == 0 && (int)OBJ_HANDLE_OBJ_ID(object->handle) >= + token->nextObjId) { + token->nextObjId = (int)OBJ_HANDLE_OBJ_ID(object->handle) + 1; + } object = object->next; } diff --git a/tests/object_handle_limit_test.c b/tests/object_handle_limit_test.c index 8b2e601d..635acea1 100644 --- a/tests/object_handle_limit_test.c +++ b/tests/object_handle_limit_test.c @@ -25,8 +25,10 @@ * object got the handle of a token object, so reading or destroying it acted * on the token object, and closing the session freed an object that was still * on the session list. Object creation must fail once the ids are used up - * instead, a token that has used every id must still load, and a stored - * counter past the limit must be rejected. + * instead, and a token that has used every id must still load. A stored + * counter that is out of range, as a token written by an older version can + * read back with TPM storage, must be replaced by one above the ids of the + * token's objects. * * Token objects and session objects have separate counters. The token counter * is stored with the token, while the session counter starts again on each @@ -64,7 +66,6 @@ #include "pkcs11_test_util.h" #define TEST_DIR "./store/object_handle_limit_test" -#define WOLFPKCS11_TOKEN_FILENAME "wp11_token_0000000000000001" #ifdef DEBUG_WOLFPKCS11 @@ -76,12 +77,6 @@ extern int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int onToken, #define OBJ_ID_MAX 0x0fffffffUL #define HANDLE_ON_TOKEN 0x10000000UL -/* A stored counter can only be corrupted and reloaded with file storage. */ -#if !defined(WOLFPKCS11_NO_STORE) && !defined(WOLFPKCS11_TPM_STORE) && \ - !defined(WOLFPKCS11_CUSTOM_STORE) - #define TEST_CORRUPT_COUNTER -#endif - static CK_SLOT_ID slot = 0; static const char* soPin = "password123456"; static const char* userPin = "wolfpkcs11-test"; @@ -215,23 +210,26 @@ static CK_RV gen_keypair(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE* pub, #endif #ifndef WOLFPKCS11_NO_STORE +static CK_RV reinitialize(void) +{ + CK_C_INITIALIZE_ARGS args; + + XMEMSET(&args, 0, sizeof(args)); + args.flags = CKF_OS_LOCKING_OK; + return funcList->C_Initialize(&args); +} + /* A token that has used every id must still load, with the session ids - * available again. */ + * available again. A stored counter that is out of range must be replaced by + * one above the ids of the token's objects. */ static void reload_test(CK_OBJECT_HANDLE tokFirst, CK_OBJECT_HANDLE tokLast) { CK_RV rv; - CK_C_INITIALIZE_ARGS args; CK_SESSION_HANDLE session = 0; CK_OBJECT_HANDLE obj; -#ifdef TEST_CORRUPT_COUNTER - char path[512]; -#else unsigned char label[32]; -#endif - XMEMSET(&args, 0, sizeof(args)); - args.flags = CKF_OS_LOCKING_OK; - rv = funcList->C_Initialize(&args); + rv = reinitialize(); CHECK_RV(rv, "reload token with every token id used", CKR_OK); if (rv != CKR_OK) return; @@ -248,38 +246,42 @@ static void reload_test(CK_OBJECT_HANDLE tokFirst, CK_OBJECT_HANDLE tokLast) CHECK_RV(rv, "session object after reload", CKR_OK); CHECK_TRUE((obj & HANDLE_ON_TOKEN) == 0, "session object after reload has no token flag"); - } -#ifdef TEST_CORRUPT_COUNTER - /* Destroying a token object stores the token, here with the counter past - * the limit. Loading that token must fail. */ - CHECK_TRUE(WP11_Test_SetNextObjId(slot, 1, (int)OBJ_ID_MAX + 2) == 0, - "set next token object id past the limit"); - rv = funcList->C_DestroyObject(session, tokFirst); - CHECK_RV(rv, "store token with counter past the limit", CKR_OK); + /* Destroying a token object stores the token, here with a counter + * that is out of range. Only the first token object is left. */ + CHECK_TRUE(WP11_Test_SetNextObjId(slot, 1, -1) == 0, + "set next token object id out of range"); + rv = funcList->C_DestroyObject(session, tokLast); + CHECK_RV(rv, "store token with counter out of range", CKR_OK); + } funcList->C_CloseSession(session); + session = 0; funcList->C_Finalize(NULL); - rv = funcList->C_Initialize(&args); - CHECK_RV(rv, "load token with counter past the limit fails", - CKR_FUNCTION_FAILED); - if (rv == CKR_OK) - funcList->C_Finalize(NULL); + rv = reinitialize(); + CHECK_RV(rv, "load token with counter out of range", CKR_OK); + if (rv != CKR_OK) + return; + rv = open_user_session(&session); + CHECK_RV(rv, "open user session after second reload", CKR_OK); + if (rv == CKR_OK) { + rv = create_data(session, CK_TRUE, "token-next", &obj); + CHECK_RV(rv, "token object after counter recovered", CKR_OK); + CHECK_TRUE(obj == tokFirst + 1, + "token object id follows the stored object's id"); + check_label(session, tokFirst, "token-first", + "first token object label after second reload"); + check_label(session, obj, "token-next", "new token object label"); + funcList->C_CloseSession(session); + } - snprintf(path, sizeof(path), "%s" PATH_SEP "%s", TEST_DIR, - WOLFPKCS11_TOKEN_FILENAME); - (void)remove(path); -#else - (void)tokFirst; - /* The token is not a file to delete, so reset it for later tests. */ - funcList->C_CloseSession(session); + /* Reset the token so a run that shares its storage starts clean. */ XMEMSET(label, ' ', sizeof(label)); XMEMCPY(label, tokenLabel, XSTRLEN(tokenLabel)); rv = funcList->C_InitToken(slot, (CK_UTF8CHAR_PTR)soPin, (CK_ULONG)XSTRLEN(soPin), label); CHECK_RV(rv, "reset token", CKR_OK); funcList->C_Finalize(NULL); -#endif } #endif From 6410fb0d99d410c85a2898a7f59e73cef4abee3e Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Fri, 16 Oct 2026 11:27:12 +0100 Subject: [PATCH 4/5] Give the test hook declaration C linkage 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. --- tests/object_handle_limit_test.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/object_handle_limit_test.c b/tests/object_handle_limit_test.c index 635acea1..dfdee6da 100644 --- a/tests/object_handle_limit_test.c +++ b/tests/object_handle_limit_test.c @@ -69,9 +69,16 @@ #ifdef DEBUG_WOLFPKCS11 -/* DEBUG_WOLFPKCS11-only test hook exported by libwolfpkcs11. */ +/* DEBUG_WOLFPKCS11-only test hook exported by libwolfpkcs11, with C linkage + * as in wolfpkcs11/internal.h. */ +#ifdef __cplusplus +extern "C" { +#endif extern int WP11_Test_SetNextObjId(CK_SLOT_ID slotId, int onToken, int nextObjId); +#ifdef __cplusplus +} +#endif /* Mirror OBJ_ID_MAX and the onToken handle flag in src/internal.c. */ #define OBJ_ID_MAX 0x0fffffffUL From b7611ff2236533aecc456166304d489391332d10 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 19 Oct 2026 09:45:43 +0100 Subject: [PATCH 5/5] Update doc comments for the separate counters and recovery 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. --- src/internal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index 2d7c3624..cb10643d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12619,8 +12619,8 @@ WP11_API void WP11_Session_SetAddObjectHook(void (*hook)(void)) * @param session [in] Session object. * @param onToken [in] Whether to put object on token. * @param object [in] Key Object object. - * @return OBJ_COUNT_E when the object list is full or the token has used - * every object id. + * @return OBJ_COUNT_E when the object list is full, or the token or session + * object ids are used up. * 0 on success. */ int WP11_Session_AddObject(WP11_Session* session, int onToken, @@ -12997,7 +12997,7 @@ WP11_API int WP11_Test_RemoveObjectStale(CK_SESSION_HANDLE hSession, * Running out of object ids takes hundreds of millions of objects through the * public API, so a white-box test sets the counter close to the limit instead. * The value is not checked, so a test can also store an out of range token - * counter and check that loading the token rejects it. + * counter and check that loading the token replaces it. * * @param slotId [in] Slot id. * @param onToken [in] Non-zero for the token object counter, zero for