Several vulnerabilities were found in NSD.
We released 4.15.1 as a security release on Wednesday 26 August including the relevant fixes.

The overview of the vulnerabilities with a brief description is:

CVE-2026-18664 - severity: HIGH
Wrong interpretation of ACL ranges

CVE-2026-18916 - severity: MEDIUM
Remote TCP DoS by throttling the TCP receive window

CVE-2026-19401 - severity: HIGH
Remote UDP DoS by sending multiple DNS Cookie options

CVE-2026-19538 - severity: HIGH
Bypass of BLOCKED ACL items on proxy protocol port over TCP or TLS

You can find detailed information on each vulnerability attached to this email along with their respective patches.

For ease of deployment we also provide a combined patch including all of them (patch_combined-4.15.1.diff).

The patches are tested to apply/work on 4.15.0.


Best regards,
-- Willem, on behalf of the NSD team.

The CVE number for this vulnerability is CVE-2026-18664

= Summary
IP range access control restrictions are bypassed for some unintended IP
addresses.


== Affected products
NSD from and including version 3.0.0 up to and including version 4.15.0


== Description
When ranges are used for access control (i.e. of the form 1.2.3.4-1.2.3.25),
because NSD wrongly compares the IP address with the range on little endian
systems, IPs that were meant to be allowed may be denied, and, IPs that were
meant to be denied access could be allowed.

An IPv4 address is compared with IPv4 ranges as unsigned 32 bit numbers
directly with the endianness of the host, but the values to compare are in
network byte order (big-endian).  With IPv6 addresses the comparison is done in
4 times a unsigned 32 bit number comparison, again with the endianness of the
host where all values are actually in network bye order.


== Mitigation

=== Downloading patched version
NSD 4.15.1 is released with the patch
https://nlnetlabs.nl/downloads/nsd/nsd-4.15.1.tar.gz

=== Applying the patch manually
For NSD 4.15.0 the patch is:
https://nlnetlabs.nl/downloads/nsd/patch_CVE-2026-18664.diff

Apply the patch on the nsd source directory with:
    patch -p1 < patch_CVE-2026-18664.diff
then run 'make install' to install nsd.

The patch is tested to work on nsd 4.15.0.


