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
--- 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();