Fix CVE-2026-10846: validate DNS response in ldns
Backport CVE-2026-10846 fix from upstream ldns 1.9.0 to 1.7.0. The patch adds DNS response validation including source address verification, transaction ID matching, query section count validation, and response question matching against the original query. New error status codes are added for specific failure modes. CVE: CVE-2026-10846 Upstream patches: - https://src.fedoraproject.org/rpms/ldns/raw/rawhide/f/ldns-1.9.0-CVE-2026-10846.patch Resolves: RHEL-210700 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir
This commit is contained in:
parent
da4629d247
commit
1a0ca6d073
202
ldns-1.7.0-CVE-2026-10846.patch
Normal file
202
ldns-1.7.0-CVE-2026-10846.patch
Normal file
@ -0,0 +1,202 @@
|
||||
From cc51382f8bf8bb5675e93ed16e816ccdfb1581ea Mon Sep 17 00:00:00 2001
|
||||
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
|
||||
Date: Wed, 15 Jul 2026 09:22:45 +0000
|
||||
Subject: [PATCH] Fix CVE-2026-10846: validate DNS response source address and
|
||||
query matching
|
||||
|
||||
Adapted from upstream patch for ldns 1.9.0. Adds:
|
||||
- ldns_sockaddr_cmp() to verify response comes from queried address
|
||||
- Transaction ID matching check
|
||||
- Query section count validation
|
||||
- Response question matching against original query
|
||||
- New error status codes for ID mismatch, qdcount, and query mismatch
|
||||
---
|
||||
ldns-1.7.0_python3/error.c | 6 +++
|
||||
ldns-1.7.0_python3/ldns/error.h | 5 +-
|
||||
ldns-1.7.0_python3/net.c | 92 ++++++++++++++++++++++++++++++++-
|
||||
3 files changed, 100 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ldns-1.7.0_python3/error.c b/ldns-1.7.0_python3/error.c
|
||||
index 35ee5bd..a3103f8 100644
|
||||
--- a/ldns-1.7.0_python3/error.c
|
||||
+++ b/ldns-1.7.0_python3/error.c
|
||||
@@ -157,6 +157,12 @@ ldns_lookup_table ldns_error_str[] = {
|
||||
"X509_STORE_CTX_set0_dane() functions within OpenSSL >= 1.1.0 "
|
||||
"to be able to verify the DANE-TA usage type." },
|
||||
#endif
|
||||
+ { 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-1.7.0_python3/ldns/error.h b/ldns-1.7.0_python3/ldns/error.h
|
||||
index 15f49a2..936df38 100644
|
||||
--- a/ldns-1.7.0_python3/ldns/error.h
|
||||
+++ b/ldns-1.7.0_python3/ldns/error.h
|
||||
@@ -129,7 +129,10 @@ enum ldns_enum_status {
|
||||
LDNS_STATUS_RDATA_OVERFLOW,
|
||||
LDNS_STATUS_SYNTAX_SUPERFLUOUS_TEXT_ERR,
|
||||
LDNS_STATUS_NSEC3_DOMAINNAME_OVERFLOW,
|
||||
- LDNS_STATUS_DANE_NEED_OPENSSL_GE_1_1_FOR_DANE_TA
|
||||
+ LDNS_STATUS_DANE_NEED_OPENSSL_GE_1_1_FOR_DANE_TA,
|
||||
+ 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/ldns-1.7.0_python3/net.c b/ldns-1.7.0_python3/net.c
|
||||
index 6e6a12b..53b4df5 100644
|
||||
--- a/ldns-1.7.0_python3/net.c
|
||||
+++ b/ldns-1.7.0_python3/net.c
|
||||
@@ -410,6 +410,50 @@ ldns_udp_bgsend(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,
|
||||
@@ -418,6 +462,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);
|
||||
|
||||
@@ -436,13 +482,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;
|
||||
@@ -481,6 +535,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);
|
||||
@@ -568,6 +626,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) {
|
||||
@@ -577,7 +645,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;
|
||||
@@ -637,6 +705,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;
|
||||
}
|
||||
10
ldns.spec
10
ldns.spec
@ -39,7 +39,7 @@
|
||||
Summary: Low-level DNS(SEC) library with API
|
||||
Name: ldns
|
||||
Version: 1.7.0
|
||||
Release: 22%{?dist}
|
||||
Release: 23%{?dist}
|
||||
|
||||
License: BSD
|
||||
Url: http://www.nlnetlabs.nl/%{name}/
|
||||
@ -48,6 +48,8 @@ Patch1: ldns-1.7.0-multilib.patch
|
||||
Patch2: ldns-1.7.0-parse-limit.patch
|
||||
Patch3: ldns-1.7.0-realloc.patch
|
||||
Patch4: ldns-1.7.0-coverity.patch
|
||||
# https://src.fedoraproject.org/rpms/ldns/raw/rawhide/f/ldns-1.9.0-CVE-2026-10846.patch
|
||||
Patch5: ldns-1.7.0-CVE-2026-10846.patch
|
||||
|
||||
Group: System Environment/Libraries
|
||||
# Only needed for builds from svn snapshot
|
||||
@ -160,6 +162,7 @@ pushd %{pkgname}
|
||||
%patch2 -p1 -b .limit
|
||||
%patch3 -p1 -b .realloc
|
||||
%patch4 -p1 -b .covscan
|
||||
%patch5 -p2 -b .cve-2026-10846
|
||||
# To built svn snapshots
|
||||
%if 0%{snapshot}
|
||||
rm config.guess config.sub ltmain.sh
|
||||
@ -355,6 +358,11 @@ rm -rf doc/man
|
||||
%doc doc
|
||||
|
||||
%changelog
|
||||
* Wed Jul 15 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 1.7.0-23
|
||||
- Fix CVE-2026-10846: validate DNS response source address, transaction
|
||||
ID, and query section matching
|
||||
- Resolves: RHEL-210700
|
||||
|
||||
* Tue Jan 16 2024 Petr Menšík <pemensik@redhat.com> - 1.7.0-22
|
||||
- Export ldns-utils, ldns-doc, perl-ldns and python3-ldns into CRB (RHEL-315)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user