VYPR
High severity7.5NVD Advisory· Published May 20, 2026· Updated May 20, 2026

CVE-2026-42944

CVE-2026-42944

Description

NLnet Labs Unbound 1.14.0 up to and including version 1.25.0 has a vulnerability that results in heap overflow when encoding multiple NSID and/or DNS Cookie EDNS and/or EDNS Padding options in the reply packet. The relevant options ('nsid', 'answer-cookie', 'pad-responses' (default)) need to be enabled for the vulnerability to be exploited. An adversary who can query Unbound can exploit the vulnerability by attaching multiple NSID and/or DNS Cookie EDNS and/or EDNS Padding options to the query. A flaw in the size calculation of the EDNS field truncates the correct value which allows the encoder to overflow the available space when writing. Those two combined lead to a heap overflow write of Unbound controlled data and eventually a crash. Unbound 1.25.1 contains a patch with a fix to de-duplicate the EDNS options and a fix to prevent truncation of the EDNS field size calculation.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Heap overflow in Unbound when encoding multiple NSID/DNS Cookie/EDNS Padding options; fixed in 1.25.1.

Vulnerability

In NLnet Labs Unbound versions 1.14.0 through 1.25.0, a heap overflow vulnerability exists when encoding multiple NSID, DNS Cookie, or EDNS Padding options in a reply packet [1]. The relevant options nsid, answer-cookie, and pad-responses (the latter enabled by default) must be enabled for the bug to be reachable [1]. A flaw in the size calculation of the EDNS field truncates the correct value, allowing the encoder to overflow the allocated buffer [1].

Exploitation

An attacker who can send queries to a vulnerable Unbound instance can exploit this by attaching multiple NSID, DNS Cookie, or EDNS Padding options to a query [1]. The flawed size calculation combined with the lack of de-duplication of EDNS options leads to a heap overflow write of attacker-controlled data [1].

Impact

Successful exploitation results in a heap overflow write of controlled data, leading to a crash of the Unbound process [1]. The attacker may potentially cause denial of service.

Mitigation

The vulnerability is fixed in Unbound 1.25.1, which includes a patch that de-duplicates EDNS options and fixes the size calculation truncation [1]. Patches are available for Unbound 1.25.0 via two diffs: patch_CVE-2026-42944_with.diff and patch_CVE-2026-42944.diff [1]. Users should upgrade to 1.25.1 or apply the patch.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Nlnetlabs/Unboundinferred2 versions
    >=1.14.0,<=1.25.0+ 1 more
    • (no CPE)range: >=1.14.0,<=1.25.0
    • (no CPE)range: >=1.14.0 <=1.25.0

Patches

1
fe946ba4e935

- Fix CVE-2026-42944, Heap overflow and crash with multiple nsid,

https://github.com/NLnetLabs/unboundW.C.A. WijngaardsMay 20, 2026Fixed in release-1.25.1via llm-release-walk
5 files changed · +37 20
  • doc/Changelog+3 0 modified
    @@ -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
    
  • testcode/unitmain.c+2 2 modified
    @@ -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);
    
  • util/data/msgencode.c+23 13 modified
    @@ -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);
    
  • util/data/msgencode.h+2 2 modified
    @@ -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
    
  • util/data/msgparse.c+7 3 modified
    @@ -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");
    

Vulnerability mechanics

Root cause

"A numeric truncation error (uint16_t instead of size_t) in calc_edns_field_size causes the EDNS field size to be under-calculated when multiple NSID, DNS Cookie, or Padding EDNS options are present, leading to a heap buffer overflow during encoding."

Attack vector

An attacker who can send queries to an Unbound resolver can exploit this vulnerability by including multiple NSID, DNS Cookie, and/or EDNS Padding options in a single query. The server must have the relevant options enabled ('nsid', 'answer-cookie', or 'pad-responses' (the default)). The attacker's multiple duplicate EDNS options are not de-duplicated during parsing [patch_id=792204, msgparse.c], so they accumulate in the option list. When the reply is encoded, the size calculation in calc_edns_field_size uses a uint16_t return type that truncates the true size [CWE-197]. The encoder then writes past the allocated buffer space, causing a heap overflow [CWE-787] with attacker-influenced data, leading to a crash.

Affected code

The vulnerability spans two files. In util/data/msgencode.c, the function calc_edns_field_size returns uint16_t, truncating the true EDNS field size; the same file's attach_edns_record_max_msg_sz lacks bounds checks before writing each EDNS option. In util/data/msgparse.c, the function parse_edns_options_from_query does not de-duplicate NSID, Cookie, or Padding options, allowing multiple copies to be added to the option list. The header util/data/msgencode.h also declares calc_edns_field_size with the too-small uint16_t return type.

What the fix does

The patch fixes two root causes. First, in util/data/msgparse.c, the functions that parse NSID, DNS Cookie, and Padding options from the query now track whether each option type has already been seen (nsid_seen, cookie_seen, padding_seen) and skip duplicates, preventing the accumulation of multiple identical options. Second, in util/data/msgencode.c and msgencode.h, the return type of calc_edns_field_size and the type of related variables (edns_field_size, ede_size, ede_txt_size) are changed from uint16_t to size_t, eliminating the numeric truncation [CWE-197]. Additionally, bounds checks are added in attach_edns_record_max_msg_sz to verify that there is enough space before writing each option, and the padding block is only written if space remains.

Preconditions

  • configThe Unbound server must have at least one of 'nsid', 'answer-cookie', or 'pad-responses' (default is enabled) configured.
  • networkThe attacker must be able to send DNS queries to the vulnerable Unbound resolver.
  • inputThe attacker's query must include multiple NSID, DNS Cookie, and/or EDNS Padding options.

Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

1

News mentions

0

No linked articles in our index yet.