Bug 1312141 - SECMOD_OpenUserDB will allow multiple opens of the same database.
authorRobert Relyea <rrelyea@redhat.com>
Wed, 26 Oct 2016 17:44:33 -0700
changeset 12770 b7b419e4e2f6c7bdda9700ac37883dec5e1ddef8
parent 12769 7701b1b052aa31ddf3159d489ebba777094e055c
child 12771 8131d772968bd84e143782c0e549f89b38417920
push id1712
push userrrelyea@redhat.com
push dateThu, 27 Oct 2016 00:44:41 +0000
bugs1312141
Bug 1312141 - SECMOD_OpenUserDB will allow multiple opens of the same database. r=mt
lib/pk11wrap/pk11pars.c
lib/pk11wrap/pk11util.c
lib/pk11wrap/secmodi.h
--- a/lib/pk11wrap/pk11pars.c
+++ b/lib/pk11wrap/pk11pars.c
@@ -12,16 +12,17 @@
 #include "seccomon.h"
 #include "secmod.h"
 #include "secmodi.h"
 #include "secmodti.h"
 #include "pki3hack.h"
 #include "secerr.h"
 #include "nss.h"
 #include "utilpars.h"
+#include "pk11pub.h"
 
 /* create a new module */
 static  SECMODModule *
 secmod_NewModule(void)
 {
     SECMODModule *newMod;
     PLArenaPool *arena;
 
@@ -1109,21 +1110,51 @@ secmod_matchPrefix(char *prefix1, char *
 	return PR_FALSE;
     }
     if (strcmp(prefix1, prefix2) == 0) {
 	return PR_TRUE;
     }
     return PR_FALSE;
 }
 
+/* do two config paramters match? Not all callers are compariing 
+ * SECMODConfigLists directly, so this function breaks them out to their
+ * components. */
+static PRBool
+secmod_matchConfig(char *configDir1, char *configDir2,
+		   char *certPrefix1, char *certPrefix2,
+		   char *keyPrefix1, char *keyPrefix2,
+		   PRBool isReadOnly1, PRBool isReadOnly2)
+{
+    if (strcmp(configDir1,configDir2) != 0) {
+	return PR_FALSE;
+    } 
+    if (!secmod_matchPrefix(certPrefix1, certPrefix2)) {
+	return PR_FALSE;
+    }
+    if (!secmod_matchPrefix(keyPrefix1, keyPrefix2)) {
+	return PR_FALSE;
+    }
+    /* these last test -- if we just need the DB open read only,
+     * than any open will suffice, but if we requested it read/write
+     * and it's only open read only, we need to open it again */
+    if (isReadOnly1) {
+	return PR_TRUE;
+    }
+    if (isReadOnly2) { /* isReadonly1 == PR_FALSE */
+	return PR_FALSE;
+    }
+    return PR_FALSE;
+}
+
 /*
  * return true if we are requesting a database that is already openned.
  */
 PRBool
