The branch main has been updated by markj:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=980ba4177b69655726485daa5ff3e931f19aa738

commit 980ba4177b69655726485daa5ff3e931f19aa738
Author:     Gordon Tetlow <[email protected]>
AuthorDate: 2026-06-07 15:09:39 +0000
Commit:     Mark Johnston <[email protected]>
CommitDate: 2026-06-09 19:13:21 +0000

    ldns: Fix query response validation
    
    Approved by:    so
    Security:       FreeBSD-SA-26:36.ldns
    Security:       CVE-2026-10846
---
 contrib/ldns/error.c      |  6 ++++
 contrib/ldns/ldns/error.h |  5 ++-
 contrib/ldns/net.c        | 92 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 100 insertions(+), 3 deletions(-)

diff --git a/contrib/ldns/error.c b/contrib/ldns/error.c
index 5723aea9b4c2..4fc05d6d0d8f 100644
--- a/contrib/ldns/error.c
+++ b/contrib/ldns/error.c
@@ -191,6 +191,12 @@ ldns_lookup_table ldns_error_str[] = {
                "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/contrib/ldns/ldns/error.h b/contrib/ldns/ldns/error.h
index a76eb2ecab5d..41d64cc0815f 100644
--- a/contrib/ldns/ldns/error.h
+++ b/contrib/ldns/ldns/error.h
@@ -144,7 +144,10 @@ enum ldns_enum_status {
        LDNS_STATUS_INVALID_SVCPARAM_VALUE,
        LDNS_STATUS_NOT_EDE,
        LDNS_STATUS_EDE_OPTION_MALFORMED,
-       LDNS_STATUS_EQUAL_RR
+       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/contrib/ldns/net.c b/contrib/ldns/net.c
index e944d018b357..4c1f405419fb 100644
--- a/contrib/ldns/net.c
+++ b/contrib/ldns/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;
        }

Reply via email to