]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add tests for CVE-2022-2795
authorMichał Kępień <michal@isc.org>
Tue, 6 Sep 2022 11:36:44 +0000 (13:36 +0200)
committerMichal Nowak <mnowak@isc.org>
Wed, 21 Dec 2022 15:08:37 +0000 (16:08 +0100)
Add a test ensuring that the amount of work fctx_getaddresses() performs
for any encountered delegation is limited: delegate example.net to a set
of 1,000 name servers in the redirect.com zone, the names of which all
resolve to IP addresses that nothing listens on, and query for a name in
the example.net domain, checking the number of times the findname()
function gets executed in the process; fail if that count is excessively
large.

Since the size of the referral response sent by ans3 is about 20 kB, it
cannot be sent back over UDP (EMSGSIZE) on some operating systems in
their default configuration (e.g. FreeBSD - see the
net.inet.udp.maxdgram sysctl).  To enable reliable reproduction of
CVE-2022-2795 (retry patterns vary across BIND 9 versions) and avoid
false positives at the same time (thread scheduling - and therefore the
number of fetch context restarts - vary across operating systems and
across test runs), extend bin/tests/system/resolver/ans3/ans.pl so that
it also listens on TCP and make "ns1" in the "resolver" system test
always use TCP when communicating with "ans3".

Also add a test (foo.bar.sub.tld1/TXT) that ensures the new limitations
imposed on the resolution process by the mitigation for CVE-2022-2795 do
not prevent valid, glueless delegation chains from working properly.

13 files changed:
bin/tests/system/resolver/ans2/ans.pl
bin/tests/system/resolver/ans3/ans.pl
bin/tests/system/resolver/ns1/named.conf.in
bin/tests/system/resolver/ns6/named.conf.in
bin/tests/system/resolver/ns6/redirect.com.db [new file with mode: 0644]
bin/tests/system/resolver/ns6/tld1.db [new file with mode: 0644]
bin/tests/system/resolver/ns7/named1.conf.in
bin/tests/system/resolver/ns7/named2.conf.in
bin/tests/system/resolver/ns7/sub.tld1.db [new file with mode: 0644]
bin/tests/system/resolver/ns7/tld2.db [new file with mode: 0644]
bin/tests/system/resolver/tests.sh
lib/dns/resolver.c
util/copyrights

index 89defcd59189c0a71cd76bfd201eb69b5b778c00..864b89608d4982117e2a2bc6ec3347c52aadfe9d 100644 (file)
@@ -65,6 +65,15 @@ for (;;) {
                # Data for the "cname + other data / 2" test: same RRs in opposite order
                $packet->push("answer", new Net::DNS::RR("cname2.example.com 300 A 1.2.3.4"));
                $packet->push("answer", new Net::DNS::RR("cname2.example.com 300 CNAME cname2.example.com"));
+       } elsif ($qname =~ /redirect\.com/) {
+               $packet->push("authority", new Net::DNS::RR("redirect.com 300 NS ns.redirect.com"));
+               $packet->push("additional", new Net::DNS::RR("ns.redirect.com 300 A 10.53.0.6"));
+       } elsif ($qname =~ /\.tld1/) {
+               $packet->push("authority", new Net::DNS::RR("tld1 300 NS ns.tld1"));
+               $packet->push("additional", new Net::DNS::RR("ns.tld1 300 A 10.53.0.6"));
+       } elsif ($qname =~ /\.tld2/) {
+               $packet->push("authority", new Net::DNS::RR("tld2 300 NS ns.tld2"));
+               $packet->push("additional", new Net::DNS::RR("ns.tld2 300 A 10.53.0.7"));
        } elsif ($qname eq "www.example.org" || $qname eq "www.example.net" ||
                 $qname eq "badcname.example.org" ||
                 $qname eq "goodcname.example.org" ||
index ca2ed08c272a25d2de1930526d30d800cf4d9eb6..486a19c566dd9f11c83bc37d08a921d6c1ad3573 100644 (file)
@@ -18,11 +18,21 @@ use IO::Socket;
 use Net::DNS;
 use Net::DNS::Packet;
 
+# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early
+local $SIG{PIPE} = 'IGNORE';
+
+# Flush logged output after every line
+local $| = 1;
+
 my $localport = int($ENV{'PORT'});
 if (!$localport) { $localport = 5300; }
 
-my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.3",
-   LocalPort => $localport, Proto => "udp") or die "$!";
+my $server_addr = "10.53.0.3";
+
+my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
+   LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!";
+my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
+   LocalPort => $localport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
 
 my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
 print $pidf "$$\n" or die "cannot write pid file: $!";
