]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2026-44690, Cross-zone wildcard cache poisoning via
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:12:38 +0000 (10:12 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 22 Jul 2026 08:12:38 +0000 (10:12 +0200)
  RRSIG.labels manipulation. Thanks to Qifan Zhang, Palo Alto
  Networks, for the report.

services/cache/rrset.c
validator/val_sigcrypt.c
validator/val_utils.c
validator/val_utils.h
validator/validator.c

index a5d320a3058703e21188bfca0b1d0c08d1b4de0e..79bf473bb53d470b8366775ecf6030b501c1f0c1 100644 (file)
@@ -50,6 +50,7 @@
 #include "util/regional.h"
 #include "util/alloc.h"
 #include "util/net_help.h"
+#include "validator/val_utils.h"
 
 void
 rrset_markdel(void* key)
@@ -261,12 +262,43 @@ rrset_cache_update(struct rrset_cache* r, struct rrset_ref* ref,
        return 0;
 }
 
+/** See if the name is a within signer authority */
+static int
+dname_subdomain_rrsig_signers(uint8_t* dname,
+       struct ub_packed_rrset_key* rrset)
+{
+       struct packed_rrset_data* d = (struct packed_rrset_data*)
+               rrset->entry.data;
+       size_t i;
+       if(!d || !d->rrsig_count)
+               return 0;
+       for(i=0; i<d->rrsig_count; i++) {
+               uint8_t* sname = NULL;
+               size_t slen = 0;
+               rrsig_get_signer(d->rr_data[d->count+i], d->rr_len[d->count+i],
+                       &sname, &slen);
+               if(!sname || !slen)
+                       return 0; /* malformed */
+               if(!dname_subdomain_c(dname, sname))
+                       return 0; /* not a subdomain */
+       }
+       return 1;
+}
+
 void rrset_cache_update_wildcard(struct rrset_cache* rrset_cache, 
        struct ub_packed_rrset_key* rrset, uint8_t* ce, size_t ce_len,
        struct alloc_cache* alloc, time_t timenow)
 {
        struct rrset_ref ref;
        uint8_t wc_dname[LDNS_MAX_DOMAINLEN+3];
+
+       /* See if the RRSIG signer name allows this wildcard,
+        * the new rrset should fall within the zone of the RRSIG signer(s). */
+       if(!dname_subdomain_rrsig_signers(ce, rrset)) {
+               verbose(VERB_ALGO, "wildcard canonical parent outside signer authority");
+               return;
+       }
+
        rrset = packed_rrset_copy_alloc(rrset, alloc, timenow);
        if(!rrset) {
                log_err("malloc failure in rrset_cache_update_wildcard");
index 9f27f9cc9bb7338d4d97140a605e8cf3132aff82..46e6ac16b7d70e3d926b2d4f05e67151ff7393f3 100644 (file)
@@ -1666,6 +1666,13 @@ dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf,
                        *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
                return sec_status_bogus;
        }
+       if((int)sig[2+3] < dname_signame_label_count(signer)) {
+               verbose(VERB_QUERY, "verify: RRSIG label count too low for signer");
+               *reason = "signature labelcount lower than signature signer";
+               if(reason_bogus)
+                       *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+               return sec_status_bogus;
+       }
 
        /* original ttl, always ok */
 
index 405cf897f4c6cf2bcb591477c48220db391325b0..6754a8bab69ede8d7ea644ced1f7c187b92156b5 100644 (file)
@@ -157,7 +157,7 @@ val_classify_response(uint16_t query_flags, struct query_info* origqinf,
 }
 
 /** Get signer name from RRSIG */
-static void
+void
 rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen)
 {
        /* RRSIG rdata is not allowed to be compressed, it is stored
index e0c6499024e890c990b6420e87fb7b6e7d5c617d..f3750742b5d14d80b5ac18603e7f3d7afd1d49a0 100644 (file)
@@ -438,4 +438,8 @@ struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen,
 int derive_cname_from_dname(struct ub_packed_rrset_key* cname,
        struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen);
 
+/** Get signer name from RRSIG, sname is NULL if malformed. */
+void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname,
+       size_t* slen);
+
 #endif /* VALIDATOR_VAL_UTILS_H */
index 8fc9ffc949cdfeababfc254352ec4f5783f38a72..2099d9c195db33f7111dda0e7dc88f69dc6e4b33 100644 (file)
@@ -1043,6 +1043,10 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
        uint8_t* wc = NULL;
        size_t wl;
        int wc_cached = 0;
+       int wc_to_cache = 0;
+       uint8_t* cache_wc = NULL;
+       size_t cache_wl = 0;
+       struct ub_packed_rrset_key* cache_s = NULL;
        int wc_NSEC_ok = 0;
        /* This is used to update the RRset cache, with the combination
         * of the dname expansion and this wildcard, for security status. */
@@ -1071,8 +1075,11 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
                        return;
                }
                if(wc && !wc_cached && env->cfg->aggressive_nsec) {
-                       rrset_cache_update_wildcard(env->rrset_cache, s, wc, wl,
-                               env->alloc, *env->now);
+                       /* Postpone cache adjust until proof has succeeded. */
+                       wc_to_cache = 1;
+                       cache_wc = wc;
+                       cache_wl = wl;
+                       cache_s = s;
                        wc_cached = 1;
                }
                if(wc) wc_rrset = s;
@@ -1137,6 +1144,10 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
                        entry.data)->security = sec_status_bogus;
                return;
        }
+       if(wc_to_cache) {
+               rrset_cache_update_wildcard(env->rrset_cache, cache_s,
+                       cache_wc, cache_wl, env->alloc, *env->now);
+       }
 
        verbose(VERB_ALGO, "Successfully validated positive response");
        chase_reply->security = sec_status_secure;