]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Additional fix for CVE-2025-11411 (possible domain hijacking attack), branch-1.24.2 release-1.24.2
authorYorgos Thessalonikefs <yorgos@nlnetlabs.nl>
Wed, 26 Nov 2025 10:09:40 +0000 (11:09 +0100)
committerYorgos Thessalonikefs <yorgos@nlnetlabs.nl>
Wed, 26 Nov 2025 10:09:40 +0000 (11:09 +0100)
  to include YXDOMAIN and non-referral nodata answers in the mitigation as
  well, reported by TaoFei Guo from Peking University, Yang Luo and JianJun
  Chen from Tsinghua University.

iterator/iter_scrub.c
testdata/iter_scrub_promiscuous.rpl
testdata/ratelimit.tdir/ratelimit.testns

index 553d3655f0e3f3a09547dab46d808b938073fe26..8507a3fb65acb7dad2c795fc1b46348f6b652520 100644 (file)
@@ -418,12 +418,13 @@ shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count)
  * @param qinfo: original query.
  * @param region: where to allocate synthesized CNAMEs.
  * @param env: module env with config options.
+ * @param zonename: name of server zone.
  * @return 0 on error.
  */
 static int
 scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, 
        struct query_info* qinfo, struct regional* region,
-       struct module_env* env)
+       struct module_env* env, uint8_t* zonename)
 {
        uint8_t* sname = qinfo->qname;
        size_t snamelen = qinfo->qname_len;
@@ -431,7 +432,8 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
        int cname_length = 0; /* number of CNAMEs, or DNAMEs */
 
        if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR &&
-               FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN)
+               FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN &&
+               FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_YXDOMAIN)
                return 1;
 
        /* For the ANSWER section, remove all "irrelevant" records and add
@@ -470,6 +472,11 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
                                &aliaslen, pkt)) {
                                verbose(VERB_ALGO, "synthesized CNAME "
                                        "too long");
+                               if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) {
+                                       prev = rrset;
+                                       rrset = rrset->rrset_all_next;
+                                       continue;
+                               }
                                return 0;
                        }
                        cname_length++;
@@ -650,6 +657,29 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
                                        "RRset:", pkt, msg, prev, &rrset);
                                continue;
                        }
+                       /* Also delete promiscuous NS for other RCODEs */
+                       if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR
+                               && env->cfg->iter_scrub_promiscuous) {
+                               remove_rrset("normalize: removing promiscuous "
+                                       "RRset:", pkt, msg, prev, &rrset);
+                               continue;
+                       }
+                       /* Also delete promiscuous NS for NOERROR with nodata
+                        * for authoritative answers, not for delegations.
+                        * NOERROR with an_rrsets!=0 already handled.
+                        * Also NOERROR and soa_in_auth already handled.
+                        * NOERROR with an_rrsets==0, and not a referral.
+                        * referral is (NS not the zonename, noSOA).
+                        */
+                       if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR
+                               && msg->an_rrsets == 0
+                               && !(dname_pkt_compare(pkt, rrset->dname,
+                                    zonename) != 0 && !soa_in_auth(msg))
+                               && env->cfg->iter_scrub_promiscuous) {
+                               remove_rrset("normalize: removing promiscuous "
+                                       "RRset:", pkt, msg, prev, &rrset);
+                               continue;
+                       }
                        if(nsset == NULL) {
                                nsset = rrset;
                        } else {
@@ -1060,7 +1090,8 @@ scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
        /* this is not required for basic operation but is a forgery 
         * resistance (security) feature */
        if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR ||
-               FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) &&
+               FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN ||
+               FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) &&
                msg->qdcount == 0)
                return 0;
 
@@ -1074,7 +1105,7 @@ scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
        }
 
        /* normalize the response, this cleans up the additional.  */
-       if(!scrub_normalize(pkt, msg, qinfo, region, env))
+       if(!scrub_normalize(pkt, msg, qinfo, region, env, zonename))
                return 0;
        /* delete all out-of-zone information */
        if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