-secmod_MatchConfigList(char *spec, SECMODConfigList *conflist, int count)
+secmod_MatchConfigList(const char *spec, SECMODConfigList *conflist, int count)
 {
     char *config;
     char *certPrefix;
     char *keyPrefix;
     PRBool isReadOnly;
     PRBool ret=PR_FALSE;
     int i;
 
@@ -1137,36 +1168,115 @@ secmod_MatchConfigList(char *spec, SECMO
      * twice from two different locations, then we can corrupt our database
      * (the cache will be inconsistent). Protect against this by claiming
      * for comparison only that we are always openning dbm databases read only.
      */
     if (secmod_configIsDBM(config)) {
 	isReadOnly = 1;
     }
     for (i=0; i < count; i++) {
-	if ((strcmp(config,conflist[i].config) == 0)  &&
-	    secmod_matchPrefix(certPrefix, conflist[i].certPrefix) &&
-	    secmod_matchPrefix(keyPrefix, conflist[i].keyPrefix) &&
-	    /* this last test -- if we just need the DB open read only,
-	     * than any open will suffice, but if we requested it read/write
-	     * and it's only open read only, we need to open it again */
-	    (isReadOnly || !conflist[i].isReadOnly)) {
+	if ( secmod_matchConfig(config,conflist[i].config,certPrefix,
+				conflist[i].certPrefix, keyPrefix,
+				conflist[i].keyPrefix, isReadOnly,
+				conflist[i].isReadOnly) ) {
 	    ret = PR_TRUE;
 	    goto done;
 	}
     }
 
     ret = PR_FALSE;
 done:
     PORT_Free(config);
     PORT_Free(certPrefix);
     PORT_Free(keyPrefix);
     return ret;
 }
 
+/*
+ * Find the slot id from the module spec. If the slot is the database slot, we
+ * can get the slot id from the default database slot.
+ */
+CK_SLOT_ID
+secmod_GetSlotIDFromModuleSpec(const char *moduleSpec, SECMODModule *module)
+{
+    char *tmp_spec = NULL;;
+    char **children, **thisChild;
+    CK_SLOT_ID *ids, *thisID, slotID = -1;
+    char *inConfig = NULL, *thisConfig = NULL;
+    char *inCertPrefix, *thisCertPrefix;
+    char *inKeyPrefix, *thisKeyPrefix;
+    PRBool inReadOnly,thisReadOnly;
+
+    inConfig = secmod_getConfigDir(moduleSpec,&inCertPrefix, &inKeyPrefix, 
+				   &inReadOnly);
+    if (!inConfig) {
+        goto done;
+    }
+
+    if (secmod_configIsDBM(inConfig)) {
+        inReadOnly = 1;
+    }
+
+    tmp_spec = secmod_ParseModuleSpecForTokens(PR_TRUE, module->isFIPS, 
+				 module->libraryParams, &children, &ids);
+    if (tmp_spec == NULL) {
+        goto done;
+    }
+
+    /* first check to see if the parent is the database */
+    thisConfig = secmod_getConfigDir(tmp_spec,&thisCertPrefix, &thisKeyPrefix, 
+				     &thisReadOnly);
+    if (secmod_matchConfig(inConfig,thisConfig,inCertPrefix,thisCertPrefix,
+          	       inKeyPrefix, thisKeyPrefix, inReadOnly, thisReadOnly)) {
+	/* yup it's the default key slot, get the id for it */
+	PK11SlotInfo *slot = PK11_GetInternalKeySlot();
+	if (slot) {
+	    slotID = slot->slotID;
+	    PK11_FreeSlot(slot);
+        }
+	goto done;
+    }
+
+    /* find id of the token */
+    for (thisChild=children, thisID=ids; thisChild && *thisChild; thisChild++, 
+								thisID++) {
+        thisConfig = secmod_getConfigDir(*thisChild, &thisCertPrefix, 
+				         &thisKeyPrefix, &thisReadOnly);
+        if (thisConfig == NULL) {
+            continue;
+        }
+	if (secmod_matchConfig(inConfig,thisConfig,inCertPrefix,thisCertPrefix,
+        	       inKeyPrefix, thisKeyPrefix, inReadOnly, thisReadOnly)) {
+	    slotID = *thisID;
+	    break;
+        }
+        PORT_Free(thisConfig);
+        PORT_Free(thisCertPrefix);
+        PORT_Free(thisKeyPrefix);
+	thisConfig = NULL;
+    }
+
+done:
+    if (inConfig) {
+        PORT_Free(inConfig);
+        PORT_Free(inCertPrefix);
+        PORT_Free(inKeyPrefix);
+    }
+    if (thisConfig) {
+        PORT_Free(thisConfig);
+        PORT_Free(thisCertPrefix);
+        PORT_Free(thisKeyPrefix);
+    }
+    if (tmp_spec) {
+        secmod_FreeChildren(children, ids);
+        PORT_Free(tmp_spec);
+    }
+    return slotID;
+}
+
 void
 secmod_FreeConfigList(SECMODConfigList *conflist, int count)
 {
     int i;
     for (i=0; i < count; i++) {
 	PORT_Free(conflist[i].config);
 	PORT_Free(conflist[i].certPrefix);
 	PORT_Free(conflist[i].keyPrefix);
--- a/lib/pk11wrap/pk11util.c
+++ b/lib/pk11wrap/pk11util.c
@@ -1406,16 +1406,30 @@ SECMOD_OpenNewSlot(SECMODModule *mod, co
 	}
 	/* force the slot info structures to properly reset */
 	(void)PK11_IsPresent(slot);
     }
     return slot;
 }
 
 /*
+ * given a module spec, find the slot in the module for it.
+ */
+PK11SlotInfo *
+secmod_FindSlotFromModuleSpec(const char *moduleSpec, SECMODModule *module)
+{
+    CK_SLOT_ID slot_id = secmod_GetSlotIDFromModuleSpec(moduleSpec,module);
+    if (slot_id == -1) {
+	return NULL;
+    }
+
+    return SECMOD_FindSlotByID(module, slot_id);
+}
+
+/*
  * Open a new database using the softoken. The caller is responsible for making
  * sure the module spec is correct and usable. The caller should ask for one
  * new database per call if the caller wants to get meaningful information 
  * about the new database.
  *
  * moduleSpec is the same data that you would pass to softoken at 
  * initialization time under the 'tokens' options. For example, if you were
  * to specify tokens=<0x4=[configdir='./mybackup' tokenDescription='Backup']>
@@ -1458,29 +1472,46 @@ SECMOD_OpenNewSlot(SECMODModule *mod, co
  *              optimizeSpace - allocate smaller hash tables and lock tables.
  *                When this flag is not specified, Softoken will allocate 
  *                large tables to prevent lock contention. 
  */
 PK11SlotInfo *
 SECMOD_OpenUserDB(const char *moduleSpec)
 {
     SECMODModule *mod;
+    SECMODConfigList *conflist = NULL;
+    int count = 0;
 
     if (moduleSpec == NULL) {
 	return NULL;
     }
 
     /* NOTE: unlike most PK11 function, this does not return a reference
      * to the module */
     mod = SECMOD_GetInternalModule();
     if (!mod) {
 	/* shouldn't happen */
 	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
 	return NULL;
     }
+
+    /* make sure we don't open the same database twice. We only understand 
+     * the moduleSpec for internal databases well enough to do this, so only
+     * do this in OpenUserDB */
+    conflist = secmod_GetConfigList(mod->isFIPS, mod->libraryParams, &count);
+    if (conflist) {
+	PK11SlotInfo *slot = NULL;
+	if (secmod_MatchConfigList(moduleSpec, conflist, count)) {
+	    slot = secmod_FindSlotFromModuleSpec(moduleSpec, mod);
+	}
+	secmod_FreeConfigList(conflist,count);
+	if (slot) {
+		return slot;
+	}
+    }
     return SECMOD_OpenNewSlot(mod, moduleSpec);
 }
 
 
 /*
  * close an already opened user database. NOTE: the database must be
  * in the internal token, and must be one created with SECMOD_OpenUserDB().
  * Once the database is closed, the slot will remain as an empty slot
--- a/lib/pk11wrap/secmodi.h
+++ b/lib/pk11wrap/secmodi.h
@@ -60,18 +60,20 @@ PRBool secmod_IsInternalKeySlot(SECMODMo
 void secmod_SetInternalKeySlotFlag(SECMODModule *mod, PRBool val);
 
 
 /* tools for checking if we are loading the same database twice */
 typedef struct SECMODConfigListStr SECMODConfigList;
 /* collect all the databases in a given spec */
 SECMODConfigList *secmod_GetConfigList(PRBool isFIPS, char *spec, int *count);
 /* see is a spec matches a database on the list */
-PRBool secmod_MatchConfigList(char *spec, 
+PRBool secmod_MatchConfigList(const char *spec, 
 			      SECMODConfigList *conflist, int count);
+/* returns the slot id from a module and modulespec */
+CK_SLOT_ID secmod_GetSlotIDFromModuleSpec(const char *moduleSpec, SECMODModule *module);
 /* free our list of databases */
 void secmod_FreeConfigList(SECMODConfigList *conflist, int count);
 
 /* parsing parameters */
 /* returned char * must be freed by caller with PORT_Free */
 /* children and ids are null terminated arrays which must be freed with
  * secmod_FreeChildren */
 char *secmod_ParseModuleSpecForTokens(PRBool convert,