From 9d6bf0bf937e7e5ca96fb0b7d2a9adf1e57bfcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Thu, 30 Oct 2025 19:01:49 +0100 Subject: [PATCH] Replace our downstream backports with upstream patches https://gitlab.isc.org/isc-projects/bind9/commit/dd614497470c6ae9cd8a963e5a0f1cc177ee7b86 https://gitlab.isc.org/isc-projects/bind9/commit/aba2fa7e355be5a5c25d61c51238ea16d231b577 Ensure patches are applied too. Resolves: RHEL-123318 --- bind-9.16-CVE-2025-40778.patch | 784 ++++++++++++++++++ ...80.patch => bind-9.16-CVE-2025-40780.patch | 134 +-- bind-9.18-CVE-2025-40778.patch | 469 ----------- bind9.16.spec | 15 +- 4 files changed, 831 insertions(+), 571 deletions(-) create mode 100644 bind-9.16-CVE-2025-40778.patch rename bind-9.18-CVE-2025-40780.patch => bind-9.16-CVE-2025-40780.patch (77%) delete mode 100644 bind-9.18-CVE-2025-40778.patch diff --git a/bind-9.16-CVE-2025-40778.patch b/bind-9.16-CVE-2025-40778.patch new file mode 100644 index 0000000..99b8c28 --- /dev/null +++ b/bind-9.16-CVE-2025-40778.patch @@ -0,0 +1,784 @@ +From 39a43add7ad19fc2d83950a9af0c4f8cf580588b Mon Sep 17 00:00:00 2001 +From: Evan Hunt +Date: Mon, 29 Sep 2025 21:46:59 -0700 +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 2f0f44d493c382a7f0a3adfe7c4976b18a3d480b) + +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). + +Modified resolver.c + +(cherry picked from commit a41054e9e606a61f1b3c8bc0c54e2f1059347165) +(cherry picked from commit 50479358efdf432d690415131b74b5df158a9d69) + +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 33a7db1fe964e55b76b4ac003ecc56cc67028bd9) +--- + bin/tests/system/chain/ans3/ans.pl | 129 ----------------- + bin/tests/system/chain/ans3/ans.py | 217 +++++++++++++++++++++++++++++ + bin/tests/system/chain/ans4/ans.py | 58 ++++++-- + lib/dns/include/dns/message.h | 10 ++ + lib/dns/message.c | 12 ++ + lib/dns/resolver.c | 114 ++++++++++++--- + 6 files changed, 380 insertions(+), 160 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 31d22d807b..0000000000 +--- a/bin/tests/system/chain/ans3/ans.pl ++++ /dev/null +@@ -1,129 +0,0 @@ +-#!/usr/bin/env perl +-# +-# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +-# +-# 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; }; +- +-$SIG{INT} = \&rmpid; +-$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, +-); +- +-$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 0000000000..0a031c1145 +--- /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 45d650417f..e4fc15a280 100755 +--- a/bin/tests/system/chain/ans4/ans.py ++++ b/bin/tests/system/chain/ans4/ans.py +@@ -276,16 +276,30 @@ except: port=5300 + try: ctrlport=int(os.environ['EXTRAPORT1']) + 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 +@@ -310,9 +324,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: +@@ -335,13 +349,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 68c13ee6c5..53c96a5827 100644 +--- a/lib/dns/include/dns/message.h ++++ b/lib/dns/include/dns/message.h +@@ -1,6 +1,8 @@ + /* + * 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/. +@@ -233,6 +235,7 @@ struct dns_message { + unsigned int cc_bad : 1; + unsigned int tkey : 1; + unsigned int rdclass_set : 1; ++ unsigned int has_dname : 1; + + unsigned int opt_reserved; + unsigned int sig_reserved; +@@ -1449,4 +1452,11 @@ dns_message_clonebuffer(dns_message_t *msg); + * \li msg be a valid message. + */ + ++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 04315bc6d0..aa434a75f1 100644 +--- a/lib/dns/message.c ++++ b/lib/dns/message.c +@@ -438,6 +438,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; +@@ -1717,6 +1718,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; + +@@ -4750,3 +4756,9 @@ dns_message_clonebuffer(dns_message_t *msg) { + msg->free_query = 1; + } + } ++ ++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 ad0a1b8102..5973bc1d55 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -751,6 +751,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 */ +@@ -7155,7 +7156,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; +@@ -7167,7 +7169,9 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { + unsigned int labels; + dns_namereln_t rel; + +- apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname : &fctx->domain; ++ apex = (/*ISDUALSTACK(fctx->addrinfo) || */!ISFORWARDER(fctx->addrinfo)) ++ ? rctx->ns_name != NULL ? rctx->ns_name : &fctx->domain ++ : fctx->fwdname; + + /* + * The name is outside the queried namespace. +@@ -7275,7 +7279,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; +@@ -7884,6 +7888,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 +@@ -8042,6 +8087,11 @@ resquery_response(isc_task_t *task, isc_event_t *event) { + break; + } + ++ /* ++ * The dispatcher should ensure we only get responses with QR set. ++ */ ++ INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); ++ + /* + * If the message is signed, check the signature. If not, this + * returns success anyway. +@@ -8058,9 +8108,16 @@ resquery_response(isc_task_t *task, isc_event_t *event) { + } + + /* +- * The dispatcher should ensure we only get responses with QR set. ++ * Remember whether this message was signed or had a ++ * valid client cookie; if not, we may need to retry over ++ * TCP later. + */ +- INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); ++ if (query->rmessage->cc_ok || query->rmessage->tsig != NULL || ++ query->rmessage->sig0 != NULL) ++ { ++ rctx.secured = true; ++ } ++ + /* + * INSIST() that the message comes from the place we sent it to, + * since the dispatch code should ensure this. +@@ -8074,10 +8131,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) { + * 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) +@@ -8103,6 +8157,17 @@ resquery_response(isc_task_t *task, isc_event_t *event) { + */ + } + ++ /* ++ * 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); + + /* +@@ -8830,8 +8895,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); + +@@ -8902,7 +8967,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; + } + +@@ -9200,14 +9265,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) { +@@ -9215,6 +9280,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) { +@@ -9223,7 +9293,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/bind-9.18-CVE-2025-40780.patch b/bind-9.16-CVE-2025-40780.patch similarity index 77% rename from bind-9.18-CVE-2025-40780.patch rename to bind-9.16-CVE-2025-40780.patch index 62fd7bb..5508810 100644 --- a/bind-9.18-CVE-2025-40780.patch +++ b/bind-9.16-CVE-2025-40780.patch @@ -1,4 +1,4 @@ -From e2bfe1eec2f492224df85a04099d4505c9698965 Mon Sep 17 00:00:00 2001 +From 6254679e5250a9f0d6079ec082cffdad4315372d 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 @@ -17,17 +17,32 @@ 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) +(cherry picked from commit d9b5ef342916462bfd63391831d96afc80c12df3) --- + lib/isc/include/isc/os.h | 5 + lib/isc/include/isc/random.h | 2 +- - lib/isc/random.c | 189 ++++++++++++++++++----------------- + lib/isc/random.c | 181 ++++++++++++++++++----------------- lib/isc/tests/random_test.c | 4 +- - lib/isc/xoshiro128starstar.c | 61 ----------- - 4 files changed, 103 insertions(+), 153 deletions(-) - delete mode 100644 lib/isc/xoshiro128starstar.c + 4 files changed, 102 insertions(+), 90 deletions(-) +diff --git a/lib/isc/include/isc/os.h b/lib/isc/include/isc/os.h +index ce7615a0e3..5def473d2b 100644 +--- a/lib/isc/include/isc/os.h ++++ b/lib/isc/include/isc/os.h +@@ -18,6 +18,11 @@ + + ISC_LANG_BEGINDECLS + ++/*%< ++ * Hardcode the L1 cacheline size of the CPU to 64. ++ */ ++#define ISC_OS_CACHELINE_SIZE 64 ++ + unsigned int + isc_os_ncpus(void); + /*%< diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h -index 556e747..2c16472 100644 +index 556e74754a..2c16472a5d 100644 --- a/lib/isc/include/isc/random.h +++ b/lib/isc/include/isc/random.h @@ -18,7 +18,7 @@ @@ -40,10 +55,10 @@ index 556e747..2c16472 100644 * */ diff --git a/lib/isc/random.c b/lib/isc/random.c -index 753453f..57430ac 100644 +index 753453ff3c..717e1f0dcd 100644 --- a/lib/isc/random.c +++ b/lib/isc/random.c -@@ -29,131 +29,140 @@ +@@ -29,131 +29,136 @@ */ #include @@ -69,14 +84,8 @@ index 753453f..57430ac 100644 - * uint32_t next(void) that provides next random number. - * - * The implementation must be thread-safe. -+/* from lib/isc/include/isc/os.h in 9.18 */ -+/*%< -+ * Hardcode the L1 cacheline size of the CPU to 64, this is checked in -+ * the os.c library constructor if operating system provide means to -+ * get the L1 cacheline size using sysconf(). - */ -+#define ISC_OS_CACHELINE_SIZE 64 - +- */ +- -/* - * Two contestants have been considered: the xoroshiro family of the - * functions by Villa&Blackman, and PCG by O'Neill. After @@ -85,9 +94,9 @@ index 753453f..57430ac 100644 - * good enough properties for our usage pattern. - */ -#include "xoshiro128starstar.c" ++#define ISC_RANDOM_BUFSIZE (ISC_OS_CACHELINE_SIZE / sizeof(uint32_t)) -ISC_THREAD_LOCAL isc_once_t isc_random_once = ISC_ONCE_INIT; -+#define ISC_RANDOM_BUFSIZE (ISC_OS_CACHELINE_SIZE / sizeof(uint32_t)) +ISC_THREAD_LOCAL uint32_t isc__random_pool[ISC_RANDOM_BUFSIZE]; +ISC_THREAD_LOCAL size_t isc__random_pos = ISC_RANDOM_BUFSIZE; @@ -97,20 +106,20 @@ index 753453f..57430ac 100644 +static uint32_t +random_u32(void) { #if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -- /* + /* - * Set a constant seed to help in problem reproduction should fuzzing - * find a crash or a hang. The seed array must be non-zero else - * xoshiro128starstar will generate an infinite series of zeroes. -- */ --#else /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ -- isc_entropy_get(useed, sizeof(useed)); --#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ -- memmove(seed, useed, sizeof(seed)); + * A fixed stream of numbers helps with problem reproduction when + * fuzzing. The first result needs to be non-zero as expected by + * random_test.c (it starts with ISC_RANDOM_BUFSIZE, see above). + */ +-#else /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ +- isc_entropy_get(useed, sizeof(useed)); + return (uint32_t)(isc__random_pos++); -+#endif + #endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ +- memmove(seed, useed, sizeof(seed)); ++ + if (isc__random_pos == ISC_RANDOM_BUFSIZE) { + isc_entropy_get(isc__random_pool, sizeof(isc__random_pool)); + isc__random_pos = 0; @@ -147,7 +156,8 @@ index 753453f..57430ac 100644 isc_random_buf(void *buf, size_t buflen) { - int i; - uint32_t r; -- ++ REQUIRE(buflen == 0 || buf != NULL); + - REQUIRE(buf != NULL); - REQUIRE(buflen > 0); - @@ -157,7 +167,6 @@ index 753453f..57430ac 100644 - for (i = 0; i + sizeof(r) <= buflen; i += sizeof(r)) { - r = next(); - memmove((uint8_t *)buf + i, &r, sizeof(r)); -+ REQUIRE(buflen == 0 || buf != NULL); + if (buf == NULL || buflen == 0) { + return; } @@ -275,7 +284,7 @@ index 753453f..57430ac 100644 + return (uint32_t)(num >> 32); } diff --git a/lib/isc/tests/random_test.c b/lib/isc/tests/random_test.c -index 7161cd9..f47137d 100644 +index 7161cd96a9..f47137d3ac 100644 --- a/lib/isc/tests/random_test.c +++ b/lib/isc/tests/random_test.c @@ -345,7 +345,9 @@ random_test(pvalue_func_t *func, isc_random_func test_func) { @@ -289,73 +298,6 @@ index 7161cd9..f47137d 100644 break; case ISC_RANDOM_UNIFORM: uniform_values = (uint16_t *)values; -diff --git a/lib/isc/xoshiro128starstar.c b/lib/isc/xoshiro128starstar.c -deleted file mode 100644 -index 9cdc258..0000000 ---- a/lib/isc/xoshiro128starstar.c -+++ /dev/null -@@ -1,61 +0,0 @@ --/* -- * Portions Copyright (C) Internet Systems Consortium, Inc. ("ISC") -- * -- * 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. -- */ -- --/* -- * Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) -- * -- * To the extent possible under law, the author has dedicated all -- * copyright and related and neighboring rights to this software to the -- * public domain worldwide. This software is distributed without any -- * warranty. -- * -- * See . -- */ -- --#include -- --#include -- --/* -- * This is xoshiro128** 1.0, our 32-bit all-purpose, rock-solid generator. -- * It has excellent (sub-ns) speed, a state size (128 bits) that is large -- * enough for mild parallelism, and it passes all tests we are aware of. -- * -- * For generating just single-precision (i.e., 32-bit) floating-point -- * numbers, xoshiro128+ is even faster. -- * -- * The state must be seeded so that it is not everywhere zero. -- */ --ISC_THREAD_LOCAL uint32_t seed[4] = { 0 }; -- --static inline uint32_t --rotl(const uint32_t x, int k) { -- return ((x << k) | (x >> (32 - k))); --} -- --static inline uint32_t --next(void) { -- uint32_t result_starstar, t; -- -- result_starstar = rotl(seed[0] * 5, 7) * 9; -- t = seed[1] << 9; -- -- seed[2] ^= seed[0]; -- seed[3] ^= seed[1]; -- seed[1] ^= seed[2]; -- seed[0] ^= seed[3]; -- -- seed[2] ^= t; -- -- seed[3] = rotl(seed[3], 11); -- -- return (result_starstar); --} -- -2.51.0 +2.51.1 diff --git a/bind-9.18-CVE-2025-40778.patch b/bind-9.18-CVE-2025-40778.patch deleted file mode 100644 index f666c79..0000000 --- a/bind-9.18-CVE-2025-40778.patch +++ /dev/null @@ -1,469 +0,0 @@ -From 83cc066e52542d3f52db98b02da33121b316f4be 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/ans4/ans.py | 58 +++++++++++++--- - lib/dns/include/dns/message.h | 8 +++ - lib/dns/message.c | 12 ++++ - lib/dns/resolver.c | 108 ++++++++++++++++++++++++----- - lib/dns/validator.c | 33 ++++++--- - 5 files changed, 180 insertions(+), 39 deletions(-) - -diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py -index 45d6504..e4fc15a 100755 ---- a/bin/tests/system/chain/ans4/ans.py -+++ b/bin/tests/system/chain/ans4/ans.py -@@ -276,16 +276,30 @@ except: port=5300 - try: ctrlport=int(os.environ['EXTRAPORT1']) - 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 -@@ -310,9 +324,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: -@@ -335,13 +349,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 68c13ee..d321a58 100644 ---- a/lib/dns/include/dns/message.h -+++ b/lib/dns/include/dns/message.h -@@ -233,6 +233,7 @@ struct dns_message { - unsigned int cc_bad : 1; - unsigned int tkey : 1; - unsigned int rdclass_set : 1; -+ unsigned int has_dname : 1; - - unsigned int opt_reserved; - unsigned int sig_reserved; -@@ -1449,4 +1450,11 @@ dns_message_clonebuffer(dns_message_t *msg); - * \li msg be a valid message. - */ - -+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 04315bc..aa434a7 100644 ---- a/lib/dns/message.c -+++ b/lib/dns/message.c -@@ -438,6 +438,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; -@@ -1717,6 +1718,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; - -@@ -4750,3 +4756,9 @@ dns_message_clonebuffer(dns_message_t *msg) { - msg->free_query = 1; - } - } -+ -+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 ad0a1b8..c284b67 100644 ---- a/lib/dns/resolver.c -+++ b/lib/dns/resolver.c -@@ -751,6 +751,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 */ -@@ -7155,7 +7156,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; -@@ -7167,7 +7169,8 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { - unsigned int labels; - dns_namereln_t rel; - -- apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname : &fctx->domain; -+ apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname : -+ (rctx->ns_name != NULL) ? rctx->ns_name : &fctx->domain; - - /* - * The name is outside the queried namespace. -@@ -7275,7 +7278,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; -@@ -7884,6 +7887,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 -@@ -8069,15 +8113,23 @@ resquery_response(isc_task_t *task, isc_event_t *event) { - * ensured by the dispatch code). - */ - -+ /* -+ * 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; -+ } -+ - /* - * If we have had a server cookie and don't get one retry over 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) -@@ -8103,6 +8155,17 @@ resquery_response(isc_task_t *task, isc_event_t *event) { - */ - } - -+ /* -+ * 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); - - /* -@@ -8830,8 +8893,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); - -@@ -8902,7 +8965,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; - } - -@@ -9200,14 +9263,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) { -@@ -9215,6 +9278,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) { -@@ -9223,7 +9291,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; - - /* -diff --git a/lib/dns/validator.c b/lib/dns/validator.c -index e416cc9..37ddd26 100644 ---- a/lib/dns/validator.c -+++ b/lib/dns/validator.c -@@ -1344,17 +1344,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; -@@ -1390,10 +1390,24 @@ selfsigned_dnskey(dns_validator_t *val) { - continue; - } - -+ /* -+ * If the REVOKE bit is not set we have a -+ * 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) { -+ match = true; -+ break; -+ } -+ - result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx, - &dstkey); - if (result != ISC_R_SUCCESS) { -- continue; -+ break; - } - - result = dns_dnssec_verify(name, rdataset, dstkey, true, -@@ -1404,16 +1418,15 @@ selfsigned_dnskey(dns_validator_t *val) { - continue; - } - -- if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) { -- answer = true; -- continue; -- } -- - dns_view_untrust(val->view, name, &key); - } - } - -- return (answer); -+ if (!match) { -+ return DNS_R_NOKEYMATCH; -+ } -+ -+ return ISC_R_SUCCESS; - } - - /*% --- -2.51.0 - diff --git a/bind9.16.spec b/bind9.16.spec index 469541d..81123f5 100644 --- a/bind9.16.spec +++ b/bind9.16.spec @@ -174,12 +174,12 @@ Patch215: bind-9.18-CVE-2024-11187-pre-test.patch Patch216: bind-9.18-CVE-2024-11187.patch # https://gitlab.isc.org/isc-projects/bind9/commit/8924adca613ca9daea63786563cce6fdbd742c56 Patch217: bind-9.16-update-b.root-servers.net.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/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/d9b5ef342916462bfd63391831d96afc80c12df3 +Patch224: bind-9.16-CVE-2025-40780.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/2f0f44d493c382a7f0a3adfe7c4976b18a3d480b +# https://gitlab.isc.org/isc-projects/bind9/commit/50479358efdf432d690415131b74b5df158a9d69 +# https://gitlab.isc.org/isc-projects/bind9/commit/33a7db1fe964e55b76b4ac003ecc56cc67028bd9 +Patch225: bind-9.16-CVE-2025-40778.patch %{?systemd_ordering} Requires: coreutils @@ -517,6 +517,8 @@ in HTML and PDF format. %patch215 -p1 -b .CVE-2024-11187-pre-test %patch216 -p1 -b .CVE-2024-11187 %patch217 -p1 -b .b.root-servers.net +%patch224 -p1 -b .CVE-2025-40780 +%patch225 -p1 -b .CVE-2025-40778 %if %{with PKCS11} %patch135 -p1 -b .config-pkcs11 @@ -1262,6 +1264,7 @@ fi; * Wed Oct 29 2025 Petr Menšík - 32:9.16.23-0.22.4 - Prevent cache poisoning due to weak PRNG (CVE-2025-40780) - Address various spoofing attacks (CVE-2025-40778) +- Replace downstream fixes with upstream changes * Wed Aug 13 2025 Petr Menšík - Update addresses of b.root-servers.net (RHEL-18449) - 32:9.16.23-0.22.3