]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2026-33278, Possible remote code execution during DNSSEC
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 20 May 2026 08:13:08 +0000 (10:13 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 20 May 2026 08:13:08 +0000 (10:13 +0200)
  validation. Thanks to Qifan Zhang, Palo Alto Networks, for the report.

doc/Changelog
services/cache/dns.c
testdata/val_nsec3_iter_high.rpl
testdata/val_nx_nsec3_collision.rpl
testdata/val_nx_nsec3_params.rpl
validator/val_nsec3.c

index a1c5e918ad0ca26837d29bc9b5035abf24aa16e6..5bbe849bc4469e0af4a513ef135175ab8b6b00fb 100644 (file)
@@ -1,3 +1,7 @@
+20 May 2026: Wouter
+       - Fix CVE-2026-33278, Possible remote code execution during DNSSEC
+         validation. Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+
 23 April 2026: Wouter
        - Merge #1441: Fix buffer overrun in
          doq_repinfo_retrieve_localaddr().
index c593dcd72ceb7177aa21505337f65158038361b8..f6ce272a5ea615b1da9d33bee0b0b72dfe46f4f8 100644 (file)
@@ -712,10 +712,16 @@ struct dns_msg*
 dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region)
 {
        size_t i;
+       struct ub_packed_rrset_key** saved_rrsets;
        struct dns_msg* res = NULL;
+       size_t rep_alloc_size = sizeof(struct reply_info)
+               - sizeof(struct rrset_ref);  /* this is the size of res->rep
+                                               allocated in gen_dns_msg() */
        res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count);
        if(!res) return NULL;
