bug 1381154 - remove smartcard monitoring threads r?jcj,mgoodwin draft
authorDavid Keeler <dkeeler@mozilla.com>
Thu, 28 Sep 2017 14:27:21 -0700
changeset 684287 ab845a62dc8ec19ebd21dfa013cd851f7b077c76
parent 683863 d1e995c8640a191cd127e87273ec96cb2fabffa9
child 736833 c1afc6e22165c90be5b89dfad13e5f1371e7872e
push id85579
push userbmo:dkeeler@mozilla.com
push dateFri, 20 Oct 2017 23:19:49 +0000
reviewersjcj, mgoodwin
bugs1381154, 1248818, 1372656
milestone58.0a1
bug 1381154 - remove smartcard monitoring threads r?jcj,mgoodwin Modified from bug 1248818 comment 11: Before this patch, if a user had a smart card (PKCS#11 device) with removable slots, Firefox would launch a thread for each module and loop, calling SECMOD_WaitForAnyTokenEvent to be alerted to any insertions/removals. At shutdown, we would call SECMOD_CancelWait, which would cancel any waiting threads. However, since that involved calling 3rd party code, we really had no idea if these modules were behaving correctly (and, indeed, they often weren't, judging by the shutdown crashes we were getting). The real solution is to stop relying on PKCS#11, but since that's unlikely in the near future, the next best thing would be to load these modules in a child process. That way, misbehaving modules don't cause Firefox to hang/crash/etc. That's a lot of engineering work, though, so what this patch does is avoids the issue by never calling SECMOD_WaitForAnyTokenEvent (and thus we never have to call SECMOD_CancelWait, etc.). Instead, every time Firefox performs an operation that may be affected by a newly added or removed smart card, it first has NSS refresh its view of any removable slots. This is similar to how we ensure the loadable roots module has been loaded (see bug 1372656). MozReview-Commit-ID: JpmLdV7Vvor
security/certverifier/CertVerifier.cpp
security/manager/pki/resources/content/certManager.js
security/manager/pki/resources/content/certManager.xul
security/manager/pki/resources/content/device_manager.js
security/manager/pki/resources/content/device_manager.xul
security/manager/ssl/PKCS11ModuleDB.cpp
security/manager/ssl/moz.build
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSComponent.h
security/manager/ssl/nsNSSIOLayer.cpp
security/manager/ssl/nsPKCS11Slot.cpp
security/manager/ssl/nsSmartCardMonitor.cpp
security/manager/ssl/nsSmartCardMonitor.h
security/manager/ssl/tests/unit/pkcs11testmodule/pkcs11testmodule.cpp
security/manager/ssl/tests/unit/test_pkcs11_insert_remove.js
security/manager/ssl/tests/unit/test_pkcs11_no_events_after_removal.js
security/manager/ssl/tests/unit/xpcshell-smartcards.ini
security/nss.symbols
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -465,16 +465,19 @@ CertVerifier::VerifyCert(CERTCertificate
   MOZ_ASSERT(cert);
   MOZ_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
   MOZ_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);
   MOZ_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult);
 
   if (NS_FAILED(BlockUntilLoadableRootsLoaded())) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
+  if (NS_FAILED(CheckForSmartCardChanges())) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
 
   if (evOidPolicy) {
     *evOidPolicy = SEC_OID_UNKNOWN;
   }
   if (ocspStaplingStatus) {
     if (usage != certificateUsageSSLServer) {
       return Result::FATAL_ERROR_INVALID_ARGS;
     }
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -51,31 +51,17 @@ var emailTreeView;
  */
 var userTreeView;
 /**
  * Cert tree for the "Other" tab.
  * @type nsICertTree
  */
 var orphanTreeView;
 
-var smartCardObserver = {
-  observe() {
-    onSmartCardChange();
-  }
-};
-
-function DeregisterSmartCardObservers() {
-  Services.obs.removeObserver(smartCardObserver, "smartcard-insert");
-  Services.obs.removeObserver(smartCardObserver, "smartcard-remove");
-}
-
 function LoadCerts() {
-  Services.obs.addObserver(smartCardObserver, "smartcard-insert");
-  Services.obs.addObserver(smartCardObserver, "smartcard-remove");
-
   certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
   var certcache = certdb.getCerts();
 
   caTreeView = Components.classes[nsCertTree]
                     .createInstance(nsICertTree);
   caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
   document.getElementById("ca-tree").view = caTreeView;
 
@@ -475,32 +461,16 @@ function addCACerts() {
     if (rv == nsIFilePicker.returnOK) {
       certdb.importCertsFromFile(fp.file, nsIX509Cert.CA_CERT);
       caTreeView.loadCerts(nsIX509Cert.CA_CERT);
       caTreeView.selection.clearSelection();
     }
   });
 }
 
-function onSmartCardChange() {
-  var certcache = certdb.getCerts();
-  // We've change the state of the smart cards inserted or removed
-  // that means the available certs may have changed. Update the display
-  userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT);
-  userTreeView.selection.clearSelection();
-  caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
-  caTreeView.selection.clearSelection();
-  serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT);
-  serverTreeView.selection.clearSelection();
-  emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT);
-  emailTreeView.selection.clearSelection();
-  orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT);
-  orphanTreeView.selection.clearSelection();
-}
-
 function addEmailCert() {
   var bundle = document.getElementById("pippki_bundle");
   var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
   fp.init(window,
           bundle.getString("importEmailCertPrompt"),
           nsIFilePicker.modeOpen);
   fp.appendFilter(bundle.getString("file_browse_Certificate_spec"),
                   gCertFileTypes);
--- a/security/manager/pki/resources/content/certManager.xul
+++ b/security/manager/pki/resources/content/certManager.xul
@@ -13,17 +13,16 @@
 
 <!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd">
 
 <dialog id="certmanager"
 	windowtype="mozilla:certmanager"
 	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="&certmgr.title;"
         onload="LoadCerts();"
-        onunload="DeregisterSmartCardObservers();"
         buttons="accept"
         style="width: 63em; height: 32em;"
         persist="screenX screenY width height">
 
   <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
 
   <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
   <script type="application/javascript" src="chrome://pippki/content/certManager.js"/>
--- a/security/manager/pki/resources/content/device_manager.js
+++ b/security/manager/pki/resources/content/device_manager.js
@@ -8,40 +8,24 @@ const nsIPKCS11Module = Components.inter
 const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
 const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB;
 const nsIPK11Token = Components.interfaces.nsIPK11Token;
 const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
 const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB;
 const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
 const nsDialogParamBlock = "@mozilla.org/embedcomp/dialogparam;1";
 
-var { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});
-
 var bundle;
 var secmoddb;
 var skip_enable_buttons = false;
 
