]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
[master] ensure rpz summary consistence during AXFR updates
authorEvan Hunt <each@isc.org>
Wed, 20 May 2015 22:00:50 +0000 (15:00 -0700)
committerEvan Hunt <each@isc.org>
Wed, 20 May 2015 22:00:50 +0000 (15:00 -0700)
4121. [bug] When updating a response-policy zone via AXFR,
summary data about other policy zones could fall
out of sync. Ultimately this could trigger an
assertion failure in rpz.c. [RT #39567]

CHANGES
doc/arm/notes.xml
lib/dns/include/dns/rpz.h
lib/dns/rbtdb.c
lib/dns/rpz.c

diff --git a/CHANGES b/CHANGES
index 5b2929a7ab5418d9052cdf8781c8487a14483333..6f1ab35ee08ecc70e72e2c53fc115c1b28aa7f64 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+4121.  [bug]           When updating a response-policy zone via AXFR,
+                       summary data about other policy zones could fall
+                       out of sync. Ultimately this could trigger an
+                       assertion failure in rpz.c. [RT #39567]
+
 4120.  [bug]           A bug in RPZ could cause the server to crash if
                        policy zones were updated while recursion was
                        pending for RPZ processing of an active query.
index 096e698df9dd99ca6283ba2582b177323c5db7d7..05092d56a546990849777b9b7a87b0ff7bd76a1e 100644 (file)
       </listitem>
       <listitem>
        <para>
-         A bug in the RPZ implementation could cause some policy
-         zones that did not specifically require recursion to be
-         treated as if they did; consequently, setting
-         <command>qname-wait-recurse no;</command> was
-         sometimes ineffective.  This has been corrected.
-         In most configurations, behavioral changes due to this
-         fix will not be noticeable.  [RT #39229]
-       </para>
-      </listitem>
-      <listitem>
-       <para>
-         A bug in RPZ could cause the server to crash if policy
-         zones were updated (e.g. via <command>rndc reload</command>
-         or an incoming zone transfer) while RPZ processing was still
-         ongoing for an active query. [RT #39415]
-       </para>
-      </listitem>
+         Several bugs have been fixed in the RPZ implementation:
+       </para>
+       <itemizedlist>
+         <para>
+           Policy zones that did not specifically require recursion
+           could be treated as if they did; consequently, setting
+           <command>qname-wait-recurse no;</command> was
+           sometimes ineffective.  This has been corrected.
+           In most configurations, behavioral changes due to this
+           fix will not be noticeable. [RT #39229]
+         </para>
+       </listitem>
+       <listitem>
+         <para>
+           The server could crash if policy zones were updated (e.g.
+           via <command>rndc reload</command> or an incoming zone
+           transfer) while RPZ processing was still ongoing for an
+           active query. [RT #39415]
+         </para>
+       </listitem>
+       <listitem>
+         <para>
+           On servers with more than one policy zone configured,
+           if one policy zone was updated via AXFR (especially when
+           the zone was very large) a race condition could allow the
+           RPZ summary data to fall out of sync with the policy zones,
+           potentially leading to an assertion failure in rpz.c.
+           [RT #39567]
+         </para>
+       </listitem>
+      </itemizedlist>
     </itemizedlist>
   </sect2>
   <sect2 id="end_of_life">
index f0201eb8ee3e995ba5744612439b21cd7fd05474..923ad436ce7e72f31d39b27c2d7aa7bdda9bc0e7 100644 (file)
@@ -101,20 +101,26 @@ typedef isc_uint32_t          dns_rpz_zbits_t;
 #define DNS_RPZ_ZMASK(n)    ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES-1) ? \
                                                0 : (1<<((n)+1))) -1))
 
+/*
+ * The trigger counter type.
+ */
+typedef ssize_t dns_rpz_trigger_counter_t;
+
 /*
  * The number of triggers of each type in a response policy zone.
  */
 typedef struct dns_rpz_triggers dns_rpz_triggers_t;
 struct dns_rpz_triggers {
-       int             client_ipv4;
-       int             client_ipv6;
-       int             qname;
-       int             ipv4;
-       int             ipv6;
-       int             nsdname;
-       int             nsipv4;
-       int             nsipv6;
+       dns_rpz_trigger_counter_t       client_ipv4;
+       dns_rpz_trigger_counter_t       client_ipv6;
+       dns_rpz_trigger_counter_t       qname;
+       dns_rpz_trigger_counter_t       ipv4;
+       dns_rpz_trigger_counter_t       ipv6;
+       dns_rpz_trigger_counter_t       nsdname;
+       dns_rpz_trigger_counter_t       nsipv4;
+       dns_rpz_trigger_counter_t       nsipv6;
 };
+
 /*
  * A single response policy zone.
  */
@@ -204,6 +210,13 @@ struct dns_rpz_zones {
         */
        dns_rpz_zbits_t         load_begun;
        dns_rpz_have_t          have;
+
+       /*
+        * total_triggers maintains the total number of triggers in all
+        * policy zones in the view. It is only used to print summary
+        * statistics after a zone load of how the trigger counts
+        * changed.
+        */
        dns_rpz_triggers_t      total_triggers;
 
        isc_mem_t               *mctx;
index 8f3d83358037a4808a416fde24fdc0acf9960738..5f3a33e485624e455a13ed6019f7735502a6f8d2 100644 (file)
@@ -1878,18 +1878,25 @@ delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
        dns_fixedname_t fname;
        dns_name_t *name;
        isc_result_t result = ISC_R_UNEXPECTED;
+       unsigned int node_has_rpz;
 
        INSIST(!ISC_LINK_LINKED(node, deadlink));
 
        switch (node->nsec) {
        case DNS_RBT_NSEC_NORMAL:
-               if (rbtdb->rpzs != NULL && node->rpz) {
-                       dns_fixedname_init(&fname);
-                       name = dns_fixedname_name(&fname);
-                       dns_rbt_fullnamefromnode(node, name);
-                       dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
-               }
+               /*
+                * Though this may be wasteful, it has to be done before
+                * node is deleted.
+                */
+               dns_fixedname_init(&fname);
+               name = dns_fixedname_name(&fname);
+               dns_rbt_fullnamefromnode(node, name);
+
+               node_has_rpz = node->rpz;
                result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
+               if (result == ISC_R_SUCCESS &&
+                   rbtdb->rpzs != NULL && node_has_rpz)
+                       dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
                break;
        case DNS_RBT_NSEC_HAS_NSEC:
                dns_fixedname_init(&fname);
@@ -1922,9 +1929,11 @@ delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
                                              isc_result_totext(result));
                        }
                }
-               if (rbtdb->rpzs != NULL && node->rpz)
-                       dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
+               node_has_rpz = node->rpz;
                result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
+               if (result == ISC_R_SUCCESS &&
+                   rbtdb->rpzs != NULL && node_has_rpz)
+                       dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
                break;
        case DNS_RBT_NSEC_NSEC:
                result = dns_rbt_deletenode(rbtdb->nsec, node, ISC_FALSE);
@@ -7178,16 +7187,21 @@ loadnode(dns_rbtdb_t *rbtdb, dns_name_t *name, dns_rbtnode_t **nodep,
        }
 
        if (noderesult == ISC_R_SUCCESS) {
-               /*
-                * Clean rpz entries added above.
-                */
-               if (rbtdb->rpzs != NULL && node->rpz)
-                       dns_rpz_delete(rbtdb->load_rpzs, rbtdb->rpz_num, name);
+               unsigned int node_has_rpz;
+
                /*
                 * Remove the node we just added above.
                 */
+               node_has_rpz = node->rpz;
                tmpresult = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
-               if (tmpresult != ISC_R_SUCCESS)
+               if (tmpresult == ISC_R_SUCCESS) {
+                       /*
+                        * Clean rpz entries added above.
+                        */
+                       if (rbtdb->rpzs != NULL && node_has_rpz)
+                               dns_rpz_delete(rbtdb->load_rpzs,
+                                              rbtdb->rpz_num, name);
+               } else {
                        isc_log_write(dns_lctx,
                                      DNS_LOGCATEGORY_DATABASE,
                                      DNS_LOGMODULE_CACHE,
@@ -7197,6 +7211,7 @@ loadnode(dns_rbtdb_t *rbtdb, dns_name_t *name, dns_rbtnode_t **nodep,
                                      "dns_rbt_addnode(NSEC): %s",
                                      isc_result_totext(tmpresult),
                                      isc_result_totext(noderesult));
+               }
        }
 
        /*
index e5ac99a312d198608d45afa2182ae84c737ff945..e547bea45b975523a6e19e0a3533f638fb73fbf1 100644 (file)
@@ -552,7 +552,7 @@ adj_trigger_cnt(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
                const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
                isc_boolean_t inc)
 {
-       int *cnt;
+       dns_rpz_trigger_counter_t *cnt;
        dns_rpz_zbits_t *have;
 
        switch (rpz_type) {
@@ -1558,8 +1558,10 @@ dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp,
         *    reload the new zone data into a new blank summary database
         *    if the reload fails, discard the new summary database
         *    if the new zone data is acceptable, copy the records for the
-        *      other zones into the new summary database and replace the
-        *      old summary database with the new.
+        *      other zones into the new summary CIDR and RBT databases
+        *      and replace the old summary databases with the new, and
+        *      correct the triggers and have values for the updated
+        *      zone.
         *
         * At the first attempt to load a zone, there is no summary data
         * for the zone and so no records that need to be deleted.
@@ -1578,30 +1580,33 @@ dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp,
                 */
                rpzs->load_begun |= tgt;
                dns_rpz_attach_rpzs(rpzs, load_rpzsp);
-               UNLOCK(&rpzs->search_lock);
-               UNLOCK(&rpzs->maint_lock);
-
        } else {
-               UNLOCK(&rpzs->search_lock);
-               UNLOCK(&rpzs->maint_lock);
-
+               /*
+                * Setup the new RPZ struct with empty summary trees.
+                */
                result = dns_rpz_new_zones(load_rpzsp, rpzs->mctx);
                if (result != ISC_R_SUCCESS)
                        return (result);
                load_rpzs = *load_rpzsp;
+               /*
+                * Initialize some members so that dns_rpz_add() works.
+                */
                load_rpzs->p.num_zones = rpzs->p.num_zones;
-               load_rpzs->total_triggers = rpzs->total_triggers;
-               memmove(load_rpzs->triggers, rpzs->triggers,
-                       sizeof(load_rpzs->triggers));
-               memset(&load_rpzs->triggers[rpz_num], 0,
-                      sizeof(load_rpzs->triggers[rpz_num]));
+               memset(&load_rpzs->triggers, 0, sizeof(load_rpzs->triggers));
                load_rpzs->zones[rpz_num] = rpz;
                isc_refcount_increment(&rpz->refs, NULL);
        }
 
+       UNLOCK(&rpzs->search_lock);
+       UNLOCK(&rpzs->maint_lock);
+
        return (ISC_R_SUCCESS);
 }
 
+/*
+ * This function updates "have" bits and also the qname_skip_recurse
+ * mask. It must be called when holding rpzs->search_lock.
+ */
 static void
 fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
        dns_rpz_num_t n;
@@ -1609,7 +1614,14 @@ fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
        dns_rpz_zbits_t zbit;
        char namebuf[DNS_NAME_FORMATSIZE];
 
-#      define SET_TRIG(n, zbit, type)                                  \
+       /*
+        * rpzs->total_triggers is only used to log a message below.
+        */
+
+       memmove(&old_totals, &rpzs->total_triggers, sizeof(old_totals));
+       memset(&rpzs->total_triggers, 0, sizeof(rpzs->total_triggers));
+
+#define SET_TRIG(n, zbit, type)                                                \
        if (rpzs->triggers[n].type == 0) {                              \
                rpzs->have.type &= ~zbit;                               \
        } else {                                                        \
@@ -1617,8 +1629,6 @@ fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
                rpzs->have.type |= zbit;                                \
        }
 
-       memmove(&old_totals, &rpzs->total_triggers, sizeof(old_totals));
-       memset(&rpzs->total_triggers, 0, sizeof(rpzs->total_triggers));
        for (n = 0; n < rpzs->p.num_zones; ++n) {
                zbit = DNS_RPZ_ZBIT(n);
                SET_TRIG(n, zbit, client_ipv4);
@@ -1631,6 +1641,8 @@ fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
                SET_TRIG(n, zbit, nsipv6);
        }
 
+#undef SET_TRIG
+
        fix_qname_skip_recurse(rpzs);
 
        dns_name_format(&rpzs->zones[rpz_num]->origin,
@@ -1638,8 +1650,8 @@ fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
        isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
                      DNS_LOGMODULE_RBTDB, DNS_RPZ_INFO_LEVEL,
                      "(re)loading policy zone '%s' changed from"
-                     " %d to %d qname, %d to %d nsdname,"
-                     " %d to %d IP, %d to %d NSIP entries",
+                     " %ld to %ld qname, %ld to %ld nsdname,"
+                     " %ld to %ld IP, %ld to %ld NSIP entries",
                      namebuf,
                      old_totals.qname, rpzs->total_triggers.qname,
                      old_totals.nsdname, rpzs->total_triggers.nsdname,
@@ -1647,13 +1659,48 @@ fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
                      rpzs->total_triggers.ipv4 + rpzs->total_triggers.ipv6,
                      old_totals.nsipv4 + old_totals.nsipv6,
                      rpzs->total_triggers.nsipv4 + rpzs->total_triggers.nsipv6);
-
-#      undef SET_TRIG
 }
 
 /*
- * Finish loading one zone.
- * The RBTDB write tree lock must be held.
+ * Finish loading one zone. This function is called during a commit when
+ * a RPZ zone loading is complete.  The RBTDB write tree lock must be
+ * held.
+ *
+ * Here, rpzs is a pointer to the view's common rpzs
+ * structure. *load_rpzsp is a rpzs structure that is local to the
+ * RBTDB, which is used during a single zone's load.
+ *
+ * During the zone load, i.e., between dns_rpz_beginload() and
+ * dns_rpz_ready(), only the zone that is being loaded updates
+ * *load_rpzsp. These updates in the summary databases inside load_rpzsp
+ * are made only for the rpz_num (and corresponding bit) of that
+ * zone. Nothing else reads or writes *load_rpzsp. The view's common
+ * rpzs is used during this time for queries.
+ *
+ * When zone loading is complete and we arrive here, the parts of the
+ * summary databases (CIDR and nsdname+qname RBT trees) from the view's
+ * common rpzs struct have to be merged into the summary databases of
+ * *load_rpzsp, as the summary databases of the view's common rpzs
+ * struct may have changed during the time the zone was being loaded.
+ *
+ * The function below carries out the merge. During the merge, it holds
+ * the maint_lock of the view's common rpzs struct so that it is not
+ * updated while the merging is taking place.
+ *
+ * After the merging is carried out, *load_rpzsp contains the most
+ * current state of the rpzs structure, i.e., the summary trees contain
+ * data for the new zone that was just loaded, as well as all other
+ * zones.
+ *
+ * Pointers to the summary databases of *load_rpzsp (CIDR and
+ * nsdname+qname RBT trees) are then swapped into the view's common rpz
+ * struct, so that the query path can continue using it. During the
+ * swap, the search_lock of the view's common rpz struct is acquired so
+ * that queries are paused while this swap occurs.
+ *
+ * The trigger counts for the new zone are also copied into the view's
+ * common rpz struct, and some other summary counts and masks are
+ * updated.
  */
 isc_result_t
 dns_rpz_ready(dns_rpz_zones_t *rpzs,
@@ -1679,10 +1726,12 @@ dns_rpz_ready(dns_rpz_zones_t *rpzs,
 
        if (load_rpzs == rpzs) {
                /*
-                * This is a successful initial zone loading,
-                * perhaps for a new instance of a view.
+                * This is a successful initial zone loading, perhaps
+                * for a new instance of a view.
                 */
+               LOCK(&rpzs->search_lock);
                fix_triggers(rpzs, rpz_num);
+               UNLOCK(&rpzs->search_lock);
                UNLOCK(&rpzs->maint_lock);
                dns_rpz_detach_rpzs(load_rpzsp);
                return (ISC_R_SUCCESS);
@@ -1785,13 +1834,14 @@ dns_rpz_ready(dns_rpz_zones_t *rpzs,
                }
        }
 
-       fix_triggers(load_rpzs, rpz_num);
-
        /*
         * Exchange the summary databases.
         */
        LOCK(&rpzs->search_lock);
 
+       rpzs->triggers[rpz_num] = load_rpzs->triggers[rpz_num];
+       fix_triggers(rpzs, rpz_num);
+
        found = rpzs->cidr;
        rpzs->cidr = load_rpzs->cidr;
        load_rpzs->cidr = found;
@@ -1800,9 +1850,6 @@ dns_rpz_ready(dns_rpz_zones_t *rpzs,
        rpzs->rbt = load_rpzs->rbt;
        load_rpzs->rbt = rbt;
 
-       rpzs->total_triggers = load_rpzs->total_triggers;
-       rpzs->have = load_rpzs->have;
-
        UNLOCK(&rpzs->search_lock);
 
        result = ISC_R_SUCCESS;