-       *res->rep = *origin->rep;
+       saved_rrsets = res->rep->rrsets; /* save rrsets alloc by gen_dns_msg */
+       memcpy(res->rep, origin->rep, rep_alloc_size);
+       res->rep->rrsets = saved_rrsets;
        if(origin->rep->reason_bogus_str) {
                res->rep->reason_bogus_str = regional_strdup(region,
                        origin->rep->reason_bogus_str);
index 2b78f0b7f0cdf847168eef48db4806739bbca420..703092d89188f937153d9774433c66b18dc604f8 100644 (file)
@@ -120,12 +120,12 @@ example.com.      IN SOA  ns.example.com. hostmaster.example.com. 2007090400 28800 720
 example.com.    3600    IN      RRSIG   SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFCNGZ+/OfElYQMCZ77O9Lw9rhk7PAhUAmDcvTAst6Bq83qPq3r6c/Dm1nFc= ;{id = 2854}
 
 ; closest encloser, H(example.com).
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com. NSEC3 1 1 8 - 6md8numosa4q9ugkffdo1bmm82t5j49s SOA NS MX DNSKEY RRSIG
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCz/LkFOFcaQzVnyySW9ZoVUnxh7gIUdxyS9vqVDzo8pGhFU+3YogN2ZRk= ;{id = 2854}
+b6fuorg741ufili49mg9j4328ig53sqg.example.com. NSEC3 1 1 123 aabb00123456bbccdd b6fuorg741ufili49mg9j4328ig53sqh SOA NS MX DNSKEY RRSIG
+b6fuorg741ufili49mg9j4328ig53sqg.example.com.  3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. AJlV5car66lq5f0ASx7W47A/OADkARAXzKt9ZLojXze+FWK9JjAX+eA=
 
-; wildcard denial, H(*.example.com.) = 4f3cnt8cu22tngec382jj4gde4rb47ub
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com. NSEC3 1 1 0 - 4f3cnt8cu22tngec382jj4gde4rb48ub A MX RRSIG
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MCwCFHS+i/OB/V/gYmS1eQTXieXIXGjsAhQQ0Ql7TW/hsUklrb0DfoyhVPG95Q== ;{id = 2854}
+; wildcard denial, H(*.example.com.) = k1a2vr9c269jummpru5d68qllbfmtdcb.
+k1a2vr9c269jummpru5d68qllbfmtacb.example.com. NSEC3 1 1 123 aabb00123456bbccdd k1a2vr9c269jummpru5d68qllbfmtgcb A MX RRSIG
+k1a2vr9c269jummpru5d68qllbfmtacb.example.com.  3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. AARB9z4C1WZUI3WP3QAR7RJXFnN0qEBkEt8ocudxXzms4/7/2l6NNWc=
 
 ; next closer name, H(www.example.com.) = s1unhcti19bkdr98fegs0v46mbu3t4m3.
 s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 123 aabb00123456bbccdd s1unhcti19bkdr98fegs0v46mbu3t4m4 A MX RRSIG
@@ -152,10 +152,10 @@ SECTION ANSWER
 SECTION AUTHORITY
 example.com.   IN SOA  ns.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
 example.com.    3600    IN      RRSIG   SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFCNGZ+/OfElYQMCZ77O9Lw9rhk7PAhUAmDcvTAst6Bq83qPq3r6c/Dm1nFc= ;{id = 2854}
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com. NSEC3 1 1 8 - 6md8numosa4q9ugkffdo1bmm82t5j49s SOA NS MX DNSKEY RRSIG
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCz/LkFOFcaQzVnyySW9ZoVUnxh7gIUdxyS9vqVDzo8pGhFU+3YogN2ZRk= ;{id = 2854}
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com. NSEC3 1 1 0 - 4f3cnt8cu22tngec382jj4gde4rb48ub A MX RRSIG
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MCwCFHS+i/OB/V/gYmS1eQTXieXIXGjsAhQQ0Ql7TW/hsUklrb0DfoyhVPG95Q== ;{id = 2854}
+b6fuorg741ufili49mg9j4328ig53sqg.example.com. NSEC3 1 1 123 aabb00123456bbccdd b6fuorg741ufili49mg9j4328ig53sqh SOA NS MX DNSKEY RRSIG
+b6fuorg741ufili49mg9j4328ig53sqg.example.com.  3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. AJlV5car66lq5f0ASx7W47A/OADkARAXzKt9ZLojXze+FWK9JjAX+eA=
+k1a2vr9c269jummpru5d68qllbfmtacb.example.com. NSEC3 1 1 123 aabb00123456bbccdd k1a2vr9c269jummpru5d68qllbfmtgcb A MX RRSIG
+k1a2vr9c269jummpru5d68qllbfmtacb.example.com.  3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. AARB9z4C1WZUI3WP3QAR7RJXFnN0qEBkEt8ocudxXzms4/7/2l6NNWc=
 s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 123 aabb00123456bbccdd s1unhcti19bkdr98fegs0v46mbu3t4m4 A MX RRSIG
 s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFFSH4klZKke48dYyddYDj17gjTS0AhUAltWicpFLWqW98/Af9Qlx70MH8o4= ;{id = 2854}
 
index 87a55f565d0fe7877b8c48476cb6e231c67a9ef9..1b1f49e80e2b3ba256883b67051096a733bba8c4 100644 (file)
@@ -89,6 +89,17 @@ ns.example.com.         IN      A       1.2.3.4
 ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
 ENTRY_END
 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.example.com. IN AAAA
+SECTION AUTHORITY
+example.com.   IN SOA  ns.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
+example.com.    3600    IN      RRSIG   SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFCNGZ+/OfElYQMCZ77O9Lw9rhk7PAhUAmDcvTAst6Bq83qPq3r6c/Dm1nFc= ;{id = 2854}
+ENTRY_END
+
 ; response to DNSKEY priming query
 ENTRY_BEGIN
 MATCH opcode qtype qname
@@ -163,29 +174,11 @@ STEP 2 TIME_PASSES ELAPSE 0.05
 STEP 10 CHECK_ANSWER
 ENTRY_BEGIN
 MATCH all
-REPLY QR RD RA DO NXDOMAIN
+REPLY QR RD RA DO SERVFAIL
 SECTION QUESTION
 www.example.com. IN A
 SECTION ANSWER
 SECTION AUTHORITY
-example.com.   IN SOA  ns.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
-example.com.    3600    IN      RRSIG   SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFCNGZ+/OfElYQMCZ77O9Lw9rhk7PAhUAmDcvTAst6Bq83qPq3r6c/Dm1nFc= ;{id = 2854}
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com. NSEC3  1 1 123 aabb00123456bbccdd 6md8numosa4q9ugkffdo1bmm82t5j49s A RRSIG
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com. NSEC3 1 1 8 - 6md8numosa4q9ugkffdo1bmm82t5j49s SOA NS MX DNSKEY RRSIG
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MCwCFHndWrEEbuzezs/4lxeiMgEuUsUbAhR72gJgd/Zmhf80yoxCauw9k5OkCw== ;{id = 2854}
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com. NSEC3 1 1 18 - 4f3cnt8cu22tngec382jj4gde4rb87ub A RRSIG
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com. NSEC3 1 1 0 - 4f3cnt8cu22tngec382jj4gde4rb48ub A MX RRSIG
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com. NSEC3 1 1 19 - 4f3cnt8cu22tngec382jj4gde4rb87ub A RRSIG
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MCwCFDRwji51WCXJg7W/3+Jx586af5qgAhQPxHegtzu1I/QbvCNrOOON05N1rw== ;{id = 2854}
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 18 -  s1unhcti19bkdr98fegs0v46mbu3t4m4 A RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 19 -  s1unhcti19bkdr98fegs0v46mbu3t4m4 A RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 20 00  s1unhcti19bkdr98fegs0v46mbu3t4m4 A RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 123 aabb00123456bbccdd s1unhcti19bkdr98fegs0v46mbu3t4m4 A MX RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 20 01  s1unhcti19bkdr98fegs0v46mbu3t4m4 A RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 20 02  s1unhcti19bkdr98fegs0v46mbu3t4m4 A RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 20 03  s1unhcti19bkdr98fegs0v46mbu3t4m4 A RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MCwCFDLy4GbR8ZaKHATVJGnGxzpsuq60AhQ1/pRbXi1ZbcYohzHgWzNC50fC5A== ;{id = 2854}
-
 SECTION ADDITIONAL
 ENTRY_END
 
index dd3ab6b57dbbee8edf56c114f553b81d6bb7e8de..59bef2be3cf6be631d5db1450b37c4bdf9e00d5e 100644 (file)
@@ -88,6 +88,17 @@ ns.example.com.         IN      A       1.2.3.4
 ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
 ENTRY_END
 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.example.com. IN AAAA
+SECTION AUTHORITY
+example.com.   IN SOA  ns.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
+example.com.    3600    IN      RRSIG   SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFCNGZ+/OfElYQMCZ77O9Lw9rhk7PAhUAmDcvTAst6Bq83qPq3r6c/Dm1nFc= ;{id = 2854}
+ENTRY_END
+
 ; response to DNSKEY priming query
 ENTRY_BEGIN
 MATCH opcode qtype qname
@@ -144,20 +155,11 @@ ENTRY_END
 STEP 10 CHECK_ANSWER
 ENTRY_BEGIN
 MATCH all
-REPLY QR RD RA DO NXDOMAIN
+REPLY QR RD RA DO SERVFAIL
 SECTION QUESTION
 www.example.com. IN A
 SECTION ANSWER
 SECTION AUTHORITY
-example.com.   IN SOA  ns.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
-example.com.    3600    IN      RRSIG   SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFCNGZ+/OfElYQMCZ77O9Lw9rhk7PAhUAmDcvTAst6Bq83qPq3r6c/Dm1nFc= ;{id = 2854}
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com. NSEC3 1 1 8 - 6md8numosa4q9ugkffdo1bmm82t5j49s SOA NS MX DNSKEY RRSIG
-6md8numosa4q9ugkffdo1bmm82t5j39s.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCz/LkFOFcaQzVnyySW9ZoVUnxh7gIUdxyS9vqVDzo8pGhFU+3YogN2ZRk= ;{id = 2854}
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com. NSEC3 1 1 0 - 4f3cnt8cu22tngec382jj4gde4rb48ub A MX RRSIG
-4f3cnt8cu22tngec382jj4gde4rb46ub.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MCwCFHS+i/OB/V/gYmS1eQTXieXIXGjsAhQQ0Ql7TW/hsUklrb0DfoyhVPG95Q== ;{id = 2854}
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com. NSEC3 1 1 123 aabb00123456bbccdd s1unhcti19bkdr98fegs0v46mbu3t4m4 A MX RRSIG
-s1unhcti19bkdr98fegs0v46mbu3t4m2.example.com.   3600    IN      RRSIG   NSEC3 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFFSH4klZKke48dYyddYDj17gjTS0AhUAltWicpFLWqW98/Af9Qlx70MH8o4= ;{id = 2854}
-
 SECTION ADDITIONAL
 ENTRY_END
 
index 998fcc4e38ee1d22942215610122f82fbb4eb87b..92d853825ddabc1ca56139153fd88fac9a000c7c 100644 (file)
@@ -456,6 +456,67 @@ filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list,
        }
 }
 
