From nobody Tue Jun 9 19:17:37 2026 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gZdw21vVlz6gTxm for ; Tue, 09 Jun 2026 19:17:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gZdw14qTDz3Lh1 for ; Tue, 09 Jun 2026 19:17:37 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1781032657; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=TY+HykSouYlZggLD78WfhyYm5RKqNHCrpj1Mee6u+g8=; b=xV8mbXm+30A3t1r2VSy51rLgLl2yJGuCp0KM3CkDVem1WemAdOMW4/EDe5RY4StmLJ8CIH 5nHgFRpwUaY3r2X3t0BJQ9nGPsp1nms8cQY5AoHemwWgs5XfNucs9xo5Zudyqt/mTVulkq m/frUpp4zLs1hhDF2I1YTTC5zRhbLhl+dSt18heZ5cE82fR12dIzbLNRINDicrJFMpv3Tq +8VjFXoXj8mwY4k/74Yhm6d7i8+Emo2fen/H9pfoRqlbN6TvAmsnYxnu3oQBbQeJDic4LV 25fxm/IsjWoSkqFXadVjrqEv36nBedDRlWuALxjkl2fLFK9EdImXaRfV9/EQFg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1781032657; a=rsa-sha256; cv=none; b=iymL6GP+oCqiX9ZLowf33JafOhaxHlfNwOcNx39S/BkhHujoSJi9VdoCr+y76ikdgqHVHf /pXIGmwQGqIYN1WWFtKBzBH783j54YE2GB4yVSVrBfnXlrj/Hr44TYA5WraxfQArFiy/8w qpmzJt8DO0Ghg3II9NXGGdKC9nd2kGPjD0CdMrj/aLdgIHbcdtD+sulste4n86JQ2PQgw4 dTyVedDu1G08AXN0jmtWoiTiT0iMvhsUleswQ7LgYCZFXqmQXpYl1skva7Nx6F0KwgCE/m 02h8eZvzNAW7ZSg1BLmLsDl1tGzLqFQUrRogpnfm2nTkA6cwzGraMTMhBgHbog== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1781032657; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=TY+HykSouYlZggLD78WfhyYm5RKqNHCrpj1Mee6u+g8=; b=Y5wPK+8aQIHwWWnBhS5XX5hyjUxrYWXJ/mO0GRXxMo2G+hOAlN8I85heQQ0sUgRnwS5Vlt SHnG0bmXFgi8zdMZXsSeg9dZ5wGbPiClqDB+MvQA12QCxcnEcS9QIPe3/qtnULacbavZgb UDY+o4qkduGGSkpBMt9zhNM/qwFmrSyWE06SegS7IgB9kuoKPypQCVCK8NIJlFU8ql5zqw NE6oeSmMB0ME1g/eS3fewEKGanLDhhUav3ofw/JtgetrdcmDKMrzThzshjUd5sWkouUMxd gcq/x3hq+g7Bd5ykJDQryV+LpEmjJP2Pwub7mdZcFhNCzELxurAd2b5czAg6/A== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gZdw14MDKznt6 for ; Tue, 09 Jun 2026 19:17:37 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3e788 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 09 Jun 2026 19:17:37 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org Cc: Gordon Tetlow From: Mark Johnston Subject: git: 20bfab98f8ae - stable/15 - ldns: Fix query response validation List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/stable/15 X-Git-Reftype: branch X-Git-Commit: 20bfab98f8ae58261bd180bdc49a17eff1b08eb9 Auto-Submitted: auto-generated Date: Tue, 09 Jun 2026 19:17:37 +0000 Message-Id: <6a2866d1.3e788.4633d0c6@gitrepo.freebsd.org> The branch stable/15 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=20bfab98f8ae58261bd180bdc49a17eff1b08eb9 commit 20bfab98f8ae58261bd180bdc49a17eff1b08eb9 Author: Gordon Tetlow AuthorDate: 2026-06-07 15:09:39 +0000 Commit: Mark Johnston CommitDate: 2026-06-09 19:15:25 +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; }