== Acknowledgments
We would like to thank Qifan Zhang from Palo Alto Networks for discovering and
responsibly disclosing the vulnerability.
diff --git a/options.c b/options.c
index ccfa13eb..486b61cf 100644
--- a/options.c
+++ b/options.c
@@ -2221,9 +2221,9 @@ acl_addr_match_range_v4(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t
 	/* check treats x as one huge number */
 
 	/* if outside bounds, we are done */
-	if(*minval > *x)
+	if(ntohl(*minval) > ntohl(*x))
 		return 0;
-	if(*maxval < *x)
+	if(ntohl(*maxval) < ntohl(*x))
 		return 0;
 
 	return 1;
@@ -2244,10 +2244,10 @@ acl_addr_match_range_v6(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t
 	{
 		/* if outside bounds, we are done */
 		if(checkmin)
-			if(minval[i] > x[i])
+			if(ntohl(minval[i]) > ntohl(x[i]))
 				return 0;
 		if(checkmax)
-			if(maxval[i] < x[i])
+			if(ntohl(maxval[i]) < ntohl(x[i]))
 				return 0;
 		/* if x is equal to a bound, that bound needs further checks */
 		if(checkmin && minval[i]!=x[i])
-- 
2.43.0

The CVE number for this vulnerability is CVE-2026-18916

= Summary
Any remote client can denial TCP service by throttling the TCP receive window 
(down to 1).


== Affected products
NSD from and including version 3.2.11 up to and including version 4.15.0


== Description
Any remote client can crash a NSD serve child, by throttling the TCP receive
window after a TCP query. By continuously crashing the serve childs, the remote
client can denial all TCP service to this NSD instance.

== Mitigation

=== Downloading patched version
NSD 4.15.1 is released with the patch
https://nlnetlabs.nl/downloads/nsd/nsd-4.15.1.tar.gz

=== Applying the patch manually
For NSD 4.15.0 the patch is:
https://nlnetlabs.nl/downloads/nsd/patch_CVE-2026-18916.diff

Apply the patch on the nsd source directory with:
    patch -p1 < patch_CVE-2026-18916.diff
then run 'make install' to install nsd.

The patch is tested to work on nsd 4.15.0.


== Acknowledgments
We would like to thank Akhil Koul (https://github.com/akoul) for discovering
and responsibly disclosing the vulnerability.
diff --git a/server.c b/server.c
index ab18f473..822c2751 100644
--- a/server.c
+++ b/server.c
@@ -4850,7 +4850,8 @@ handle_tcp_writing(int fd, short event, void* arg)
 		}
 
 #ifdef HAVE_WRITEV
-		sent -= sizeof(n_tcplen);
+		/* The number of bytes transmitted for the message content. */
+		sent = data->bytes_transmitted - sizeof(n_tcplen);
 		/* handle potential 'packet done' code */
 		goto packet_could_be_done;
 #endif
diff --git a/xfrd-tcp.c b/xfrd-tcp.c
index acea5e2d..5f3abb51 100644
--- a/xfrd-tcp.c
+++ b/xfrd-tcp.c
@@ -1252,8 +1252,8 @@ int conn_write(struct xfrd_tcp* tcp)
 		}
 
 		tcp->total_bytes += sent;
-		if(sent > (ssize_t)sizeof(tcp->msglen))
-			buffer_skip(tcp->packet, sent-sizeof(tcp->msglen));
+		if(tcp->total_bytes > (ssize_t)sizeof(tcp->msglen))
+			buffer_skip(tcp->packet, tcp->total_bytes-sizeof(tcp->msglen));
 		if(tcp->total_bytes < sizeof(tcp->msglen)) {
 			/* incomplete write, resume later */
 			return 0;
-- 
2.54.0

The CVE number for this vulnerability is CVE-2026-19401

= Summary
Any remote client can denial UDP service by sending a specifically crafted
query with multiple DNS Cookie options.


== Affected products
NSD from and including version 4.3.7 up to and including version 4.15.0


== Description
Any remote client can crash a (debugging/non-release build type) NSD serve
child by sending it a special crafted message with a specially tuned number of
DNS Cookie options (17 when UDP payload size is 512). By continuously crashing
the serve childs, the remote client can severely hamper or, when positioned
sufficiently close, deny all DNS service.

== Mitigation

=== Downloading patched version
NSD 4.15.1 is released with the patch
https://nlnetlabs.nl/downloads/nsd/nsd-4.15.1.tar.gz

=== Applying the patch manually
For NSD 4.15.0 the patch is:
https://nlnetlabs.nl/downloads/nsd/patch_CVE-2026-19401.diff

Apply the patch on the nsd source directory with:
    patch -p1 < patch_CVE-2026-19401.diff
then run 'make install' to install nsd.

The patch is tested to work on nsd 4.15.0.


== Acknowledgments
We would like to thank Qifan Zhang from Palo Alto Networks for discovering and
responsibly disclosing the vulnerability.

diff --git a/edns.c b/edns.c
index d77a146e..7a186fde 100644
--- a/edns.c
+++ b/edns.c
@@ -72,5 +72,6 @@ edns_init_record(edns_record_type *edns)
 	edns->nsid = 0;
 	edns->zoneversion = 0;
+	edns->cookie_seen = 0;
 	edns->cookie_status = COOKIE_NOT_PRESENT;
 	edns->cookie_len = 0;
 	edns->ede = -1; /* -1 means no Extended DNS Error */
@@ -89,7 +90,7 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet,
 	switch(optcode) {
 	case NSID_CODE:
 		/* is NSID enabled? */
-		if(nsd->nsid_len > 0) {
+		if(nsd->nsid_len > 0 && !edns->nsid) {
 			edns->nsid = 1;
 			/* we have to check optlen, and move the buffer along */
 			buffer_skip(packet, optlen);
@@ -102,7 +103,8 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet,
 		break;
 	case COOKIE_CODE:
 		/* Cookies enabled? */
-		if(nsd->do_answer_cookie) {
+		if(nsd->do_answer_cookie && !edns->cookie_seen) {
+			edns->cookie_seen = 1;
 			if (optlen == 8) 
 				edns->cookie_status = COOKIE_INVALID;
 			else if (optlen < 16 || optlen > 40)
diff --git a/edns.h b/edns.h
--- a/edns.h
+++ b/edns.h
@@ -69,5 +69,6 @@ struct edns_record
 	int                nsid;
 	int                zoneversion;
+	int		   cookie_seen;
 	cookie_status_type cookie_status;
 	size_t             cookie_len;
 	uint8_t            cookie[40];
diff --git a/query.c b/query.c
index 5e5c9361..03cc0bf1 100644
--- a/query.c
+++ b/query.c
@@ -1783,6 +1783,16 @@ query_process(query_type *q, nsd_type *nsd, uint32_t *now_p)
 		cookie_verify(q, nsd, now_p);
 
 	query_prepare_response(q);
+	if(q->reserved_space + QHEADERSZ + (size_t)q->qname->name_size +
+		2 /* qtype */ + 2 /* qclass */ > q->maxlen) {
+		/* Clear out some space, and return error, it does not fit. */
+		q->edns.status = EDNS_NOT_PRESENT;
+		q->tsig.status = TSIG_NOT_PRESENT;
+		if(q->tcp)
+			return query_error(q, NSD_RC_SERVFAIL);
+		TC_SET(q->packet);
+		return query_error(q, NSD_RC_OK);
+	}
 
 	if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) {
 		if (q->qclass == CLASS_CH) {
-- 
2.54.0

The CVE number for this vulnerability is CVE-2026-19538

= Summary
Anyone with access to the proxy protocol port over TCP or TLS can bypass
BLOCKED access control items.


== Affected products
NSD from and including version 4.8.0 up to and including version 4.15.0


== Description
The BLOCKED access control list items that are evaluated to deny access on the
the proxy protocol port can be bypassed completely when connecting over TCP or
TLS and sending the query twice on connection that is kept open.

With access to the proxy protocol port, the adversary can then spoof any IP and
potentially bypass all IP based access control (for queries, transfers and
notifies).

== Mitigation

=== Downloading patched version
NSD 4.15.1 is released with the patch
https://nlnetlabs.nl/downloads/nsd/nsd-4.15.1.tar.gz

=== Applying the patch manually
For NSD 4.15.0 the patch is:
https://nlnetlabs.nl/downloads/nsd/patch_CVE-2026-19538.diff

Apply the patch on the nsd source directory with:
    patch -p1 < patch_CVE-2026-19538.diff
then run 'make install' to install nsd.

The patch is tested to work on nsd 4.15.0.


== Acknowledgments
We would like to thank Qifan Zhang from Palo Alto Networks for discovering and
responsibly disclosing the vulnerability.

diff --git a/query.c b/query.c
index 5e5c9361..9bf6d153 100644
--- a/query.c
+++ b/query.c
@@ -249,7 +249,8 @@ query_reset(query_type *q, size_t maxlen, int is_tcp)
 	region_free_all(q->region);
 	q->remote_addrlen = (socklen_t)sizeof(q->remote_addr);
 	q->client_addrlen = (socklen_t)sizeof(q->client_addr);
-	q->is_proxied = 0;
+	if(!is_tcp)
+		q->is_proxied = 0;
 	q->maxlen = maxlen;
 	q->reserved_space = 0;
 	buffer_clear(q->packet);
-- 
2.54.0

diff --git a/edns.c b/edns.c
index 035ce4f4..e9e322d8 100644
--- a/edns.c
+++ b/edns.c
@@ -71,6 +71,7 @@ edns_init_record(edns_record_type *edns)
 	edns->dnssec_ok = 0;
 	edns->nsid = 0;
 	edns->zoneversion = 0;
+	edns->cookie_seen = 0;
 	edns->cookie_status = COOKIE_NOT_PRESENT;
 	edns->cookie_len = 0;
 	edns->ede = -1; /* -1 means no Extended DNS Error */
@@ -88,7 +89,7 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet,
 	switch(optcode) {
 	case NSID_CODE:
 		/* is NSID enabled? */
-		if(nsd->nsid_len > 0) {
+		if(nsd->nsid_len > 0 && !edns->nsid) {
 			edns->nsid = 1;
 			/* we have to check optlen, and move the buffer along */
 			buffer_skip(packet, optlen);
@@ -101,7 +102,8 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet,
 		break;
 	case COOKIE_CODE:
 		/* Cookies enabled? */
-		if(nsd->do_answer_cookie) {
+		if(nsd->do_answer_cookie && !edns->cookie_seen) {
+			edns->cookie_seen = 1;
 			if (optlen == 8) 
 				edns->cookie_status = COOKIE_INVALID;
 			else if (optlen < 16 || optlen > 40)
diff --git a/edns.h b/edns.h
index 3e1f5e79..1b182e1b 100644
--- a/edns.h
+++ b/edns.h
@@ -64,6 +64,7 @@ struct edns_record
 	int                dnssec_ok;
 	int                nsid;
 	int                zoneversion;
+	int		   cookie_seen;
 	cookie_status_type cookie_status;
 	size_t             cookie_len;
 	uint8_t            cookie[40];
diff --git a/options.c b/options.c
index ccfa13eb..486b61cf 100644
--- a/options.c
+++ b/options.c
@@ -2221,9 +2221,9 @@ acl_addr_match_range_v4(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t
 	/* check treats x as one huge number */
 
 	/* if outside bounds, we are done */
-	if(*minval > *x)
+	if(ntohl(*minval) > ntohl(*x))
 		return 0;
-	if(*maxval < *x)
+	if(ntohl(*maxval) < ntohl(*x))
 		return 0;
 
 	return 1;
@@ -2244,10 +2244,10 @@ acl_addr_match_range_v6(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t
 	{
 		/* if outside bounds, we are done */
 		if(checkmin)
-			if(minval[i] > x[i])
+			if(ntohl(minval[i]) > ntohl(x[i]))
 				return 0;
 		if(checkmax)
-			if(maxval[i] < x[i])
+			if(ntohl(maxval[i]) < ntohl(x[i]))
 				return 0;
 		/* if x is equal to a bound, that bound needs further checks */
 		if(checkmin && minval[i]!=x[i])
diff --git a/query.c b/query.c
index 9954c5f0..fd90a3a8 100644
--- a/query.c
+++ b/query.c
@@ -249,7 +249,8 @@ query_reset(query_type *q, size_t maxlen, int is_tcp)
 	region_free_all(q->region);
 	q->remote_addrlen = (socklen_t)sizeof(q->remote_addr);
 	q->client_addrlen = (socklen_t)sizeof(q->client_addr);
-	q->is_proxied = 0;
+	if(!is_tcp)
+		q->is_proxied = 0;
 	q->maxlen = maxlen;
 	q->reserved_space = 0;
 	buffer_clear(q->packet);
@@ -1776,6 +1777,16 @@ query_process(query_type *q, nsd_type *nsd, uint32_t *now_p)
 		cookie_verify(q, nsd, now_p);
 
 	query_prepare_response(q);
+	if(q->reserved_space + QHEADERSZ + (size_t)q->qname->name_size +
+		2 /* qtype */ + 2 /* qclass */ > q->maxlen) {
+		/* Clear out some space, and return error, it does not fit. */
+		q->edns.status = EDNS_NOT_PRESENT;
+		q->tsig.status = TSIG_NOT_PRESENT;
+		if(q->tcp)
+			return query_error(q, NSD_RC_SERVFAIL);
+		TC_SET(q->packet);
+		return query_error(q, NSD_RC_OK);
+	}
 
 	if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) {
 		if (q->qclass == CLASS_CH) {
diff --git a/server.c b/server.c
index ccf6e2dc..ea9a858a 100644
--- a/server.c
+++ b/server.c
@@ -4813,7 +4813,8 @@ handle_tcp_writing(int fd, short event, void* arg)
 		}
 
 #ifdef HAVE_WRITEV
-		sent -= sizeof(n_tcplen);
+		/* The number of bytes transmitted for the message content. */
+		sent = data->bytes_transmitted - sizeof(n_tcplen);
 		/* handle potential 'packet done' code */
 		goto packet_could_be_done;
 #endif
diff --git a/xfrd-tcp.c b/xfrd-tcp.c
index acea5e2d..5f3abb51 100644
--- a/xfrd-tcp.c
+++ b/xfrd-tcp.c
@@ -1252,8 +1252,8 @@ int conn_write(struct xfrd_tcp* tcp)
 		}
 
 		tcp->total_bytes += sent;
-		if(sent > (ssize_t)sizeof(tcp->msglen))
-			buffer_skip(tcp->packet, sent-sizeof(tcp->msglen));
+		if(tcp->total_bytes > (ssize_t)sizeof(tcp->msglen))
+			buffer_skip(tcp->packet, tcp->total_bytes-sizeof(tcp->msglen));
 		if(tcp->total_bytes < sizeof(tcp->msglen)) {
 			/* incomplete write, resume later */
 			return 0;

Attachment: OpenPGP_0xE5F8F8212F77A498.asc
Description: OpenPGP public key

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to