]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- When a granchild delegation is returned, remove any cached child delegations 1053/head
authorYorgos Thessalonikefs <yorgos@nlnetlabs.nl>
Mon, 22 Apr 2024 13:46:06 +0000 (15:46 +0200)
committerYorgos Thessalonikefs <yorgos@nlnetlabs.nl>
Mon, 22 Apr 2024 13:46:06 +0000 (15:46 +0200)
  up to parent to not cause delegation invalidation because of an
  expired child delegation that would never be updated. Most likely to
  happen without qname-minimisation. Reported by Roland van Rijswijk-Deij.

iterator/iterator.c
services/cache/dns.c
services/cache/rrset.c
services/cache/rrset.h
testdata/iter_ghost_grandchild_delegation.rpl [new file with mode: 0644]

index 6ec8af401c8e67a653f16106ac572502f069e420..89073ae305ef73b264e3cfc0c7db3351996b9c59 100644 (file)
@@ -52,6 +52,7 @@
 #include "iterator/iter_priv.h"
 #include "validator/val_neg.h"
 #include "services/cache/dns.h"
+#include "services/cache/rrset.h"
 #include "services/cache/infra.h"
 #include "services/authzone.h"
 #include "util/module.h"
@@ -3255,6 +3256,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
                }
                return final_state(iq);
        } else if(type == RESPONSE_TYPE_REFERRAL) {
+               struct delegpt* old_dp = NULL;
                /* REFERRAL type responses get a reset of the 
                 * delegation point, and back to the QUERYTARGETS_STATE. */
                verbose(VERB_DETAIL, "query response was REFERRAL");
@@ -3306,6 +3308,8 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
                /* Reset the event state, setting the current delegation 
                 * point to the referral. */
                iq->deleg_msg = iq->response;
+               /* Keep current delegation point for label comparison */
+               old_dp = iq->dp;
                iq->dp = delegpt_from_message(iq->response, qstate->region);
                if (qstate->env->cfg->qname_minimisation)
                        iq->minimisation_state = INIT_MINIMISE_STATE;
@@ -3313,6 +3317,20 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
                        errinf(qstate, "malloc failure, for delegation point");
                        return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
                }
+               if(old_dp->namelabs + 1 < iq->dp->namelabs) {
+                       /* We got a grandchild delegation (more than one label
+                        * difference) than expected. Check for in-between
+                        * delegations in the cache and remove them.
+                        * They could prove problematic when they expire
+                        * and rrset_expired_above() encounters them during
+                        * delegation cache lookups. */
+                       uint8_t* qname = iq->dp->name;
+                       size_t qnamelen = iq->dp->namelen;
+                       rrset_cache_remove_above(qstate->env->rrset_cache,
+                               &qname, &qnamelen, LDNS_RR_TYPE_NS,
+                               iq->qchase.qclass, *qstate->env->now,
+                               old_dp->name, old_dp->namelen);
+               }
                if(!cache_fill_missing(qstate->env, iq->qchase.qclass, 
                        qstate->region, iq->dp)) {
                        errinf(qstate, "malloc failure, copy extra info into delegation point");
index 6a980548d72f7c6b690349a362ae2d996b54207d..632ed79ace490a7a825a96a8f197b919612f778a 100644 (file)
@@ -193,46 +193,6 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
        slabhash_insert(env->msg_cache, hash, &e->entry, rep, env->alloc);
 }
 
-/** see if an rrset is expired above the qname, return upper qname. */
-static int
-rrset_expired_above(struct module_env* env, uint8_t** qname, size_t* qnamelen,
-       uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* expiretop,
-       size_t expiretoplen)
-{
-       struct ub_packed_rrset_key *rrset;
-       uint8_t lablen;
-
-       while(*qnamelen > 0) {
-               /* look one label higher */
-               lablen = **qname;
-               *qname += lablen + 1;
-               *qnamelen -= lablen + 1;
-               if(*qnamelen <= 0)
-                       break;
-
-               /* looks up with a time of 0, to see expired entries */
-               if((rrset = rrset_cache_lookup(env->rrset_cache, *qname,
-                       *qnamelen, searchtype, qclass, 0, 0, 0))) {
-                       struct packed_rrset_data* data =
-                               (struct packed_rrset_data*)rrset->entry.data;
-                       if(now > data->ttl) {
-                               /* it is expired, this is not wanted */
-                               lock_rw_unlock(&rrset->entry.lock);
-                               log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass);
-                               return 1;
-                       }
-                       /* it is not expired, continue looking */
-                       lock_rw_unlock(&rrset->entry.lock);
-               }
-
-               /* do not look above the expiretop. */
-               if(expiretop && *qnamelen == expiretoplen &&
-                       query_dname_compare(*qname, expiretop)==0)
-                       break;
-       }
-       return 0;
-}
-
 /** find closest NS or DNAME and returns the rrset (locked) */
 static struct ub_packed_rrset_key*
 find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen, 
