c401cc
From 544908ecc824823a5e7340bede05c5bee77b20f6 Mon Sep 17 00:00:00 2001
c401cc
Message-Id: <544908ecc824823a5e7340bede05c5bee77b20f6@dist-git>
c401cc
From: "Daniel P. Berrange" <berrange@redhat.com>
c401cc
Date: Mon, 10 Mar 2014 15:05:32 +0000
c401cc
Subject: [PATCH] Add a mutex to serialize updates to firewall
c401cc
c401cc
For
c401cc
c401cc
  https://bugzilla.redhat.com/show_bug.cgi?id=1074003
c401cc
c401cc
The nwfilter conf update mutex previously serialized
c401cc
updates to the internal data structures for firewall
c401cc
rules, and updates to the firewall itself. The latter
c401cc
was recently turned into a read/write lock, and filter
c401cc
instantiation allowed to proceed in parallel. It was
c401cc
believed that this was ok, since each filter is created
c401cc
on a separate iptables/ebtables chain.
c401cc
c401cc
It turns out that there is a subtle lock ordering problem
c401cc
on virNWFilterObjPtr instances. __virNWFilterInstantiateFilter
c401cc
will hold a lock on the virNWFilterObjPtr it is instantiating.
c401cc
This in turn invokes virNWFilterInstantiate which then invokes
c401cc
virNWFilterDetermineMissingVarsRec which then invokes
c401cc
virNWFilterObjFindByName. This iterates over every single
c401cc
virNWFilterObjPtr in the list, locking them and checking their
c401cc
name. So if 2 or more threads try to instantiate a filter in
c401cc
parallel, they'll all hold 1 lock at the top level in the
c401cc
__virNWFilterInstantiateFilter method which will cause the
c401cc
other thread to deadlock in virNWFilterObjFindByName.
c401cc
c401cc
The fix is to add an exclusive mutex to serialize the
c401cc
execution of __virNWFilterInstantiateFilter.
c401cc
c401cc
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
c401cc
(cherry picked from commit 925de19ed7f13e0d12d0b993496d314bab886589)
c401cc
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
c401cc
---
c401cc
 src/nwfilter/nwfilter_driver.c         |  6 +++--
c401cc
 src/nwfilter/nwfilter_gentech_driver.c | 41 ++++++++++++++++++++++++++++++----
c401cc
 src/nwfilter/nwfilter_gentech_driver.h |  2 +-
c401cc
 3 files changed, 42 insertions(+), 7 deletions(-)
