Blame SOURCES/nss-is-token-present-race.patch

74fc46
# HG changeset patch
74fc46
# User Robert Relyea <rrelyea@redhat.com>
74fc46
# Date 1516007838 -3600
74fc46
#      Mon Jan 15 10:17:18 2018 +0100
74fc46
# Node ID 33d9c969cd6548c335ce43fa8909b96ef323f670
74fc46
# Parent  db32ef3be38eb06a91babbcbb48285284d704dbd
74fc46
Bug 1054373, Crash in PK11_DoesMechanism due to race condition, r=rsleevi
74fc46
74fc46
diff --git a/lib/dev/devslot.c b/lib/dev/devslot.c
74fc46
--- a/lib/dev/devslot.c
74fc46
+++ b/lib/dev/devslot.c
74fc46
@@ -33,6 +33,8 @@ nssSlot_Destroy(
74fc46
         if (PR_ATOMIC_DECREMENT(&slot->base.refCount) == 0) {
74fc46
             PK11_FreeSlot(slot->pk11slot);
74fc46
             PZ_DestroyLock(slot->base.lock);
74fc46
+            PZ_DestroyCondVar(slot->isPresentCondition);
74fc46
+            PZ_DestroyLock(slot->isPresentLock);
74fc46
             return nssArena_Destroy(slot->base.arena);
74fc46
         }
74fc46
     }