-var smartCardObserver = {
-  observe() {
-    onSmartCardChange();
-  }
-};
-
-function DeregisterSmartCardObservers() {
-  Services.obs.removeObserver(smartCardObserver, "smartcard-insert");
-  Services.obs.removeObserver(smartCardObserver, "smartcard-remove");
-}
-
 /* Do the initial load of all PKCS# modules and list them. */
 function LoadModules() {
   bundle = document.getElementById("pippki_bundle");
   secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
-  Services.obs.addObserver(smartCardObserver, "smartcard-insert");
-  Services.obs.addObserver(smartCardObserver, "smartcard-remove");
-
   RefreshDeviceList();
 }
 
 function getNSSString(name) {
   return document.getElementById("pipnss_bundle").getString(name);
 }
 
 function doPrompt(msg) {
@@ -363,27 +347,16 @@ function deleteSelected() {
 
 function doUnload() {
   if (deleteSelected()) {
     ClearDeviceList();
     RefreshDeviceList();
   }
 }
 
-// handle card insertion and removal
-function onSmartCardChange() {
-  var tree = document.getElementById("device_tree");
-  var index = tree.currentIndex;
-  tree.currentIndex = 0;
-  ClearDeviceList();
-  RefreshDeviceList();
-  tree.currentIndex = index;
-  enableButtons();
-}
-
 function changePassword() {
   getSelectedItem();
   let params = Components.classes[nsDialogParamBlock]
                          .createInstance(nsIDialogParamBlock);
   params.SetString(1, selected_slot.tokenName);
   window.openDialog("changepassword.xul", "", "chrome,centerscreen,modal",
                     params);
   showSlotInfo();
--- a/security/manager/pki/resources/content/device_manager.xul
+++ b/security/manager/pki/resources/content/device_manager.xul
@@ -14,17 +14,16 @@
 
 <dialog id="devicemanager"
 	windowtype="mozilla:devicemanager"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="&devmgr.title;"
         style="&devmgr.style2;"
         persist="screenX screenY width height"
         onload="LoadModules();"
-        onunload="DeregisterSmartCardObservers();"
         buttons="accept">
 
 <stringbundleset id="stringbundleset">
   <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
   <stringbundle id="pipnss_bundle" src="chrome://pipnss/locale/pipnss.properties"/>
 </stringbundleset>
 
 <script type="application/javascript" src="chrome://pippki/content/device_manager.js"/>
--- a/security/manager/ssl/PKCS11ModuleDB.cpp
+++ b/security/manager/ssl/PKCS11ModuleDB.cpp
@@ -41,30 +41,16 @@ PKCS11ModuleDB::DeleteModule(const nsASt
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (aModuleName.IsEmpty()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   NS_ConvertUTF16toUTF8 moduleName(aModuleName);
-  // Introduce additional scope for module so all references to it are released
-  // before we call SECMOD_DeleteModule, below.
-#ifndef MOZ_NO_SMART_CARDS
-  {
-    UniqueSECMODModule module(SECMOD_FindModule(moduleName.get()));
-    if (!module) {
-      return NS_ERROR_FAILURE;
-    }
-    nsCOMPtr<nsINSSComponent> nssComponent(
-      do_GetService(PSM_COMPONENT_CONTRACTID));
-    nssComponent->ShutdownSmartCardThread(module.get());
-  }
-#endif
-
   // modType is an output variable. We ignore it.
   int32_t modType;
   SECStatus srv = SECMOD_DeleteModule(moduleName.get(), &modType);
   if (srv != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
@@ -144,22 +130,16 @@ PKCS11ModuleDB::AddModule(const nsAStrin
     return NS_ERROR_FAILURE;
   }
 
   UniqueSECMODModule module(SECMOD_FindModule(moduleName.get()));
   if (!module) {
     return NS_ERROR_FAILURE;
   }
 
-#ifndef MOZ_NO_SMART_CARDS
-  nsCOMPtr<nsINSSComponent> nssComponent(
-    do_GetService(PSM_COMPONENT_CONTRACTID));
-  nssComponent->LaunchSmartCardThread(module.get());
-#endif
-
   nsAutoString scalarKey;
   GetModuleNameForTelemetry(module.get(), scalarKey);
   // Scalar keys must be between 0 and 70 characters (exclusive).
   // GetModuleNameForTelemetry takes care of keys that are too long.
   // If for some reason it couldn't come up with an appropriate name and
   // returned an empty result, however, we need to not attempt to record this
   // (it wouldn't give us anything useful anyway).
   if (scalarKey.Length() > 0) {
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -139,21 +139,16 @@ UNIFIED_SOURCES += [
     'SSLServerCertVerification.cpp',
     'TransportSecurityInfo.cpp',
 ]
 
 IPDL_SOURCES += [
     'PPSMContentDownloader.ipdl',
 ]
 
-if not CONFIG['MOZ_NO_SMART_CARDS']:
-    UNIFIED_SOURCES += [
-        'nsSmartCardMonitor.cpp',
-    ]
-
 if CONFIG['MOZ_XUL']:
     UNIFIED_SOURCES += [
         'nsCertTree.cpp',
     ]
 
 UNIFIED_SOURCES += [
     'md4.c',
 ]
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1291,16 +1291,21 @@ nsNSSCertificateDB::GetCerts(nsIX509Cert
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsresult rv = BlockUntilLoadableRootsLoaded();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  rv = CheckForSmartCardChanges();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
   nsCOMPtr<nsIX509CertList> nssCertList;
   UniqueCERTCertList certList(PK11_ListCerts(PK11CertListUnique, ctx));
 
   // nsNSSCertList 1) adopts certList, and 2) handles the nullptr case fine.
   // (returns an empty list)
   nssCertList = new nsNSSCertList(Move(certList), locker);
 
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/PodOperations.h"
 #include "mozilla/PublicSSL.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsCRT.h"
 #include "nsClientAuthRemember.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsICertOverrideService.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
@@ -52,20 +53,16 @@
 #include "pkix/pkixnss.h"
 #include "secerr.h"
 #include "secmod.h"
 #include "ssl.h"
 #include "sslerr.h"
 #include "sslproto.h"
 #include "prmem.h"
 
-#ifndef MOZ_NO_SMART_CARDS
-#include "nsSmartCardMonitor.h"
-#endif
-
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #include "nsILocalFileWin.h"
 
 #include "windows.h" // this needs to be before the following includes
 #include "lmcons.h"
 #include "sddl.h"
 #include "wincrypt.h"
@@ -198,19 +195,16 @@ GetRevocationBehaviorFromPrefs(/*out*/ C
 }
 
 nsNSSComponent::nsNSSComponent()
   : mLoadableRootsLoadedMonitor("nsNSSComponent.mLoadableRootsLoadedMonitor")
   , mLoadableRootsLoaded(false)
   , mLoadableRootsLoadedResult(NS_ERROR_FAILURE)
   , mMutex("nsNSSComponent.mMutex")
   , mNSSInitialized(false)
-#ifndef MOZ_NO_SMART_CARDS
-  , mThreadList(nullptr)
-#endif
 {
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::ctor\n"));
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MOZ_ASSERT(mInstanceCount == 0,
              "nsNSSComponent is a singleton, but instantiated multiple times!");
   ++mInstanceCount;
 }
@@ -282,86 +276,16 @@ nsNSSComponent::GetNSSBundleString(const
       outString = result;
       rv = NS_OK;
     }
   }
 
   return rv;
 }
 
-#ifndef MOZ_NO_SMART_CARDS
-nsresult
-nsNSSComponent::LaunchSmartCardThreads()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!NS_IsMainThread()) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  AutoSECMODListReadLock lock;
-  SECMODModuleList* list = SECMOD_GetDefaultModuleList();
-  nsresult rv;
-  while (list) {
-    rv = LaunchSmartCardThread(list->module);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    list = list->next;
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsNSSComponent::LaunchSmartCardThread(SECMODModule* module)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!NS_IsMainThread()) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  SmartCardMonitoringThread* newThread;
-  if (SECMOD_HasRemovableSlots(module)) {
-    if (!mThreadList) {
-      mThreadList = new SmartCardThreadList();
-    }
-    newThread = new SmartCardMonitoringThread(module);
-    // newThread is adopted by the add.
-    return mThreadList->Add(newThread);
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsNSSComponent::ShutdownSmartCardThread(SECMODModule* module)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!NS_IsMainThread()) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  if (!mThreadList) {
-    return NS_OK;
-  }
-  mThreadList->Remove(module);
-  return NS_OK;
-}
-
-void
-nsNSSComponent::ShutdownSmartCardThreads()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!NS_IsMainThread()) {
-    return;
-  }
-
-  delete mThreadList;
-  mThreadList = nullptr;
-}
-#endif // MOZ_NO_SMART_CARDS
-
 #ifdef XP_WIN
 static bool
 GetUserSid(nsAString& sidString)
 {
   // UNLEN is the maximum user name length (see Lmcons.h). +1 for the null
   // terminator.
   WCHAR lpAccountName[UNLEN + 1];
   DWORD lcAccountName = sizeof(lpAccountName) / sizeof(lpAccountName[0]);
@@ -1144,20 +1068,24 @@ nsNSSComponent::HasActiveSmartCards(bool
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
 #ifndef MOZ_NO_SMART_CARDS
   nsNSSShutDownPreventionLock lock;
   MutexAutoLock nsNSSComponentLock(mMutex);
 
-  // A non-null list means at least one smart card thread was active
-  if (mThreadList) {
-    result = true;
-    return NS_OK;
+  AutoSECMODListReadLock secmodLock;
+  SECMODModuleList* list = SECMOD_GetDefaultModuleList();
+  while (list) {
+    if (SECMOD_HasRemovableSlots(list->module)) {
+      result = true;
+      return NS_OK;
+    }
+    list = list->next;
   }
 #endif
   result = false;
   return NS_OK;
 }
 
 nsresult
 nsNSSComponent::HasUserCertsInstalled(bool& result)
@@ -1198,19 +1126,63 @@ nsNSSComponent::BlockUntilLoadableRootsL
   MonitorAutoLock rootsLoadedLock(mLoadableRootsLoadedMonitor);
   while (!mLoadableRootsLoaded) {
     nsresult rv = rootsLoadedLock.Wait();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   MOZ_ASSERT(mLoadableRootsLoaded);
+
   return mLoadableRootsLoadedResult;
 }
 
+nsresult
+nsNSSComponent::CheckForSmartCardChanges()
+{
+#ifndef MOZ_NO_SMART_CARDS
+  nsNSSShutDownPreventionLock lock;
+  MutexAutoLock nsNSSComponentLock(mMutex);
+
+  if (!mNSSInitialized) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  // SECMOD_UpdateSlotList attempts to acquire the list lock as well,
+  // so we have to do this in two steps. The lock protects the list itself, so
+  // if we get our own owned references to the modules we're interested in,
+  // there's no thread safety concern here.
+  Vector<UniqueSECMODModule> modulesWithRemovableSlots;
+  {
+    AutoSECMODListReadLock secmodLock;
+    SECMODModuleList* list = SECMOD_GetDefaultModuleList();
+    while (list) {
+      if (SECMOD_HasRemovableSlots(list->module)) {
+        UniqueSECMODModule module(SECMOD_ReferenceModule(list->module));
+        if (!modulesWithRemovableSlots.append(Move(module))) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+      }
+      list = list->next;
+    }
+  }
+  for (auto& module : modulesWithRemovableSlots) {
+    // Best-effort.
+    Unused << SECMOD_UpdateSlotList(module.get());
+    for (int i = 0; i < module->slotCount; i++) {
+      // We actually don't care about the return value here - we just need to
+      // call this to get NSS to update its view of this slot.
+      Unused << PK11_IsPresent(module->slots[i]);
+    }
+  }
+#endif
+
+  return NS_OK;
+}
+
 // Returns by reference the path to the directory containing the file that has
 // been loaded as DLL_PREFIX nss3 DLL_SUFFIX.
 static nsresult
 GetNSS3Directory(nsCString& result)
 {
   UniquePRString nss3Path(
     PR_GetLibraryFilePathname(DLL_PREFIX "nss3" DLL_SUFFIX,
                               reinterpret_cast<PRFuncPtr>(NSS_Initialize)));
@@ -2124,25 +2096,16 @@ nsNSSComponent::InitializeNSS()
   // (otherwise memory will leak).
   if (SSL_ConfigServerSessionIDCache(1000, 0, 0, nullptr) != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
   // dynamic options from prefs
   setValidationOptions(true);
 
-#ifndef MOZ_NO_SMART_CARDS
-  rv = LaunchSmartCardThreads();
-  if (NS_FAILED(rv)) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Error,
-            ("failed to start smart card threads"));
-    return rv;
-  }
-#endif
-
   mozilla::pkix::RegisterErrorTable();
 
   if (PK11_IsFIPS()) {
     Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true);
   }
 
   // Gather telemetry on any PKCS#11 modules we have loaded. Note that because
   // we load the built-in root module asynchronously after this, the telemetry
@@ -2224,19 +2187,16 @@ nsNSSComponent::ShutdownNSS()
   mFamilySafetyRoot = nullptr;
   mEnterpriseRoots = nullptr;
 #endif
 
   PK11_SetPasswordFunc((PK11PasswordFunc)nullptr);
 
   Preferences::RemoveObserver(this, "security.");
 
-#ifndef MOZ_NO_SMART_CARDS
-  ShutdownSmartCardThreads();
-#endif
   SSL_ClearSessionCache();
   // TLSServerSocket may be run with the session cache enabled. This ensures
   // those resources are cleaned up.
   Unused << SSL_ShutdownServerSessionIDCache();
 
   // Release the default CertVerifier. This will cause any held NSS resources
   // to be released (it's not an nsNSSShutDownObject, so we have to do this
   // manually).
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -62,33 +62,28 @@ public:
                                            uint32_t numParams,
                                            nsAString& outString) = 0;
 
   NS_IMETHOD GetNSSBundleString(const char* name,
                                 nsAString& outString) = 0;
 
   NS_IMETHOD LogoutAuthenticatedPK11() = 0;
 
-#ifndef MOZ_NO_SMART_CARDS
-  NS_IMETHOD LaunchSmartCardThread(SECMODModule* module) = 0;
-
-  NS_IMETHOD ShutdownSmartCardThread(SECMODModule* module) = 0;
-#endif
-
 #ifdef DEBUG
   NS_IMETHOD IsCertTestBuiltInRoot(CERTCertificate* cert, bool& result) = 0;
 #endif
 
   NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) = 0;
 
 #ifdef XP_WIN
   NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) = 0;
 #endif
 
   NS_IMETHOD BlockUntilLoadableRootsLoaded() = 0;