+/** Check if the NSEC3s have the same parameter set. */
+static int
+param_set_same(struct nsec3_filter* flt, char** reason)
+{
+       size_t rrsetnum;
+       int rrnum;
+       struct ub_packed_rrset_key* rrset;
+       int have_params = 0;
+       int first_algo = 0;
+       size_t first_iter = 0;
+       uint8_t* first_salt = NULL;
+       size_t first_saltlen = 0;
+
+       /* If the NSEC3 parameter sets have distinct values, then they are
+        * from different NSEC3 chains, and we do not want that. */
+       for(rrset=filter_first(flt, &rrsetnum, &rrnum); rrset;
+               rrset=filter_next(flt, &rrsetnum, &rrnum)) {
+               if(!have_params) {
+                       first_algo = nsec3_get_algo(rrset, rrnum);
+                       first_iter = nsec3_get_iter(rrset, rrnum);
+                       if(!nsec3_get_salt(rrset, rrnum, &first_salt,
+                               &first_saltlen)) {
+                               verbose(VERB_ALGO, "NSEC3 salt malformed");
+                               if(reason)
+                                       *reason = "NSEC3 salt malformed";
+                               return 0;
+                       }
+                       have_params = 1;
+               } else {
+                       uint8_t* salt = NULL;
+                       size_t saltlen = 0;
+                       if(nsec3_get_algo(rrset, rrnum) != first_algo) {
+                               verbose(VERB_ALGO, "NSEC3 algorithm mismatch");
+                               if(reason)
+                                       *reason = "NSEC3 algorithm mismatch";
+                               return 0;
+                       }
+                       if(nsec3_get_iter(rrset, rrnum) != first_iter) {
+                               verbose(VERB_ALGO, "NSEC3 iterations mismatch");
+                               if(reason)
+                                       *reason = "NSEC3 iterations mismatch";
+                               return 0;
+                       }
+                       if(!nsec3_get_salt(rrset, rrnum, &salt, &saltlen)) {
+                               verbose(VERB_ALGO, "NSEC3 salt malformed");
+                               if(reason)
+                                       *reason = "NSEC3 salt malformed";
+                               return 0;
+                       }
+                       if(saltlen != first_saltlen ||
+                               memcmp(salt, first_salt, saltlen) != 0) {
+                               verbose(VERB_ALGO, "NSEC3 salt mismatch");
+                               if(reason)
+                                       *reason = "NSEC3 salt mismatch";
+                               return 0;
+                       }
+               }
+       }
+       return 1;
+}
+
 /**
  * Find max iteration count using config settings and key size
  * @param ve: validator environment with iteration count config settings.
@@ -1192,6 +1253,8 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
        filter_init(&flt, list, num, qinfo); /* init RR iterator */
        if(!flt.zone)
                return sec_status_bogus; /* no RRs */
