mirror of
https://gitlab.com/buildroot.org/buildroot.git
synced 2026-09-10 16:24:17 -09:00
package/libldns: add patch for CVE-2026-10846
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@mind.be>
Signed-off-by: Raphaël Mélotte <raphael.melotte@mind.be>
This commit is contained in:
committed by
Raphaël Mélotte
parent
82eff78dea
commit
8e44aff085
225
package/libldns/0001-fix-cve-2026-10846.patch
Normal file
225
package/libldns/0001-fix-cve-2026-10846.patch
Normal file
@@ -0,0 +1,225 @@
|
||||
From f179ebe97b0c7309edb87a14d6f73119dbecb708 Mon Sep 17 00:00:00 2001
|
||||
From: Titouan Christophe <titouan.christophe@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@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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user