diff --git a/lib/softoken/lgglue.c b/lib/softoken/lgglue.c --- a/lib/softoken/lgglue.c +++ b/lib/softoken/lgglue.c @@ -179,23 +179,23 @@ sftkdb_encrypt_stub(PLArenaPool *arena, } /* if we aren't the key handle, try the other handle */ if (handle->type != SFTK_KEYDB_TYPE) { handle = handle->peerDB; } /* not a key handle */ - if (handle == NULL || handle->passwordLock == NULL) { + if (handle == NULL || !sftkdb_passwordLockIsInited(handle)) { return SECFailure; } - PZ_Lock(handle->passwordLock); + sftkdb_passwordReaderLock(handle); if (handle->passwordKey.data == NULL) { - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); /* PORT_SetError */ return SECFailure; } key = handle->newKey ? handle->newKey : &handle->passwordKey; if (sftk_isLegacyIterationCountAllowed()) { if (handle->newKey) { iterationCount = handle->newDefaultIterationCount; } else { @@ -203,17 +203,17 @@ sftkdb_encrypt_stub(PLArenaPool *arena, } } else { iterationCount = 1; } rv = sftkdb_EncryptAttribute(arena, handle, sdb, key, iterationCount, CK_INVALID_HANDLE, CKT_INVALID_TYPE, plainText, cipherText); - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); return rv; } /* * stub files for legacy db's to be able to encrypt and decrypt * various keys and attributes. */ @@ -230,31 +230,31 @@ sftkdb_decrypt_stub(SDB *sdb, SECItem *c /* if we aren't the key handle, try the other handle */ oldKey = handle->oldKey; if (handle->type != SFTK_KEYDB_TYPE) { handle = handle->peerDB; } /* not a key handle */ - if (handle == NULL || handle->passwordLock == NULL) { + if (handle == NULL || !sftkdb_passwordLockIsInited(handle)) { return SECFailure; } - PZ_Lock(handle->passwordLock); + sftkdb_passwordReaderLock(handle); if (handle->passwordKey.data == NULL) { - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); /* PORT_SetError */ return SECFailure; } rv = sftkdb_DecryptAttribute(NULL, oldKey ? oldKey : &handle->passwordKey, CK_INVALID_HANDLE, CKT_INVALID_TYPE, cipherText, plainText); - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); return rv; } static const char *LEGACY_LIB_NAME = SHLIB_PREFIX "nssdbm" SHLIB_VERSION "." SHLIB_SUFFIX; /* * 2 bools to tell us if we've check the legacy library successfully or diff --git a/lib/softoken/sftkdb.c b/lib/softoken/sftkdb.c --- a/lib/softoken/sftkdb.c +++ b/lib/softoken/sftkdb.c @@ -376,29 +376,29 @@ sftkdb_fixupTemplateOut(CK_ATTRIBUTE *te /* This code depends on the fact that the cipherText is bigger * than the plain text */ SECItem cipherText; SECItem *plainText; SECStatus rv; cipherText.data = ntemplate[i].pValue; cipherText.len = ntemplate[i].ulValueLen; - PZ_Lock(handle->passwordLock); + sftkdb_passwordReaderLock(handle); if (handle->passwordKey.data == NULL) { - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); template[i].ulValueLen = -1; crv = CKR_USER_NOT_LOGGED_IN; continue; } rv = sftkdb_DecryptAttribute(handle, &handle->passwordKey, objectID, ntemplate[i].type, &cipherText, &plainText); - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); if (rv != SECSuccess) { PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); template[i].ulValueLen = -1; crv = CKR_GENERAL_ERROR; continue; } PORT_Assert(template[i].ulValueLen >= plainText->len); if (template[i].ulValueLen < plainText->len) { @@ -441,30 +441,30 @@ sftkdb_fixupTemplateOut(CK_ATTRIBUTE *te plainText.data = ntemplate[i].pValue; plainText.len = ntemplate[i].ulValueLen; /* * we do a second check holding the lock just in case the user * loggout while we were trying to get the signature. */ - PZ_Lock(keyHandle->passwordLock); + sftkdb_passwordReaderLock(keyHandle); if (keyHandle->passwordKey.data == NULL) { /* if we are no longer logged in, no use checking the other * Signatures either. */ checkSig = PR_FALSE; - PZ_Unlock(keyHandle->passwordLock); + sftkdb_passwordReaderUnlock(keyHandle); continue; } rv = sftkdb_VerifyAttribute(keyHandle, &keyHandle->passwordKey, objectID, ntemplate[i].type, &plainText, &signText); - PZ_Unlock(keyHandle->passwordLock); + sftkdb_passwordReaderUnlock(keyHandle); if (rv != SECSuccess) { PORT_Memset(template[i].pValue, 0, template[i].ulValueLen); template[i].ulValueLen = -1; crv = CKR_SIGNATURE_INVALID; /* better error code? */ } /* This Attribute is fine */ } } @@ -553,28 +553,28 @@ sftk_signTemplate(PLArenaPool *arena, SF for (i = 0; i < count; i++) { if (sftkdb_isAuthenticatedAttribute(template[i].type)) { SECStatus rv; SECItem *signText; SECItem plainText; plainText.data = template[i].pValue; plainText.len = template[i].ulValueLen; - PZ_Lock(keyHandle->passwordLock); + sftkdb_passwordReaderLock(keyHandle); if (keyHandle->passwordKey.data == NULL) { - PZ_Unlock(keyHandle->passwordLock); + sftkdb_passwordReaderUnlock(keyHandle); crv = CKR_USER_NOT_LOGGED_IN; goto loser; } rv = sftkdb_SignAttribute(arena, keyHandle, keyTarget, &keyHandle->passwordKey, keyHandle->defaultIterationCount, objectID, template[i].type, &plainText, &signText); - PZ_Unlock(keyHandle->passwordLock); + sftkdb_passwordReaderUnlock(keyHandle); if (rv != SECSuccess) { crv = CKR_GENERAL_ERROR; /* better error code here? */ goto loser; } crv = sftkdb_PutAttributeSignature(handle, keyTarget, objectID, template[i].type, signText); if (crv != CKR_OK) { goto loser; @@ -733,29 +733,29 @@ sftk_ExtractTemplate(PLArenaPool *arena, if (doEnc && sftkdb_isPrivateAttribute(tp->type)) { /* we have a private attribute */ SECItem *cipherText; SECItem plainText; SECStatus rv; plainText.data = tp->pValue; plainText.len = tp->ulValueLen; - PZ_Lock(handle->passwordLock); + sftkdb_passwordReaderLock(handle); if (handle->passwordKey.data == NULL) { - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); *crv = CKR_USER_NOT_LOGGED_IN; break; } rv = sftkdb_EncryptAttribute(arena, handle, db, &handle->passwordKey, handle->defaultIterationCount, objectID, tp->type, &plainText, &cipherText); - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); if (rv == SECSuccess) { tp->pValue = cipherText->data; tp->ulValueLen = cipherText->len; } else { *crv = CKR_GENERAL_ERROR; /* better error code here? */ break; } PORT_Memset(plainText.data, 0, plainText.len); @@ -1604,18 +1604,18 @@ sftkdb_CloseDB(SFTKDBHandle *handle) if (handle->db->sdb_SetForkState) { (*handle->db->sdb_SetForkState)(parentForkedAfterC_Initialize); } (*handle->db->sdb_Close)(handle->db); } if (handle->passwordKey.data) { SECITEM_ZfreeItem(&handle->passwordKey, PR_FALSE); } - if (handle->passwordLock) { - SKIP_AFTER_FORK(PZ_DestroyLock(handle->passwordLock)); + if (sftkdb_passwordLockIsInited(handle)) { + SKIP_AFTER_FORK(sftkdb_passwordLockDestroy(handle)); } if (handle->updatePasswordKey) { SECITEM_ZfreeItem(handle->updatePasswordKey, PR_TRUE); } if (handle->updateID) { PORT_Free(handle->updateID); } PORT_Free(handle); @@ -2665,18 +2665,20 @@ sftk_NewDBHandle(SDB *sdb, int type, PRB handle->oldKey = NULL; handle->updatePasswordKey = NULL; handle->updateID = NULL; handle->type = type; handle->usesLegacyStorage = legacy; handle->passwordKey.data = NULL; handle->passwordKey.len = 0; handle->passwordLock = NULL; + handle->passwordWriterCond = NULL; + handle->passwordReaderCond = NULL; if (type == SFTK_KEYDB_TYPE) { - handle->passwordLock = PZ_NewLock(nssILockAttribute); + (void) sftkdb_passwordLockInit(handle); } sdb->app_private = handle; return handle; } /* * reset the key database to it's uninitialized state. This call * will clear all the key entried. diff --git a/lib/softoken/sftkdbti.h b/lib/softoken/sftkdbti.h --- a/lib/softoken/sftkdbti.h +++ b/lib/softoken/sftkdbti.h @@ -1,29 +1,35 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SFTKDBTI_H #define SFTKDBTI_H 1 +#include /* * private defines */ struct SFTKDBHandleStr { SDB *db; PRInt32 ref; CK_OBJECT_HANDLE type; SECItem passwordKey; int defaultIterationCount; SECItem *newKey; int newDefaultIterationCount; SECItem *oldKey; SECItem *updatePasswordKey; PZLock *passwordLock; + PRCondVar *passwordWriterCond; + PRCondVar *passwordReaderCond; + PRBool passwordWriterActive; + int passwordWriters; + int passwordReaders; SFTKDBHandle *peerDB; SDB *update; char *updateID; PRBool updateDBIsInit; PRBool usesLegacyStorage; }; #define SFTK_KEYDB_TYPE 0x40000000 @@ -74,9 +80,18 @@ CK_RV sftkdb_GetAttributeSignature(SFTKD CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type, SECItem *signText); CK_RV sftkdb_DestroyAttributeSignature(SFTKDBHandle *handle, SDB *db, CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE type); +/* password lock functions */ +SECStatus sftkdb_passwordLockInit(SFTKDBHandle *keydb); +void sftkdb_passwordLockDestroy(SFTKDBHandle *keydb); +PRBool sftkdb_passwordLockIsInited(SFTKDBHandle *keydb); +void sftkdb_passwordReaderLock(SFTKDBHandle *keydb); +void sftkdb_passwordWriterLock(SFTKDBHandle *keydb); +void sftkdb_passwordReaderUnlock(SFTKDBHandle *keydb); +void sftkdb_passwordWriterUnlock(SFTKDBHandle *keydb); + #endif diff --git a/lib/softoken/sftkpwd.c b/lib/softoken/sftkpwd.c --- a/lib/softoken/sftkpwd.c +++ b/lib/softoken/sftkpwd.c @@ -627,44 +627,164 @@ sftkdb_SignAttribute(PLArenaPool *arena, loser: PORT_Memset(signData, 0, sizeof signData); if (param) { nsspkcs5_DestroyPBEParameter(param); } return rv; } +SECStatus +sftkdb_passwordLockInit(SFTKDBHandle *keydb) +{ + keydb->passwordLock = PZ_NewLock(nssILockAttribute); + if (keydb->passwordLock == NULL) { + return SECFailure; + } + keydb->passwordWriterCond = PR_NewCondVar(keydb->passwordLock); + if (keydb->passwordWriterCond == NULL) { + PZ_DestroyLock(keydb->passwordLock); + keydb->passwordLock = NULL; + return SECFailure; + } + keydb->passwordReaderCond = PR_NewCondVar(keydb->passwordLock); + if (keydb->passwordReaderCond == NULL) { + PR_DestroyCondVar(keydb->passwordWriterCond); + PZ_DestroyLock(keydb->passwordLock); + keydb->passwordWriterCond = NULL; + keydb->passwordLock = NULL; + return SECFailure; + } + keydb->passwordWriters = 0; + keydb->passwordReaders = 0; + keydb->passwordWriterActive = PR_FALSE; + return SECSuccess; +} + +void +sftkdb_passwordLockDestroy(SFTKDBHandle *keydb) +{ + PR_DestroyCondVar(keydb->passwordWriterCond); + keydb->passwordWriterCond = NULL; + PR_DestroyCondVar(keydb->passwordReaderCond); + keydb->passwordReaderCond = NULL; + PZ_DestroyLock(keydb->passwordLock); + keydb->passwordLock = NULL; +} + +PRBool +sftkdb_passwordLockIsInited(SFTKDBHandle *keydb) +{ + return (keydb->passwordLock) && (keydb->passwordWriterCond) + && (keydb->passwordReaderCond); +} + +/* we need reader/writer locks for the database because we have servers + * that use the database key in their transactions on multi-cpu systems. + * This means that all the other cpus doing private key operations become + * effectively single threaded. We implement our specific reader/writer + * locks because we want writer locks to interrupt reader streams to + * prevent starvation on the writer side. */ +void +sftkdb_passwordReaderLock(SFTKDBHandle *keydb) +{ + PZ_Lock(keydb->passwordLock); + /* if there is a writer waiting or running wait until the + * writer has completed before we continue. This prevents + * writer starvation in a loaded system */ + while (keydb->passwordWriters) { + PR_WaitCondVar(keydb->passwordReaderCond, PR_INTERVAL_NO_TIMEOUT); + } + /* allow multiple readers */ + keydb->passwordReaders++; + PORT_Assert(keydb->passwordWriterActive == PR_FALSE); + PZ_Unlock(keydb->passwordLock); +} + +/* We can only have 1 active writer or multiple active readers. + * Readers will wait for an active writer. This means lots of write + * operations can stall the readers. Write operations are rare, though and + * only happen on database update, or on initial login. + */ +void +sftkdb_passwordWriterLock(SFTKDBHandle *keydb) +{ + PZ_Lock(keydb->passwordLock); + keydb->passwordWriters++; + /* if we have any readers or writers, wait for the writer condition */ + while (keydb->passwordReaders || keydb->passwordWriterActive) { + PR_WaitCondVar(keydb->passwordWriterCond, PR_INTERVAL_NO_TIMEOUT); + } + /* only one writer allowed at a time */ + keydb->passwordWriterActive = PR_TRUE; + PZ_Unlock(keydb->passwordLock); +} + +/* unlock, decrements the reader counter, and if we have not active readers, + * notify any waiting writers. */ +void +sftkdb_passwordReaderUnlock(SFTKDBHandle *keydb) +{ + PZ_Lock(keydb->passwordLock); + PORT_Assert(keydb->passwordReaders); + keydb->passwordReaders--; + if (keydb->passwordReaders == 0) { + if (keydb->passwordWriters) { + PR_NotifyCondVar(keydb->passwordWriterCond); + } + } + PZ_Unlock(keydb->passwordLock); +} + +/* unlock, if there are more writers, wake one of them up. If there or no more + * writers but readers, wake up all the readers. */ +void +sftkdb_passwordWriterUnlock(SFTKDBHandle *keydb) +{ + PZ_Lock(keydb->passwordLock); + PORT_Assert(keydb->passwordWriters); + PORT_Assert(keydb->passwordWriterActive); + keydb->passwordWriterActive = PR_FALSE; + keydb->passwordWriters--; + if (keydb->passwordWriters) { + PR_NotifyCondVar(keydb->passwordWriterCond); + } else { + PR_NotifyAllCondVar(keydb->passwordReaderCond); + } + PZ_Unlock(keydb->passwordLock); +} + /* * safely swith the passed in key for the one caches in the keydb handle * * A key attached to the handle tells us the the token is logged in. * We can used the key attached to the handle in sftkdb_EncryptAttribute * and sftkdb_DecryptAttribute calls. */ static void sftkdb_switchKeys(SFTKDBHandle *keydb, SECItem *passKey, int iterationCount) { unsigned char *data; int len; - if (keydb->passwordLock == NULL) { + if (!sftkdb_passwordLockIsInited(keydb)) { PORT_Assert(keydb->type != SFTK_KEYDB_TYPE); return; } /* an atomic pointer set would be nice */ - SKIP_AFTER_FORK(PZ_Lock(keydb->passwordLock)); + SKIP_AFTER_FORK(sftkdb_passwordWriterLock(keydb)); data = keydb->passwordKey.data; len = keydb->passwordKey.len; keydb->passwordKey.data = passKey->data; keydb->passwordKey.len = passKey->len; keydb->defaultIterationCount = iterationCount; passKey->data = data; passKey->len = len; - SKIP_AFTER_FORK(PZ_Unlock(keydb->passwordLock)); + SKIP_AFTER_FORK(sftkdb_passwordWriterUnlock(keydb)); } /* * returns true if we are in a middle of a merge style update. */ PRBool sftkdb_InUpdateMerge(SFTKDBHandle *keydb) { @@ -700,21 +820,21 @@ sftkdb_GetUpdatePasswordKey(SFTKDBHandle handle = handle->peerDB; } /* don't have one */ if (!handle) { return NULL; } - PZ_Lock(handle->passwordLock); + sftkdb_passwordReaderLock(handle); if (handle->updatePasswordKey) { key = SECITEM_DupItem(handle->updatePasswordKey); } - PZ_Unlock(handle->passwordLock); + sftkdb_passwordReaderUnlock(handle); return key; } /* * free the update password key from a handle. */ void @@ -727,22 +847,22 @@ sftkdb_FreeUpdatePasswordKey(SFTKDBHandl return; } /* if we're a cert db, we don't have one */ if (handle->type == SFTK_CERTDB_TYPE) { return; } - PZ_Lock(handle->passwordLock); + sftkdb_passwordWriterLock(handle); if (handle->updatePasswordKey) { key = handle->updatePasswordKey; handle->updatePasswordKey = NULL; } - PZ_Unlock(handle->passwordLock); + sftkdb_passwordWriterUnlock(handle); if (key) { SECITEM_ZfreeItem(key, PR_TRUE); } return; } @@ -999,25 +1119,25 @@ sftkdb_finishPasswordCheck(SFTKDBHandle * the user. Clear our sessions out to simulate a token * removal. C_GetTokenInfo will change the token description * and the token will still appear to be logged out. * 2) If we already have the source DB password, this password is * for the target database. We can now move forward with the * update, as we now have both required passwords. * */ - PZ_Lock(keydb->passwordLock); + sftkdb_passwordWriterLock(keydb); if (sftkdb_NeedUpdateDBPassword(keydb)) { /* Squirrel this special key away. * This has the side effect of turning sftkdb_NeedLegacyPW off, * as well as changing which database is returned from * SFTK_GET_PW_DB (thus effecting both sftkdb_CheckPassword() * and sftkdb_HasPasswordSet()) */ keydb->updatePasswordKey = SECITEM_DupItem(key); - PZ_Unlock(keydb->passwordLock); + sftkdb_passwordWriterUnlock(keydb); if (keydb->updatePasswordKey == NULL) { /* PORT_Error set by SECITEM_DupItem */ rv = SECFailure; goto done; } /* Simulate a token removal -- we need to do this any * any case at this point so the token name is correct. */ @@ -1072,17 +1192,17 @@ sftkdb_finishPasswordCheck(SFTKDBHandle goto done; } else { /* there is no password, just fall through to update. * update will write the source DB's password record * into the target DB just like it would in a non-merge * update case. */ } } else { - PZ_Unlock(keydb->passwordLock); + sftkdb_passwordWriterUnlock(keydb); } /* load the keys, so the keydb can parse it's key set */ sftkdb_switchKeys(keydb, key, iterationCount); /* we need to update, do it now */ if (((keydb->db->sdb_flags & SDB_RDONLY) == 0) && keydb->update) { /* update the peer certdb if it exists */ if (keydb->peerDB) {