+       if(!param_set_same(&flt, NULL))
+               return sec_status_bogus; /* nsec3 params from distinct chains*/
        if(nsec3_iteration_count_high(ve, &flt, kkey))
                return sec_status_insecure; /* iteration count too high */
        log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", 
@@ -1378,6 +1441,8 @@ nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
        filter_init(&flt, list, num, qinfo); /* init RR iterator */
        if(!flt.zone)
                return sec_status_bogus; /* no RRs */
+       if(!param_set_same(&flt, NULL))
+               return sec_status_bogus; /* nsec3 params from distinct chains*/
        if(nsec3_iteration_count_high(ve, &flt, kkey))
                return sec_status_insecure; /* iteration count too high */
        return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
@@ -1401,6 +1466,8 @@ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
        filter_init(&flt, list, num, qinfo); /* init RR iterator */
        if(!flt.zone)
                return sec_status_bogus; /* no RRs */
+       if(!param_set_same(&flt, NULL))
+               return sec_status_bogus; /* nsec3 params from distinct chains*/
        if(nsec3_iteration_count_high(ve, &flt, kkey))
                return sec_status_insecure; /* iteration count too high */
 
@@ -1503,6 +1570,8 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve,
                *reason = "no NSEC3 records";
                return sec_status_bogus; /* no RRs */
        }
+       if(!param_set_same(&flt, reason))
+               return sec_status_bogus; /* nsec3 params from distinct chains*/
        if(nsec3_iteration_count_high(ve, &flt, kkey))
                return sec_status_insecure; /* iteration count too high */
 
@@ -1596,6 +1665,8 @@ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
        filter_init(&flt, list, num, qinfo); /* init RR iterator */
        if(!flt.zone)
                return sec_status_bogus; /* no RRs */
+       if(!param_set_same(&flt, NULL))
+               return sec_status_bogus; /* nsec3 params from distinct chains*/
        if(nsec3_iteration_count_high(ve, &flt, kkey))
                return sec_status_insecure; /* iteration count too high */