index 61fca0d2806146c385dcfc579cefdec9e9fa90bb..febbee81ce4ed9e7d6f81a20b244a198dd398325 100644 (file)
@@ -16,6 +16,7 @@ SCENARIO_BEGIN Test iterator with scrub of promiscuous records
 ; The spoofed contents are ns.attacker.mesa and its IPs 5.6.7.8 and 5.6.7.9.
 ; The pollute1.mesa NS, ns.pollute2.mesa A, and test3.atkr.pollute3.mesa NS
 ; with ns.pollute3.mesa A records are tested for cache placement.
+; pollute4.mesa uses YXDOMAIN.
 
 ; ns.root
 RANGE_BEGIN 0 400
@@ -84,6 +85,18 @@ SECTION ADDITIONAL
 ns.pollute3.mesa. IN A 1.2.4.3
 ENTRY_END
 
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+pollute4.mesa. IN NS
+SECTION AUTHORITY
+pollute4.mesa. IN NS ns.pollute4.mesa.
+SECTION ADDITIONAL
+ns.pollute4.mesa. IN A 1.2.4.4
+ENTRY_END
+
 ENTRY_BEGIN
 MATCH opcode subdomain
 ADJUST copy_id copy_query
@@ -188,6 +201,35 @@ check.pollute3.mesa. IN A 1.8.9.3
 ENTRY_END
 RANGE_END
 
+; ns.pollute4.mesa
+RANGE_BEGIN 0 400
+       ADDRESS 1.2.4.4
+
+; This is the spoofed answer that is returned.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA YXDOMAIN
+SECTION QUESTION
+test4.atkr.pollute4.mesa. IN A
+SECTION ANSWER
+test4.atkr.pollute4.mesa. 86400 IN A 1.2.3.4
+SECTION AUTHORITY
+pollute4.mesa. 86400 IN NS ns.attacker.mesa.
+ENTRY_END
+
+; correct answer for the check query.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+check.pollute4.mesa. IN A
+SECTION ANSWER
+check.pollute4.mesa. IN A 1.8.9.4
+ENTRY_END
+RANGE_END
+
 ; ns.attacker.mesa
 RANGE_BEGIN 0 400
        ADDRESS 5.6.7.8
@@ -370,4 +412,46 @@ check.pollute3.mesa. IN A 1.8.9.3
 ;check.pollute3.mesa. IN A 5.6.7.9
 ENTRY_END
 
+; Test query 4
+STEP 120 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test4.atkr.pollute4.mesa. IN A
+ENTRY_END
+
+STEP 130 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA YXDOMAIN
+SECTION QUESTION
+test4.atkr.pollute4.mesa. IN A
+SECTION ANSWER
+test4.atkr.pollute4.mesa. 86400 IN A 1.2.3.4
+SECTION AUTHORITY
+; removed record
+;pollute4.mesa.       0       IN      NS      ns.attacker.mesa.
+ENTRY_END
+
+; Check the cache contents, for query 4.
+STEP 140 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+check.pollute4.mesa. IN A
+ENTRY_END
+
+STEP 150 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+check.pollute4.mesa. IN A
+SECTION ANSWER
+; good answer
+check.pollute4.mesa. IN A 1.8.9.4
+; bad answer
+;check.pollute4.mesa. IN A 5.6.7.9
+ENTRY_END
+
 SCENARIO_END
index 563c1db6a1f2f74d989bed80020f2f421c663c6d..5c22c292d6fd701e5ac499817133517498741bbf 100644 (file)
@@ -3,13 +3,31 @@ $ORIGIN example.com.
 $TTL 3600
 
 ENTRY_BEGIN
-MATCH opcode qtype
+MATCH opcode qname qtype
 REPLY QR AA NOERROR
-ADJUST copy_id copy_query
+ADJUST copy_id
 SECTION QUESTION
-wild   IN      A
+www1   IN      A
 SECTION ANSWER
-wild   IN      A       10.20.30.40
-SECTION AUTHORITY
-example.com. IN NS ns.example.com.
+www1   IN      A       1.1.1.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+REPLY QR AA NOERROR
+ADJUST copy_id
+SECTION QUESTION
+www2   IN      A
+SECTION ANSWER
+www2   IN      A       2.2.2.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+REPLY QR AA NOERROR
+ADJUST copy_id
+SECTION QUESTION
+www3   IN      A
+SECTION ANSWER
+www3   IN      A       3.3.3.3
 ENTRY_END