+  NS_IMETHOD CheckForSmartCardChanges() = 0;
 
   // Main thread only
   NS_IMETHOD HasActiveSmartCards(bool& result) = 0;
   NS_IMETHOD HasUserCertsInstalled(bool& result) = 0;
 
   virtual ::already_AddRefed<mozilla::psm::SharedCertVerifier>
     GetDefaultCertVerifier() = 0;
 };
@@ -121,37 +116,28 @@ public:
                                    nsAString& outString) override;
   NS_IMETHOD PIPBundleFormatStringFromName(const char* name,
                                            const char16_t** params,
                                            uint32_t numParams,
                                            nsAString& outString) override;
   NS_IMETHOD GetNSSBundleString(const char* name, nsAString& outString) override;
   NS_IMETHOD LogoutAuthenticatedPK11() override;
 
-#ifndef MOZ_NO_SMART_CARDS
-  NS_IMETHOD LaunchSmartCardThread(SECMODModule* module) override;
-  NS_IMETHOD ShutdownSmartCardThread(SECMODModule* module) override;
-  nsresult LaunchSmartCardThreads();
-  void ShutdownSmartCardThreads();
-  nsresult DispatchEventToWindow(nsIDOMWindow* domWin,
-                                 const nsAString& eventType,
-                                 const nsAString& token);
-#endif
-
 #ifdef DEBUG
   NS_IMETHOD IsCertTestBuiltInRoot(CERTCertificate* cert, bool& result) override;
 #endif
 
   NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) override;
 
 #ifdef XP_WIN
   NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) override;
 #endif
 
   NS_IMETHOD BlockUntilLoadableRootsLoaded() override;