@@ -32,11 +42,8 @@ sub rmpid { unlink "ans.pid"; exit 1; };
 $SIG{INT} = \&rmpid;
 $SIG{TERM} = \&rmpid;
 
-for (;;) {
-       $sock->recv($buf, 512);
-
-       print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n";
-
+sub handleQuery {
+       my $buf = shift;
        my $packet;
 
        if ($Net::DNS::VERSION > 0.68) {
@@ -73,6 +80,13 @@ for (;;) {
                $packet->push("answer",
                              new Net::DNS::RR($qname .
                                       " 300 CNAME goodcname.example.org"));
+       } elsif ($qname eq "large-referral.example.net") {
+               for (my $i = 1; $i < 1000; $i++) {
+                       $packet->push("authority", new Net::DNS::RR("large-referral.example.net 300 NS ns" . $i . ".fake.redirect.com"));
+               }
+               # No glue records
+       } elsif ($qname eq "foo.bar.sub.tld1") {
+               $packet->push("answer", new Net::DNS::RR("$qname 300 TXT baz"));
        } elsif ($qname eq "cname.sub.example.org") {
                $packet->push("answer",
                              new Net::DNS::RR($qname .
@@ -91,9 +105,46 @@ for (;;) {
                $packet->push("answer", new Net::DNS::RR("www.example.com 300 A 1.2.3.4"));
        }
 
-       $sock->send($packet->data);
-
        print "RESPONSE:\n";
        $packet->print;
-       print "\n";
+
+       return $packet->data;
+}
+
+# Main
+my $rin;
+my $rout;
+for (;;) {
+       $rin = '';
+       vec($rin, fileno($tcpsock), 1) = 1;
+       vec($rin, fileno($udpsock), 1) = 1;
+
+       select($rout = $rin, undef, undef, undef);
+
+       if (vec($rout, fileno($udpsock), 1)) {
+               printf "UDP request\n";
+               my $buf;
+               $udpsock->recv($buf, 512);
+               my $result = handleQuery($buf);
+               my $num_chars = $udpsock->send($result);
+               print "  Sent $num_chars bytes via UDP\n";
+       } elsif (vec($rout, fileno($tcpsock), 1)) {
+               my $conn = $tcpsock->accept;
+               my $buf;
+               for (;;) {
+                       my $lenbuf;
+                       my $n = $conn->sysread($lenbuf, 2);
+                       last unless $n == 2;
+                       my $len = unpack("n", $lenbuf);
+                       $n = $conn->sysread($buf, $len);
+                       last unless $n == $len;
+                       print "TCP request\n";
+                       my $result = handleQuery($buf);
+                       $len = length($result);
+                       $conn->syswrite(pack("n", $len), 2);
+                       $n = $conn->syswrite($result, $len);
+                       print "    Sent: $n chars via TCP\n";
+               }
+               $conn->close;
+       }
 }
index 49cd56ec0ae1e7fca36cf279169be96d424d25d4..9c60f871508813d7de2cffe9e31223661ff8181b 100644 (file)
@@ -29,6 +29,10 @@ options {
        attach-cache "globalcache";
 };
 
+server 10.53.0.3 {
+       tcp-only yes;
+};
+
 server 10.42.23.3/32 {
      notify-source 10.42.22.1;
      query-source address 10.42.22.1 port 0;
@@ -59,3 +63,12 @@ view "default" {
                file "root.hint";
        };
 };
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm hmac-sha256;
+};
+
+controls {
+       inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
index cbf8888177e26c4ca514b7b75a68cdca5fc3cea9..5b6be8a45001337e045af0ec866b894d7ac3559f 100644 (file)
@@ -59,6 +59,16 @@ zone "broken" {
        allow-update { any; };
 };
 
+zone "redirect.com" {
+       type master;
+       file "redirect.com.db";
+};
+
+zone "tld1" {
+       type master;
+       file "tld1.db";
+};
+
 zone "no-edns-version.tld" {
        type master;
        file "no-edns-version.tld.db";
diff --git a/bin/tests/system/resolver/ns6/redirect.com.db b/bin/tests/system/resolver/ns6/redirect.com.db
new file mode 100644 (file)
index 0000000..8f4fd8a
--- /dev/null
@@ -0,0 +1,25 @@
+; 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.
+
+$TTL 600
+@      IN SOA  ns hostmaster 1 1800 900 604800 600
+@      IN NS   ns
+ns     IN A    10.53.0.6
+
+; 10.53.1.* are non-responsive IP addresses
+$GENERATE 1-100    ns$.fake IN A 10.53.1.$
+$GENERATE 101-200  ns$.fake IN A 10.53.1.${-100}
+$GENERATE 201-300  ns$.fake IN A 10.53.1.${-200}
+$GENERATE 301-400  ns$.fake IN A 10.53.1.${-300}
+$GENERATE 401-500  ns$.fake IN A 10.53.1.${-400}
+$GENERATE 501-600  ns$.fake IN A 10.53.1.${-500}
+$GENERATE 601-700  ns$.fake IN A 10.53.1.${-600}
+$GENERATE 701-800  ns$.fake IN A 10.53.1.${-700}
+$GENERATE 801-900  ns$.fake IN A 10.53.1.${-800}
+$GENERATE 901-1000 ns$.fake IN A 10.53.1.${-900}
diff --git a/bin/tests/system/resolver/ns6/tld1.db b/bin/tests/system/resolver/ns6/tld1.db
new file mode 100644 (file)
index 0000000..65d14c6
--- /dev/null
@@ -0,0 +1,15 @@
+; 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.
+
+$TTL 600
+@      IN SOA  ns hostmaster 1 1800 900 604800 600
+@      IN NS   ns
+ns     IN A    10.53.0.6
+
+$GENERATE 1-21 sub IN NS sub-ns$.tld2.
index 3c6f33268a4d940bd90c9e9ad498ccec589d5607..e42a151f6a353e2979c54980338a7c3e111fd735 100644 (file)
@@ -59,3 +59,13 @@ zone "all-cnames" {
        type master;
        file "all-cnames.db";
 };
+
+zone "tld2" {
+       type master;
+       file "tld2.db";
+};
+
+zone "sub.tld1" {
+       type master;
+       file "sub.tld1.db";
+};
index ba298a8c2258a146634910fe5a2b7234cfa10feb..c4f95aa180db94889cea2084ee83b0a29fbed2f6 100644 (file)
@@ -59,3 +59,13 @@ zone "all-cnames" {
        type master;
        file "all-cnames.db";
 };
+
+zone "tld2" {
+       type master;
+       file "tld2.db";
+};
+
+zone "sub.tld1" {
+       type master;
+       file "sub.tld1.db";
+};
diff --git a/bin/tests/system/resolver/ns7/sub.tld1.db b/bin/tests/system/resolver/ns7/sub.tld1.db
new file mode 100644 (file)
index 0000000..11be810
--- /dev/null
@@ -0,0 +1,15 @@
+; 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.
+
+$TTL 600
+@      IN SOA  ns hostmaster 1 1800 900 604800 600
+
+$GENERATE 1-21 @ IN NS sub-ns$.tld2.
+
+$GENERATE 1-21 bar IN NS bar-sub-ns$.tld2.
diff --git a/bin/tests/system/resolver/ns7/tld2.db b/bin/tests/system/resolver/ns7/tld2.db
new file mode 100644 (file)
index 0000000..e1fb63d
--- /dev/null
@@ -0,0 +1,16 @@
+; 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.
+
+$TTL 600
+@      IN SOA  ns hostmaster 1 1800 900 604800 600
+@      IN NS   ns
+ns     IN A    10.53.0.7
+
+$GENERATE 1-21 sub-ns$ IN A 10.53.0.7
+$GENERATE 1-21 bar-sub-ns$ IN A 10.53.0.3
index 6eb52fe0f7023dfa7aac864acf32e80b60bf7560..b3c9f2179c7bd127a8734969d8d9fc5141dc8662 100755 (executable)
@@ -823,5 +823,36 @@ grep "running as: .* -m record,size,mctx " ns1/named.run > /dev/null || ret=1
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=`expr $status + $ret`
 
+n=$((n+1))
+echo_i "check handling of large referrals to unresponsive name servers ($n)"
+ret=0
+$DIG $DIGOPTS +timeout=15 large-referral.example.net @10.53.0.1 a > dig.out.ns1.test${n} || ret=1
+grep "status: SERVFAIL" dig.out.ns1.test${n} > /dev/null || ret=1
+# Check the total number of findname() calls triggered by a single query
+# for large-referral.example.net/A.
+findname_call_count="$(grep -c "large-referral\.example\.net.*FINDNAME" ns1/named.run)"
+if [ "${findname_call_count}" -gt 1000 ]; then
+       echo_i "failed: ${findname_call_count} (> 1000) findname() calls detected for large-referral.example.net"
+       ret=1
+fi
+# Check whether the limit of NS RRs processed for any delegation
+# encountered was not exceeded.
+if grep -Eq "dns_adb_createfind: started (A|AAAA) fetch for name ns21.fake.redirect.com" ns1/named.run; then
+       echo_i "failed: unexpected address fetch(es) were triggered for ns21.fake.redirect.com"
+       ret=1
+fi
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n+1))
+echo_i "checking query resolution for a domain with a valid glueless delegation chain ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 flush || ret=1
+$DIG $DIGOPTS foo.bar.sub.tld1 @10.53.0.1 TXT > dig.out.ns1.test${n} || ret=1
+grep "status: NOERROR" dig.out.ns1.test${n} > /dev/null || ret=1
+grep "IN.*TXT.*baz" dig.out.ns1.test${n} > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
index ac9a9ef5d009c7e28cf608b48b19c03587190d0c..077b3ef03db106c3112193c6d70b38817799a5c1 100644 (file)
@@ -3186,6 +3186,7 @@ findname(fetchctx_t *fctx, dns_name_t *name, in_port_t port,
        bool unshared;
        isc_result_t result;
 
+       FCTXTRACE("FINDNAME");
        res = fctx->res;
        unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0);
        /*
index 7b48b21346611eedfb72b384240349303354558e..bf39ec3e8ef1528de3c1b0c22cf45df1dd4d0a13 100644 (file)
 ./bin/tests/system/resolver/ns6/moves.db       ZONE    2011,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns6/named.conf.in  CONF-C  2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns6/no-edns-version.tld.db ZONE    2014,2016,2018,2019,2020,2021,2022
+./bin/tests/system/resolver/ns6/redirect.com.db        ZONE    2022
 ./bin/tests/system/resolver/ns6/root.db                ZONE    2010,2011,2014,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns6/targetns.db    ZONE    2020,2021,2022
+./bin/tests/system/resolver/ns6/tld1.db                ZONE    2022
 ./bin/tests/system/resolver/ns6/to-be-removed.tld.db.in        ZONE    2012,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns7/all-cnames.db  ZONE    2014,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns7/edns-version.tld.db    ZONE    2014,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns7/named2.conf.in CONF-C  2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns7/root.hint      ZONE    2010,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/ns7/server.db.in   ZONE    2011,2016,2018,2019,2020,2021,2022
+./bin/tests/system/resolver/ns7/sub.tld1.db    ZONE    2022
+./bin/tests/system/resolver/ns7/tld2.db                ZONE    2022
 ./bin/tests/system/resolver/prereq.sh          SH      2000,2001,2004,2007,2012,2014,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/setup.sh           SH      2010,2011,2012,2013,2014,2016,2018,2019,2020,2021,2022
 ./bin/tests/system/resolver/tests.sh           SH      2000,2001,2004,2007,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022