ldns: fix CVE-2026-10846

This patch backports the upstream fixes released in ldns 1.9.2 for
CVE-2026-10846.The upstream commits are referenced in [1], [2], and
[3], and the public CVE advisory is referenced in [4].The individual
backported commit links are also recorded in the embedded patch headers.

[1] https://github.com/NLnetLabs/ldns/commit/a21fb16686bbe3355886905f95e13eab5144d805
[2] https://github.com/NLnetLabs/ldns/commit/9ea51a68d458b43a17ccf4ee98a71325300df524
[3] https://github.com/NLnetLabs/ldns/commit/dc117528dfc60b2dda82d9171b7e9e0b6890da2f
[4] https://www.nlnetlabs.nl/downloads/ldns/CVE-2026-10846.txt

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Deepak Rathore
2026-07-17 11:28:41 +05:30
committed by Anuj Mittal
parent 2b04d2036c
commit 8b23408f0d
4 changed files with 273 additions and 1 deletions
@@ -0,0 +1,117 @@
From 844fb7e197b646a11dc058c5c7ce0877371bd4d4 Mon Sep 17 00:00:00 2001
From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
Date: Fri, 29 May 2026 09:44:27 +0200
Subject: [PATCH] Fix to check address and port and TXID
CVE: CVE-2026-10846
Upstream-Status: Backport [https://github.com/NLnetLabs/ldns/commit/a21fb16686bbe3355886905f95e13eab5144d805]
(cherry picked from commit a21fb16686bbe3355886905f95e13eab5144d805)
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
net.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 64 insertions(+), 1 deletion(-)
diff --git a/net.c b/net.c
index 57d4dff2..03911637 100644
--- a/net.c
+++ b/net.c
@@ -441,6 +441,50 @@ ldns_udp_bgsend2(ldns_buffer *qbin,
return ldns_udp_bgsend_from(qbin, to, tolen, NULL, 0, timeout);
}
+/** helper sockaddr compare function. returns -1, 0 or 1. */
+static int
+ldns_sockaddr_cmp(const struct sockaddr_storage* addr1, socklen_t len1,
+ const struct sockaddr_storage* addr2, socklen_t len2)
+{
+ struct sockaddr_in* p1_in = (struct sockaddr_in*)addr1;
+ struct sockaddr_in* p2_in = (struct sockaddr_in*)addr2;
+ struct sockaddr_in6* p1_in6 = (struct sockaddr_in6*)addr1;
+ struct sockaddr_in6* p2_in6 = (struct sockaddr_in6*)addr2;
+ if(len1 < len2)
+ return -1;
+ if(len1 > len2)
+ return 1;
+ assert(len1 == len2);
+ if( p1_in->sin_family < p2_in->sin_family)
+ return -1;
+ if( p1_in->sin_family > p2_in->sin_family)
+ return 1;
+ assert( p1_in->sin_family == p2_in->sin_family );
+ /* compare ip4 */
+ if( p1_in->sin_family == AF_INET ) {
+ /* just order it, ntohs not required */
+ if(p1_in->sin_port < p2_in->sin_port)
+ return -1;
+ if(p1_in->sin_port > p2_in->sin_port)
+ return 1;
+ assert(p1_in->sin_port == p2_in->sin_port);
+ return memcmp(&p1_in->sin_addr, &p2_in->sin_addr,
+ sizeof(p1_in->sin_addr));
+ } else if (p1_in6->sin6_family == AF_INET6) {
+ /* just order it, ntohs not required */
+ if(p1_in6->sin6_port < p2_in6->sin6_port)
+ return -1;
+ if(p1_in6->sin6_port > p2_in6->sin6_port)
+ return 1;
+ assert(p1_in6->sin6_port == p2_in6->sin6_port);
+ return memcmp(&p1_in6->sin6_addr, &p2_in6->sin6_addr,
+ sizeof(p1_in6->sin6_addr));
+ } else {
+ /* eek unknown type, perform this comparison for sanity. */
+ return memcmp(addr1, addr2, len1);
+ }
+}
+
static ldns_status
ldns_udp_send_from(uint8_t **result, ldns_buffer *qbin,
const struct sockaddr_storage *to , socklen_t tolen,
@@ -449,6 +493,8 @@ ldns_udp_send_from(uint8_t **result, ldns_buffer *qbin,
{
int sockfd;
uint8_t *answer;
+ struct sockaddr_storage reply_addr;
+ socklen_t reply_addr_len;
sockfd = ldns_udp_bgsend_from(qbin, to, tolen, from, fromlen, timeout);
@@ -467,13 +513,19 @@ ldns_udp_send_from(uint8_t **result, ldns_buffer *qbin,
* but returns a 'NETWORK_ERROR' much like a timeout. */
ldns_sock_nonblock(sockfd);
- answer = ldns_udp_read_wire(sockfd, answer_size, NULL, NULL);
+ reply_addr_len = sizeof(reply_addr);
+ answer = ldns_udp_read_wire(sockfd, answer_size, &reply_addr,
+ &reply_addr_len);
close_socket(sockfd);
if (!answer) {
/* oops */
return LDNS_STATUS_NETWORK_ERR;
}
+ /* Check that the reply came from the to addr. */
+ if(ldns_sockaddr_cmp(to, tolen, &reply_addr, reply_addr_len) != 0) {
+ return LDNS_STATUS_NETWORK_ERR;
+ }
*result = answer;
return LDNS_STATUS_OK;
@@ -599,6 +651,17 @@ ldns_send_buffer(ldns_pkt **result, ldns_resolver *r, ldns_buffer *qb, ldns_rdf
ldns_resolver_set_nameserver_rtt(r, i, LDNS_RESOLV_RTT_INF);
status = send_status;
}
+ if(reply_bytes && ldns_buffer_limit(qb) >= 2) {
+ uint16_t txid = ldns_buffer_read_u16_at(qb, 0);
+ if(reply_size < 2 ||
+ ldns_read_uint16(reply_bytes) != txid) {
+ printf("wrong TXID!\n");
+ status = LDNS_STATUS_ERR; /* wrong ID */
+ LDNS_FREE(reply_bytes);
+ reply_bytes = NULL;
+ reply_size = 0;
+ }
+ }
/* obey the fail directive */
if (!reply_bytes) {
@@ -0,0 +1,117 @@
From 0e291b494fc5e4d3800b27d7016fc92c7aac269e Mon Sep 17 00:00:00 2001
From: Willem Toorop <willem@nlnetlabs.nl>
Date: Tue, 2 Jun 2026 12:19:38 +0200
Subject: [PATCH] Match question from query in response, and...
... error codes for unmatched ID and for QDCOUNT != 1 in both queries
and responses
CVE: CVE-2026-10846
Upstream-Status: Backport [https://github.com/NLnetLabs/ldns/commit/9ea51a68d458b43a17ccf4ee98a71325300df524]
Backport Changes:
- Omitted upstream test commit 7f6364cd7f595de766a43401bbecf935e042faa5
because the ldns recipe does not run the upstream test framework.
(cherry picked from commit 9ea51a68d458b43a17ccf4ee98a71325300df524)
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
error.c | 6 ++++++
ldns/error.h | 5 ++++-
net.c | 29 ++++++++++++++++++++++++++---
3 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/error.c b/error.c
index e3fd1211..f046c962 100644
--- a/error.c
+++ b/error.c
@@ -184,6 +184,12 @@ ldns_lookup_table ldns_error_str[] = {
{ LDNS_STATUS_INVALID_SVCPARAM_VALUE,
"Invalid wireformat of a value "
"in the ServiceParam rdata field of SVCB or HTTPS RR" },
+ { LDNS_STATUS_ID_DID_NOT_MATCH,
+ "Response ID did not match the query ID" },
+ { LDNS_STATUS_QDCOUNT_MUST_BE_ONE,
+ "The query section MUST contain exactly one question" },
+ { LDNS_STATUS_QUERY_DID_NOT_MATCH,
+ "The question in the response did not match the query" },
{ 0, NULL }
};
diff --git a/ldns/error.h b/ldns/error.h
index 2429b770..5754f5a0 100644
--- a/ldns/error.h
+++ b/ldns/error.h
@@ -141,7 +141,10 @@ enum ldns_enum_status {
LDNS_STATUS_RESERVED_SVCPARAM_KEY,
LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED,
LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE,
- LDNS_STATUS_INVALID_SVCPARAM_VALUE
+ LDNS_STATUS_INVALID_SVCPARAM_VALUE,
+ LDNS_STATUS_ID_DID_NOT_MATCH,
+ LDNS_STATUS_QDCOUNT_MUST_BE_ONE,
+ LDNS_STATUS_QUERY_DID_NOT_MATCH
};
typedef enum ldns_enum_status ldns_status;
diff --git a/net.c b/net.c
index 03911637..ab7cf028 100644
--- a/net.c
+++ b/net.c
@@ -564,6 +564,10 @@ ldns_send_buffer(ldns_pkt **result, ldns_resolver *r, ldns_buffer *qb, ldns_rdf
assert(r != NULL);
+ /* The query should at least have one question */
+ if(ldns_buffer_limit(qb) < 6 || ldns_buffer_read_u16_at(qb, 4) != 1)
+ return LDNS_STATUS_QDCOUNT_MUST_BE_ONE;
+
status = LDNS_STATUS_OK;
rtt = ldns_resolver_rtt(r);
ns_array = ldns_resolver_nameservers(r);
@@ -655,8 +659,7 @@ ldns_send_buffer(ldns_pkt **result, ldns_resolver *r, ldns_buffer *qb, ldns_rdf
uint16_t txid = ldns_buffer_read_u16_at(qb, 0);
if(reply_size < 2 ||
ldns_read_uint16(reply_bytes) != txid) {
- printf("wrong TXID!\n");
- status = LDNS_STATUS_ERR; /* wrong ID */
+ status = LDNS_STATUS_ID_DID_NOT_MATCH;
LDNS_FREE(reply_bytes);
reply_bytes = NULL;
reply_size = 0;
@@ -671,7 +674,7 @@ ldns_send_buffer(ldns_pkt **result, ldns_resolver *r, ldns_buffer *qb, ldns_rdf
LDNS_FREE(src);
}
LDNS_FREE(ns);
- return LDNS_STATUS_ERR;
+ return status ? status : LDNS_STATUS_ERR;
} else {
LDNS_FREE(ns);
continue;
@@ -733,6 +736,26 @@ ldns_send_buffer(ldns_pkt **result, ldns_resolver *r, ldns_buffer *qb, ldns_rdf
#endif /* HAVE_SSL */
LDNS_FREE(reply_bytes);
+ if (reply) {
+ ldns_pkt *query = NULL;
+
+ if(ldns_pkt_qdcount(reply) != 1) {
+ status = LDNS_STATUS_QDCOUNT_MUST_BE_ONE;
+ ldns_pkt_free(reply);
+ reply = NULL;
+
+ } else if(ldns_wire2pkt(&query
+ , ldns_buffer_begin(qb)
+ , ldns_buffer_position(qb)) != LDNS_STATUS_OK
+ || ldns_pkt_qdcount(query) != 1
+ || ldns_rr_compare(ldns_rr_list_rr(ldns_pkt_question(query),0)
+ ,ldns_rr_list_rr(ldns_pkt_question(reply),0))){
+ status = LDNS_STATUS_QUERY_DID_NOT_MATCH;
+ ldns_pkt_free(reply);
+ reply = NULL;
+ }
+ ldns_pkt_free(query);
+ }
if (result) {
*result = reply;
}
@@ -0,0 +1,34 @@
From b2cae7389bc43613fdd21bed1f9d41b3870c5545 Mon Sep 17 00:00:00 2001
From: Willem Toorop <willem@nlnetlabs.nl>
Date: Tue, 2 Jun 2026 12:42:38 +0200
Subject: [PATCH] Issues from clang analysis
CVE: CVE-2026-10846
Upstream-Status: Backport [https://github.com/NLnetLabs/ldns/commit/dc117528dfc60b2dda82d9171b7e9e0b6890da2f]
(cherry picked from commit dc117528dfc60b2dda82d9171b7e9e0b6890da2f)
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
net.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net.c b/net.c
index ab7cf028..215c0cac 100644
--- a/net.c
+++ b/net.c
@@ -514,6 +514,7 @@ ldns_udp_send_from(uint8_t **result, ldns_buffer *qbin,
ldns_sock_nonblock(sockfd);
reply_addr_len = sizeof(reply_addr);
+ memset(&reply_addr, 0, reply_addr_len);
answer = ldns_udp_read_wire(sockfd, answer_size, &reply_addr,
&reply_addr_len);
close_socket(sockfd);
@@ -524,6 +525,7 @@ ldns_udp_send_from(uint8_t **result, ldns_buffer *qbin,
}
/* Check that the reply came from the to addr. */
if(ldns_sockaddr_cmp(to, tolen, &reply_addr, reply_addr_len) != 0) {
+ free(answer);
return LDNS_STATUS_NETWORK_ERR;
}
+5 -1
View File
@@ -3,7 +3,11 @@ HOMEPAGE = "https://nlnetlabs.nl/ldns"
LICENSE = "BSD-3-Clause"
LIC_FILES_CHKSUM = "file://LICENSE;md5=34330f15b2b4abbbaaa7623f79a6a019"
SRC_URI = "https://www.nlnetlabs.nl/downloads/ldns/ldns-${PV}.tar.gz"
SRC_URI = "https://www.nlnetlabs.nl/downloads/ldns/ldns-${PV}.tar.gz \
file://CVE-2026-10846_p1.patch \
file://CVE-2026-10846_p2.patch \
file://CVE-2026-10846_p3.patch \
"
SRC_URI[sha256sum] = "c3f72dd1036b2907e3a56e6acf9dfb2e551256b3c1bbd9787942deeeb70e7860"
DEPENDS = "openssl"