import OL bind9.18-9.18.29-4.el9_6.2

This commit is contained in:
eabdullin 2025-11-11 08:53:04 +00:00
parent b9e5ae32ea
commit 198f831a73
9 changed files with 1320 additions and 1 deletions

View File

@ -0,0 +1,781 @@
From 69783c72b5db5f96518839508829e20b75b96f86 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
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

View File

@ -0,0 +1,120 @@
From c7d94eb33a2de5a0f3fdcb4eae7ffdee711cc3e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
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 <isc/types.h>
/*! \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

View File

@ -0,0 +1,223 @@
From c30c944e424a6c2281e0b1d53e25fc8ed3f71d41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
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 <matthijs@isc.org>
(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

View File

@ -0,0 +1,137 @@
From 748bf1ee0681bd2a2bc0b3dfa8634787017818c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
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

View File

@ -0,0 +1,36 @@
From 5126c74ec19f56294cbfdc312f75778d8f249e59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
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

0
SOURCES/generate-rndc-key.sh Executable file → Normal file
View File

0
SOURCES/setup-named-chroot.sh Executable file → Normal file
View File

0
SOURCES/setup-named-softhsm.sh Executable file → Normal file
View File

View File

@ -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 <pemensik@redhat.com> - 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 <pemensik@redhat.com> - 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 <pemensik@redhat.com> - 32:9.18.29-4
- Prevent name.c:670 attributes assertion failed (RHEL-30407)
- Add extra checks for relative names