@@ -266,12 +226,12 @@ find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen,
                        /* check for expiry, but we have to let go of the rrset
                         * for the lock ordering */
                        lock_rw_unlock(&rrset->entry.lock);
-                       /* the expired_above function always takes off one
-                        * label (if qnamelen>0) and returns the final qname
-                        * where it searched, so we can continue from there
-                        * turning the O N*N search into O N. */
-                       if(!rrset_expired_above(env, &qname, &qnamelen,
-                               searchtype, qclass, now, expiretop,
+                       /* the rrset_cache_expired_above function always takes
+                        * off one label (if qnamelen>0) and returns the final
+                        * qname where it searched, so we can continue from
+                        * there turning the O N*N search into O N. */
+                       if(!rrset_cache_expired_above(env->rrset_cache, &qname,
+                               &qnamelen, searchtype, qclass, now, expiretop,
                                expiretoplen)) {
                                /* we want to return rrset, but it may be
                                 * gone from cache, if so, just loop like
index f41a1955cfc6646d055c437c5230c9167ae414fa..2c03214c8fe22d6c34b8ce436cdb822e1e11560b 100644 (file)
@@ -46,6 +46,7 @@
 #include "util/data/packed_rrset.h"
 #include "util/data/msgreply.h"
 #include "util/data/msgparse.h"
+#include "util/data/dname.h"
 #include "util/regional.h"
 #include "util/alloc.h"
 #include "util/net_help.h"
@@ -443,6 +444,89 @@ rrset_check_sec_status(struct rrset_cache* r,
        lock_rw_unlock(&e->lock);
 }
 
+void
+rrset_cache_remove_above(struct rrset_cache* r, uint8_t** qname, size_t*
+       qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t*
+       qnametop, size_t qnametoplen)
+{
+       struct ub_packed_rrset_key *rrset;
+       uint8_t lablen;
+
+       while(*qnamelen > 0) {
+               /* look one label higher */
+               lablen = **qname;
+               *qname += lablen + 1;
+               *qnamelen -= lablen + 1;
+               if(*qnamelen <= 0)
+                       return;
+
+               /* stop at qnametop */
+               if(qnametop && *qnamelen == qnametoplen &&
+                       query_dname_compare(*qname, qnametop)==0)
+                       return;
+
+               if(verbosity >= VERB_ALGO) {
+                       /* looks up with a time of 0, to see expired entries */
+                       if((rrset = rrset_cache_lookup(r, *qname,
+                               *qnamelen, searchtype, qclass, 0, 0, 0))) {
+                               struct packed_rrset_data* data =
+                                       (struct packed_rrset_data*)rrset->entry.data;
+                               int expired = (now > data->ttl);
+                               lock_rw_unlock(&rrset->entry.lock);
+                               if(expired)
+                                       log_nametypeclass(verbosity, "this "
+                                               "(grand)parent rrset will be "
+                                               "removed (expired)",
+                                               *qname, searchtype, qclass);
+                               else    log_nametypeclass(verbosity, "this "
+                                               "(grand)parent rrset will be "
+                                               "removed",
+                                               *qname, searchtype, qclass);
+                       }
+               }
+               rrset_cache_remove(r, *qname, *qnamelen, searchtype, qclass, 0);
+       }
+}
+
+int
+rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname, size_t*
+       qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t*
+       qnametop, size_t qnametoplen)
+{
+       struct ub_packed_rrset_key *rrset;
+       uint8_t lablen;
+
+       while(*qnamelen > 0) {
+               /* look one label higher */
+               lablen = **qname;
+               *qname += lablen + 1;
+               *qnamelen -= lablen + 1;
+               if(*qnamelen <= 0)
+                       break;
+
+               /* looks up with a time of 0, to see expired entries */
+               if((rrset = rrset_cache_lookup(r, *qname,
+                       *qnamelen, searchtype, qclass, 0, 0, 0))) {
+                       struct packed_rrset_data* data =
+                               (struct packed_rrset_data*)rrset->entry.data;
+                       if(now > data->ttl) {
+                               /* it is expired, this is not wanted */
+                               lock_rw_unlock(&rrset->entry.lock);
+                               log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass);
+                               return 1;
+                       }
+                       /* it is not expired, continue looking */
+                       lock_rw_unlock(&rrset->entry.lock);
+               }
+
+               /* do not look above the qnametop. */
+               if(qnametop && *qnamelen == qnametoplen &&
+                       query_dname_compare(*qname, qnametop)==0)
+                       break;
+       }
+       return 0;
+}
+
 void rrset_cache_remove(struct rrset_cache* r, uint8_t* nm, size_t nmlen,
        uint16_t type, uint16_t dclass, uint32_t flags)
 {
index 7c36d4032ebc9ec8700b0a5a959606d8dd72a3bb..6db79d90f5324db46858fa66455fe1e781d7120a 100644 (file)
@@ -231,6 +231,37 @@ void rrset_update_sec_status(struct rrset_cache* r,
 void rrset_check_sec_status(struct rrset_cache* r, 
        struct ub_packed_rrset_key* rrset, time_t now);
 
+/**
+ * Removes rrsets above the qname, returns upper qname.
+ * @param r: the rrset cache.
+ * @param qname: the start qname, also used as the output.
+ * @param qnamelen: length of qname, updated when it returns.
+ * @param searchtype: qtype to search for.
+ * @param qclass: qclass to search for.
+ * @param now: current time.
+ * @param qnametop: the top qname to stop removal (it is not removed).
+ * @param qnametoplen: length of qnametop.
+ */
+void rrset_cache_remove_above(struct rrset_cache* r, uint8_t** qname,
+       size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now,
+       uint8_t* qnametop, size_t qnametoplen);
+
+/**
+ * Sees if an rrset is expired above the qname, returns upper qname.
+ * @param r: the rrset cache.
+ * @param qname: the start qname, also used as the output.
+ * @param qnamelen: length of qname, updated when it returns.
+ * @param searchtype: qtype to search for.
+ * @param qclass: qclass to search for.
+ * @param now: current time.
+ * @param qnametop: the top qname, don't look farther than that.
+ * @param qnametoplen: length of qnametop.
+ * @return true if there is an expired rrset above, false otherwise.
+ */
+int rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname,
+       size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now,
+       uint8_t* qnametop, size_t qnametoplen);
+
 /**
  * Remove an rrset from the cache, by name and type and flags
  * @param r: rrset cache
diff --git a/testdata/iter_ghost_grandchild_delegation.rpl b/testdata/iter_ghost_grandchild_delegation.rpl
new file mode 100644 (file)
index 0000000..d1e521b
--- /dev/null
@@ -0,0 +1,256 @@
+; config options
+server:
+       target-fetch-policy: "0 0 0 0 0"
+       qname-minimisation: "no"
+       minimal-responses: no
+
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test that deep delegation from the parent deletes intermediate delegations to avoid triggering the ghost domain countermeasure.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 19
+       ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. 86400 IN NS  K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.    86400 IN        A       193.0.14.129
+ENTRY_END
+
+; we will explicitly ask for this
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION AUTHORITY
+com.   10 IN NS        a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    86400 IN        A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION AUTHORITY
+example.com.   86400 IN NS     ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.        86400 IN        A       1.2.3.4
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+       ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.   10 IN NS        a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    86400 IN        A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ns.example.com. IN A
+SECTION ANSWER
+ns.example.com. IN A   1.2.3.4
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ns.example.com. IN AAAA
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A   1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a.example.com. IN A
+SECTION ANSWER
+a.example.com. IN A    10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com         IN A    1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+b.example.com. IN A
+SECTION ANSWER
+b.example.com. IN A    10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com         IN A    1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+c.example.com. IN A
+SECTION ANSWER
+c.example.com. IN A    10.20.30.40
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com         IN A    1.2.3.4
+ENTRY_END
+RANGE_END
+
+; get the com. IN NS delegation in cache
+STEP 0 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+com. IN NS
+ENTRY_END
+
+STEP 1 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. 10 IN NS  a.gtld-servers.net.
+ENTRY_END
+
+STEP 2 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.example.com. IN A
+ENTRY_END
+
+STEP 3 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+a.example.com. IN A
+SECTION ANSWER
+a.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; time passes for com. IN NS to expire.
+STEP 9 TIME_PASSES ELAPSE 11
+
+; the following query should go to the root instead of example.com. IN NS
+; because com. IN NS is expired
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+b.example.com. IN A
+ENTRY_END
+
+; root replies with the example.com IN NS delegation
+; the expired com. IN NS delegation should be deleted
+STEP 12 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+b.example.com. IN A
+SECTION ANSWER
+b.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+; root is offline in this range.
+; the following query should go straight to the example.com. IN NS delegation
+; because the expired com. IN NS should not be in the cache anymore
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+c.example.com. IN A
+ENTRY_END
+
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+c.example.com. IN A
+SECTION ANSWER
+c.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+SCENARIO_END