bug 1375709 - avoid deadlock when shutting down NSS r?Cykesiopka,ttaubert draft
authorDavid Keeler <dkeeler@mozilla.com>
Mon, 10 Jul 2017 16:25:51 -0700
changeset 609132 a586cd85a8bd70b0d3ed9afa36b06dcfe9b2f3ab
parent 607669 03bcd6d65af62c5e60a0ab9247ccce43885e707b
child 637510 8fe7fc91468db57496e3652f0aa3d27c7dff66ca
push id68512
push userbmo:dkeeler@mozilla.com
push dateFri, 14 Jul 2017 21:01:15 +0000
reviewersCykesiopka, ttaubert
bugs1375709, 1273475
milestone56.0a1
bug 1375709 - avoid deadlock when shutting down NSS r?Cykesiopka,ttaubert The deadlock fix attempted in bug 1273475 was incomplete. This should prevent the issue by preventing nsNSSShutDownPreventionLocks from attempting to increment the NSS activity state count when shutdown is in progress (this is acceptible because when code that creates any nsNSSShutDownPreventionLocks then checks isAlreadyShutDown(), it will return true because sInShutdown is true, thus preventing that code from unsafely using NSS resources and functions). MozReview-Commit-ID: 4o5DGbU2TCq
security/manager/ssl/nsNSSShutDown.cpp
security/manager/ssl/nsNSSShutDown.h
--- a/security/manager/ssl/nsNSSShutDown.cpp
+++ b/security/manager/ssl/nsNSSShutDown.cpp
@@ -130,92 +130,92 @@ nsresult nsNSSShutDownList::doPK11Logout
 
 nsresult nsNSSShutDownList::evaporateAllNSSResourcesAndShutDown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
+  // This makes this function idempotent and protects against reentering it
+  // (which really shouldn't happen anyway, but just in case).
+  if (sInShutdown) {
+    return NS_OK;
+  }
+
   StaticMutexAutoLock lock(sListLock);
   // Other threads can acquire an nsNSSShutDownPreventionLock and cause this
   // thread to block when it calls restructActivityToCurrentThread, below. If
-  // those other threads then attempt to create an object that must be
-  // remembered by the shut down list, they will call
-  // nsNSSShutDownList::remember, which attempts to acquire sListLock.
+  // those other threads then attempt to create an nsNSSShutDownObject, they
+  // will call nsNSSShutDownList::remember, which attempts to acquire sListLock.
   // Consequently, holding sListLock while we're in
   // restrictActivityToCurrentThread would result in deadlock. sListLock
   // protects the singleton, so if we enforce that the singleton only be created
   // and destroyed on the main thread, and if we similarly enforce that this
   // function is only called on the main thread, what we can do is check that
   // the singleton hasn't already gone away and then we don't actually have to
   // hold sListLock while calling restrictActivityToCurrentThread.
   if (!singleton) {
     return NS_OK;
   }
 
+  // Setting sInShutdown here means that threads calling
+  // nsNSSShutDownList::remember will return early (because
+  // nsNSSShutDownList::construct will return false) and not attempt to remember
+  // new objects. If they properly check isAlreadyShutDown(), they will also not
+  // attempt to call NSS functions or use NSS resources.
   sInShutdown = true;
 
   {
     StaticMutexAutoUnlock unlock(sListLock);
     PRStatus rv = singleton->mActivityState.restrictActivityToCurrentThread();
     if (rv != PR_SUCCESS) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
               ("failed to restrict activity to current thread"));
       return NS_ERROR_FAILURE;
     }
   }
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("now evaporating NSS resources"));
-
-  // Never free more than one entry, because other threads might be calling
-  // us and remove themselves while we are iterating over the list,
-  // and the behaviour of changing the list while iterating is undefined.
-  while (singleton) {
-    auto iter = singleton->mObjects.Iter();
-    if (iter.Done()) {
-      break;
-    }
+  for (auto iter = singleton->mObjects.Iter(); !iter.Done(); iter.Next()) {
     auto entry = static_cast<ObjectHashEntry*>(iter.Get());
-    {
-      StaticMutexAutoUnlock unlock(sListLock);
-      entry->obj->shutdown(nsNSSShutDownObject::ShutdownCalledFrom::List);
-    }
+    entry->obj->shutdown(nsNSSShutDownObject::ShutdownCalledFrom::List);
     iter.Remove();
   }
 
-  if (!singleton) {
-    return NS_ERROR_FAILURE;
-  }
-
   singleton->mActivityState.releaseCurrentThreadActivityRestriction();
   delete singleton;
 
   return NS_OK;
 }
 
