Commit 25b33bed authored by Evan Hunt's avatar Evan Hunt

[master] improve handling of qcount=0 replies

4717.	[bug]		Treat replies with QCOUNT=0 as truncated if TC=1,
			FORMERR if TC=0, and log the error correctly.
			[RT #45836]
parent 88d3c4a2
4717. [bug] Treat replies with QCOUNT=0 as truncated if TC=1,
FORMERR if TC=0, and log the error correctly.
[RT #45836]
4716. [placeholder]
--- 9.12.0a1 released ---
......
#!/usr/bin/perl
#
# Copyright (C) 2011, 2012, 2014, 2016 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 http://mozilla.org/MPL/2.0/.
use IO::File;
use IO::Socket;
use Data::Dumper;
use Net::DNS;
use Net::DNS::Packet;
use strict;
# 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 $server_addr = "10.53.0.8";
my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
LocalPort => 5300, Proto => "udp", Reuse => 1) or die "$!";
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
LocalPort => 5300, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
print "listening on $server_addr:5300.\n";
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;
sub handleUDP {
my ($buf) = @_;
my $request;
if ($Net::DNS::VERSION > 0.68) {
$request = new Net::DNS::Packet(\$buf, 0);
$@ and die $@;
} else {
my $err;
($request, $err) = new Net::DNS::Packet(\$buf, 0);
$err and die $err;
}
my @questions = $request->question;
my $qname = $questions[0]->qname;
my $qtype = $questions[0]->qtype;
my $qclass = $questions[0]->qclass;
my $id = $request->header->id;
my $packet = new Net::DNS::Packet();
$packet->header->qr(1);
$packet->header->aa(0);
$packet->header->id($id);
if ($qname eq "truncated.no-questions") {
$packet->header->tc(1);
} else {
$packet->header->tc(0);
}
return $packet->data;
}
sub handleTCP {
my ($buf) = @_;
my $request;
if ($Net::DNS::VERSION > 0.68) {
$request = new Net::DNS::Packet(\$buf, 0);
$@ and die $@;
} else {
my $err;
($request, $err) = new Net::DNS::Packet(\$buf, 0);
$err and die $err;
}
my @questions = $request->question;
my $qname = $questions[0]->qname;
my $qtype = $questions[0]->qtype;
my $qclass = $questions[0]->qclass;
my $id = $request->header->id;
my @results = ();
my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
$packet->header->qr(1);
$packet->header->aa(1);
$packet->header->id($id);
$packet->push("answer", new Net::DNS::RR("$qname 300 A 1.2.3.4"));
push(@results, $packet->data);
return \@results;
}
# 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 = handleUDP($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 = handleTCP($buf);
foreach my $response (@$result) {
$len = length($response);
$n = $conn->syswrite(pack("n", $len), 2);
$n = $conn->syswrite($response, $len);
print " Sent: $n chars via TCP\n";
}
}
$conn->close;
}
}
......@@ -21,3 +21,5 @@ delegation-only. NS ns.delegation-only.
ns.delegation-only. A 10.53.0.6
example.net. NS ns.example.net.
ns.example.net. A 10.53.0.6
no-questions. NS ns.no-questions.
ns.no-questions. A 10.53.0.8
......@@ -750,5 +750,25 @@ grep "flags: qr aa tc; QUERY: 1, ANSWER: 0, AUTHORITY: 0" dig.out.$n > /dev/null
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check that the resolver accepts a reply with empty question section with TC=1 and retries over TCP ($n)"
ret=0
$DIG @10.53.0.5 -p 5300 truncated.no-questions. a > dig.ns5.out.${n} || ret=1
grep "status: NOERROR" dig.ns5.out.${n} > /dev/null || ret=1
grep "ANSWER: 1," dig.ns5.out.${n} > /dev/null || ret=1
grep "1.2.3.4" dig.ns5.out.${n} > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo "I: check that the resolver rejects a reply with empty question section with TC=0 ($n)"
ret=0
$DIG @10.53.0.5 -p 5300 not-truncated.no-questions. a > dig.ns5.out.${n} || ret=1
grep "status: NOERROR" dig.ns5.out.${n} > /dev/null && ret=1
grep "ANSWER: 1," dig.ns5.out.${n} > /dev/null && ret=1
grep "1.2.3.4" dig.ns5.out.${n} > /dev/null && ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:exit status: $status"
[ $status -eq 0 ] || exit 1
......@@ -4856,7 +4856,7 @@ log_formerr(fetchctx_t *fctx, const char *format, ...) {
nsbuf, fctx->info, clmsg, clbuf, msgbuf);
}
static inline isc_result_t
static isc_result_t
same_question(fetchctx_t *fctx) {
isc_result_t result;
dns_message_t *message = fctx->rmessage;
......@@ -4870,7 +4870,32 @@ same_question(fetchctx_t *fctx) {
/*
* XXXRTH Currently we support only one question.
*/
if (message->counts[DNS_SECTION_QUESTION] != 1) {
if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] == 0)) {
if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) {
/*
* If TC=1 and the question section is empty, we
* accept the reply message as a truncated
* answer, to be retried over TCP.
*
* It is really a FORMERR condition, but this is
* a workaround to accept replies from some
* implementations.
*
* Because the question section matching is not
* performed, the worst that could happen is
* that an attacker who gets past the ID and
* source port checks can force the use of
* TCP. This is considered an acceptable risk.
*/
log_formerr(fctx,
"empty question section, "
"accepting it anyway as TC=1");
return (ISC_R_SUCCESS);
} else {
log_formerr(fctx, "empty question section");
return (DNS_R_FORMERR);
}
} else if (ISC_UNLIKELY(message->counts[DNS_SECTION_QUESTION] > 1)) {
log_formerr(fctx, "too many questions");
return (DNS_R_FORMERR);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment