Blob Blame History Raw
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 <prcvar.h>
 
 /*
  * 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) {