diff --git a/SOURCES/bind-9.18-CVE-2025-40778.patch b/SOURCES/bind-9.18-CVE-2025-40778.patch new file mode 100644 index 0000000..a546728 --- /dev/null +++ b/SOURCES/bind-9.18-CVE-2025-40778.patch @@ -0,0 +1,781 @@ +From 69783c72b5db5f96518839508829e20b75b96f86 Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Thu, 10 Jul 2025 09:37:36 +1000 +Subject: [PATCH] Tighten restrictions on caching NS RRsets in authority + section + +To prevent certain spoofing attacks, a new check has been added +to the existing rules for whether NS data can be cached: the owner +name of the NS RRset must be an ancestor of the name being queried. + +(cherry picked from commit fa153f791f9324bf84abf8d259e11c0531fe6e25) +(cherry picked from commit 025d61bacd0f57f994a631654aff7a933d89a547) + +Further restrict addresses that are cached when processing referrals + +Use the owner name of the NS record as the bailwick apex name +when determining which additional records to cache, rather than +the name of the delegating zone (or a parent thereof). + +(cherry picked from commit a41054e9e606a61f1b3c8bc0c54e2f1059347165) +(cherry picked from commit cd17dfe696cdf9b8ef23fbc8738de7c79f957846) + +Retry lookups with unsigned DNAME over TCP + +To prevent spoofed unsigned DNAME responses being accepted retry +response with unsigned DNAMEs over TCP if the response is not TSIG +signed or there isn't a good DNS CLIENT COOKIE. + +To prevent test failures, this required adding TCP support to the +ans3 and ans4 servers in the chain system test. + +(cherry picked from commit 2e40705c06831988106335ed77db3cf924d431f6) +(cherry picked from commit 4c6d03b0bb2ffbafcde8e8a5bc0e49908b978a72) +--- + bin/tests/system/chain/ans3/ans.pl | 143 ------------------- + bin/tests/system/chain/ans3/ans.py | 217 +++++++++++++++++++++++++++++ + bin/tests/system/chain/ans4/ans.py | 57 ++++++-- + lib/dns/include/dns/message.h | 8 ++ + lib/dns/message.c | 12 ++ + lib/dns/resolver.c | 110 ++++++++++++--- + 6 files changed, 374 insertions(+), 173 deletions(-) + delete mode 100644 bin/tests/system/chain/ans3/ans.pl + create mode 100644 bin/tests/system/chain/ans3/ans.py + +diff --git a/bin/tests/system/chain/ans3/ans.pl b/bin/tests/system/chain/ans3/ans.pl +deleted file mode 100644 +index e42240b..0000000 +--- a/bin/tests/system/chain/ans3/ans.pl ++++ /dev/null +@@ -1,143 +0,0 @@ +-#!/usr/bin/env perl +- +-# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +-# +-# SPDX-License-Identifier: MPL-2.0 +-# +-# This Source Code Form is subject to the terms of the Mozilla Public +-# License, v. 2.0. If a copy of the MPL was not distributed with this +-# file, you can obtain one at https://mozilla.org/MPL/2.0/. +-# +-# See the COPYRIGHT file distributed with this work for additional +-# information regarding copyright ownership. +- +-use strict; +-use warnings; +- +-use IO::File; +-use Getopt::Long; +-use Net::DNS::Nameserver; +- +-my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; +-print $pidf "$$\n" or die "cannot write pid file: $!"; +-$pidf->close or die "cannot close pid file: $!"; +-sub rmpid { unlink "ans.pid"; exit 1; }; +-sub term { }; +- +-$SIG{INT} = \&rmpid; +-if ($Net::DNS::VERSION > 1.41) { +- $SIG{TERM} = \&term; +-} else { +- $SIG{TERM} = \&rmpid; +-} +- +-my $localaddr = "10.53.0.3"; +- +-my $localport = int($ENV{'PORT'}); +-if (!$localport) { $localport = 5300; } +- +-my $verbose = 0; +-my $ttl = 60; +-my $zone = "example.broken"; +-my $nsname = "ns3.$zone"; +-my $synth = "synth-then-dname.$zone"; +-my $synth2 = "synth2-then-dname.$zone"; +- +-sub reply_handler { +- my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_; +- my ($rcode, @ans, @auth, @add); +- +- print ("request: $qname/$qtype\n"); +- STDOUT->flush(); +- +- if ($qname eq "example.broken") { +- if ($qtype eq "SOA") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass SOA . . 0 0 0 0 0"); +- push @ans, $rr; +- } elsif ($qtype eq "NS") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass NS $nsname"); +- push @ans, $rr; +- $rr = new Net::DNS::RR("$nsname $ttl $qclass A $localaddr"); +- push @add, $rr; +- } +- $rcode = "NOERROR"; +- } elsif ($qname eq "cname-to-$synth2") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name.$synth2"); +- push @ans, $rr; +- $rr = new Net::DNS::RR("name.$synth2 $ttl $qclass CNAME name"); +- push @ans, $rr; +- $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME ."); +- push @ans, $rr; +- $rcode = "NOERROR"; +- } elsif ($qname eq "$synth" || $qname eq "$synth2") { +- if ($qtype eq "DNAME") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME ."); +- push @ans, $rr; +- } +- $rcode = "NOERROR"; +- } elsif ($qname eq "name.$synth") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name."); +- push @ans, $rr; +- $rr = new Net::DNS::RR("$synth $ttl $qclass DNAME ."); +- push @ans, $rr; +- $rcode = "NOERROR"; +- } elsif ($qname eq "name.$synth2") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name."); +- push @ans, $rr; +- $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME ."); +- push @ans, $rr; +- $rcode = "NOERROR"; +- # The following three code branches referring to the "example.dname" +- # zone are necessary for the resolver variant of the CVE-2021-25215 +- # regression test to work. A named instance cannot be used for +- # serving the DNAME records below as a version of BIND vulnerable to +- # CVE-2021-25215 would crash while answering the queries asked by +- # the tested resolver. +- } elsif ($qname eq "ns3.example.dname") { +- if ($qtype eq "A") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass A 10.53.0.3"); +- push @ans, $rr; +- } +- if ($qtype eq "AAAA") { +- my $rr = new Net::DNS::RR("example.dname. $ttl $qclass SOA . . 0 0 0 0 $ttl"); +- push @auth, $rr; +- } +- $rcode = "NOERROR"; +- } elsif ($qname eq "self.example.self.example.dname") { +- my $rr = new Net::DNS::RR("self.example.dname. $ttl $qclass DNAME dname."); +- push @ans, $rr; +- $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME self.example.dname."); +- push @ans, $rr; +- $rcode = "NOERROR"; +- } elsif ($qname eq "self.example.dname") { +- if ($qtype eq "DNAME") { +- my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME dname."); +- push @ans, $rr; +- } +- $rcode = "NOERROR"; +- } else { +- $rcode = "REFUSED"; +- } +- return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); +-} +- +-GetOptions( +- 'port=i' => \$localport, +- 'verbose!' => \$verbose, +-); +- +-my $ns = Net::DNS::Nameserver->new( +- LocalAddr => $localaddr, +- LocalPort => $localport, +- ReplyHandler => \&reply_handler, +- Verbose => $verbose, +-); +- +-if ($Net::DNS::VERSION >= 1.42) { +- $ns->start_server(); +- select(undef, undef, undef, undef); +- $ns->stop_server(); +- unlink "ans.pid"; +-} else { +- $ns->main_loop; +-} +diff --git a/bin/tests/system/chain/ans3/ans.py b/bin/tests/system/chain/ans3/ans.py +new file mode 100644 +index 0000000..0a031c1 +--- /dev/null ++++ b/bin/tests/system/chain/ans3/ans.py +@@ -0,0 +1,217 @@ ++# Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++# ++# SPDX-License-Identifier: MPL-2.0 ++# ++# This Source Code Form is subject to the terms of the Mozilla Public ++# License, v. 2.0. If a copy of the MPL was not distributed with this ++# file, you can obtain one at https://mozilla.org/MPL/2.0/. ++# ++# See the COPYRIGHT file distributed with this work for additional ++# information regarding copyright ownership. ++ ++############################################################################ ++# ans.py: See README.anspy for details. ++############################################################################ ++ ++from __future__ import print_function ++import os ++import sys ++import signal ++import socket ++import select ++from datetime import datetime, timedelta ++import functools ++ ++import dns, dns.message, dns.query ++from dns.rdatatype import * ++from dns.rdataclass import * ++from dns.rcode import * ++from dns.name import * ++ ++ ++############################################################################ ++# Respond to a DNS query. ++############################################################################ ++def create_response(msg): ++ ttl = 60 ++ zone = "example.broken." ++ nsname = f"ns3.{zone}" ++ synth = f"synth-then-dname.{zone}" ++ synth2 = f"synth2-then-dname.{zone}" ++ ++ m = dns.message.from_wire(msg) ++ qname = m.question[0].name.to_text() ++ ++ # prepare the response and convert to wire format ++ r = dns.message.make_response(m) ++ ++ # get qtype ++ rrtype = m.question[0].rdtype ++ qtype = dns.rdatatype.to_text(rrtype) ++ print(f"request: {qname}/{qtype}") ++ ++ rcode = "NOERROR" ++ if qname == zone: ++ if qtype == "SOA": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, SOA, ". . 0 0 0 0 0")) ++ elif qtype == "NS": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, NS, nsname)) ++ r.additional.append(dns.rrset.from_text(nsname, ttl, IN, A, ip4)) ++ elif qname == f"cname-to-{synth2}": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, f"name.{synth2}")) ++ r.answer.append(dns.rrset.from_text(f"name.{synth2}", ttl, IN, CNAME, "name.")) ++ r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, ".")) ++ elif qname == f"{synth}" or qname == f"{synth2}": ++ if qtype == "DNAME": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, ".")) ++ elif qname == f"name.{synth}": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name.")) ++ r.answer.append(dns.rrset.from_text(synth, ttl, IN, DNAME, ".")) ++ elif qname == f"name.{synth2}": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name.")) ++ r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, ".")) ++ elif qname == "ns3.example.dname.": ++ # This and the next two code branches referring to the "example.dname" ++ # zone are necessary for the resolver variant of the CVE-2021-25215 ++ # regression test to work. A named instance cannot be used for ++ # serving the DNAME records below as a version of BIND vulnerable to ++ # CVE-2021-25215 would crash while answering the queries asked by ++ # the tested resolver. ++ if qtype == "A": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, A, ip4)) ++ elif qtype == "AAAA": ++ r.authority.append( ++ dns.rrset.from_text("example.dname.", ttl, IN, SOA, ". . 0 0 0 0 0") ++ ) ++ elif qname == "self.example.self..example.dname.": ++ r.answer.append( ++ dns.rrset.from_text("self.example.dname.", ttl, IN, DNAME, "dname.") ++ ) ++ r.answer.append( ++ dns.rrset.from_text(qname, ttl, IN, CNAME, "self.example.dname.") ++ ) ++ elif qname == "self.example.dname.": ++ if qtype == "DNAME": ++ r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, "dname.")) ++ else: ++ rcode = "REFUSED" ++ ++ r.flags |= dns.flags.AA ++ r.use_edns() ++ return r.to_wire() ++ ++ ++def sigterm(signum, frame): ++ print("Shutting down now...") ++ os.remove("ans.pid") ++ running = False ++ sys.exit(0) ++ ++ ++############################################################################ ++# Main ++# ++# Set up responder and control channel, open the pid file, and start ++# the main loop, listening for queries on the query channel or commands ++# on the control channel and acting on them. ++############################################################################ ++ip4 = "10.53.0.3" ++ip6 = "fd92:7065:b8e:ffff::3" ++ ++try: ++ port = int(os.environ["PORT"]) ++except: ++ port = 5300 ++ ++query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ++query4_udp.bind((ip4, port)) ++ ++query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ++query4_tcp.bind((ip4, port)) ++query4_tcp.listen(1) ++query4_tcp.settimeout(1) ++ ++havev6 = True ++try: ++ query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) ++ try: ++ query6_udp.bind((ip6, port)) ++ except: ++ query6_udp.close() ++ havev6 = False ++ ++ query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ++ try: ++ query6_tcp.bind((ip4, port)) ++ query6_tcp.listen(1) ++ query6_tcp.settimeout(1) ++ except: ++ query6_tcp.close() ++ havev6 = False ++except: ++ havev6 = False ++ ++signal.signal(signal.SIGTERM, sigterm) ++ ++f = open("ans.pid", "w") ++pid = os.getpid() ++print(pid, file=f) ++f.close() ++ ++running = True ++ ++print("Listening on %s port %d" % (ip4, port)) ++if havev6: ++ print("Listening on %s port %d" % (ip6, port)) ++print("Ctrl-c to quit") ++ ++if havev6: ++ input = [query4_udp, query4_tcp, query6_udp, query6_tcp] ++else: ++ input = [query4_udp, query4_tcp] ++ ++while running: ++ try: ++ inputready, outputready, exceptready = select.select(input, [], []) ++ except select.error as e: ++ break ++ except socket.error as e: ++ break ++ except KeyboardInterrupt: ++ break ++ ++ for s in inputready: ++ if s == query4_udp or s == query6_udp: ++ print("Query received on %s" % (ip4 if s == query4_udp else ip6)) ++ # Handle incoming queries ++ msg = s.recvfrom(65535) ++ rsp = create_response(msg[0]) ++ if rsp: ++ s.sendto(rsp, msg[1]) ++ elif s == query4_tcp or s == query6_tcp: ++ try: ++ conn, _ = s.accept() ++ if s == query4_tcp or s == query6_tcp: ++ print( ++ "TCP Query received on %s" % (ip4 if s == query4_tcp else ip6), ++ end=" ", ++ ) ++ # get TCP message length ++ msg = conn.recv(2) ++ if len(msg) != 2: ++ print("couldn't read TCP message length") ++ continue ++ length = struct.unpack(">H", msg[:2])[0] ++ msg = conn.recv(length) ++ if len(msg) != length: ++ print("couldn't read TCP message") ++ continue ++ rsp = create_response(msg) ++ if rsp: ++ conn.send(struct.pack(">H", len(rsp))) ++ conn.send(rsp) ++ conn.close() ++ except socket.error as e: ++ print("error: %s" % str(e)) ++ if not running: ++ break +diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py +index 839067f..66f0193 100755 +--- a/bin/tests/system/chain/ans4/ans.py ++++ b/bin/tests/system/chain/ans4/ans.py +@@ -316,16 +316,30 @@ try: + except: + ctrlport = 5300 + +-query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +-query4_socket.bind((ip4, port)) ++query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ++query4_udp.bind((ip4, port)) ++ ++query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ++query4_tcp.bind((ip4, port)) ++query4_tcp.listen(1) ++query4_tcp.settimeout(1) + + havev6 = True + try: +- query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) ++ query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) ++ try: ++ query6_udp.bind((ip6, port)) ++ except: ++ query6_udp.close() ++ havev6 = False ++ ++ query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: +- query6_socket.bind((ip6, port)) ++ query6_tcp.bind((ip4, port)) ++ query6_tcp.listen(1) ++ query6_tcp.settimeout(1) + except: +- query6_socket.close() ++ query6_tcp.close() + havev6 = False + except: + havev6 = False +@@ -350,9 +364,9 @@ print("Control channel on %s port %d" % (ip4, ctrlport)) + print("Ctrl-c to quit") + + if havev6: +- input = [query4_socket, query6_socket, ctrl_socket] ++ input = [query4_udp, query4_tcp, query6_udp, query6_tcp, ctrl_socket] + else: +- input = [query4_socket, ctrl_socket] ++ input = [query4_udp, query4_tcp, ctrl_socket] + + while running: + try: +@@ -375,12 +389,37 @@ while running: + break + ctl_channel(msg) + conn.close() +- if s == query4_socket or s == query6_socket: +- print("Query received on %s" % (ip4 if s == query4_socket else ip6)) ++ elif s == query4_udp or s == query6_udp: ++ print("Query received on %s" % (ip4 if s == query4_udp else ip6)) + # Handle incoming queries + msg = s.recvfrom(65535) + rsp = create_response(msg[0]) + if rsp: + s.sendto(rsp, msg[1]) ++ elif s == query4_tcp or s == query6_tcp: ++ try: ++ conn, _ = s.accept() ++ if s == query4_tcp or s == query6_tcp: ++ print( ++ "TCP Query received on %s" % (ip4 if s == query4_tcp else ip6), ++ end=" ", ++ ) ++ # get TCP message length ++ msg = conn.recv(2) ++ if len(msg) != 2: ++ print("couldn't read TCP message length") ++ continue ++ length = struct.unpack(">H", msg[:2])[0] ++ msg = conn.recv(length) ++ if len(msg) != length: ++ print("couldn't read TCP message") ++ continue ++ rsp = create_response(msg) ++ if rsp: ++ conn.send(struct.pack(">H", len(rsp))) ++ conn.send(rsp) ++ conn.close() ++ except socket.error as e: ++ print("error: %s" % str(e)) + if not running: + break +diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h +index f15884a..c2efc19 100644 +--- a/lib/dns/include/dns/message.h ++++ b/lib/dns/include/dns/message.h +@@ -283,6 +283,7 @@ struct dns_message { + unsigned int tkey : 1; + unsigned int rdclass_set : 1; + unsigned int fuzzing : 1; ++ unsigned int has_dname : 1; + + unsigned int opt_reserved; + unsigned int sig_reserved; +@@ -1526,4 +1527,11 @@ dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl); + * \li 'pttl != NULL'. + */ + ++bool ++dns_message_hasdname(dns_message_t *msg); ++/*%< ++ * Return whether a DNAME was detected in the ANSWER section of a QUERY ++ * message when it was parsed. ++ */ ++ + ISC_LANG_ENDDECLS +diff --git a/lib/dns/message.c b/lib/dns/message.c +index 67190af..4dae88a 100644 +--- a/lib/dns/message.c ++++ b/lib/dns/message.c +@@ -428,6 +428,7 @@ msginit(dns_message_t *m) { + m->cc_bad = 0; + m->tkey = 0; + m->rdclass_set = 0; ++ m->has_dname = 0; + m->querytsig = NULL; + m->indent.string = "\t"; + m->indent.count = 0; +@@ -1710,6 +1711,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + */ + msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; + free_name = false; ++ } else if (rdtype == dns_rdatatype_dname && ++ sectionid == DNS_SECTION_ANSWER && ++ msg->opcode == dns_opcode_query) ++ { ++ msg->has_dname = 1; + } + rdataset = NULL; + +@@ -4861,3 +4867,9 @@ dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl) { + + return (ISC_R_SUCCESS); + } ++ ++bool ++dns_message_hasdname(dns_message_t *msg) { ++ REQUIRE(DNS_MESSAGE_VALID(msg)); ++ return msg->has_dname; ++} +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 19b9b1a..72d83b6 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -796,6 +796,7 @@ typedef struct respctx { + bool get_nameservers; /* get a new NS rrset at + * zone cut? */ + bool resend; /* resend this query? */ ++ bool secured; /* message was signed or had a valid cookie */ + bool nextitem; /* invalid response; keep + * listening for the correct one */ + bool truncated; /* response was truncated */ +@@ -6972,7 +6973,8 @@ mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external, + * locally served zone. + */ + static inline bool +-name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { ++name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) { ++ fetchctx_t *fctx = rctx->fctx; + isc_result_t result; + dns_forwarders_t *forwarders = NULL; + dns_fixedname_t fixed, zfixed; +@@ -6985,7 +6987,7 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { + dns_namereln_t rel; + + apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo)) +- ? fctx->domain ++ ? rctx->ns_name != NULL ? rctx->ns_name : fctx->domain + : fctx->fwdname; + + /* +@@ -7094,7 +7096,7 @@ check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type, + result = dns_message_findname(rctx->query->rmessage, section, addname, + dns_rdatatype_any, 0, &name, NULL); + if (result == ISC_R_SUCCESS) { +- external = name_external(name, type, fctx); ++ external = name_external(name, type, rctx); + if (type == dns_rdatatype_a) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; +@@ -7725,6 +7727,47 @@ betterreferral(respctx_t *rctx) { + return (false); + } + ++static bool ++rctx_need_tcpretry(respctx_t *rctx) { ++ resquery_t *query = rctx->query; ++ if ((rctx->retryopts & DNS_FETCHOPT_TCP) != 0) { ++ /* TCP is already in the retry flags */ ++ return false; ++ } ++ ++ /* ++ * If the message was secured, no need to continue. ++ */ ++ if (rctx->secured) { ++ return false; ++ } ++ ++ /* ++ * Currently the only extra reason why we might need to ++ * retry a UDP response over TCP is a DNAME in the message. ++ */ ++ if (dns_message_hasdname(query->rmessage)) { ++ return true; ++ } ++ ++ return false; ++} ++ ++static isc_result_t ++rctx_tcpretry(respctx_t *rctx) { ++ /* ++ * Do we need to retry a UDP response over TCP? ++ */ ++ if (rctx_need_tcpretry(rctx)) { ++ rctx->retryopts |= DNS_FETCHOPT_TCP; ++ rctx->resend = true; ++ rctx_done(rctx, ISC_R_SUCCESS); ++ return ISC_R_COMPLETE; ++ } ++ ++ return ISC_R_SUCCESS; ++} ++ + /* + * resquery_response(): + * Handles responses received in response to iterative queries sent by +@@ -7914,6 +7957,17 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { + return; + } + ++ /* ++ * Remember whether this message was signed or had a ++ * valid client cookie; if not, we may need to retry over ++ * TCP later. ++ */ ++ if (query->rmessage->cc_ok || query->rmessage->tsig != NULL || ++ query->rmessage->sig0 != NULL) ++ { ++ rctx.secured = true; ++ } ++ + /* + * The dispatcher should ensure we only get responses with QR + * set. +@@ -7925,10 +7979,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { + * TCP. This may be a misconfigured anycast server or an attempt + * to send a spoofed response. Skip if we have a valid tsig. + */ +- if (dns_message_gettsig(query->rmessage, NULL) == NULL && +- !query->rmessage->cc_ok && !query->rmessage->cc_bad && +- (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) +- { ++ if (!rctx.secured && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) { + unsigned char cookie[COOKIE_BUFFER_SIZE]; + if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie, + sizeof(cookie)) > CLIENT_COOKIE_SIZE) +@@ -7940,8 +7991,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, +- "missing expected cookie " +- "from %s", ++ "missing expected cookie from %s", + addrbuf); + } + rctx.retryopts |= DNS_FETCHOPT_TCP; +@@ -7951,6 +8001,17 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { + } + } + ++ /* ++ * Check whether we need to retry over TCP for some other reason. ++ */ ++ result = rctx_tcpretry(&rctx); ++ if (result == ISC_R_COMPLETE) { ++ return; ++ } ++ ++ /* ++ * Check for EDNS issues. ++ */ + rctx_edns(&rctx); + + /* +@@ -8733,8 +8794,8 @@ rctx_answer_positive(respctx_t *rctx) { + } + + /* +- * Cache records in the authority section, if +- * there are any suitable for caching. ++ * Cache records in the authority section, if there are ++ * any suitable for caching. + */ + rctx_authority_positive(rctx); + +@@ -8806,7 +8867,7 @@ rctx_answer_scan(respctx_t *rctx) { + /* + * Don't accept DNAME from parent namespace. + */ +- if (name_external(name, dns_rdatatype_dname, fctx)) { ++ if (name_external(name, dns_rdatatype_dname, rctx)) { + continue; + } + +@@ -9107,14 +9168,14 @@ rctx_answer_dname(respctx_t *rctx) { + + /* + * rctx_authority_positive(): +- * Examine the records in the authority section (if there are any) for a +- * positive answer. We expect the names for all rdatasets in this +- * section to be subdomains of the domain being queried; any that are +- * not are skipped. We expect to find only *one* owner name; any names +- * after the first one processed are ignored. We expect to find only +- * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever +- * remains can be cached at trust level authauthority or additional +- * (depending on whether the AA bit was set on the answer). ++ * If a positive answer was received over TCP or secured with a cookie ++ * or TSIG, examine the authority section. We expect names for all ++ * rdatasets in this section to be subdomains of the domain being queried; ++ * any that are not are skipped. We expect to find only *one* owner name; ++ * any names after the first one processed are ignored. We expect to find ++ * only rdatasets of type NS; all others are ignored. Whatever remains can ++ * be cached at trust level authauthority or additional (depending on ++ * whether the AA bit was set on the answer). + */ + static void + rctx_authority_positive(respctx_t *rctx) { +@@ -9122,6 +9183,11 @@ rctx_authority_positive(respctx_t *rctx) { + bool done = false; + isc_result_t result; + ++ /* If it's spoofable, don't cache it. */ ++ if (!rctx->secured && (rctx->query->options & DNS_FETCHOPT_TCP) == 0) { ++ return; ++ } ++ + result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + while (!done && result == ISC_R_SUCCESS) { +@@ -9130,7 +9196,9 @@ rctx_authority_positive(respctx_t *rctx) { + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + +- if (!name_external(name, dns_rdatatype_ns, fctx)) { ++ if (!name_external(name, dns_rdatatype_ns, rctx) && ++ dns_name_issubdomain(fctx->name, name)) ++ { + dns_rdataset_t *rdataset = NULL; + + /* +-- +2.51.1 + diff --git a/SOURCES/bind-9.18-CVE-2025-40780.patch b/SOURCES/bind-9.18-CVE-2025-40780.patch new file mode 100644 index 0000000..760a35d --- /dev/null +++ b/SOURCES/bind-9.18-CVE-2025-40780.patch @@ -0,0 +1,120 @@ +From c7d94eb33a2de5a0f3fdcb4eae7ffdee711cc3e1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Tue, 19 Aug 2025 19:22:18 +0200 +Subject: [PATCH] Use cryptographically-secure pseudo-random generator + everywhere + +It was discovered in an upcoming academic paper that a xoshiro128** +internal state can be recovered by an external 3rd party allowing to +predict UDP ports and DNS IDs in the outgoing queries. This could lead +to an attacker spoofing the DNS answers with great efficiency and +poisoning the DNS cache. + +Change the internal random generator to system CSPRNG with buffering to +avoid excessive syscalls. + +Thanks Omer Ben Simhon and Amit Klein of Hebrew University of Jerusalem +for responsibly reporting this to us. Very cool research! + +(cherry picked from commit cffcab9d5f3e709002f331b72498fcc229786ae2) +(cherry picked from commit 8330b49fb90bfeae14b47b7983e9459cc2bbaffe) +--- + lib/isc/include/isc/random.h | 2 +- + lib/isc/random.c | 14 +++++++------- + tests/isc/random_test.c | 4 +++- + 3 files changed, 11 insertions(+), 9 deletions(-) + +diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h +index 1e30d0c..fd55343 100644 +--- a/lib/isc/include/isc/random.h ++++ b/lib/isc/include/isc/random.h +@@ -20,7 +20,7 @@ + #include + + /*! \file isc/random.h +- * \brief Implements wrapper around a non-cryptographically secure ++ * \brief Implements wrapper around a cryptographically secure + * pseudo-random number generator. + * + */ +diff --git a/lib/isc/random.c b/lib/isc/random.c +index 7eead66..fb04669 100644 +--- a/lib/isc/random.c ++++ b/lib/isc/random.c +@@ -85,7 +85,7 @@ static thread_local uint32_t seed[4] = { 0 }; + + static uint32_t + rotl(const uint32_t x, int k) { +- return ((x << k) | (x >> (32 - k))); ++ return (x << k) | (x >> (32 - k)); + } + + static uint32_t +@@ -104,7 +104,7 @@ next(void) { + + seed[3] = rotl(seed[3], 11); + +- return (result_starstar); ++ return result_starstar; + } + + static thread_local isc_once_t isc_random_once = ISC_ONCE_INIT; +@@ -128,21 +128,21 @@ uint8_t + isc_random8(void) { + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); +- return (next() & 0xff); ++ return next() & 0xff; + } + + uint16_t + isc_random16(void) { + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); +- return (next() & 0xffff); ++ return next() & 0xffff; + } + + uint32_t + isc_random32(void) { + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); +- return (next()); ++ return next(); + } + + void +@@ -174,7 +174,7 @@ isc_random_uniform(uint32_t upper_bound) { + ISC_R_SUCCESS); + + if (upper_bound < 2) { +- return (0); ++ return 0; + } + + #if (ULONG_MAX > 0xffffffffUL) +@@ -202,5 +202,5 @@ isc_random_uniform(uint32_t upper_bound) { + } + } + +- return (r % upper_bound); ++ return r % upper_bound; + } +diff --git a/tests/isc/random_test.c b/tests/isc/random_test.c +index 1935846..0016252 100644 +--- a/tests/isc/random_test.c ++++ b/tests/isc/random_test.c +@@ -321,7 +321,9 @@ random_test(pvalue_func_t *func, isc_random_func test_func) { + } + break; + case ISC_RANDOM_BYTES: +- isc_random_buf(values, sizeof(values)); ++ for (i = 0; i < ARRAY_SIZE(values); i++) { ++ values[i] = isc_random32(); ++ } + break; + case ISC_RANDOM_UNIFORM: + uniform_values = (uint16_t *)values; +-- +2.51.1 + diff --git a/SOURCES/bind-9.18-CVE-2025-8677.patch b/SOURCES/bind-9.18-CVE-2025-8677.patch new file mode 100644 index 0000000..78c79c2 --- /dev/null +++ b/SOURCES/bind-9.18-CVE-2025-8677.patch @@ -0,0 +1,223 @@ +From c30c944e424a6c2281e0b1d53e25fc8ed3f71d41 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Tue, 22 Jul 2025 08:07:02 +0200 +Subject: [PATCH] Fail the DNSSEC validation if matching but invalid DNSKEY is + found + +If a matching but cryptographically invalid key was encountered during +the DNSSEC validation, the key would be just skipped and not counted +towards validation failures. Treat such DNSSEC keys as hard failures +and fail the DNSSEC validation immediatelly instead of continuing the +DNSSEC validation with the next DNSKEYs in the RRset. + +Co-authored-by: Matthijs Mekking + +(cherry picked from commit f00117a4226be90d1bc865aff19bddf114242914) +(cherry picked from commit 7c5b8ef055900224f0424c341927562c5a9ebe19) + +Fix an issue with selfsigned_dnskey() return value + +The selfsigned_dnskey() function currently returns boolean. There +was a recent change to make it return a isc_result_t error code, +which is implicitly converted to bool, which is obviously an error. + +If instead of the result code we return true/false, it still doesn't +indicate the error to the caller that has happened before. + +Change the function to return isc_result_t, and change the caller +routine to process the new return type. + +(cherry picked from commit 40c396ba2d955c32d70db04e900e40bf96519c59) +--- + lib/dns/validator.c | 79 +++++++++++++++++++++++++++++---------------- + 1 file changed, 51 insertions(+), 28 deletions(-) + +diff --git a/lib/dns/validator.c b/lib/dns/validator.c +index 696a464..1d84b75 100644 +--- a/lib/dns/validator.c ++++ b/lib/dns/validator.c +@@ -431,6 +431,8 @@ fetch_callback_dnskey(isc_task_t *task, isc_event_t *event) { + result = select_signing_key(val, rdataset); + if (result == ISC_R_SUCCESS) { + val->keyset = &val->frdataset; ++ } else { ++ val->failed = true; + } + } + result = validate_answer(val, true); +@@ -1161,6 +1163,8 @@ select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) { + goto done; + } + dst_key_free(&val->key); ++ } else { ++ break; + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); +@@ -1285,13 +1289,15 @@ seek_dnskey(dns_validator_t *val) { + "keyset with trust %s", + dns_trust_totext(val->frdataset.trust)); + result = select_signing_key(val, val->keyset); +- if (result != ISC_R_SUCCESS) { ++ if (result == ISC_R_NOTFOUND) { + /* +- * Either the key we're looking for is not +- * in the rrset, or something bad happened. +- * Give up. ++ * The key we're looking for is not ++ * in the rrset + */ + result = DNS_R_CONTINUE; ++ } else if (result != ISC_R_SUCCESS) { ++ /* Something bad happened. Give up. */ ++ break; + } + } + break; +@@ -1352,17 +1358,17 @@ compute_keytag(dns_rdata_t *rdata) { + /*% + * Is the DNSKEY rrset in val->event->rdataset self-signed? + */ +-static bool ++static isc_result_t + selfsigned_dnskey(dns_validator_t *val) { + dns_rdataset_t *rdataset = val->event->rdataset; + dns_rdataset_t *sigrdataset = val->event->sigrdataset; + dns_name_t *name = val->event->name; + isc_result_t result; + isc_mem_t *mctx = val->view->mctx; +- bool answer = false; ++ bool match = false; + + if (rdataset->type != dns_rdatatype_dnskey) { +- return (false); ++ return DNS_R_NOKEYMATCH; + } + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; +@@ -1384,8 +1390,6 @@ selfsigned_dnskey(dns_validator_t *val) { + result == ISC_R_SUCCESS; + result = dns_rdataset_next(sigrdataset)) + { +- dst_key_t *dstkey = NULL; +- + dns_rdata_reset(&sigrdata); + dns_rdataset_current(sigrdataset, &sigrdata); + result = dns_rdata_tostruct(&sigrdata, &sig, NULL); +@@ -1400,18 +1404,16 @@ selfsigned_dnskey(dns_validator_t *val) { + + /* + * If the REVOKE bit is not set we have a +- * theoretically self signed DNSKEY RRset. +- * This will be verified later. ++ * theoretically self-signed DNSKEY RRset; ++ * this will be verified later. ++ * ++ * We don't return the answer yet, though, ++ * because we need to check the remaining keys ++ * and possbly remove them if they're revoked. + */ + if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) { +- answer = true; +- continue; +- } +- +- result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx, +- &dstkey); +- if (result != ISC_R_SUCCESS) { +- continue; ++ match = true; ++ break; + } + + /* +@@ -1421,6 +1423,14 @@ selfsigned_dnskey(dns_validator_t *val) { + if (DNS_TRUST_PENDING(rdataset->trust) && + dns_view_istrusted(val->view, name, &key)) + { ++ dst_key_t *dstkey = NULL; ++ ++ result = dns_dnssec_keyfromrdata( ++ name, &keyrdata, mctx, &dstkey); ++ if (result != ISC_R_SUCCESS) { ++ break; ++ } ++ + result = dns_dnssec_verify( + name, rdataset, dstkey, true, + val->view->maxbits, mctx, &sigrdata, +@@ -1433,6 +1443,8 @@ selfsigned_dnskey(dns_validator_t *val) { + */ + dns_view_untrust(val->view, name, &key); + } ++ ++ dst_key_free(&dstkey); + } else if (rdataset->trust >= dns_trust_secure) { + /* + * We trust this RRset so if the key is +@@ -1440,12 +1452,14 @@ selfsigned_dnskey(dns_validator_t *val) { + */ + dns_view_untrust(val->view, name, &key); + } +- +- dst_key_free(&dstkey); + } + } + +- return (answer); ++ if (!match) { ++ return DNS_R_NOKEYMATCH; ++ } ++ ++ return ISC_R_SUCCESS; + } + + /*% +@@ -1680,10 +1694,7 @@ check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid, + val->event->name, keyrdata, val->view->mctx, + &dstkey); + if (result != ISC_R_SUCCESS) { +- /* +- * This really shouldn't happen, but... +- */ +- continue; ++ return result; + } + } + result = verify(val, dstkey, &rdata, sig.keyid); +@@ -3064,11 +3075,22 @@ validator_start(isc_task_t *task, isc_event_t *event) { + + INSIST(dns_rdataset_isassociated(val->event->rdataset)); + INSIST(dns_rdataset_isassociated(val->event->sigrdataset)); +- if (selfsigned_dnskey(val)) { ++ ++ result = selfsigned_dnskey(val); ++ switch (result) { ++ case ISC_R_SUCCESS: + result = validate_dnskey(val); +- } else { ++ break; ++ case DNS_R_NOKEYMATCH: + result = validate_answer(val, false); ++ break; ++ default: ++ validator_log(val, ISC_LOG_INFO, ++ "invalid selfsigned DNSKEY: %s", ++ isc_result_totext(result)); ++ goto cleanup; + } ++ + if (result == DNS_R_NOVALIDSIG && + (val->attributes & VALATTR_TRIEDVERIFY) == 0) + { +@@ -3137,6 +3159,7 @@ validator_start(isc_task_t *task, isc_event_t *event) { + UNREACHABLE(); + } + ++cleanup: + if (result != DNS_R_WAIT) { + want_destroy = exit_check(val); + validator_done(val, result); +-- +2.51.1 + diff --git a/SOURCES/bind-9.20-CVE-2025-8677-dual-signing-test.patch b/SOURCES/bind-9.20-CVE-2025-8677-dual-signing-test.patch new file mode 100644 index 0000000..cbfff8e --- /dev/null +++ b/SOURCES/bind-9.20-CVE-2025-8677-dual-signing-test.patch @@ -0,0 +1,137 @@ +From 748bf1ee0681bd2a2bc0b3dfa8634787017818c3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Sat, 1 Nov 2025 12:00:59 +0100 +Subject: [PATCH] Add a system test with one good and one bad algorithm + +The case where there would be one supported algorithm and one already +unsupported (like RSAMD5 or RSASHA1) was missing. + +(cherry picked from commit 3aa6f585e0466700e5d4b64fffccf883bb1c21dd) +--- + bin/tests/system/dnssec/clean.sh | 1 + + bin/tests/system/dnssec/ns2/example.db.in | 4 +++ + bin/tests/system/dnssec/ns2/sign.sh | 3 ++- + bin/tests/system/dnssec/ns3/named.conf.in | 6 +++++ + bin/tests/system/dnssec/ns3/sign.sh | 31 +++++++++++++++++++++++ + bin/tests/system/dnssec/tests.sh | 11 ++++++++ + 6 files changed, 55 insertions(+), 1 deletion(-) + +diff --git a/bin/tests/system/dnssec/clean.sh b/bin/tests/system/dnssec/clean.sh +index 1a933ad..aa35122 100644 +--- a/bin/tests/system/dnssec/clean.sh ++++ b/bin/tests/system/dnssec/clean.sh +@@ -69,6 +69,7 @@ rm -f ./ns3/dnskey-unsupported.example.db.tmp + rm -f ./ns3/dynamic.example.db ./ns3/dynamic.example.db.signed.jnl + rm -f ./ns3/expired.example.db ./ns3/update-nsec3.example.db + rm -f ./ns3/expiring.example.db ./ns3/nosign.example.db ++rm -f ./ns3/extrabadkey.example.db + rm -f ./ns3/future.example.db ./ns3/trusted-future.key + rm -f ./ns3/inline.example.db.signed + rm -f ./ns3/kskonly.example.db +diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in +index f711f58..63d41e5 100644 +--- a/bin/tests/system/dnssec/ns2/example.db.in ++++ b/bin/tests/system/dnssec/ns2/example.db.in +@@ -168,4 +168,8 @@ ns.managed-future A 10.53.0.3 + revkey NS ns.revkey + ns.revkey A 10.53.0.3 + ++; A secure subdomain with extra bad key ++extrabadkey NS ns3.extrabadkey ++ns3.extrabadkey A 10.53.0.3 ++ + dname-at-apex-nsec3 NS ns3 +diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh +index b60e82a..eb00806 100644 +--- a/bin/tests/system/dnssec/ns2/sign.sh ++++ b/bin/tests/system/dnssec/ns2/sign.sh +@@ -62,7 +62,8 @@ for subdomain in secure badds bogus dynamic keyless nsec3 optout \ + ttlpatch split-dnssec split-smart expired expiring upper lower \ + dnskey-unknown dnskey-unsupported dnskey-unsupported-2 \ + dnskey-nsec3-unknown managed-future revkey \ +- dname-at-apex-nsec3 occluded; do ++ dname-at-apex-nsec3 occluded rsasha1 rsasha1-1024 \ ++ extrabadkey; do + cp "../ns3/dsset-$subdomain.example." . + done + +diff --git a/bin/tests/system/dnssec/ns3/named.conf.in b/bin/tests/system/dnssec/ns3/named.conf.in +index 680cff5..3536046 100644 +--- a/bin/tests/system/dnssec/ns3/named.conf.in ++++ b/bin/tests/system/dnssec/ns3/named.conf.in +@@ -84,6 +84,12 @@ zone "insecure2.example" { + allow-update { any; }; + }; + ++zone "extrabadkey.example" { ++ type primary; ++ file "extrabadkey.example.db.signed"; ++ allow-update { any; }; ++}; ++ + zone "insecure.nsec3.example" { + type primary; + file "insecure.nsec3.example.db"; +diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh +index 14fc709..743a0e4 100644 +--- a/bin/tests/system/dnssec/ns3/sign.sh ++++ b/bin/tests/system/dnssec/ns3/sign.sh +@@ -673,3 +673,34 @@ $DSFROMKEY "$dnskeyname.key" >"dsset-delegation.${zone}." + cat "$infile" "${kskname}.key" "${zskname}.key" "${keyname}.key" \ + "${dnskeyname}.key" "dsset-delegation.${zone}." >"$zonefile" + "$SIGNER" -P -o "$zone" "$zonefile" >/dev/null ++ ++# ++# ++# ++zone=extrabadkey.example. ++infile=template.db.in ++zonefile=extrabadkey.example.db ++ ++# Add KSK and ZSK that we will mangle to RSAMD5 ++ksk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "$zone") ++zsk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") ++cat "$infile" "$ksk.key" "$zsk.key" > "$zonefile" ++"$SIGNER" -g -O full -o "$zone" "$zonefile" >/dev/null 2>&1 ++ ++# Mangle the signatures to RSAMD5 and save them for future use ++sed -ne "s/\(IN[[:space:]]*RRSIG[[:space:]]*[A-Z]*\) $DEFAULT_ALGORITHM_NUMBER /\1 1 /p" < "$zonefile.signed" > "$zonefile.signed.rsamd5" ++ ++# Now add normal KSK and ZSK to the zone file ++ksk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "$zone") ++zsk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") ++cat "$infile" "$ksk.key" "$zsk.key" > "$zonefile" ++ ++# Mangle the DNSKEY algorithm numbers and add them to the signed zone file ++cat "$ksk.key" "$zsk.key" | sed -e "s/\(IN[[:space:]]*DNSKEY[[:space:]]*[0-9]* 3\) $DEFAULT_ALGORITHM_NUMBER /\1 1 /" >> "$zonefile" ++ ++# Sign normally ++"$SIGNER" -g -o "$zone" "$zonefile" >/dev/null 2>&1 ++ ++# Add the mangled signatures to signed zone file ++cat "$zonefile.signed.rsamd5" >> "$zonefile.signed" ++rm "$zonefile.signed.rsamd5" +diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh +index c7b1c3a..e908317 100644 +--- a/bin/tests/system/dnssec/tests.sh ++++ b/bin/tests/system/dnssec/tests.sh +@@ -4468,5 +4468,16 @@ n=$((n + 1)) + if [ "$ret" -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + ++echo_i "checking extra-bad-algorithm positive validation ($n)" ++ret=0 ++dig_with_opts +noauth a.extrabadkey.example. @10.53.0.3 A >dig.out.ns3.test$n || ret=1 ++dig_with_opts +noauth a.extrabadkey.example. @10.53.0.4 A >dig.out.ns4.test$n || ret=1 ++digcomp --lc dig.out.ns3.test$n dig.out.ns4.test$n || ret=1 ++grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1 ++grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null || ret=1 ++n=$((n + 1)) ++test "$ret" -eq 0 || echo_i "failed" ++status=$((status + ret)) ++ + echo_i "exit status: $status" + [ $status -eq 0 ] || exit 1 +-- +2.51.1 + diff --git a/SOURCES/bind-9.20-CVE-2025-8677-dual-signing.patch b/SOURCES/bind-9.20-CVE-2025-8677-dual-signing.patch new file mode 100644 index 0000000..40ad87e --- /dev/null +++ b/SOURCES/bind-9.20-CVE-2025-8677-dual-signing.patch @@ -0,0 +1,36 @@ +From 5126c74ec19f56294cbfdc312f75778d8f249e59 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Mon, 3 Nov 2025 22:03:54 +0100 +Subject: [PATCH] Do not abort key search on unsupported algorithm + +When supported and unsupported algorithm rrsig is present, some keys may +return unsupported algorithm error. Continue to next key without +counting this to validation failures. + +(cherry picked from commit 38ddff3336e08983a4c0b5f3ea4eb35bb0f6ac81) +--- + lib/dns/validator.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/lib/dns/validator.c b/lib/dns/validator.c +index 15e177e4d7..a9db844c27 100644 +--- a/lib/dns/validator.c ++++ b/lib/dns/validator.c +@@ -1163,7 +1163,13 @@ select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) { + goto done; + } + dst_key_free(&val->key); +- } else { ++ } else if (result != DST_R_UNSUPPORTEDALG) { ++ /* Unsupported alg happens when RRset is signed by both ++ * supported and unsupported alg. */ ++ validator_log(val, ISC_LOG_DEBUG(3), ++ "select_signing_key alg %d keyid %d: %s", ++ siginfo->algorithm, siginfo->keyid, ++ isc_result_totext(result)); + break; + } + dns_rdata_reset(&rdata); +-- +2.51.1 + diff --git a/SOURCES/generate-rndc-key.sh b/SOURCES/generate-rndc-key.sh old mode 100755 new mode 100644 diff --git a/SOURCES/setup-named-chroot.sh b/SOURCES/setup-named-chroot.sh old mode 100755 new mode 100644 diff --git a/SOURCES/setup-named-softhsm.sh b/SOURCES/setup-named-softhsm.sh old mode 100755 new mode 100644 diff --git a/SPECS/bind9.18.spec b/SPECS/bind9.18.spec index e1a46be..1581dd0 100644 --- a/SPECS/bind9.18.spec +++ b/SPECS/bind9.18.spec @@ -77,7 +77,7 @@ License: MPL-2.0 AND ISC AND MIT AND BSD-3-Clause AND BSD-2-Clause # ./lib/isc/tm.c BSD-2-clause and/or MPL-2.0 # ./lib/isccfg/parser.c BSD-2-clause and/or MPL-2.0 Version: 9.18.29 -Release: 4%{?dist} +Release: 4%{?dist}.2 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -130,6 +130,19 @@ Patch32: bind-9.18-CVE-2024-12705.patch Patch33: bind-9.21-resume-qmin-cname.patch # downstream only, extra check for above change, RHEL-30407 Patch34: bind-9.18-query-fname-relative.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/40c396ba2d955c32d70db04e900e40bf96519c59 +# https://gitlab.isc.org/isc-projects/bind9/commit/7c5b8ef055900224f0424c341927562c5a9ebe19 +Patch223: bind-9.18-CVE-2025-8677.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/025d61bacd0f57f994a631654aff7a933d89a547 +# https://gitlab.isc.org/isc-projects/bind9/commit/cd17dfe696cdf9b8ef23fbc8738de7c79f957846 +# https://gitlab.isc.org/isc-projects/bind9/commit/4c6d03b0bb2ffbafcde8e8a5bc0e49908b978a72 +Patch224: bind-9.18-CVE-2025-40778.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/8330b49fb90bfeae14b47b7983e9459cc2bbaffe +Patch225: bind-9.18-CVE-2025-40780.patch +# https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/11194 +Patch226: bind-9.20-CVE-2025-8677-dual-signing.patch +# https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/11195 +Patch227: bind-9.20-CVE-2025-8677-dual-signing-test.patch %{?systemd_ordering} Requires: coreutils @@ -977,6 +990,15 @@ fi; %endif %changelog +* Fri Oct 31 2025 Petr Menšík - 32:9.18.29-4.2 +- Fix upstream reported regression in recent CVE fix (CVE-2025-8677) +- Add upstream created test to this regression + +* Thu Oct 23 2025 Petr Menšík - 32:9.18.29-4.1 +- Refuse malformed DNSKEY records (CVE-2025-8677) +- Address various spoofing attacks (CVE-2025-40778) +- Prevent cache poisoning due to weak PRNG (CVE-2025-40780) + * Tue Jun 10 2025 Petr Mensik - 32:9.18.29-4 - Prevent name.c:670 attributes assertion failed (RHEL-30407) - Add extra checks for relative names