The branch main has been updated by gordon:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=2a3a6a1771148a709c2d9694c1d66c41ce8dee79

commit 2a3a6a1771148a709c2d9694c1d66c41ce8dee79
Author:     Gordon Tetlow <[email protected]>
AuthorDate: 2025-11-21 21:24:58 +0000
Commit:     Gordon Tetlow <[email protected]>
CommitDate: 2025-11-26 15:57:33 +0000

    Mitigate YXDOMAIN and nodata non-referral answer poisoning.
    
    Add a fix to apply scrubbing of unsolicited NS RRSets (and their
    respective address records) for YXDOMAIN and nodata non-referral
    answers. This prevents a malicious actor from exploiting a possible
    cache poison attack.
    
    Obtained from:  NLnet Labs
    Security:       CVE-2025-11411
---
 contrib/unbound/iterator/iter_scrub.c | 39 +++++++++++++++++++++++++++++++----
 1 file changed, 35 insertions(+), 4 deletions(-)

diff --git a/contrib/unbound/iterator/iter_scrub.c 
b/contrib/unbound/iterator/iter_scrub.c
index 553d3655f0e3..8507a3fb65ac 100644
--- a/contrib/unbound/iterator/iter_scrub.c
+++ b/contrib/unbound/iterator/iter_scrub.c
@@ -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))

Reply via email to