782 lines
26 KiB
Diff
782 lines
26 KiB
Diff
From 918f465fd4157c821f135aa53405c37e05b68903 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 e42240be63..0000000000
|
|
--- 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 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 839067faa5..66f0193cac 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 f15884a183..c2efc19ca6 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 1ac370bc05..f939fa94a7 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 a0c8d42565..f57fa80b19 100644
|
|
--- a/lib/dns/resolver.c
|
|
+++ b/lib/dns/resolver.c
|
|
@@ -798,6 +798,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 */
|
|
@@ -7021,7 +7022,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;
|
|
@@ -7034,7 +7036,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;
|
|
|
|
/*
|
|
@@ -7143,7 +7145,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;
|
|
@@ -7773,6 +7775,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
|
|
@@ -7962,6 +8005,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.
|
|
@@ -7973,10 +8027,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)
|
|
@@ -7988,8 +8039,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;
|
|
@@ -7999,6 +8049,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);
|
|
|
|
/*
|
|
@@ -8781,8 +8842,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);
|
|
|
|
@@ -8854,7 +8915,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;
|
|
}
|
|
|
|
@@ -9155,14 +9216,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) {
|
|
@@ -9170,6 +9231,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) {
|
|
@@ -9178,7 +9244,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.0
|
|
|