c401cc
c401cc
diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c
c401cc
index 77fc608..b1bdd3f 100644
c401cc
--- a/src/nwfilter/nwfilter_driver.c
c401cc
+++ b/src/nwfilter/nwfilter_driver.c
c401cc
@@ -202,7 +202,8 @@ nwfilterStateInitialize(bool privileged,
c401cc
     if (virNWFilterDHCPSnoopInit() < 0)
c401cc
         goto err_exit_learnshutdown;
c401cc
 
c401cc
-    virNWFilterTechDriversInit(privileged);
c401cc
+    if (virNWFilterTechDriversInit(privileged) < 0)
c401cc
+        goto err_dhcpsnoop_shutdown;
c401cc
 
c401cc
     if (virNWFilterConfLayerInit(virNWFilterDomainFWUpdateCB,
c401cc
                                  driverState) < 0)
c401cc
@@ -253,6 +254,7 @@ error:
c401cc
 
c401cc
 err_techdrivers_shutdown:
c401cc
     virNWFilterTechDriversShutdown();
c401cc
+err_dhcpsnoop_shutdown:
c401cc
     virNWFilterDHCPSnoopShutdown();
c401cc
 err_exit_learnshutdown:
c401cc
     virNWFilterLearnShutdown();
c401cc
@@ -329,10 +331,10 @@ nwfilterStateCleanup(void) {
c401cc
 
c401cc
     if (driverState->privileged) {
c401cc
         virNWFilterConfLayerShutdown();
c401cc
-        virNWFilterTechDriversShutdown();
c401cc
         virNWFilterDHCPSnoopShutdown();
c401cc
         virNWFilterLearnShutdown();
c401cc
         virNWFilterIPAddrMapShutdown();
c401cc
+        virNWFilterTechDriversShutdown();
c401cc
 
c401cc
         nwfilterDriverLock(driverState);
c401cc
 
c401cc
diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c
c401cc
index 203ba0e..21b38ae 100644
c401cc
--- a/src/nwfilter/nwfilter_gentech_driver.c
c401cc
+++ b/src/nwfilter/nwfilter_gentech_driver.c
c401cc
@@ -55,30 +55,53 @@ static virNWFilterTechDriverPtr filter_tech_drivers[] = {
c401cc
     NULL
c401cc
 };
c401cc
 
c401cc
+/* Serializes instantiation of filters. This is necessary
c401cc
+ * to avoid lock ordering deadlocks. eg __virNWFilterInstantiateFilter
c401cc
+ * will hold a lock on a virNWFilterObjPtr. This in turn invokes
c401cc
+ * virNWFilterInstantiate which invokes virNWFilterDetermineMissingVarsRec
c401cc
+ * which invokes virNWFilterObjFindByName. This iterates over every single
c401cc
+ * virNWFilterObjPtr in the list. So if 2 threads try to instantiate a
c401cc
+ * filter in parallel, they'll both hold 1 lock at the top level in
c401cc
+ * __virNWFilterInstantiateFilter which will cause the other thread
c401cc
+ * to deadlock in virNWFilterObjFindByName.
c401cc
+ *
c401cc
+ * XXX better long term solution is to make virNWFilterObjList use a
c401cc
+ * hash table as is done for virDomainObjList. You can then get
c401cc
+ * lockless lookup of objects by name.
c401cc
+ */
c401cc
+static virMutex updateMutex;
c401cc
 
c401cc
-void virNWFilterTechDriversInit(bool privileged) {
c401cc
+int virNWFilterTechDriversInit(bool privileged)
c401cc
+{
c401cc
     size_t i = 0;
c401cc
     VIR_DEBUG("Initializing NWFilter technology drivers");
c401cc
+    if (virMutexInitRecursive(&updateMutex) < 0)
c401cc
+        return -1;
c401cc
+
c401cc
     while (filter_tech_drivers[i]) {
c401cc
         if (!(filter_tech_drivers[i]->flags & TECHDRV_FLAG_INITIALIZED))
c401cc
             filter_tech_drivers[i]->init(privileged);
c401cc
         i++;
c401cc
     }
c401cc
+    return 0;
c401cc
 }
c401cc
 
c401cc
 
c401cc
-void virNWFilterTechDriversShutdown(void) {
c401cc
+void virNWFilterTechDriversShutdown(void)
c401cc
+{
c401cc
     size_t i = 0;
c401cc
     while (filter_tech_drivers[i]) {
c401cc
         if ((filter_tech_drivers[i]->flags & TECHDRV_FLAG_INITIALIZED))
c401cc
             filter_tech_drivers[i]->shutdown();
c401cc
         i++;
c401cc
     }
c401cc
+    virMutexDestroy(&updateMutex);
c401cc
 }
c401cc
 
c401cc
 
c401cc
 virNWFilterTechDriverPtr
c401cc
-virNWFilterTechDriverForName(const char *name) {
c401cc
+virNWFilterTechDriverForName(const char *name)
c401cc
+{
c401cc
     size_t i = 0;
c401cc
     while (filter_tech_drivers[i]) {
c401cc
        if (STREQ(filter_tech_drivers[i]->name, name)) {
c401cc
@@ -934,6 +957,8 @@ _virNWFilterInstantiateFilter(virNWFilterDriverStatePtr driver,
c401cc
     int ifindex;
c401cc
     int rc;
c401cc
 
c401cc
+    virMutexLock(&updateMutex);
c401cc
+
c401cc
     /* after grabbing the filter update lock check for the interface; if
c401cc
        it's not there anymore its filters will be or are being removed
c401cc
        (while holding the lock) and we don't want to build new ones */
c401cc
@@ -961,6 +986,8 @@ _virNWFilterInstantiateFilter(virNWFilterDriverStatePtr driver,
c401cc
                                         foundNewFilter);
c401cc
 
c401cc
 cleanup:
c401cc
+    virMutexUnlock(&updateMutex);
c401cc
+
c401cc
     return rc;
c401cc
 }
c401cc
 
c401cc
@@ -980,6 +1007,7 @@ virNWFilterInstantiateFilterLate(virNWFilterDriverStatePtr driver,
c401cc
     bool foundNewFilter = false;
c401cc
 
c401cc
     virNWFilterReadLockFilterUpdates();
c401cc
+    virMutexLock(&updateMutex);
c401cc
 
c401cc
     rc = __virNWFilterInstantiateFilter(driver,
c401cc
                                         vmuuid,
c401cc
@@ -1005,6 +1033,7 @@ virNWFilterInstantiateFilterLate(virNWFilterDriverStatePtr driver,
c401cc
     }
c401cc
 
c401cc
     virNWFilterUnlockFilterUpdates();
c401cc
+    virMutexUnlock(&updateMutex);
c401cc
 
c401cc
     return rc;
c401cc
 }
c401cc
@@ -1128,7 +1157,11 @@ _virNWFilterTeardownFilter(const char *ifname)
c401cc
 int
c401cc
 virNWFilterTeardownFilter(const virDomainNetDefPtr net)
c401cc
 {
c401cc
-    return _virNWFilterTeardownFilter(net->ifname);
c401cc
+    int ret;
c401cc
+    virMutexLock(&updateMutex);
c401cc
+    ret = _virNWFilterTeardownFilter(net->ifname);
c401cc
+    virMutexUnlock(&updateMutex);
c401cc
+    return ret;
c401cc
 }
c401cc
 
c401cc
 
c401cc
diff --git a/src/nwfilter/nwfilter_gentech_driver.h b/src/nwfilter/nwfilter_gentech_driver.h
c401cc
index 8528e2a..48ee1df 100644
c401cc
--- a/src/nwfilter/nwfilter_gentech_driver.h
c401cc
+++ b/src/nwfilter/nwfilter_gentech_driver.h
c401cc
@@ -30,7 +30,7 @@ virNWFilterTechDriverPtr virNWFilterTechDriverForName(const char *name);
c401cc
 int virNWFilterRuleInstAddData(virNWFilterRuleInstPtr res,
c401cc
                                void *data);
c401cc
 
c401cc
-void virNWFilterTechDriversInit(bool privileged);
c401cc
+int virNWFilterTechDriversInit(bool privileged);
c401cc
 void virNWFilterTechDriversShutdown(void);
c401cc
 
c401cc
 enum instCase {
c401cc
-- 
c401cc
1.9.0
c401cc