bind/SOURCES/bind-9.11-CVE-2025-40778.patch
2025-11-06 07:29:34 +00:00

1085 lines
38 KiB
Diff

From 3b7a43eb2fcdec7e31587a88464f5eb8df5c23d6 Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Mon, 29 Sep 2025 22:17:39 -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 41ab0709d1bde6fb8a2dde623d00e69bc48fab0d)
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 eba8e3eb33f907a1a622c065138e19b087b6e4f1)
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 0e4cd87bed5efc61443337034a9d96287b4885dc)
---
bin/tests/system/chain/ans3/ans.pl | 129 -----------------
bin/tests/system/chain/ans3/ans.py | 216 ++++++++++++++++++++++++++++
bin/tests/system/chain/ans4/ans.py | 58 ++++++--
bin/tests/system/cookie/ans9/ans.py | 139 ++++++++++--------
lib/dns/include/dns/message.h | 8 ++
lib/dns/message.c | 12 ++
lib/dns/resolver.c | 84 ++++++++---
lib/dns/win32/libdns.def.in | 1 +
8 files changed, 432 insertions(+), 215 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..d6d4eea6d9
--- /dev/null
+++ b/bin/tests/system/chain/ans3/ans.py
@@ -0,0 +1,216 @@
+# 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 = "ns3." + zone
+ synth = "synth-then-dname." + zone
+ synth2 = "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("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 == "cname-to-" + synth2:
+ r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name." + synth2))
+ r.answer.append(dns.rrset.from_text("name." + synth2, ttl, IN, CNAME, "name."))
+ r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, "."))
+ elif qname == synth or qname == synth2:
+ if qtype == "DNAME":
+ r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, "."))
+ elif qname == "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 == "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/bin/tests/system/cookie/ans9/ans.py b/bin/tests/system/cookie/ans9/ans.py
index b454fc8c91..dd48bdb095 100644
--- a/bin/tests/system/cookie/ans9/ans.py
+++ b/bin/tests/system/cookie/ans9/ans.py
@@ -1,13 +1,13 @@
-############################################################################
# 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
+# 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.
-############################################################################
from __future__ import print_function
import os
@@ -35,28 +35,27 @@ from dns.rdataclass import *
from dns.rdatatype import *
from dns.tsig import *
+
# Log query to file
def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+
# DNS 2.0 keyring specifies the algorithm
try:
- keyring = dns.tsigkeyring.from_text({ "foo" : {
- "hmac-sha256",
- "aaaaaaaaaaaa"
- } ,
- "fake" : {
- "hmac-sha256",
- "aaaaaaaaaaaa"
- }
- })
+ keyring = dns.tsigkeyring.from_text(
+ {
+ "foo": {"hmac-sha256", "aaaaaaaaaaaa"},
+ "fake": {"hmac-sha256", "aaaaaaaaaaaa"},
+ }
+ )
except:
- keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
- "fake" : "aaaaaaaaaaaa" })
+ keyring = dns.tsigkeyring.from_text({"foo": "aaaaaaaaaaaa", "fake": "aaaaaaaaaaaa"})
dopass2 = False
+
############################################################################
#
# This server will serve valid and spoofed answers. A spoofed answer will
@@ -81,7 +80,7 @@ def create_response(msg, tcp, first, ns10):
m = dns.message.from_wire(msg, keyring=keyring)
qname = m.question[0].name.to_text()
lqname = qname.lower()
- labels = lqname.split('.')
+ labels = lqname.split(".")
rrtype = m.question[0].rdtype
typename = dns.rdatatype.to_text(rrtype)
@@ -113,27 +112,35 @@ def create_response(msg, tcp, first, ns10):
# Add a server cookie to the response
if labels[0] != "nocookie":
for o in m.options:
- if o.otype == 10: # Use 10 instead of COOKIE
- if first and labels[0] == "withtsig" and not tcp:
- r.use_tsig(keyring = keyring,
- keyname = dns.name.from_text("fake"),
- algorithm = HMAC_SHA256)
- elif labels[0] != "tcponly" or tcp:
- cookie = o
- if len(o.data) == 8:
- cookie.data = o.data + o.data
- else:
- cookie.data = o.data
- r.use_edns(options=[cookie])
+ if o.otype == 10: # Use 10 instead of COOKIE
+ if first and labels[0] == "withtsig" and not tcp:
+ r.use_tsig(
+ keyring=keyring,
+ keyname=dns.name.from_text("fake"),
+ algorithm=HMAC_SHA256,
+ )
+ elif labels[0] != "tcponly" or tcp:
+ cookie = o
+ try:
+ if len(o.server) == 0:
+ cookie.server = o.client
+ except AttributeError: # dnspython<2.7.0 compat
+ if len(o.data) == 8:
+ cookie.data = o.data + o.data
+ else:
+ cookie.data = o.data
+ r.use_edns(options=[cookie])
r.flags |= dns.flags.AA
return r
+
def sigterm(signum, frame):
- print ("Shutting down now...")
- os.remove('ans.pid')
+ print("Shutting down now...")
+ os.remove("ans.pid")
running = False
sys.exit(0)
+
############################################################################
# Main
#
@@ -146,8 +153,10 @@ ip4_addr2 = "10.53.0.10"
ip6_addr1 = "fd92:7065:b8e:ffff::9"
ip6_addr2 = "fd92:7065:b8e:ffff::10"
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+ port = int(os.environ["PORT"])
+except:
+ port = 5300
query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
query4_udp1.bind((ip4_addr1, port))
@@ -195,24 +204,32 @@ except:
signal.signal(signal.SIGTERM, sigterm)
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
f.close()
running = True
-print ("Using DNS version %s" % dns.version.version)
-print ("Listening on %s port %d" % (ip4_addr1, port))
-print ("Listening on %s port %d" % (ip4_addr2, port))
+print("Using DNS version %s" % dns.version.version)
+print("Listening on %s port %d" % (ip4_addr1, port))
+print("Listening on %s port %d" % (ip4_addr2, port))
if havev6:
- print ("Listening on %s port %d" % (ip6_addr1, port))
- print ("Listening on %s port %d" % (ip6_addr2, port))
-print ("Ctrl-c to quit")
+ print("Listening on %s port %d" % (ip6_addr1, port))
+ print("Listening on %s port %d" % (ip6_addr2, port))
+print("Ctrl-c to quit")
if havev6:
- input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1,
- query4_udp2, query6_udp2, query4_tcp2, query6_tcp2]
+ input = [
+ query4_udp1,
+ query6_udp1,
+ query4_tcp1,
+ query6_tcp1,
+ query4_udp2,
+ query6_udp2,
+ query4_tcp2,
+ query6_tcp2,
+ ]
else:
input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
@@ -228,14 +245,19 @@ while running:
for s in inputready:
ns10 = False
- if s == query4_udp1 or s == query6_udp1 or \
- s == query4_udp2 or s == query6_udp2:
+ if s == query4_udp1 or s == query6_udp1 or s == query4_udp2 or s == query6_udp2:
if s == query4_udp1 or s == query6_udp1:
- print ("UDP Query received on %s" %
- (ip4_addr1 if s == query4_udp1 else ip6_addr1), end=" ")
+ print(
+ "UDP Query received on %s"
+ % (ip4_addr1 if s == query4_udp1 else ip6_addr1),
+ end=" ",
+ )
if s == query4_udp2 or s == query6_udp2:
- print ("UDP Query received on %s" %
- (ip4_addr2 if s == query4_udp2 else ip6_addr2), end=" ")
+ print(
+ "UDP Query received on %s"
+ % (ip4_addr2 if s == query4_udp2 else ip6_addr2),
+ end=" ",
+ )
ns10 = True
# Handle incoming queries
msg = s.recvfrom(65535)
@@ -244,31 +266,36 @@ while running:
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
if dopass2:
- print ("Sending second UDP response without TSIG", end=" ")
+ print("Sending second UDP response without TSIG", end=" ")
rsp = create_response(msg[0], False, False, ns10)
s.sendto(rsp.to_wire(), msg[1])
print(dns.rcode.to_text(rsp.rcode()))
- if s == query4_tcp1 or s == query6_tcp1 or \
- s == query4_tcp2 or s == query6_tcp2:
+ if s == query4_tcp1 or s == query6_tcp1 or s == query4_tcp2 or s == query6_tcp2:
try:
(cs, _) = s.accept()
if s == query4_tcp1 or s == query6_tcp1:
- print ("TCP Query received on %s" %
- (ip4_addr1 if s == query4_tcp1 else ip6_addr1), end=" ")
+ print(
+ "TCP Query received on %s"
+ % (ip4_addr1 if s == query4_tcp1 else ip6_addr1),
+ end=" ",
+ )
if s == query4_tcp2 or s == query6_tcp2:
- print ("TCP Query received on %s" %
- (ip4_addr2 if s == query4_tcp2 else ip6_addr2), end=" ")
+ print(
+ "TCP Query received on %s"
+ % (ip4_addr2 if s == query4_tcp2 else ip6_addr2),
+ end=" ",
+ )
ns10 = True
# get TCP message length
buf = cs.recv(2)
- length = struct.unpack('>H', buf[:2])[0]
+ length = struct.unpack(">H", buf[:2])[0]
# grep DNS message
msg = cs.recv(length)
rsp = create_response(msg, True, True, ns10)
print(dns.rcode.to_text(rsp.rcode()))
wire = rsp.to_wire()
- cs.send(struct.pack('>H', len(wire)))
+ cs.send(struct.pack(">H", len(wire)))
cs.send(wire)
cs.close()
except s.timeout:
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
index 96c5ef1f79..54208a49e6 100644
--- a/lib/dns/include/dns/message.h
+++ b/lib/dns/include/dns/message.h
@@ -226,6 +226,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;
@@ -1408,6 +1409,13 @@ dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass);
* \li msg be a valid message with parsing intent.
*/
+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
#endif /* DNS_MESSAGE_H */
diff --git a/lib/dns/message.c b/lib/dns/message.c
index 9ea2b9e7fb..5603bcadf9 100644
--- a/lib/dns/message.c
+++ b/lib/dns/message.c
@@ -459,6 +459,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 = dns_master_indentstr;
m->indent.count = dns_master_indent;
@@ -1829,6 +1830,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;
@@ -4699,3 +4705,9 @@ dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) {
msg->rdclass = rdclass;
msg->rdclass_set = 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 487107614c..1c704551aa 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -428,6 +428,7 @@ typedef struct {
typedef struct {
fetchctx_t * fctx;
dns_message_t * rmessage;
+ dns_name_t * ns_name;
} dns_chkarg_t;
struct dns_fetch {
@@ -6259,7 +6260,9 @@ mark_related(dns_name_t *name, dns_rdataset_t *rdataset,
* locally served zone.
*/
static inline bool
-name_external(dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
+name_external(dns_name_t *name, dns_name_t *ns_name, dns_rdatatype_t type,
+ fetchctx_t *fctx)
+{
isc_result_t result;
dns_forwarders_t *forwarders = NULL;
dns_fixedname_t fixed, zfixed;
@@ -6277,7 +6280,9 @@ name_external(dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
int _orderp = 0;
unsigned int _nlabelsp = 0;
- apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname : &fctx->domain;
+ apex = ISFORWARDER(fctx->addrinfo)
+ ? fctx->fwdname
+ : (ns_name != NULL) ? ns_name : &fctx->domain;
/*
* The name is outside the queried namespace.
@@ -6386,7 +6391,7 @@ check_section(void *arg, dns_name_t *addname, dns_rdatatype_t type,
result = dns_message_findname(rmessage, section, addname,
dns_rdatatype_any, 0, &name, NULL);
if (result == ISC_R_SUCCESS) {
- external = name_external(name, type, fctx);
+ external = name_external(name, chkarg->ns_name, type, fctx);
if (type == dns_rdatatype_a) {
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
@@ -6460,7 +6465,7 @@ chase_additional(fetchctx_t *fctx, dns_message_t *rmessage) {
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
if (CHASE(rdataset)) {
- dns_chkarg_t chkarg;
+ dns_chkarg_t chkarg = { 0 };
chkarg.fctx = fctx;
chkarg.rmessage = rmessage;
rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
@@ -7068,7 +7073,7 @@ noanswer_response(fetchctx_t *fctx, dns_message_t *message,
* we're not following a chain.)
*/
if (!negative_response && ns_name != NULL && oqname == NULL) {
- dns_chkarg_t chkarg;
+ dns_chkarg_t chkarg = { 0 };
/*
* We already know ns_name is a subdomain of fctx->domain.
* If ns_name is equal to fctx->domain, we're not making
@@ -7100,6 +7105,7 @@ noanswer_response(fetchctx_t *fctx, dns_message_t *message,
FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING);
chkarg.fctx = fctx;
chkarg.rmessage = message;
+ chkarg.ns_name = ns_name;
(void)dns_rdataset_additionaldata(ns_rdataset, check_related,
&chkarg);
#if CHECK_FOR_GLUE_IN_ANSWER
@@ -7113,7 +7119,7 @@ noanswer_response(fetchctx_t *fctx, dns_message_t *message,
if ((look_in_options & LOOK_FOR_GLUE_IN_ANSWER) != 0 &&
(fctx->type == dns_rdatatype_aaaa ||
fctx->type == dns_rdatatype_a)) {
- dns_chkarg_t chkarg;
+ dns_chkarg_t chkarg = { 0 };
chkarg.fcx = fctx;
chkarg.rmessage = message;
(void)dns_rdataset_additionaldata(ns_rdataset,
@@ -7193,8 +7199,9 @@ validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
}
static isc_result_t
-answer_response(fetchctx_t *fctx, dns_message_t *message) {
+answer_response(resquery_t *query, dns_message_t *message) {
isc_result_t result;
+ fetchctx_t *fctx = NULL;
dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL;
dns_name_t *aname = NULL, *cname = NULL, *dname = NULL;
dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
@@ -7207,6 +7214,8 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
dns_view_t *view = NULL;
dns_trust_t trust;
+ REQUIRE(VALID_QUERY(query));
+ fctx = query->fctx;
REQUIRE(VALID_FCTX(fctx));
FCTXTRACE("answer_response");
@@ -7271,7 +7280,9 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
/*
* Don't accept DNAME from parent namespace.
*/
- if (name_external(name, dns_rdatatype_dname, fctx)) {
+ if (name_external(name, NULL, dns_rdatatype_dname,
+ fctx))
+ {
continue;
}
@@ -7326,7 +7337,7 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link))
{
- dns_chkarg_t chkarg;
+ dns_chkarg_t chkarg = { 0 };
if (!validinanswer(rdataset, fctx)) {
return (DNS_R_FORMERR);
}
@@ -7357,12 +7368,13 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
chkarg.fctx = fctx;
chkarg.rmessage = message;
+ chkarg.ns_name = ns_name;
(void)dns_rdataset_additionaldata(rdataset,
check_related,
&chkarg);
}
} else if (aname != NULL) {
- dns_chkarg_t chkarg;
+ dns_chkarg_t chkarg = { 0 };
if (!validinanswer(ardataset, fctx))
return (DNS_R_FORMERR);
if ((ardataset->type == dns_rdatatype_a ||
@@ -7386,6 +7398,7 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
ardataset->trust = trust;
chkarg.fctx = fctx;
chkarg.rmessage = message;
+ chkarg.ns_name = ns_name;
(void)dns_rdataset_additionaldata(ardataset, check_related,
&chkarg);
for (sigrdataset = ISC_LIST_HEAD(aname->list);
@@ -7507,12 +7520,23 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
*
* We expect there to be only one owner name for all the rdatasets
* in this section, and we expect that it is not external.
+ *
+ * If the message was not sent over TCP or otherwise secured,
+ * skip this.
*/
- result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ if (message->cc_ok || message->tsig != NULL || message->sig0 != NULL ||
+ (query->options & DNS_FETCHOPT_TCP) != 0)
+ {
+ result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+ } else {
+ done = true;
+ }
while (!done && result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
- if (!name_external(name, dns_rdatatype_ns, fctx)) {
+ if (!name_external(name, ns_name, dns_rdatatype_ns, fctx) &&
+ dns_name_issubdomain(&fctx->name, name))
+ {
/*
* We expect to find NS or SIG NS rdatasets, and
* nothing else.
@@ -7523,7 +7547,7 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
if (rdataset->type == dns_rdatatype_ns ||
(rdataset->type == dns_rdatatype_rrsig &&
rdataset->covers == dns_rdatatype_ns)) {
- dns_chkarg_t chkarg;
+ dns_chkarg_t chkarg = { 0 };
name->attributes |=
DNS_NAMEATTR_CACHE;
rdataset->attributes |=
@@ -7547,6 +7571,7 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
*/
chkarg.fctx = fctx;
chkarg.rmessage = message;
+ chkarg.ns_name = ns_name;
(void)dns_rdataset_additionaldata(
rdataset,
check_related,
@@ -7988,6 +8013,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
unsigned int bucketnum;
dns_resolver_t *res;
bool bucket_empty;
+ bool secured = false;
#ifdef HAVE_DNSTAP
isc_socket_t *sock = NULL;
isc_sockaddr_t localaddr, *la = NULL;
@@ -8298,6 +8324,17 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
goto done;
}
+ /*
+ * Remember whether this message was signed or had a
+ * valid client cookie; if not, we may need to retry over
+ * TCP later.
+ */
+ if (rmessage->cc_ok || rmessage->tsig != NULL ||
+ rmessage->sig0 != NULL)
+ {
+ secured = true;
+ }
+
/*
* The dispatcher should ensure we only get responses with QR set.
*/
@@ -8315,10 +8352,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(rmessage, NULL) == NULL &&
- !rmessage->cc_ok && !rmessage->cc_bad &&
- (options & DNS_FETCHOPT_TCP) == 0)
- {
+ if (!secured && (options & DNS_FETCHOPT_TCP) == 0) {
unsigned char cookie[COOKIE_BUFFER_SIZE];
if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
sizeof(cookie)) > CLIENT_COOKIE_SIZE)
@@ -8341,6 +8375,16 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
* XXXMPA When support for DNS COOKIE becomes ubiquitous, fall
* back to TCP for all non-COOKIE responses.
*/
+
+ /*
+ * Check whether we need to retry over TCP for some other
+ * reason.
+ */
+ if (dns_message_hasdname(rmessage)) {
+ options |= DNS_FETCHOPT_TCP;
+ resend = true;
+ goto done;
+ }
}
/*
@@ -8697,7 +8741,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
if ((rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 ||
ISFORWARDER(query->addrinfo))
{
- result = answer_response(fctx, rmessage);
+ result = answer_response(query, rmessage);
if (result != ISC_R_SUCCESS)
FCTXTRACE3("answer_response (AA/fwd)", result);
} else if (iscname(fctx, rmessage) &&
@@ -8709,7 +8753,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
* answer when a CNAME is followed. We should treat
* it as a valid answer.
*/
- result = answer_response(fctx, rmessage);
+ result = answer_response(query, rmessage);
if (result != ISC_R_SUCCESS)
FCTXTRACE3("answer_response (!ANY/!CNAME)",
result);
@@ -8718,7 +8762,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
/*
* Lame response !!!.
*/
- result = answer_response(fctx, rmessage);
+ result = answer_response(query, rmessage);
if (result != ISC_R_SUCCESS)
FCTXTRACE3("answer_response (!NS)", result);
} else {
diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in
index 7320653439..b48bc89bbd 100644
--- a/lib/dns/win32/libdns.def.in
+++ b/lib/dns/win32/libdns.def.in
@@ -537,6 +537,7 @@ dns_message_gettemprdataset
dns_message_gettimeadjust
dns_message_gettsig
dns_message_gettsigkey
+dns_message_hasdname
dns_message_logfmtpacket
dns_message_logfmtpacket2
dns_message_logpacket
--
2.51.1