]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2026-42944, Heap overflow and crash with multiple nsid,
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 20 May 2026 08:13:55 +0000 (10:13 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 20 May 2026 08:13:55 +0000 (10:13 +0200)
  cookie, padding EDNS options. Thanks to Qifan Zhang, Palo Alto
  Networks, for the report.

doc/Changelog
testcode/unitmain.c
util/data/msgencode.c
util/data/msgencode.h
util/data/msgparse.c

index 5bbe849bc4469e0af4a513ef135175ab8b6b00fb..e003ed3fe784042cb3be9cfa9b1f672d9e3ab646 100644 (file)
@@ -1,6 +1,9 @@
 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.
+       - Fix CVE-2026-42944, Heap overflow and crash with multiple nsid,
+         cookie, padding EDNS options. Thanks to Qifan Zhang, Palo Alto
+         Networks, for the report.
 
 23 April 2026: Wouter
        - Merge #1441: Fix buffer overrun in
index 79ce45f395598da3a0f3f7c683919b77ba8f20e7..4bc756a0705fbb6c6759b05b0c427639ac2f81ea 100644 (file)
@@ -1092,7 +1092,7 @@ static void edns_ede_encode_notxt_fit_test( struct query_info* qinfo,
 {
        struct edns_data edns;
        sldns_buffer* pkt;
-       uint16_t edns_field_size, ede_txt_size;
+       size_t edns_field_size, ede_txt_size;
        int found_ede = 0, found_ede_other = 0, found_ede_txt = 0;
        int found_other_edns = 0;
        edns_ede_encode_setup(&edns, region);
@@ -1123,7 +1123,7 @@ static void edns_ede_encode_no_fit_test( struct query_info* qinfo,
 {
        struct edns_data edns;
        sldns_buffer* pkt;
-       uint16_t edns_field_size, ede_size, ede_txt_size;
+       size_t edns_field_size, ede_size, ede_txt_size;
        int found_ede = 0, found_ede_other = 0, found_ede_txt = 0;
        int found_other_edns = 0;
        edns_ede_encode_setup(&edns, region);
index 8f46395194a6cad57a66b407da1b3707c86b9ed0..10979df9c7e5f71a159b9de428631cc4fdcb8346 100644 (file)
@@ -820,7 +820,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
        return 1;
 }
 
-uint16_t
+size_t
 calc_edns_field_size(struct edns_data* edns)
 {
        size_t rdatalen = 0;
@@ -856,7 +856,7 @@ calc_edns_option_size(struct edns_data* edns, uint16_t code)
 }
 
 uint16_t
-calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size)
+calc_ede_option_size(struct edns_data* edns, size_t* txt_size)
 {
        size_t rdatalen = 0;
        struct edns_option* opt;
@@ -958,6 +958,10 @@ attach_edns_record_max_msg_sz(sldns_buffer* pkt, struct edns_data* edns,
                        padding_option = opt;
                        continue;
                }
+               if(sldns_buffer_position(pkt) + opt->opt_len + 4 > max_msg_sz)
+                       break; /* no space for it */
+               if(!sldns_buffer_available(pkt, 4 + opt->opt_len))
+                       break;
                sldns_buffer_write_u16(pkt, opt->opt_code);
                sldns_buffer_write_u16(pkt, opt->opt_len);
                if(opt->opt_len != 0)
@@ -968,12 +972,18 @@ attach_edns_record_max_msg_sz(sldns_buffer* pkt, struct edns_data* edns,
                        padding_option = opt;
                        continue;
                }
+               if(sldns_buffer_position(pkt) + opt->opt_len + 4 > max_msg_sz)
+                       break; /* no space for it */
+               if(!sldns_buffer_available(pkt, 4 + opt->opt_len))
+                       break;
                sldns_buffer_write_u16(pkt, opt->opt_code);
                sldns_buffer_write_u16(pkt, opt->opt_len);
                if(opt->opt_len != 0)
                        sldns_buffer_write(pkt, opt->opt_data, opt->opt_len);
        }
-       if (padding_option && edns->padding_block_size ) {
+       if (padding_option && edns->padding_block_size &&
+               sldns_buffer_position(pkt)+4 <= max_msg_sz &&
+               sldns_buffer_available(pkt, 4) /* if there is space for it */) {
                size_t pad_pos = sldns_buffer_position(pkt);
                size_t msg_sz = ((pad_pos + 3) / edns->padding_block_size + 1)
                                               * edns->padding_block_size;
@@ -1017,7 +1027,7 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep,
 {
        uint16_t flags;
        unsigned int attach_edns = 0;
-       uint16_t edns_field_size, ede_size, ede_txt_size;
+       size_t edns_field_size, ede_size, ede_txt_size;
 
        if(!cached || rep->authoritative) {
                /* original flags, copy RD and CD bits from query. */
@@ -1044,12 +1054,12 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep,
         * calculate sizes once here */
        edns_field_size = calc_edns_field_size(edns);
        ede_size = calc_ede_option_size(edns, &ede_txt_size);
-       if(sldns_buffer_capacity(pkt) < udpsize)
+       if(sldns_buffer_capacity(pkt) < (size_t)udpsize)
                udpsize = sldns_buffer_capacity(pkt);
        if(!edns || !edns->edns_present) {
                attach_edns = 0;
        /* EDEs are optional, try to fit anything else before them */
-       } else if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) {
+       } else if((size_t)udpsize < (size_t)LDNS_HEADER_SIZE + edns_field_size - ede_size) {
                /* packet too small to contain edns, omit it. */
                attach_edns = 0;
        } else {
@@ -1063,13 +1073,13 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep,
                return 0;
        }
        if(attach_edns) {
-               if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size)
+               if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size)
                        attach_edns_record_max_msg_sz(pkt, edns, udpsize);
-               else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) {
+               else if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) {
                        ede_trim_text(&edns->opt_list_inplace_cb_out);
                        ede_trim_text(&edns->opt_list_out);
                        attach_edns_record_max_msg_sz(pkt, edns, udpsize);
-               } else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) {
+               } else if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) {
                        edns_opt_list_remove(&edns->opt_list_inplace_cb_out, LDNS_EDNS_EDE);
                        edns_opt_list_remove(&edns->opt_list_out, LDNS_EDNS_EDE);
                        attach_edns_record_max_msg_sz(pkt, edns, udpsize);
@@ -1132,7 +1142,7 @@ extended_error_encode(sldns_buffer* buf, uint16_t rcode,
        }
        sldns_buffer_flip(buf);
        if(edns && edns->edns_present) {
-               uint16_t edns_field_size, ede_size, ede_txt_size;
+               size_t edns_field_size, ede_size, ede_txt_size;
                struct edns_data es = *edns;
                es.edns_version = EDNS_ADVERTISED_VERSION;
                es.udp_size = EDNS_ADVERTISED_SIZE;
@@ -1144,13 +1154,13 @@ extended_error_encode(sldns_buffer* buf, uint16_t rcode,
                 * to see if EDNS can fit. */
                edns_field_size = calc_edns_field_size(&es);
                ede_size = calc_ede_option_size(&es, &ede_txt_size);
-               if(edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size)
+               if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size)
                        attach_edns_record_max_msg_sz(buf, &es, edns->udp_size);
-               else if(edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_txt_size) {
+               else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_txt_size) {
                        ede_trim_text(&es.opt_list_inplace_cb_out);
                        ede_trim_text(&es.opt_list_out);
                        attach_edns_record_max_msg_sz(buf, &es, edns->udp_size);
-               } else if(edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_size) {
+               } else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_size) {
                        edns_opt_list_remove(&es.opt_list_inplace_cb_out, LDNS_EDNS_EDE);
                        edns_opt_list_remove(&es.opt_list_out, LDNS_EDNS_EDE);
                        attach_edns_record_max_msg_sz(buf, &es, edns->udp_size);
index 0363a90bf06f47c36a82326df6bfc67e7927f6d6..f561fe1dd063e6f76d8863119f431db93c6e545d 100644 (file)
@@ -106,7 +106,7 @@ void qinfo_query_encode(struct sldns_buffer* pkt, struct query_info* qinfo);
  * @param edns: edns data or NULL.
  * @return octets to reserve for EDNS.
  */
-uint16_t calc_edns_field_size(struct edns_data* edns);
+size_t calc_edns_field_size(struct edns_data* edns);
 
 /**
  * Calculate the size of a specific EDNS option in packet.
@@ -127,7 +127,7 @@ uint16_t calc_edns_option_size(struct edns_data* edns, uint16_t code);
  *     extra text.
  * @return octets the option will take up.
  */
-uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size);
+uint16_t calc_ede_option_size(struct edns_data* edns, size_t* txt_size);
 
 /**
  * Attach EDNS record to buffer. Buffer has complete packet. There must
index afbcbca5bc9d75c15533bd7e9c92959498b90e9d..a38bed62ea27ce3712281693ff20418c71ea59ad 100644 (file)
@@ -950,6 +950,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
        struct comm_reply* repinfo, uint32_t now, struct regional* region,
        struct cookie_secrets* cookie_secrets)
 {
+       int nsid_seen = 0, cookie_seen = 0, padding_seen = 0;
        /* To respond with a Keepalive option, the client connection must have
         * received one message with a TCP Keepalive EDNS option, and that
         * option must have 0 length data. Subsequent messages sent on that
@@ -984,8 +985,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
                /* handle parse time edns options here */
                switch(opt_code) {
                case LDNS_EDNS_NSID:
-                       if (!cfg || !cfg->nsid)
+                       if (!cfg || !cfg->nsid || nsid_seen)
                                break;
+                       nsid_seen = 1;
                        if(!edns_opt_list_append(&edns->opt_list_out,
                                                LDNS_EDNS_NSID, cfg->nsid_len,
                                                cfg->nsid, region)) {
@@ -1027,8 +1029,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
 
                case LDNS_EDNS_PADDING:
                        if(!cfg || !cfg->pad_responses ||
-                                       !c || c->type != comm_tcp ||!c->ssl)
+                                       !c || c->type != comm_tcp ||!c->ssl || padding_seen)
                                break;
+                       padding_seen = 1;
                        if(!edns_opt_list_append(&edns->opt_list_out,
                                                LDNS_EDNS_PADDING,
                                                0, NULL, region)) {
@@ -1039,8 +1042,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
                        break;
 
                case LDNS_EDNS_COOKIE:
-                       if(!cfg || !cfg->do_answer_cookie || !repinfo)
+                       if(!cfg || !cfg->do_answer_cookie || !repinfo || cookie_seen)
                                break;
+                       cookie_seen = 1;
                        if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
                                verbose(VERB_ALGO, "worker request: "
                                        "badly formatted cookie");