Skip to content

Commit d974a0d

Browse files
authored
Merge pull request #205 from LinuxJedi/concurrent-destroy-object-double-free
Prevent double free in concurrent C_DestroyObject on shared token object
2 parents 639baff + d5c00fa commit d974a0d

5 files changed

Lines changed: 688 additions & 63 deletions

File tree

src/crypto.c

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,8 +1278,7 @@ static CK_RV AddRSAPrivateKeyObject(WP11_Session* session,
12781278
err_out:
12791279
if (rv != CKR_OK) {
12801280
if (*phKey != CK_INVALID_HANDLE) {
1281-
/* ignore return value, logged in function */
1282-
(void)WP11_Session_RemoveObject(session, privKeyObject);
1281+
WP11_Session_RemoveObject(session, privKeyObject);
12831282
*phKey = CK_INVALID_HANDLE;
12841283
}
12851284
if (privKeyObject != NULL) {
@@ -1475,8 +1474,7 @@ CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
14751474
}
14761475
rv = AddObject(session, object, pTemplate, ulCount, phObject);
14771476
if (rv != CKR_OK) {
1478-
/* ignore return value, logged in function */
1479-
(void)WP11_Session_RemoveObject(session, object);
1477+
WP11_Session_RemoveObject(session, object);
14801478
WP11_Object_Free(object);
14811479
}
14821480

@@ -1664,6 +1662,7 @@ CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession,
16641662
CK_OBJECT_HANDLE hObject)
16651663
{
16661664
int ret;
1665+
int onToken;
16671666
CK_RV rv;
16681667
WP11_Session* session;
16691668
WP11_Object* obj = NULL;
@@ -1692,29 +1691,47 @@ CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession,
16921691
WOLFPKCS11_LEAVE("C_DestroyObject", rv);
16931692
return rv;
16941693
}
1694+
/* Derive onToken from the handle, not the object: it stays valid even if a
1695+
* concurrent destroy of the same handle frees the object out from under
1696+
* us, and it lets WP11_Session_RemoveObjectByHandle unlink token objects
1697+
* using pointer identity alone. */
1698+
onToken = WP11_Object_HandleOnToken(hObject);
16951699

16961700
/* Only require R/W session for token objects */
1697-
if (!WP11_Session_IsRW(session) && WP11_Object_OnToken(obj)) {
1701+
if (!WP11_Session_IsRW(session) && onToken) {
16981702
rv = CKR_SESSION_READ_ONLY;
16991703
WOLFPKCS11_LEAVE("C_DestroyObject", rv);
17001704
return rv;
17011705
}
17021706

1703-
/* Reject destruction of objects whose CKA_DESTROYABLE has been set to
1704-
* CK_FALSE. Read the flag bit directly so the gate stays consistent
1705-
* regardless of which getter view applies. */
1706-
if (!WP11_Object_IsDestroyable(obj)) {
1707+
/* Remove and reject-if-not-destroyable in one locked step. The
1708+
* CKA_DESTROYABLE check is performed inside WP11_Session_RemoveObjectByHandle,
1709+
* once the object is confirmed still linked (and therefore alive), so it
1710+
* cannot race a concurrent free of the same handle. */
1711+
ret = WP11_Session_RemoveObjectByHandle(session, obj, onToken,
1712+
1 /* checkDestroyable */);
1713+
if (ret == WP11_OBJECT_ALREADY_REMOVED) {
1714+
/* Another thread destroyed this object first (a concurrent
1715+
* C_DestroyObject on the same shared token-object handle). That thread
1716+
* owns the free; do not touch the object again. */
1717+
rv = CKR_OBJECT_HANDLE_INVALID;
1718+
WOLFPKCS11_LEAVE("C_DestroyObject", rv);
1719+
return rv;
1720+
}
1721+
if (ret == WP11_OBJECT_NOT_DESTROYABLE) {
1722+
/* CKA_DESTROYABLE is CK_FALSE; object left in place. */
17071723
rv = CKR_ACTION_PROHIBITED;
17081724
WOLFPKCS11_LEAVE("C_DestroyObject", rv);
17091725
return rv;
17101726
}
1711-
1712-
rv = WP11_Session_RemoveObject(session, obj);
17131727
/* Drop any active-operation reference to this object before freeing it so a
17141728
* pending operation cannot use freed memory. */
17151729
WP11_Slot_ClearActiveObject(WP11_Session_GetSlot(session), obj);
17161730
WP11_Object_Free(obj);
17171731

1732+
/* The object was unlinked; a negative status means persisting the token
1733+
* afterwards failed. Surface it rather than reporting success. */
1734+
rv = (ret < 0) ? CKR_FUNCTION_FAILED : CKR_OK;
17181735
WOLFPKCS11_LEAVE("C_DestroyObject", rv);
17191736
return rv;
17201737
}
@@ -7915,12 +7932,12 @@ CK_RV C_GenerateKeyPair(CK_SESSION_HANDLE hSession,
79157932

79167933
if (rv != CKR_OK && pub != NULL) {
79177934
if (*phPublicKey != CK_INVALID_HANDLE)
7918-
(void)WP11_Session_RemoveObject(session, pub);
7935+
WP11_Session_RemoveObject(session, pub);
79197936
WP11_Object_Free(pub);
79207937
}
79217938
if (rv != CKR_OK && priv != NULL) {
79227939
if (*phPrivateKey != CK_INVALID_HANDLE)
7923-
(void)WP11_Session_RemoveObject(session, priv);
7940+
WP11_Session_RemoveObject(session, priv);
79247941
WP11_Object_Free(priv);
79257942
}
79267943

@@ -8417,8 +8434,7 @@ CK_RV C_UnwrapKey(CK_SESSION_HANDLE hSession,
84178434
}
84188435
if (rv != CKR_OK) {
84198436
if (*phKey != CK_INVALID_HANDLE) {
8420-
/* ignore return value, logged in function */
8421-
(void)WP11_Session_RemoveObject(session, keyObj);
8437+
WP11_Session_RemoveObject(session, keyObj);
84228438
*phKey = CK_INVALID_HANDLE;
84238439
}
84248440
if (keyObj != NULL) {

src/internal.c

Lines changed: 158 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ static void wp11_Session_Final(WP11_Session* session)
922922
/* Free objects in session. */
923923
while ((obj = session->object) != NULL) {
924924
/* ignore return value, logged in function */
925-
(void)WP11_Session_RemoveObject(session, obj);
925+
WP11_Session_RemoveObject(session, obj);
926926
WP11_Object_Free(obj);
927927
}
928928
session->inUse = 0;
@@ -8815,51 +8815,122 @@ int WP11_Session_AddObject(WP11_Session* session, int onToken,
88158815
}
88168816

88178817
/**
8818-
* Remove object to the session or token.
8818+
* Remove an object from its session or token list, addressing it by a
8819+
* caller-supplied onToken flag rather than by dereferencing the object.
88198820
*
8820-
* @param session [in] Session object.
8821-
* @param object [in] Key Object object.
8822-
* @return -ve on failure.
8823-
* 0 on success.
8821+
* Membership is established by pointer identity alone, so *object is never
8822+
* dereferenced until it is confirmed still linked (and therefore still alive).
8823+
* onToken must be derived from the handle (see WP11_Object_HandleOnToken), not
8824+
* from *object, so it stays valid even after a concurrent caller frees the
8825+
* object. This is the entry point for C_DestroyObject, where a second thread may
8826+
* be destroying the same shared token-object handle at the same time.
8827+
*
8828+
* Callers that hold the only reference to a live object should use the simpler
8829+
* WP11_Session_RemoveObject() wrapper instead: it derives onToken from the
8830+
* object itself and so cannot mismatch onToken against the wrong object.
8831+
*
8832+
* @param session [in] Session performing the removal.
8833+
* @param object [in] Object to remove, matched by pointer identity.
8834+
* @param onToken [in] Non-zero if the handle names a token object.
8835+
* Derive from the handle, never from *object.
8836+
* @param checkDestroyable [in] Non-zero to honor CKA_DESTROYABLE and refuse to
8837+
* remove an object marked not destroyable.
8838+
* @return WP11_OBJECT_ALREADY_REMOVED if the object was no longer linked, i.e.
8839+
* a concurrent caller already removed it; the caller must not free it.
8840+
* WP11_OBJECT_NOT_DESTROYABLE if checkDestroyable is set and the object
8841+
* has CKA_DESTROYABLE = CK_FALSE; the object is left in place and must
8842+
* not be freed.
8843+
* -ve on a store failure after the object was unlinked.
8844+
* 0 on success; the caller now owns the object and must free it.
88248845
*/
8825-
int WP11_Session_RemoveObject(WP11_Session* session, WP11_Object* object)
8846+
int WP11_Session_RemoveObjectByHandle(WP11_Session* session,
8847+
WP11_Object* object, int onToken,
8848+
int checkDestroyable)
88268849
{
88278850
int ret = 0;
8851+
int found = 0;
88288852
WP11_Object** curr;
8829-
WP11_Token* token;
8830-
int id;
8853+
WP11_Token* token = &session->slot->token;
8854+
WP11_Session* owner = session;
8855+
int id = 0;
8856+
8857+
/* The token lock guards every object list on the slot (session lists
8858+
* included). Take it before touching any list or the object. Token objects
8859+
* are shared across sessions, so two C_DestroyObject calls on the same
8860+
* handle can both pass the lock-free WP11_Object_Find and reach here with
8861+
* the same pointer; the loser must detect that the winner already unlinked
8862+
* (and is about to free) the object and bail out without a second free.
8863+
*
8864+
* Membership is established by pointer identity alone so that *object is
8865+
* never dereferenced until it is confirmed still linked (and therefore
8866+
* still alive). onToken comes from the handle, not from *object, so it is
8867+
* safe to trust even after the object has been freed. */
8868+
WP11_Lock_LockRW(&token->lock);
88318869

8870+
if (onToken) {
8871+
/* Token objects live on the shared token list. */
8872+
for (curr = &token->object; *curr != NULL; curr = &(*curr)->next) {
8873+
if (*curr == object) {
8874+
found = 1;
8875+
break;
8876+
}
8877+
}
8878+
}
8879+
else {
8880+
/* Session objects live on their owning session's list. Check the
8881+
* caller's own list first: it is the only possibility in the standard
8882+
* build and the common case under NSS, and needs no dereference of
8883+
* *object. */
8884+
for (curr = &owner->object; *curr != NULL; curr = &(*curr)->next) {
8885+
if (*curr == object) {
8886+
found = 1;
8887+
break;
8888+
}
8889+
}
88328890
#ifdef WOLFPKCS11_NSS
8833-
if (object->session && (session != object->session))
8834-
session = object->session;
8891+
/* NSS makes objects visible across sessions, so the object may belong
8892+
* to a different session. Fall back to its recorded owner. This is
8893+
* reached only for a genuinely cross-session object - a freed object is
8894+
* normally caught as not-found above - and carries the same
8895+
* out-of-contract residual as a single-session concurrent misuse. */
8896+
if (!found && object->session != NULL && object->session != session) {
8897+
owner = object->session;
8898+
for (curr = &owner->object; *curr != NULL; curr = &(*curr)->next) {
8899+
if (*curr == object) {
8900+
found = 1;
8901+
break;
8902+
}
8903+
}
8904+
}
88358905
#endif
8906+
}
88368907

8837-
/* Find the object in list and relink. */
8838-
if (object->onToken) {
8839-
WP11_Lock_LockRW(object->lock);
8840-
token = &session->slot->token;
8908+
if (!found) {
8909+
WP11_Lock_UnlockRW(&token->lock);
8910+
return WP11_OBJECT_ALREADY_REMOVED;
8911+
}
8912+
8913+
/* The object is confirmed linked, and therefore alive, so its fields may
8914+
* now be read safely under the lock. Enforce CKA_DESTROYABLE here rather
8915+
* than in the caller so the check cannot race a concurrent free. */
8916+
if (checkDestroyable && (object->opFlag & WP11_FLAG_NOT_DESTROYABLE)) {
8917+
WP11_Lock_UnlockRW(&token->lock);
8918+
return WP11_OBJECT_NOT_DESTROYABLE;
8919+
}
8920+
8921+
if (onToken) {
88418922
token->objCnt--;
88428923
/* Id of first object on token. */
88438924
id = token->objCnt;
88448925
curr = &token->object;
8845-
}
8846-
else {
8847-
session->objCnt--;
8848-
/* Id of first object in session. */
8849-
id = session->objCnt;
8850-
curr = &session->object;
8851-
WP11_Lock_LockRW(&session->slot->token.lock);
8852-
}
8853-
8854-
/* walk list to get id for object to remove */
8855-
while (*curr != NULL) {
8856-
if (*curr == object) {
8857-
*curr = object->next;
8858-
break;
8859-
}
8926+
/* walk list to get id for object to remove */
8927+
while (*curr != NULL) {
8928+
if (*curr == object) {
8929+
*curr = object->next;
8930+
break;
8931+
}
88608932

8861-
#ifndef WOLFPKCS11_NO_STORE
8862-
if (object->onToken) {
8933+
#ifndef WOLFPKCS11_NO_STORE
88638934
/* remove any id's with higher value */
88648935
ret = wp11_Object_Unstore(*curr, (int)session->slotId, id);
88658936
#ifdef DEBUG_WOLFPKCS11
@@ -8868,16 +8939,14 @@ int WP11_Session_RemoveObject(WP11_Session* session, WP11_Object* object)
88688939
(int)session->slotId, id, ret);
88698940
}
88708941
#endif
8871-
}
8872-
#endif
8942+
#endif
88738943

8874-
curr = &(*curr)->next;
8875-
/* Id of next object as it isn't the one being removed. */
8876-
id--;
8877-
}
8944+
curr = &(*curr)->next;
8945+
/* Id of next object as it isn't the one being removed. */
8946+
id--;
8947+
}
88788948

8879-
if (object->onToken) {
8880-
#ifndef WOLFPKCS11_NO_STORE
8949+
#ifndef WOLFPKCS11_NO_STORE
88818950
ret = wp11_Object_Unstore(object, (int)session->slotId, id);
88828951
#ifdef DEBUG_WOLFPKCS11
88838952
if (ret != 0) {
@@ -8893,16 +8962,42 @@ int WP11_Session_RemoveObject(WP11_Session* session, WP11_Object* object)
88938962
(int)session->slotId, ret);
88948963
}
88958964
#endif
8896-
#endif
8897-
WP11_Lock_UnlockRW(object->lock);
8965+
#endif
88988966
}
88998967
else {
8900-
WP11_Lock_UnlockRW(&session->slot->token.lock);
8968+
owner->objCnt--;
8969+
for (curr = &owner->object; *curr != NULL; curr = &(*curr)->next) {
8970+
if (*curr == object) {
8971+
*curr = object->next;
8972+
break;
8973+
}
8974+
}
89018975
}
8976+
8977+
WP11_Lock_UnlockRW(&token->lock);
89028978
(void)id; /* set but not used with WOLFPKCS11_NO_STORE */
89038979
return ret;
89048980
}
89058981

8982+
/**
8983+
* Remove a live, exclusively-owned object from its session or token list.
8984+
*
8985+
* Thin wrapper over WP11_Session_RemoveObjectByHandle() for cleanup paths that
8986+
* hold the only reference to the object (object-creation rollback, keygen
8987+
* failure). onToken is read from the object itself, which is safe because no
8988+
* other thread can be removing it, and supplying onToken this way removes any
8989+
* risk of passing the wrong object's onToken. CKA_DESTROYABLE is not enforced
8990+
* (internal cleanup is not a user destroy) and any store error is logged in the
8991+
* callee, so the return value is intentionally discarded.
8992+
*
8993+
* @param session [in] Session performing the removal.
8994+
* @param object [in] Object to remove; must be live and owned by the caller.
8995+
*/
8996+
void WP11_Session_RemoveObject(WP11_Session* session, WP11_Object* object)
8997+
{
8998+
(void)WP11_Session_RemoveObjectByHandle(session, object, object->onToken, 0);
8999+
}
9000+
89069001
/**
89079002
* Drop any session reference to an object that is about to be freed.
89089003
*
@@ -10197,6 +10292,22 @@ int WP11_Object_SetClass(WP11_Object* object, CK_OBJECT_CLASS objClass)
1019710292
return 0;
1019810293
}
1019910294

10295+
/**
10296+
* Determine whether an object handle refers to a token object.
10297+
*
10298+
* The onToken bit is encoded in the handle value itself, so this is safe to
10299+
* call even when the WP11_Object has already been freed by another thread -
10300+
* useful when deciding which list a racing C_DestroyObject must consult.
10301+
*
10302+
* @param handle [in] Object handle.
10303+
* @return 1 when the handle refers to a token object.
10304+
* 0 when the handle refers to a session object.
10305+
*/
10306+
int WP11_Object_HandleOnToken(CK_OBJECT_HANDLE handle)
10307+
{
10308+
return OBJ_HANDLE_ON_TOKEN(handle);
10309+
}
10310+
1020010311
/**
1020110312
* Find an object based on the handle.
1020210313
*
@@ -10221,13 +10332,13 @@ int WP11_Object_Find(WP11_Session* session, CK_OBJECT_HANDLE objHandle,
1022110332
/* The NSS cross-session walk below needs the slot lock so a concurrent
1022210333
* remove-session can't reclaim a session node. Acquire it before the
1022310334
* token lock to match the global lock order (slot then token) used by
10224-
* WP11_Session_RemoveObject / WP11_Slot_CloseSessions; acquiring them
10225-
* in the opposite order would risk an AB-BA deadlock. */
10335+
* WP11_Session_RemoveObjectByHandle / WP11_Slot_CloseSessions; acquiring
10336+
* them in the opposite order would risk an AB-BA deadlock. */
1022610337
WP11_Lock_LockRO(&session->slot->lock);
1022710338
#endif
1022810339
/* Hold the token lock across the session->object walk so a concurrent
10229-
* WP11_Session_RemoveObject (which holds the same lock around the
10230-
* unlink) can't free a node we're stepping through. */
10340+
* WP11_Session_RemoveObjectByHandle (which holds the same lock around
10341+
* the unlink) can't free a node we're stepping through. */
1023110342
WP11_Lock_LockRO(&session->slot->token.lock);
1023210343
obj = session->object;
1023310344
while (obj != NULL) {

0 commit comments

Comments
 (0)