-void nsNSSShutDownList::enterActivityState()
+void nsNSSShutDownList::enterActivityState(/*out*/ bool& enteredActivityState)
 {
   StaticMutexAutoLock lock(sListLock);
   if (nsNSSShutDownList::construct(lock)) {
     singleton->mActivityState.enter();
+    enteredActivityState = true;
   }
 }
 
 void nsNSSShutDownList::leaveActivityState()
 {
   StaticMutexAutoLock lock(sListLock);
   if (singleton) {
     singleton->mActivityState.leave();
   }
 }
 
 bool nsNSSShutDownList::construct(const StaticMutexAutoLock& /*proofOfLock*/)
 {
-  if (!singleton && !sInShutdown && XRE_IsParentProcess()) {
+  if (sInShutdown) {
+    return false;
+  }
+
+  if (!singleton && XRE_IsParentProcess()) {
     singleton = new nsNSSShutDownList();
   }
 
   return !!singleton;
 }
 
 nsNSSActivityState::nsNSSActivityState()
 :mNSSActivityStateLock("nsNSSActivityState.mNSSActivityStateLock"),
@@ -268,22 +268,25 @@ void nsNSSActivityState::releaseCurrentT
   MutexAutoLock lock(mNSSActivityStateLock);
 
   mNSSRestrictedThread = nullptr;
 
   mNSSActivityChanged.NotifyAll();
 }
 
 nsNSSShutDownPreventionLock::nsNSSShutDownPreventionLock()
+  : mEnteredActivityState(false)
 {
-  nsNSSShutDownList::enterActivityState();
+  nsNSSShutDownList::enterActivityState(mEnteredActivityState);
 }
 
 nsNSSShutDownPreventionLock::~nsNSSShutDownPreventionLock()
 {
-  nsNSSShutDownList::leaveActivityState();
+  if (mEnteredActivityState) {
+    nsNSSShutDownList::leaveActivityState();
+  }
 }
 
 bool
 nsNSSShutDownObject::isAlreadyShutDown() const
 {
   return mAlreadyShutDown || sInShutdown;
 }
--- a/security/manager/ssl/nsNSSShutDown.h
+++ b/security/manager/ssl/nsNSSShutDown.h
@@ -52,16 +52,22 @@ private:
 };
 
 // Helper class that automatically enters/leaves the global activity state
 class nsNSSShutDownPreventionLock
 {
 public:
   nsNSSShutDownPreventionLock();
   ~nsNSSShutDownPreventionLock();
+private:
+  // Keeps track of whether or not we actually managed to enter the NSS activity
+  // state. This is important because if we're attempting to shut down and we
+  // can't enter the NSS activity state, we need to not attempt to leave it when
+  // our destructor runs.
+  bool mEnteredActivityState;
 };
 
 // Singleton, used by nsNSSComponent to track the list of PSM objects,
 // which hold NSS resources and support the "early cleanup mechanism".
 class nsNSSShutDownList
 {
 public:
   // track instances that support early cleanup
@@ -77,17 +83,19 @@ public:
   // using NSS functions.
   static nsresult evaporateAllNSSResourcesAndShutDown();
 
   // PSM has been asked to log out of a token.
   // Notify all registered instances that want to react to that event.
   static nsresult doPK11Logout();
 
   // Signal entering/leaving a scope where shutting down NSS is prohibited.
-  static void enterActivityState();
+  // enteredActivityState will be set to true if we actually managed to enter
+  // the NSS activity state.
+  static void enterActivityState(/*out*/ bool& enteredActivityState);
   static void leaveActivityState();
 
 private:
   static bool construct(const mozilla::StaticMutexAutoLock& /*proofOfLock*/);
 
   nsNSSShutDownList();
   ~nsNSSShutDownList();