+  NS_IMETHOD CheckForSmartCardChanges() override;
 
   // Main thread only
   NS_IMETHOD HasActiveSmartCards(bool& result) override;
   NS_IMETHOD HasUserCertsInstalled(bool& result) override;
 
   ::already_AddRefed<mozilla::psm::SharedCertVerifier>
     GetDefaultCertVerifier() override;
 
@@ -212,32 +198,43 @@ private:
   nsString mContentSigningRootHash;
   RefPtr<mozilla::psm::SharedCertVerifier> mDefaultCertVerifier;
 #ifdef XP_WIN
   mozilla::UniqueCERTCertificate mFamilySafetyRoot;
   mozilla::UniqueCERTCertList mEnterpriseRoots;
 #endif // XP_WIN
 
   // The following members are accessed only on the main thread:
-#ifndef MOZ_NO_SMART_CARDS
-  SmartCardThreadList* mThreadList;
-#endif
   static int mInstanceCount;
 };
 
 inline nsresult
 BlockUntilLoadableRootsLoaded()
 {
   nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
   if (!component) {
     return NS_ERROR_FAILURE;
   }
   return component->BlockUntilLoadableRootsLoaded();
 }
 
+inline nsresult
+CheckForSmartCardChanges()
+{
+#ifndef MOZ_NO_SMART_CARDS
+  nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
+  if (!component) {
+    return NS_ERROR_FAILURE;
+  }
+  return component->CheckForSmartCardChanges();
+#else
+  return NS_OK;
+#endif
+}
+
 class nsNSSErrors
 {
 public:
   static const char* getDefaultErrorStringName(PRErrorCode err);
   static const char* getOverrideErrorStringName(PRErrorCode aErrorCode);
   static nsresult getErrorMessageFromCode(PRErrorCode err,
                                           nsINSSComponent* component,
                                           nsString& returnedMessage);
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -2228,16 +2228,24 @@ ClientAuthDataRunnable::RunOnTargetThrea
 
   UniquePLArenaPool arena;
   char** caNameStrings;
   UniqueCERTCertificate cert;
   UniqueSECKEYPrivateKey privKey;
   void* wincx = mSocketInfo;
   nsresult rv;
 
+  if (NS_FAILED(CheckForSmartCardChanges())) {
+    mRV = SECFailure;
+    *mPRetCert = nullptr;
+    *mPRetKey = nullptr;
+    mErrorCodeToReport = SEC_ERROR_LIBRARY_FAILURE;
+    return;
+  }
+
   nsCOMPtr<nsIX509Cert> socketClientCert;
   mSocketInfo->GetClientCert(getter_AddRefs(socketClientCert));
 
   // If a client cert preference was set on the socket info, use that and skip
   // the client cert UI and/or search of the user's past cert decisions.
   if (socketClientCert) {
     cert.reset(socketClientCert->GetCert());
     if (!cert) {
--- a/security/manager/ssl/nsPKCS11Slot.cpp
+++ b/security/manager/ssl/nsPKCS11Slot.cpp
@@ -332,31 +332,35 @@ NS_IMETHODIMP
 nsPKCS11Module::ListSlots(nsISimpleEnumerator** _retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
 
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
+  nsresult rv = CheckForSmartCardChanges();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
   if (!array) {
     return NS_ERROR_FAILURE;
   }
 
   /* applications which allow new slot creation (which Firefox now does
    * since it uses the WaitForSlotEvent call) need to hold the
    * ModuleList Read lock to prevent the slot array from changing out
    * from under it. */
   AutoSECMODListReadLock lock;
   for (int i = 0; i < mModule->slotCount; i++) {
     if (mModule->slots[i]) {
       nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(mModule->slots[i]);
-      nsresult rv = array->AppendElement(slot, false);
+      rv = array->AppendElement(slot, false);
       if (NS_FAILED(rv)) {
         return rv;
       }
     }
   }
 
   return array->Enumerate(_retval);
 }
deleted file mode 100644
--- a/security/manager/ssl/nsSmartCardMonitor.cpp
+++ /dev/null
@@ -1,397 +0,0 @@
-/* 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/. */
-
-#include "nsSmartCardMonitor.h"
-
-#include "ScopedNSSTypes.h"
-#include "mozilla/Services.h"
-#include "mozilla/Unused.h"
-#include "nsIObserverService.h"
-#include "nsServiceManagerUtils.h"
-#include "nsThreadUtils.h"
-#include "GeckoProfiler.h"
-#include "nspr.h"
-#include "pk11func.h"
-
-using namespace mozilla;
-
-//
-// The SmartCard monitoring thread should start up for each module we load
-// that has removable tokens. This code calls an NSS function which waits
-// until there is a change in the token state. NSS uses the
-// C_WaitForSlotEvent() call in PKCS #11 if the module implements the call,
-// otherwise NSS will poll the token in a loop with a delay of 'latency'
-// between polls. Note that the C_WaitForSlotEvent() may wake up on any type
-// of token event, so it's necessary to filter these events down to just the
-// insertion and removal events we are looking for.
-//
-// Once the event is found, it is dispatched to the main thread to notify
-// any window where window.crypto.enableSmartCardEvents is true.
-// Additionally, all observers of the topics |kSmartcardInsert| and
-// |kSmartcardRemove| are notified by the observer service of the appropriate
-// event.
-//
-
-#define kSmartcardInsert "smartcard-insert"
-#define kSmartcardRemove "smartcard-remove"
-
-class nsTokenEventRunnable : public nsIRunnable {
-public:
-  nsTokenEventRunnable(const char* aType, const nsAString& aTokenName)
-    : mType(aType)
-    , mTokenName(aTokenName)
-  {
-  }
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-private:
-  virtual ~nsTokenEventRunnable() {}
-
-  const char* mType;
-  nsString mTokenName;
-};
-
-NS_IMPL_ISUPPORTS(nsTokenEventRunnable, nsIRunnable)
-
-NS_IMETHODIMP
-nsTokenEventRunnable::Run()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIObserverService> observerService =
-    mozilla::services::GetObserverService();
-  if (!observerService) {
-    return NS_ERROR_FAILURE;
-  }
-  return observerService->NotifyObservers(nullptr, mType, mTokenName.get());
-}
-
-// self linking and removing double linked entry
-// adopts the thread it is passed.
-class SmartCardThreadEntry
-{
-public:
-  friend class SmartCardThreadList;
-  SmartCardThreadEntry(SmartCardMonitoringThread *thread,
-                       SmartCardThreadEntry *next,
-                       SmartCardThreadEntry *prev,
-                       SmartCardThreadEntry **head)
-    : next(next)
-    , prev(prev)
-    , head(head)
-    , thread(thread)
-  {
-    if (prev) {
-      prev->next = this;
-    } else {
-      *head = this;
-    }
-    if (next) {
-      next->prev = this;
-    }
-  }
-
-  ~SmartCardThreadEntry()
-  {
-    if (prev) {
-      prev->next = next;
-    } else {
-      *head = next;
-    }
-    if (next) {
-      next->prev = prev;
-    }
-    // NOTE: automatically stops the thread
-    delete thread;
-  }
-
-private:
-  SmartCardThreadEntry *next;
-  SmartCardThreadEntry *prev;
-  SmartCardThreadEntry **head;
-  SmartCardMonitoringThread *thread;
-};
-
-//
-// SmartCardThreadList is a class to help manage the running threads.
-// That way new threads could be started and old ones terminated as we
-// load and unload modules.
-//
-SmartCardThreadList::SmartCardThreadList() : head(0)
-{
-}
-
-SmartCardThreadList::~SmartCardThreadList()
-{
-  // the head is self linking and unlinking, the following
-  // loop removes all entries on the list.
-  // it will also stop the thread if it happens to be running
-  while (head) {
-    delete head;
-  }
-}
-
-void
-SmartCardThreadList::Remove(SECMODModule *aModule)
-{
-  for (SmartCardThreadEntry* current = head; current;
-       current = current->next) {
-    if (current->thread->GetModule() == aModule) {
-      // NOTE: automatically stops the thread and dequeues it from the list
-      delete current;
-      return;
-    }
-  }
-}
-
-// adopts the thread passed to it. Starts the thread as well
-nsresult
-SmartCardThreadList::Add(SmartCardMonitoringThread* thread)
-{
-  SmartCardThreadEntry* current = new SmartCardThreadEntry(thread, head,
-                                                           nullptr, &head);
-  // OK to forget current here, it's on the list.
-  Unused << current;
-
-  return thread->Start();
-}
-
-
-// We really should have a Unity PL Hash function...
-static PLHashNumber
-unity(const void* key) { return PLHashNumber(NS_PTR_TO_INT32(key)); }
-
-SmartCardMonitoringThread::SmartCardMonitoringThread(SECMODModule* module_)
-  : mThread(nullptr)
-{
-  mModule = SECMOD_ReferenceModule(module_);
-  // simple hash functions, most modules have less than 3 slots, so 10 buckets
-  // should be plenty
-  mHash = PL_NewHashTable(10, unity, PL_CompareValues, PL_CompareStrings,
-                          nullptr, 0);
-}
-
-//
-// when we shutdown the thread, be sure to stop it first. If not, it just might
-// crash when the mModule it is looking at disappears.
-//
-SmartCardMonitoringThread::~SmartCardMonitoringThread()
-{
-  Stop();
-  SECMOD_DestroyModule(mModule);
-  if (mHash) {
-    PL_HashTableDestroy(mHash);
-  }
-}
-
-nsresult
-SmartCardMonitoringThread::Start()
-{
-  if (!mThread) {
-    mThread = PR_CreateThread(PR_SYSTEM_THREAD, LaunchExecute, this,
-                              PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
-                              PR_JOINABLE_THREAD, 0);
-  }
-  return mThread ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-//
-// Should only stop if we are through with the module.
-// CancelWait has the side effect of losing all the keys and
-// current operations on the module!. (See the comment in
-// SECMOD_CancelWait for why this is so..).
-//
-void SmartCardMonitoringThread::Stop()
-{
-  SECStatus rv;
-
-  rv = SECMOD_CancelWait(mModule);
-  if (rv != SECSuccess) {
-    // we didn't wake up the Wait, so don't try to join the thread
-    // otherwise we will hang forever...
-    return;
-  }
-
-  // confused about the memory model here? NSPR owns the memory for
-  // threads. non-joinable threads are freed when the thread dies.
-  // joinable threads are freed after the call to PR_JoinThread.
-  // That means if SECMOD_CancelWait fails, we'll leak the mThread
-  // structure. this is considered preferable to hanging (which is
-  // what will happen if we try to join a thread that blocked).
-  if (mThread) {
-    PR_JoinThread(mThread);
-    mThread = 0;
-  }
-}
-
-//
-// remember the name and series of a token in a particular slot.
-// This is important because the name is no longer available when
-// the token is removed. If listeners depended on this information,
-// They would be out of luck. It also is a handy way of making sure
-// we don't generate spurious insertion and removal events as the slot
-// cycles through various states.
-//
-void
-SmartCardMonitoringThread::SetTokenName(CK_SLOT_ID slotid,
-                                       const char* tokenName, uint32_t series)
-{
-  if (mHash) {
-    if (tokenName) {
-      int len = strlen(tokenName) + 1;
-      // Use PR_Malloc() because PLHashAllocOps.freeEntry for mHash is
-      // DefaultFreeEntry(), which uses PR_Free().
-      char* entry = (char*)PR_Malloc(len + sizeof(uint32_t));
-
-      if (entry) {
-        memcpy(entry, &series, sizeof(uint32_t));
-        memcpy(&entry[sizeof(uint32_t)], tokenName, len);
-
-        PL_HashTableAdd(mHash, (void*)(uintptr_t)slotid, entry); /* adopt */
-        return;
-      }
-    } else {
-      // if tokenName was not provided, remove the old one (implicit delete)
-      PL_HashTableRemove(mHash, (void*)(uintptr_t)slotid);
-    }
-  }
-}
-
-// retrieve the name saved above
-const char*
-SmartCardMonitoringThread::GetTokenName(CK_SLOT_ID slotid)
-{
-  const char* tokenName = nullptr;
-  const char* entry;
-
-  if (mHash) {
-    entry = (const char*)PL_HashTableLookupConst(mHash,
-                                                 (void*)(uintptr_t)slotid);
-    if (entry) {
-      tokenName = &entry[sizeof(uint32_t)];
-    }
-  }
-  return tokenName;
-}
-
-// retrieve the series saved in SetTokenName above
-uint32_t
-SmartCardMonitoringThread::GetTokenSeries(CK_SLOT_ID slotid)
-{
-  uint32_t series = 0;
-  const char* entry;
-
-  if (mHash) {
-    entry = (const char*)PL_HashTableLookupConst(mHash,
-                                                 (void*)(uintptr_t)slotid);
-    if (entry) {
-      memcpy(&series, entry, sizeof(uint32_t));
-    }
-  }
-  return series;
-}
-
-//
-// helper function to pass the event off to nsNSSComponent.
-//
-void
-SmartCardMonitoringThread::SendEvent(const char* eventType,
-                                     const char* tokenName)
-{
-  // The token name should be UTF8, but it's not clear that this is enforced
-  // by NSS. To be safe, we explicitly check here before converting it to
-  // UTF16. If it isn't UTF8, we just use an empty string with the idea that
-  // consumers of these events should at least be notified that something
-  // happened.
-  nsAutoString tokenNameUTF16(NS_LITERAL_STRING(""));
-  if (IsUTF8(nsDependentCString(tokenName))) {
-    tokenNameUTF16.Assign(NS_ConvertUTF8toUTF16(tokenName));
-  }
-  nsCOMPtr<nsIRunnable> runnable(new nsTokenEventRunnable(eventType,
-                                                          tokenNameUTF16));
-  NS_DispatchToMainThread(runnable);
-}
-
-//
-// This is the main loop.
-//
-void SmartCardMonitoringThread::Execute()
-{
-  const char* tokenName;
-
-  //
-  // populate token names for already inserted tokens.
-  //
-  PK11SlotList* sl = PK11_FindSlotsByNames(mModule->dllName, nullptr, nullptr,
-                                           true);
-
-  PK11SlotListElement* sle;
-  if (sl) {
-    for (sle = PK11_GetFirstSafe(sl); sle;
-         sle = PK11_GetNextSafe(sl, sle, false)) {
-      SetTokenName(PK11_GetSlotID(sle->slot), PK11_GetTokenName(sle->slot),
-                   PK11_GetSlotSeries(sle->slot));
-    }
-    PK11_FreeSlotList(sl);
-  }
-
-  // loop starts..
-  do {
-    UniquePK11SlotInfo slot(
-      SECMOD_WaitForAnyTokenEvent(mModule, 0, PR_SecondsToInterval(1)));
-    if (!slot) {
-      break;
-    }
-
-    // now we have a potential insertion or removal event, see if the slot
-    // is present to determine which it is...
-    if (PK11_IsPresent(slot.get())) {
-      // insertion
-      CK_SLOT_ID slotID = PK11_GetSlotID(slot.get());
-      uint32_t series = PK11_GetSlotSeries(slot.get());
-
-      // skip spurious insertion events...
-      if (series != GetTokenSeries(slotID)) {
-        // if there's a token name, then we have not yet issued a remove
-        // event for the previous token, do so now...
-        tokenName = GetTokenName(slotID);
-        if (tokenName) {
-          SendEvent(kSmartcardRemove, tokenName);
-        }
-        tokenName = PK11_GetTokenName(slot.get());
-        // save the token name and series
-        SetTokenName(slotID, tokenName, series);
-        SendEvent(kSmartcardInsert, tokenName);
-      }
-    } else {
-      // retrieve token name
-      CK_SLOT_ID slotID = PK11_GetSlotID(slot.get());
-      tokenName = GetTokenName(slotID);
-      // if there's not a token name, then the software isn't expecting
-      // a (or another) remove event.
-      if (tokenName) {
-        SendEvent(kSmartcardRemove, tokenName);
-        // clear the token name (after we send it)
-        SetTokenName(slotID, nullptr, 0);
-      }
-    }
-  } while (1);
-}
-
-// accessor to help searching active Monitoring threads
-const SECMODModule* SmartCardMonitoringThread::GetModule()
-{
-  return mModule;
-}
-
-// C-like calling sequence to glue into PR_CreateThread.
-void SmartCardMonitoringThread::LaunchExecute(void* arg)
-{
-  AUTO_PROFILER_REGISTER_THREAD("SmartCard");
-  NS_SetCurrentThreadName("SmartCard");
-
-  ((SmartCardMonitoringThread*)arg)->Execute();
-}
deleted file mode 100644
--- a/security/manager/ssl/nsSmartCardMonitor.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* 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 nsSmartCardMonitor_h
-#define nsSmartCardMonitor_h
-
-#include "prthread.h"
-#include "secmod.h"
-#include "plhash.h"
-#include "pkcs11t.h"
-
-class SmartCardThreadEntry;
-class SmartCardMonitoringThread;
-
-//
-// manage a group of SmartCardMonitoringThreads
-//
-class SmartCardThreadList {
-public:
-  SmartCardThreadList();
-  ~SmartCardThreadList();
-  void Remove(SECMODModule* module);
-  nsresult Add(SmartCardMonitoringThread* thread);
-
-private:
-  SmartCardThreadEntry* head;
-};
-
-//
-// monitor a Module for token insertion and removal
-//
-// NOTE: this provides the application the ability to dynamically add slots
-// on the fly as necessary.
-//
-class SmartCardMonitoringThread
-{
- public:
-  explicit SmartCardMonitoringThread(SECMODModule* module);
-  ~SmartCardMonitoringThread();
-
-  nsresult Start();
-  void Stop();
-
-  void Execute();
-  void Interrupt();
-
-  const SECMODModule* GetModule();
-
- private:
-  static void LaunchExecute(void* arg);
-  void SetTokenName(CK_SLOT_ID slotid, const char* tokenName, uint32_t series);
-  const char* GetTokenName(CK_SLOT_ID slotid);
-  uint32_t GetTokenSeries(CK_SLOT_ID slotid);
-  void SendEvent(const char* type, const char* tokenName);
-
-  SECMODModule* mModule;
-  PLHashTable* mHash;
-  PRThread* mThread;
-};
-
-#endif // nsSmartCardMonitor_h
--- a/security/manager/ssl/tests/unit/pkcs11testmodule/pkcs11testmodule.cpp
+++ b/security/manager/ssl/tests/unit/pkcs11testmodule/pkcs11testmodule.cpp
@@ -1,19 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 // This is a testing PKCS #11 module that simulates a token being inserted and
 // removed from a slot every 50ms. This is achieved mainly in
-// Test_C_WaitForSlotEvent. The smartcard monitoring code essentially calls
-// this function in a tight loop. Each time, this module waits for 50ms and
-// returns, having changed its internal state to report that the token has
-// either been inserted or removed, as appropriate.
+// Test_C_WaitForSlotEvent. If the application that loaded this module calls
+// C_WaitForSlotEvent, this module waits for 50ms and returns, having changed
+// its internal state to report that the token has either been inserted or
+// removed, as appropriate.
 // This module also provides an alternate token that is always present for tests
 // that don't want the cyclic behavior described above.
 
 #include <string.h>
 
 #if defined(WIN32)
 #  include <windows.h> // for Sleep
 #else
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_pkcs11_insert_remove.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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/. */
-"use strict";
-
-// This test loads a testing PKCS #11 module that simulates a token being
-// inserted and removed from a slot every 50ms. This causes the observer
-// service to broadcast the observation topics "smartcard-insert" and
-// "smartcard-remove", respectively. This test ensures that one of each event
-// has been succssfully observed, and then it unloads the test module.
-
-// Ensure that the appropriate initialization has happened.
-do_get_profile();
-Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
-
-const gExpectedTokenLabel = "Test PKCS11 TokeƱ Label";
-
-function SmartcardObserver(type) {
-  this.type = type;
-  do_test_pending();
-}
-
-SmartcardObserver.prototype = {
-  observe(subject, topic, data) {
-    equal(topic, this.type, "Observed and expected types should match");
-    equal(gExpectedTokenLabel, data,
-          "Expected and observed token labels should match");
-    Services.obs.removeObserver(this, this.type);
-    do_test_finished();
-  }
-};
-
-function run_test() {
-  Services.obs.addObserver(new SmartcardObserver("smartcard-insert"),
-                           "smartcard-insert");
-  Services.obs.addObserver(new SmartcardObserver("smartcard-remove"),
-                           "smartcard-remove");
-
-  loadPKCS11TestModule(false);
-}
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_pkcs11_no_events_after_removal.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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/. */
-"use strict";
-
-// This test loads a testing PKCS #11 module that simulates a token being
-// inserted and removed from a slot every 50ms. This causes the observer
-// service to broadcast the observation topics "smartcard-insert" and
-// "smartcard-remove", respectively. This test ensures that these events
-// are no longer emitted once the module has been unloaded.
-
-// Ensure that the appropriate initialization has happened.
-do_get_profile();
-Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
-
-function run_test() {
-  let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
-                         .getService(Ci.nsIPKCS11ModuleDB);
-  loadPKCS11TestModule(true);
-  pkcs11ModuleDB.deleteModule("PKCS11 Test Module");
-  Services.obs.addObserver(function() {
-    ok(false, "smartcard-insert event should not have been emitted");
-  }, "smartcard-insert");
-  Services.obs.addObserver(function() {
-    ok(false, "smartcard-remove event should not have been emitted");
-  }, "smartcard-remove");
-  do_timeout(500, do_test_finished);
-  do_test_pending();
-}
--- a/security/manager/ssl/tests/unit/xpcshell-smartcards.ini
+++ b/security/manager/ssl/tests/unit/xpcshell-smartcards.ini
@@ -1,16 +1,14 @@
 [DEFAULT]
 head = head_psm.js
 tail =
 tags = psm
 skip-if = toolkit == 'android'
 support-files =
 
-[test_pkcs11_insert_remove.js]
 [test_pkcs11_module.js]
 [test_pkcs11_moduleDB.js]
-[test_pkcs11_no_events_after_removal.js]
 [test_pkcs11_safe_mode.js]
 skip-if = coverage # bug 1336728
 [test_pkcs11_slot.js]
 [test_pkcs11_token.js]
 [test_pkcs11_tokenDB.js]
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -565,16 +565,17 @@ SECMOD_LoadModule
 SECMOD_LoadUserModule
 SECMOD_OpenUserDB
 SECMOD_PubCipherFlagstoInternal
 SECMOD_PubMechFlagstoInternal
 SECMOD_ReferenceModule
 SECMOD_ReleaseReadLock
 SECMOD_UnloadUserModule
 SECMOD_UpdateModule
+SECMOD_UpdateSlotList
 SECMOD_WaitForAnyTokenEvent
 SEC_NullTemplate_Util @DATA@
 SEC_ObjectIDTemplate_Util @DATA@
 SEC_OctetStringTemplate @DATA@
 SEC_OctetStringTemplate_Util @DATA@
 SECOID_AddEntry
 SECOID_AddEntry_Util
 SECOID_AlgorithmIDTemplate @DATA@