74fc46
@@ -117,35 +119,61 @@ nssSlot_IsTokenPresent(
74fc46
     nssSession *session;
74fc46
     CK_SLOT_INFO slotInfo;
74fc46
     void *epv;
74fc46
+    PRBool isPresent = PR_FALSE;
74fc46
+
74fc46
     /* permanent slots are always present unless they're disabled */
74fc46
     if (nssSlot_IsPermanent(slot)) {
74fc46
         return !PK11_IsDisabled(slot->pk11slot);
74fc46
     }
74fc46
+
74fc46
     /* avoid repeated calls to check token status within set interval */
74fc46
+    PZ_Lock(slot->isPresentLock);
74fc46
     if (within_token_delay_period(slot)) {
74fc46
-        return ((slot->ckFlags & CKF_TOKEN_PRESENT) != 0);
74fc46
+        CK_FLAGS ckFlags = slot->ckFlags;
74fc46
+        PZ_Unlock(slot->isPresentLock);
74fc46
+        return ((ckFlags & CKF_TOKEN_PRESENT) != 0);
74fc46
     }
74fc46
+    PZ_Unlock(slot->isPresentLock);
74fc46
 
74fc46
-    /* First obtain the slot info */
74fc46
+    /* First obtain the slot epv before we set up the condition
74fc46
+     * variable, so we can just return if we couldn't get it. */
74fc46
     epv = slot->epv;
74fc46
     if (!epv) {
74fc46
         return PR_FALSE;
74fc46
     }
74fc46
+
74fc46
+    /* set up condition so only one thread is active in this part of the code at a time */
74fc46
+    PZ_Lock(slot->isPresentLock);
74fc46
+    while (slot->inIsPresent) {
74fc46
+        PR_WaitCondVar(slot->isPresentCondition, 0);
74fc46
+    }
74fc46
+    /* if we were one of multiple threads here, the first thread will have
74fc46
+     * given us the answer, no need to make more queries of the token. */
74fc46
+    if (within_token_delay_period(slot)) {
74fc46
+        CK_FLAGS ckFlags = slot->ckFlags;
74fc46
+        PZ_Unlock(slot->isPresentLock);
74fc46
+        return ((ckFlags & CKF_TOKEN_PRESENT) != 0);
74fc46
+    }
74fc46
+    /* this is the winning thread, block all others until we've determined
74fc46
+     * if the token is present and that it needs initialization. */
74fc46
+    slot->inIsPresent = PR_TRUE;
74fc46
+    PZ_Unlock(slot->isPresentLock);
74fc46
+
74fc46
     nssSlot_EnterMonitor(slot);
74fc46
     ckrv = CKAPI(epv)->C_GetSlotInfo(slot->slotID, &slotInfo);
74fc46
     nssSlot_ExitMonitor(slot);
74fc46
     if (ckrv != CKR_OK) {
74fc46
         slot->token->base.name[0] = 0; /* XXX */
74fc46
-        slot->lastTokenPing = PR_IntervalNow();
74fc46
-        return PR_FALSE;
74fc46
+        isPresent = PR_FALSE;
74fc46
+        goto done;
74fc46
     }
74fc46
     slot->ckFlags = slotInfo.flags;
74fc46
     /* check for the presence of the token */
74fc46
     if ((slot->ckFlags & CKF_TOKEN_PRESENT) == 0) {
74fc46
         if (!slot->token) {
74fc46
             /* token was never present */
74fc46
-            slot->lastTokenPing = PR_IntervalNow();
74fc46
-            return PR_FALSE;
74fc46
+            isPresent = PR_FALSE;
74fc46
+            goto done;
74fc46
         }
74fc46
         session = nssToken_GetDefaultSession(slot->token);
74fc46
         if (session) {
74fc46
@@ -167,15 +195,15 @@ nssSlot_IsTokenPresent(
74fc46
         slot->token->base.name[0] = 0; /* XXX */
74fc46
         /* clear the token cache */
74fc46
         nssToken_Remove(slot->token);
74fc46
-        slot->lastTokenPing = PR_IntervalNow();
74fc46
-        return PR_FALSE;
74fc46
+        isPresent = PR_FALSE;
74fc46
+        goto done;
74fc46
     }
74fc46
     /* token is present, use the session info to determine if the card
74fc46
      * has been removed and reinserted.
74fc46
      */
74fc46
     session = nssToken_GetDefaultSession(slot->token);
74fc46
     if (session) {
74fc46
-        PRBool isPresent = PR_FALSE;
74fc46
+        PRBool tokenRemoved;
74fc46
         nssSession_EnterMonitor(session);
74fc46
         if (session->handle != CK_INVALID_SESSION) {
74fc46
             CK_SESSION_INFO sessionInfo;
74fc46
@@ -187,12 +215,12 @@ nssSlot_IsTokenPresent(
74fc46
                 session->handle = CK_INVALID_SESSION;
74fc46
             }
74fc46
         }
74fc46
-        isPresent = session->handle != CK_INVALID_SESSION;
74fc46
+        tokenRemoved = (session->handle == CK_INVALID_SESSION);
74fc46
         nssSession_ExitMonitor(session);
74fc46
         /* token not removed, finished */
74fc46
-        if (isPresent) {
74fc46
-            slot->lastTokenPing = PR_IntervalNow();
74fc46
-            return PR_TRUE;
74fc46
+        if (!tokenRemoved) {
74fc46
+            isPresent = PR_TRUE;
74fc46
+            goto done;
74fc46
         }
74fc46
     }
74fc46
     /* the token has been removed, and reinserted, or the slot contains
74fc46
@@ -203,15 +231,27 @@ nssSlot_IsTokenPresent(
74fc46
     nssToken_Remove(slot->token);
74fc46
     /* token has been removed, need to refresh with new session */
74fc46
     nssrv = nssSlot_Refresh(slot);
74fc46
+    isPresent = PR_TRUE;
74fc46
     if (nssrv != PR_SUCCESS) {
74fc46
         slot->token->base.name[0] = 0; /* XXX */
74fc46
         slot->ckFlags &= ~CKF_TOKEN_PRESENT;
74fc46
-        /* TODO: insert a barrier here to avoid reordering of the assingments */
74fc46
-        slot->lastTokenPing = PR_IntervalNow();
74fc46
-        return PR_FALSE;
74fc46
+        isPresent = PR_FALSE;
74fc46
     }
74fc46
+done:
74fc46
+    /* Once we've set up the condition variable,
74fc46
+     * Before returning, it's necessary to:
74fc46
+     *  1) Set the lastTokenPing time so that any other threads waiting on this
74fc46
+     *     initialization and any future calls within the initialization window
74fc46
+     *     return the just-computed status.
74fc46
+     *  2) Indicate we're complete, waking up all other threads that may still
74fc46
+     *     be waiting on initialization can progress.
74fc46
+     */
74fc46
+    PZ_Lock(slot->isPresentLock);
74fc46
     slot->lastTokenPing = PR_IntervalNow();
74fc46
-    return PR_TRUE;
74fc46
+    slot->inIsPresent = PR_FALSE;
74fc46
+    PR_NotifyAllCondVar(slot->isPresentCondition);
74fc46
+    PZ_Unlock(slot->isPresentLock);
74fc46
+    return isPresent;
74fc46
 }
74fc46
 
74fc46
 NSS_IMPLEMENT void *
74fc46
@@ -229,7 +269,7 @@ nssSlot_GetToken(
74fc46
 
74fc46
     if (nssSlot_IsTokenPresent(slot)) {
74fc46
         /* Even if a token should be present, check `slot->token` too as it
74fc46
-	 * might be gone already. This would happen mostly on shutdown. */
74fc46
+         * might be gone already. This would happen mostly on shutdown. */
74fc46
         nssSlot_EnterMonitor(slot);
74fc46
         if (slot->token)
74fc46
             rvToken = nssToken_AddRef(slot->token);
74fc46
diff --git a/lib/dev/devt.h b/lib/dev/devt.h
74fc46
--- a/lib/dev/devt.h
74fc46
+++ b/lib/dev/devt.h
74fc46
@@ -81,6 +81,9 @@ struct NSSSlotStr {
74fc46
     PZLock *lock;
74fc46
     void *epv;
74fc46
     PK11SlotInfo *pk11slot;
74fc46
+    PZLock *isPresentLock;
74fc46
+    PRCondVar *isPresentCondition;
74fc46
+    PRBool inIsPresent;
74fc46
 };
74fc46
 
74fc46
 struct nssSessionStr {
74fc46
diff --git a/lib/pk11wrap/dev3hack.c b/lib/pk11wrap/dev3hack.c
74fc46
--- a/lib/pk11wrap/dev3hack.c
74fc46
+++ b/lib/pk11wrap/dev3hack.c
74fc46
@@ -120,6 +120,9 @@ nssSlot_CreateFromPK11SlotInfo(NSSTrustD
74fc46
     /* Grab the slot name from the PKCS#11 fixed-length buffer */
74fc46
     rvSlot->base.name = nssUTF8_Duplicate(nss3slot->slot_name, td->arena);
74fc46
     rvSlot->lock = (nss3slot->isThreadSafe) ? NULL : nss3slot->sessionLock;
74fc46
+    rvSlot->isPresentLock = PZ_NewLock(nssiLockOther);
74fc46
+    rvSlot->isPresentCondition = PR_NewCondVar(rvSlot->isPresentLock);
74fc46
+    rvSlot->inIsPresent = PR_FALSE;
74fc46
     return rvSlot;
74fc46
 }
74fc46