diff options
Diffstat (limited to 'security/manager/ssl/nsNSSShutDown.h')
-rw-r--r-- | security/manager/ssl/nsNSSShutDown.h | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSShutDown.h b/security/manager/ssl/nsNSSShutDown.h new file mode 100644 index 000000000..00d017239 --- /dev/null +++ b/security/manager/ssl/nsNSSShutDown.h @@ -0,0 +1,271 @@ +/* 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 nsNSSShutDown_h +#define nsNSSShutDown_h + +#include "PLDHashTable.h" +#include "mozilla/Assertions.h" +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "nscore.h" +#include "nspr.h" + +class nsNSSShutDownObject; +class nsOnPK11LogoutCancelObject; + +// Singleton, owned by nsNSSShutDownList +class nsNSSActivityState +{ +public: + nsNSSActivityState(); + ~nsNSSActivityState(); + + // Call enter/leave when PSM enters a scope during which + // shutting down NSS is prohibited. + void enter(); + void leave(); + // Wait for all activity to stop, and block any other thread on entering + // relevant PSM code. + PRStatus restrictActivityToCurrentThread(); + + // Go back to normal state. + void releaseCurrentThreadActivityRestriction(); + +private: + // The lock protecting all our member variables. + mozilla::Mutex mNSSActivityStateLock; + + // The activity variable, bound to our lock, + // used either to signal the activity counter reaches zero, + // or a thread restriction has been released. + mozilla::CondVar mNSSActivityChanged; + + // The number of active scopes holding resources. + int mNSSActivityCounter; + + // nullptr means "no restriction" + // if not null, activity is only allowed on that thread + PRThread* mNSSRestrictedThread; +}; + +// Helper class that automatically enters/leaves the global activity state +class nsNSSShutDownPreventionLock +{ +public: + nsNSSShutDownPreventionLock(); + ~nsNSSShutDownPreventionLock(); +}; + +// Singleton, used by nsNSSComponent to track the list of PSM objects, +// which hold NSS resources and support the "early cleanup mechanism". +class nsNSSShutDownList +{ +public: + static void shutdown(); + + // track instances that support early cleanup + static void remember(nsNSSShutDownObject *o); + static void forget(nsNSSShutDownObject *o); + + // track instances that would like notification when + // a PK11 logout operation is performed. + static void remember(nsOnPK11LogoutCancelObject *o); + static void forget(nsOnPK11LogoutCancelObject *o); + + // Do the "early cleanup", if possible. + static nsresult evaporateAllNSSResources(); + + // 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(); + static void leaveActivityState(); + +private: + static bool construct(const mozilla::StaticMutexAutoLock& /*proofOfLock*/); + + nsNSSShutDownList(); + ~nsNSSShutDownList(); + +protected: + PLDHashTable mObjects; + PLDHashTable mPK11LogoutCancelObjects; + nsNSSActivityState mActivityState; +}; + +/* + A class deriving from nsNSSShutDownObject will have its instances + automatically tracked in a list. However, it must follow some rules + to assure correct behaviour. + + The tricky part is that it is not possible to call virtual + functions from a destructor. + + The deriving class must override virtualDestroyNSSReference(). + Within this function, it should clean up all resources held to NSS. + The function will be called by the global list, if it is time to + shut down NSS before all references have been freed. + + The same code that goes into virtualDestroyNSSReference must + also be called from the destructor of the deriving class, + which is the standard cleanup (not called from the tracking list). + + Because of that duplication, it is suggested to implement a + function destructorSafeDestroyNSSReference() in the deriving + class, and make the implementation of virtualDestroyNSSReference() + call destructorSafeDestroyNSSReference(). + + The destructor of the derived class must prevent NSS shutdown on + another thread by acquiring an nsNSSShutDownPreventionLock. It must + then check to see if NSS has already been shut down by calling + isAlreadyShutDown(). If NSS has not been shut down, the destructor + must then call destructorSafeDestroyNSSReference() and then + shutdown(ShutdownCalledFrom::Object). The second call will deregister with + the tracking list, to ensure no additional attempt to free the resources + will be made. + + ---------------------------------------------------------------------------- + IMPORTANT NOTE REGARDING CLASSES THAT IMPLEMENT nsNSSShutDownObject BUT DO + NOT DIRECTLY HOLD NSS RESOURCES: + ---------------------------------------------------------------------------- + Currently, classes that do not hold NSS resources but do call NSS functions + inherit from nsNSSShutDownObject (and use the lock/isAlreadyShutDown + mechanism) as a way of ensuring it is safe to call those functions. Because + these classes do not hold any resources, however, it is tempting to skip the + destructor component of this interface. This MUST NOT be done, because + if an object of such a class is destructed before the nsNSSShutDownList + processes all of its entries, this essentially causes a use-after-free when + nsNSSShutDownList reaches the entry that has been destroyed. The safe way to + do this is to implement the destructor as usual but omit the call to + destructorSafeDestroyNSSReference() as it is unnecessary and probably isn't + defined for that class. + + destructorSafeDestroyNSSReference() does not need to acquire an + nsNSSShutDownPreventionLock or check isAlreadyShutDown() as long as it + is only called by the destructor that has already acquired the lock and + checked for shutdown or by the NSS shutdown code itself (which acquires + the same lock and checks if objects it cleans up have already cleaned + up themselves). + + destructorSafeDestroyNSSReference() MUST NOT cause any other + nsNSSShutDownObject to be deconstructed. Doing so can cause + unsupported concurrent operations on the hash table in the + nsNSSShutDownList. + + class derivedClass : public nsISomeInterface, + public nsNSSShutDownObject + { + virtual void virtualDestroyNSSReference() + { + destructorSafeDestroyNSSReference(); + } + + void destructorSafeDestroyNSSReference() + { + // clean up all NSS resources here + } + + virtual ~derivedClass() + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); + } + + NS_IMETHODIMP doSomething() + { + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + // use the NSS resources and do something + } + }; +*/ + +class nsNSSShutDownObject +{ +public: + enum class ShutdownCalledFrom { + List, + Object, + }; + + nsNSSShutDownObject() + { + mAlreadyShutDown = false; + nsNSSShutDownList::remember(this); + } + + virtual ~nsNSSShutDownObject() + { + // The derived class must call + // shutdown(ShutdownCalledFrom::Object); + // in its destructor + } + + void shutdown(ShutdownCalledFrom calledFrom) + { + if (!mAlreadyShutDown) { + switch (calledFrom) { + case ShutdownCalledFrom::Object: + nsNSSShutDownList::forget(this); + break; + case ShutdownCalledFrom::List: + virtualDestroyNSSReference(); + break; + default: + MOZ_CRASH("shutdown() called from an unknown source"); + } + mAlreadyShutDown = true; + } + } + + bool isAlreadyShutDown() const { return mAlreadyShutDown; } + +protected: + virtual void virtualDestroyNSSReference() = 0; +private: + volatile bool mAlreadyShutDown; +}; + +class nsOnPK11LogoutCancelObject +{ +public: + nsOnPK11LogoutCancelObject() + : mIsLoggedOut(false) + { + nsNSSShutDownList::remember(this); + } + + virtual ~nsOnPK11LogoutCancelObject() + { + nsNSSShutDownList::forget(this); + } + + void logout() + { + // We do not care for a race condition. + // Once the bool arrived at false, + // later calls to isPK11LoggedOut() will see it. + // This is a one-time change from 0 to 1. + mIsLoggedOut = true; + } + + bool isPK11LoggedOut() + { + return mIsLoggedOut; + } + +private: + volatile bool mIsLoggedOut; +}; + +#endif // nsNSSShutDown_h |