[Buildroot] [PATCH for 2025.02.x] package/libldns: add patch for CVE-2026-10846
Thomas Perale
thomas.perale at mind.be
Fri Sep 4 12:17:07 UTC 2026
In reply of:
> This fixes the following vulnerability:
> - CVE-2026-10846:
> NLnet Labs ldns 1.2.0 up to and including versions 1.9.0, when used in
> applications as (stub) resolver over UDP, lacks matching the query
> destination address and port with the response source address and
> port. Furthermore not the query ID, neither the question of the query
> is matched with that of the response. This makes applications, that
> use ldns for (stub) resolver functionality over UDP, vulnerable for
> off-path poisoning attacks. The drill tool, which is shipped with
> ldns, suffers from this vulnerability.
> https://www.cve.org/CVERecord?id=CVE-2026-10846
> https://www.nlnetlabs.nl/downloads/ldns/CVE-2026-10846.txt
>
> Signed-off-by: Titouan Christophe <titouan.christophe at mind.be>
Applied to 2025.02.x. Thanks
> ---
> package/libldns/0001-fix-cve-2026-10846.patch | 225 ++++++++++++++++++
> package/libldns/libldns.mk | 3 +
> 2 files changed, 228 insertions(+)
> create mode 100644 package/libldns/0001-fix-cve-2026-10846.patch
>
> diff --git a/package/libldns/0001-fix-cve-2026-10846.patch b/package/libldns/0001-fix-cve-2026-10846.patch
> new file mode 100644
> index 0000000000..989cd7c958
> --- /dev/null
> +++ b/package/libldns/0001-fix-cve-2026-10846.patch
> @@ -0,0 +1,225 @@
> +From f179ebe97b0c7309edb87a14d6f73119dbecb708 Mon Sep 17 00:00:00 2001
> +From: Titouan Christophe <titouan.christophe at mind.be>
> +Date: Tue, 1 Sep 2026 19:09:47 +0200
> +Subject: [PATCH] Fix CVE-2026-10846
> +
> +== Summary
> +When ldns is used by applications for (stub) resolving, it does not
> +sufficiently verify that received responses belong to a sent query.
> +
> +== Affected products
> +ldns 1.2.0 up to and including 1.9.0
> +
> +== Description
> +NLnet Labs ldns 1.2.0 up to and including versions 1.9.0, when used in
> +applications as (stub) resolver over UDP, lacks matching the query destination
> +address and port with the response source address and port. Furthermore not the
> +query ID, neither the question of the query is matched with that of the
> +response. This makes applications, that use ldns for (stub) resolver
> +functionality, vulnerable for off-path poisoning attacks.
> +
> +The drill tool, which is shipped with ldns and uses ldns for stub resolving,
> +inherently suffers from this vulnerability too.
> +
> +== Solution
> +Download a patched version of ldns, or apply the patch manually
> +
> +CVE: CVE-2026-10846
> +Upstream: https://nlnetlabs.nl/downloads/ldns/patch_cve_2026-10846.diff
> +Signed-off-by: Titouan Christophe <titouan.christophe at mind.be>
> +---
> + error.c | 8 +++++
> + ldns/error.h | 6 +++-
> + net.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++--
> + 3 files changed, 103 insertions(+), 3 deletions(-)
> +
> +diff --git a/error.c b/error.c
> +index 50cdc630..4fc05d6d 100644
> +--- a/error.c
> ++++ b/error.c
> +@@ -189,6 +189,14 @@ ldns_lookup_table ldns_error_str[] = {
> + { LDNS_STATUS_EDE_OPTION_MALFORMED,
> + "The extended error code option is malformed, expected "
> + "at least 2 bytes of option data" },
> ++ { LDNS_STATUS_EQUAL_RR,
> ++ "An identical RR already existed in the zone" },
> ++ { 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 011df284..41d64cc0 100644
> +--- a/ldns/error.h
> ++++ b/ldns/error.h
> +@@ -143,7 +143,11 @@ enum ldns_enum_status {
> + LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE,
> + LDNS_STATUS_INVALID_SVCPARAM_VALUE,
> + LDNS_STATUS_NOT_EDE,
> +- LDNS_STATUS_EDE_OPTION_MALFORMED
> ++ LDNS_STATUS_EDE_OPTION_MALFORMED,
> ++ LDNS_STATUS_EQUAL_RR,
> ++ 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 8fc9c8c4..100226d4 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,21 @@ 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);
> ++ memset(&reply_addr, 0, reply_addr_len);
> ++ 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) {
> ++ free(answer);
> ++ return LDNS_STATUS_NETWORK_ERR;
> ++ }
> +
> + *result = answer;
> + return LDNS_STATUS_OK;
> +@@ -512,6 +566,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);
> +@@ -599,6 +657,16 @@ 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) {
> ++ status = LDNS_STATUS_ID_DID_NOT_MATCH;
> ++ LDNS_FREE(reply_bytes);
> ++ reply_bytes = NULL;
> ++ reply_size = 0;
> ++ }
> ++ }
> +
> + /* obey the fail directive */
> + if (!reply_bytes) {
> +@@ -608,7 +676,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;
> +@@ -670,6 +738,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;
> + }
> +--
> +2.55.0
> +
> diff --git a/package/libldns/libldns.mk b/package/libldns/libldns.mk
> index 0740d09fab..81ac03b173 100644
> --- a/package/libldns/libldns.mk
> +++ b/package/libldns/libldns.mk
> @@ -23,6 +23,9 @@ LIBLDNS_CONF_OPTS = \
> --without-pyldns \
> --without-pyldnsx
>
> +# 0001-fix-cve-2026-10846.patch
> +LIBLDNS_IGNORE_CVES += CVE-2026-10846
> +
> ifeq ($(BR2_PACKAGE_LIBOPENSSL),y)
> LIBLDNS_CONF_OPTS += --enable-dane-verify
> else
> --
> 2.55.0
>
> _______________________________________________
> buildroot mailing list
> buildroot at buildroot.org
> https://lists.buildroot.org/mailman/listinfo/buildroot
More information about the buildroot
mailing list