[9.20] [CVE-2026-5946] sec: usr: Disable recursion, UPDATE, and NOTIFY for non-IN views

Recursion, dynamic updates (UPDATE), and zone change notifications
(NOTIFY) are now disabled for views with a class other than IN
(such as CHAOS or HESIOD); authoritative service for non-IN zones
(e.g. version.bind in class CHAOS) continues to work as before.
Servers configured with recursion yes in a non-IN view will log a
warning at startup, and named-checkconf flags the same condition.
UPDATE and NOTIFY messages that specify the meta-classes ANY or NONE
in the question section are now rejected with FORMERR.

This addresses a set of closely related security issues collectively
identified as CVE-2026-5946. ISC would like to thank Mcsky23 for
bringing these issues to our attention.

Backport of https://gitlab.isc.org/isc-private/bind9/-/merge_requests/936

Merge branch 'each-security-disable-chaos-recursion-security-bind-9.20' into 'security-bind-9.20'

See merge request isc-private/bind9!1009
This commit is contained in:
Ondřej Surý 2026-05-04 12:46:34 +02:00 committed by Michał Kępień
commit 4c1547e4b5
No known key found for this signature in database
26 changed files with 537 additions and 94 deletions

View file

@ -1917,10 +1917,12 @@ dlzconfigure_callback(dns_view_t *view, dns_dlzdb_t *dlzdb, dns_zone_t *zone) {
dns_rdataclass_t zclass = view->rdclass;
isc_result_t result;
dns_zone_setclass(zone, zclass);
result = dns_zonemgr_managezone(named_g_server->zonemgr, zone);
if (result != ISC_R_SUCCESS) {
return result;
}
dns_zone_setstats(zone, named_g_server->zonestats);
return named_zone_configure_writeable_dlz(dlzdb, zone, zclass, origin);
@ -4428,7 +4430,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
obj = NULL;
result = named_config_get(maps, "recursion", &obj);
INSIST(result == ISC_R_SUCCESS);
view->recursion = cfg_obj_asboolean(obj);
view->recursion = (view->rdclass == dns_rdataclass_in &&
cfg_obj_asboolean(obj));
if (named_g_maxcachesize != 0) {
/*
@ -5144,35 +5147,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
}
/*
* We have default hints for class IN if we need them.
* We have default root hints for class IN if we need them.
* Each view gets its own rootdb so a priming response only
* writes into that view's copy. Other classes don't support
* recursion and don't need hints.
*/
if (view->rdclass == dns_rdataclass_in && view->hints == NULL) {
dns_view_sethints(view, named_g_server->in_roothints);
}
/*
* If we still have no hints, this is a non-IN view with no
* "hints zone" configured. Issue a warning, except if this
* is a root server. Root servers never need to consult
* their hints, so it's no point requiring users to configure
* them.
*/
if (view->hints == NULL) {
dns_zone_t *rootzone = NULL;
(void)dns_view_findzone(view, dns_rootname, DNS_ZTFIND_EXACT,
&rootzone);
if (rootzone != NULL) {
dns_zone_detach(&rootzone);
need_hints = false;
}
if (need_hints) {
isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING,
"no root hints for view '%s'",
view->name);
}
}
/*
* Configure the view's transports (DoT/DoH)
*/
@ -5408,14 +5391,13 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
"allow-proxy-on", NULL, actx, named_g_mctx,
&view->proxyonacl));
if (strcmp(view->name, "_bind") != 0 &&
view->rdclass != dns_rdataclass_chaos)
{
/* named.conf only */
if (view->rdclass != dns_rdataclass_in) {
dns_acl_none(named_g_mctx, &view->recursionacl);
dns_acl_none(named_g_mctx, &view->recursiononacl);
} else {
CHECK(configure_view_acl(vconfig, config, NULL,
"allow-recursion", NULL, actx,
named_g_mctx, &view->recursionacl));
/* named.conf only */
CHECK(configure_view_acl(vconfig, config, NULL,
"allow-recursion-on", NULL, actx,
named_g_mctx, &view->recursiononacl));

View file

@ -703,7 +703,7 @@ $DIG -p ${PORT} @10.53.1.2 d.normal.example a >dig.out.ns3.4.$n || ret=1
grep 'recursion requested but not available' dig.out.ns3.4.$n >/dev/null || ret=1
grep 'status: REFUSED' dig.out.ns3.4.$n >/dev/null || ret=1
grep 'EDE: 18 (Prohibited)' dig.out.ns3.4.$n >/dev/null || ret=1
nextpart ns3/named.run | grep 'allow-recursion-on did not match' >/dev/null || ret=1
nextpart ns3/named.run | grep 'allow-query-cache-on did not match' >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -545,6 +545,7 @@ $CHECKCONF -l good.conf \
| grep -v "is not implemented" \
| grep -v "is not recommended" \
| grep -v "no longer exists" \
| grep -v "recursion will be disabled" \
| grep -v "is obsolete" >checkconf.out$n || ret=1
diff good.zonelist checkconf.out$n >diff.out$n || ret=1
if [ $ret -ne 0 ]; then
@ -805,5 +806,16 @@ if [ $ret != 0 ]; then
fi
status=$((status + ret))
n=$((n + 1))
echo_i "check 'recursion yes;' is warned and disabled in a non-IN view ($n)"
ret=0
$CHECKCONF warn-chaos-recursion.conf >checkconf.out$n 2>&1 || ret=1
grep -F "recursion will be disabled" checkconf.out$n >/dev/null || ret=1
if [ $ret != 0 ]; then
echo_i "failed"
ret=1
fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -0,0 +1,12 @@
options {
directory ".";
};
view chaos ch {
match-clients { any; };
recursion yes;
zone "." {
type hint;
file "chaos.hints";
};
};

View file

@ -0,0 +1,4 @@
. CH NS ns.root.
ns.root. CH A ns.root. 1
ns.root. CH AAAA \# 1 00

View file

@ -0,0 +1,31 @@
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
view chaos ch {
match-clients { any; };
recursion yes;
zone "." {
type hint;
file "chaos.db";
};
zone "version.bind" {
type primary;
database "_builtin version";
};
};

View file

@ -0,0 +1,6 @@
$TTL 300
@ CH SOA ns.example. hostmaster.example. 1 3600 1200 604800 300
@ CH NS ns.example.
ns CH TXT "ns"
a CH A target.example. 1
target CH TXT "target"

View file

@ -0,0 +1,11 @@
$ORIGIN 1.0.0.127.in-addr.arpa.
$TTL 300
@ IN SOA ns hostmaster 1 3600 900 604800 300
@ IN NS ns
ns IN A 127.0.0.1
@ IN KX 10 target.example.
@ IN PX 10 map822.example. mapx400.example.
@ IN NSAP 0x47000580ffff0000000001e133ffffff00016200
@ IN NSAP-PTR target.example.
@ in EID \# 01 aa

View file

@ -0,0 +1,42 @@
options {
directory ".";
query-source address 10.53.0.2;
notify-source 10.53.0.2;
transfer-source 10.53.0.2;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.2; };
listen-on-v6 { none; };
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
view default {
match-clients { any; };
recursion no;
dnssec-validation no;
zone "1.0.0.127.in-addr.arpa." {
type primary;
file "localhost.db";
update-policy {
grant * tcp-self . ANY;
};
};
};
view chaos ch {
match-clients { any; };
recursion no;
zone example {
type primary;
file "example.db";
allow-update { any; };
};
};

View file

@ -0,0 +1,28 @@
options {
directory ".";
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
view chaos ch {
match-clients { any; };
recursion yes;
dnssec-validation no;
forward only;
forwarders port @PORT@ { 10.53.0.2; };
deny-answer-addresses { 0.0.0.0/0; ::/0; };
};

View file

@ -0,0 +1,19 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../conf.sh
cp ns1/chaos.db.in ns1/chaos.db
cp ns2/example.db.in ns2/example.db
cp ns2/localhost.db.in ns2/localhost.db

View file

@ -0,0 +1,54 @@
# 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.
import dns.opcode
import pytest
import isctest
pytestmark = pytest.mark.extra_artifacts(
[
"*/*.db",
]
)
def test_chaos_recursion():
msg = isctest.query.create("foo.example.", "TXT", qclass="CH")
res = isctest.query.udp(msg, "10.53.0.1")
isctest.check.refused(res)
def test_chaos_auth():
msg = isctest.query.create("a.example.", "A", qclass="CH")
res = isctest.query.udp(msg, "10.53.0.2")
isctest.check.noerror(res)
def test_chaos_forward():
msg = isctest.query.create("a.example.", "A", qclass="CH")
res = isctest.query.udp(msg, "10.53.0.3")
isctest.check.refused(res)
def test_chaos_notify():
msg = isctest.query.create("example.", "SOA", qclass="CH", rd=False, dnssec=False)
msg.set_opcode(dns.opcode.NOTIFY)
msg.flags = dns.opcode.to_flags(dns.opcode.NOTIFY)
res = isctest.query.udp(msg, "10.53.0.2")
isctest.check.notimp(res)
def test_query_class_none():
msg = isctest.query.create("example.", "A", qclass="NONE")
res = isctest.query.udp(msg, "10.53.0.2")
isctest.check.formerr(res)

View file

@ -0,0 +1,137 @@
# 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.
import socket
import struct
from dns import message, rdataclass, rdatatype, update
import pytest
import isctest
pytestmark = pytest.mark.extra_artifacts(
[
"*/*.db",
]
)
def encode_name(name: str) -> bytes:
out = b""
for label in name.rstrip(".").split("."):
out += bytes([len(label)]) + label.encode("ascii")
return out + b"\x00"
@pytest.mark.parametrize(
"rdtype,rdclass,ttl,rdata",
[
(rdatatype.SRV, rdataclass.NONE, 0, b"\x00\x00\x00\x00\x00\x00\x01"),
(rdatatype.SRV, rdataclass.NONE, 0, b"\x00"),
(rdatatype.KX, rdataclass.NONE, 0, b""),
(rdatatype.PX, rdataclass.NONE, 0, b""),
(rdatatype.NSAP, rdataclass.NONE, 0, b""),
(rdatatype.NSAP_PTR, rdataclass.NONE, 0, b""),
(31, rdataclass.NONE, 0, b""), # dnspython doesn't define type EID
],
)
def test_class_invalid(rdtype, rdclass, ttl, rdata, named_port):
# these update messages are badly formatted, so we construct
# them manually instead of using dnspython.
# opcode=UPDATE, 1 RRset in ZONE, 1 RRset in UPDATE
header = struct.pack("!HHHHHH", 0, 0x2800, 1, 0, 1, 0)
# ZONE section: QNAME=<zone>, QTYPE=SOA, QCLASS=ANY
zone_q = encode_name("1.0.0.127.in-addr.arpa") + struct.pack("!HH", 6, 255)
# UPDATE section RR:
update_rr = (
encode_name("1.0.0.127.in-addr.arpa")
+ struct.pack("!HHIH", rdtype, rdclass, ttl, len(rdata))
+ rdata
)
m = header + zone_q + update_rr
packet = struct.pack("!H", len(m)) + m
with socket.create_connection(
("10.53.0.2", named_port), source_address=("127.0.0.1", 0), timeout=2.0
) as s:
s.sendall(packet)
try:
rwire = s.recv(4096)
res = message.from_wire(rwire)
isctest.check.formerr(res)
except Exception: # pylint: disable=broad-except
pass
# check the server is answering
msg = isctest.query.create("1.0.0.127.in-addr.arpa", "SRV")
res = isctest.query.udp(msg, "10.53.0.2")
isctest.check.noerror(res)
isctest.check.rr_count_eq(res.answer, 0)
@pytest.mark.parametrize(
"rdtype,rdata",
[
(rdatatype.SVCB, "\\# 02 0000"),
(rdatatype.WKS, "\\# 02 4142"),
(rdatatype.WKS, "\\# 02 4344"),
],
)
def test_class_chaosupdate(rdtype, rdata):
up = update.UpdateMessage("example.", rdclass=rdataclass.CHAOS)
up.add("foo.example.", 300, rdtype, rdata)
res = isctest.query.tcp(up, "10.53.0.2")
isctest.check.notimp(res)
def test_class_undefined(ns2):
up = update.UpdateMessage(".", rdclass=257)
up.present(".", 0)
up.answer[0].rdclass = rdataclass.NONE
with ns2.watch_log_from_here() as watcher:
res = isctest.query.tcp(up, "10.53.0.2")
isctest.check.notimp(res)
watcher.wait_for_line("invalid message class: CLASS257")
def test_class_zero(ns2):
up = update.UpdateMessage(".", rdclass=0)
up.present(".", 0)
up.answer[0].rdclass = rdataclass.NONE
with ns2.watch_log_from_here() as watcher:
res = isctest.query.tcp(up, "10.53.0.2")
isctest.check.formerr(res)
watcher.wait_for_line("message class could not be determined")
def test_class_any(ns2):
up = update.UpdateMessage(".", rdclass=rdataclass.ANY)
up.present(".", 0)
up.answer[0].rdclass = rdataclass.NONE
with ns2.watch_log_from_here() as watcher:
res = isctest.query.tcp(up, "10.53.0.2")
isctest.check.formerr(res)
watcher.wait_for_line("message parsing failed: FORMERR")
def test_class_none(ns2):
up = update.UpdateMessage(".", rdclass=rdataclass.NONE)
up.present(".", 0)
up.answer[0].rdclass = rdataclass.NONE
with ns2.watch_log_from_here() as watcher:
res = isctest.query.tcp(up, "10.53.0.2")
isctest.check.formerr(res)
watcher.wait_for_line("message parsing failed: FORMERR")

View file

@ -46,6 +46,10 @@ def servfail(message: dns.message.Message) -> None:
rcode(message, dns.rcode.SERVFAIL)
def formerr(message: dns.message.Message) -> None:
rcode(message, dns.rcode.FORMERR)
def adflag(message: dns.message.Message) -> None:
assert (message.flags & dns.flags.AD) != 0, str(message)

View file

@ -136,6 +136,7 @@ def create(
qtype,
qclass=dns.rdataclass.IN,
dnssec: bool = True,
rd: bool = True,
cd: bool = False,
ad: bool = True,
) -> dns.message.Message:
@ -143,7 +144,9 @@ def create(
msg = dns.message.make_query(
qname, qtype, qclass, use_edns=True, want_dnssec=dnssec
)
msg.flags = dns.flags.RD
msg.flags = 0
if rd:
msg.flags = dns.flags.RD
if ad:
msg.flags |= dns.flags.AD
if cd:

View file

@ -35,6 +35,7 @@ update.nil IN SOA ns1.example.nil. hostmaster.example.nil. (
3600 ; minimum (1 hour)
)
update.nil. NS ns1.update.nil.
update.nil. KX 0 .
ns1.update.nil. A 10.53.0.2
ns2.update.nil. AAAA ::1
EOF

View file

@ -340,8 +340,10 @@ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
n=$((n + 1))
ret=0
echo_i "check that TYPE=0 update is handled ($n)"
nextpart ns1/named.run >/dev/null
echo "a0e4280000010000000100000000060001c00c000000fe000000000000" \
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp -b >/dev/null || ret=1
wait_for_log 2 "message parsing failed: FORMERR" ns1/named.run || ret=1
$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
[ $ret = 0 ] || {
@ -352,20 +354,10 @@ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
n=$((n + 1))
ret=0
echo_i "check that TYPE=0 additional data is handled ($n)"
nextpart ns1/named.run >/dev/null
echo "a0e4280000010000000000010000060001c00c000000fe000000000000" \
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
[ $ret = 0 ] || {
echo_i "failed"
status=1
}
n=$((n + 1))
ret=0
echo_i "check that update to undefined class is handled ($n)"
echo "a0e4280000010001000000000000060101c00c000000fe000000000000" \
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp -b >/dev/null || ret=1
wait_for_log 2 "message parsing failed: FORMERR" ns1/named.run || ret=1
$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
[ $ret = 0 ] || {

View file

@ -40,6 +40,7 @@
# -p <port>: specify port
# -t <protocol>: specify UDP or TCP
# -r <num>: send packet <num> times
# -b: blocking io
# -d: dump response packets
#
# If not specified, address defaults to 127.0.0.1, port to 53, protocol
@ -51,6 +52,8 @@ use strict;
use Getopt::Std;
use IO::File;
use IO::Socket;
use Net::DNS;
use Net::DNS::Packet;
sub usage {
print ("Usage: packet.pl [-a address] [-d] [-p port] [-t (tcp|udp)] [-r <repeats>] [file]\n");
@ -61,8 +64,6 @@ my $sock;
my $proto;
sub dumppacket {
use Net::DNS;
use Net::DNS::Packet;
my $rin;
my $rout;
@ -96,7 +97,7 @@ sub dumppacket {
}
my %options={};
getopts("a:dp:t:r:", \%options);
getopts("a:bdp:t:r:", \%options);
my $addr = "127.0.0.1";
$addr = $options{a} if defined $options{a};
@ -111,6 +112,8 @@ usage if ($proto !~ /^(udp|tcp)$/);
my $repeats = 1;
$repeats = $options{r} if defined $options{r};
my $blocking = defined $options{b} ? 1 : 0;
my $file = "STDIN";
if (@ARGV >= 1) {
my $filename = shift @ARGV;
@ -132,8 +135,22 @@ my $len = length $data;
my $output = unpack("H*", $data);
print ("sending $repeats time(s): $output\n");
if (defined $options{d}) {
my $request;
if ($Net::DNS::VERSION > 0.68) {
$request = new Net::DNS::Packet(\$data, 0);
$@ and die $@;
} else {
my $err;
($request, $err) = new Net::DNS::Packet(\$data, 0);
$err and die $err;
}
$request->print;
}
$sock = IO::Socket::INET->new(PeerAddr => $addr, PeerPort => $port,
Blocking => 0,
Blocking => $blocking,
Proto => $proto,) or die "$!";
STDOUT->autoflush(1);

View file

@ -782,10 +782,12 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking NXDOMAIN is returned when querying non existing domain in CH class ($n)"
echo_i "checking REFUSED is returned when querying non existing domain in CH class ($n)"
ret=0
dig_with_opts @10.53.0.1 id.hostname txt ch >dig.ns1.out.${n} || ret=1
grep "status: NXDOMAIN" dig.ns1.out.${n} >/dev/null || ret=1
dig_with_opts @10.53.0.1 hostname.chaostest txt ch >dig.ns1.out.1.${n} || ret=1
grep "status: NOERROR" dig.ns1.out.1.${n} >/dev/null || ret=1
dig_with_opts @10.53.0.1 id.hostname txt ch >dig.ns1.out.2.${n} || ret=1
grep "status: REFUSED" dig.ns1.out.2.${n} >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -25,6 +25,11 @@ dig_cmd() {
"$DIG" $DIGOPTS "$@" | grep -v '^;'
}
dig_full() {
# shellcheck disable=SC2086
"$DIG" $DIGOPTS "$@"
}
n=$((n + 1))
echo_i "querying for various representations of an IN A record ($n)"
for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
@ -81,8 +86,8 @@ n=$((n + 1))
echo_i "querying for various representations of a CLASS10 TYPE1 record ($n)"
for i in 1 2; do
ret=0
dig_cmd +short @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n
echo '\# 4 0A000001' | diff - dig.out.$i.test$n || ret=1
dig_full @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n
grep -q "NOTIMP" dig.out.$i.test$n || ret=1
if [ $ret != 0 ]; then
echo_i "#$i failed"
fi
@ -93,8 +98,8 @@ n=$((n + 1))
echo_i "querying for various representations of a CLASS10 TXT record ($n)"
for i in 1 2 3 4; do
ret=0
dig_cmd +short @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n
echo '"hello"' | diff - dig.out.$i.test$n || ret=1
dig_full @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n
grep -q "NOTIMP" dig.out.$i.test$n || ret=1
if [ $ret != 0 ]; then
echo_i "#$i failed"
fi
@ -105,8 +110,8 @@ n=$((n + 1))
echo_i "querying for various representations of a CLASS10 TYPE123 record ($n)"
for i in 1 2; do
ret=0
dig_cmd +short @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n
echo '\# 1 00' | diff - dig.out.$i.test$n || ret=1
dig_full @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n
grep -q "NOTIMP" dig.out.$i.test$n || ret=1
if [ $ret != 0 ]; then
echo_i "#$i failed"
fi

View file

@ -567,6 +567,9 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
rdtype = rdataset->type;
REQUIRE(rdataset->rdclass == dns_rdataclass_in);
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
switch (rdataset->trust) {
case dns_trust_glue:
case dns_trust_additional:
@ -579,8 +582,6 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
rdataset->ttl = ttlclamp(rdataset->ttl);
}
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
result = dns_rdataset_next(rdataset))
{

View file

@ -1073,6 +1073,17 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
rdtype = isc_buffer_getuint16(source);
rdclass = isc_buffer_getuint16(source);
/*
* Notify and update messages need to specify the data class.
*/
if ((msg->opcode == dns_opcode_update ||
msg->opcode == dns_opcode_notify) &&
(rdclass == dns_rdataclass_none ||
rdclass == dns_rdataclass_any))
{
DO_ERROR(DNS_R_FORMERR);
}
/*
* If this class is different than the one we already read,
* this is an error.

View file

@ -6967,6 +6967,13 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
return true;
}
/*
* deny-answer-address doesn't apply to non-IN classes.
*/
if (rdataset->rdclass != dns_rdataclass_in) {
return true;
}
/*
* Otherwise, search the filter list for a match for each
* address record. If a match is found, the address should be

View file

@ -3041,13 +3041,17 @@ check_mirror_zone_notify(const cfg_obj_t *zoptions, const char *znamestr,
*/
static bool
check_recursion(const cfg_obj_t *config, const cfg_obj_t *voptions,
const cfg_obj_t *goptions, isc_log_t *logctx,
cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
dns_rdataclass_t vclass, const cfg_obj_t *goptions,
isc_log_t *logctx, cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
dns_acl_t *acl = NULL;
const cfg_obj_t *obj;
isc_result_t result;
bool retval = true;
if (vclass != dns_rdataclass_in) {
return false;
}
/*
* Check the "recursion" option first.
*/
@ -3905,7 +3909,8 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
* contradicts the purpose of the former.
*/
if (ztype == CFG_ZONE_MIRROR &&
!check_recursion(config, voptions, goptions, logctx, actx, mctx))
!check_recursion(config, voptions, zclass, goptions, logctx, actx,
mctx))
{
cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
"zone '%s': mirror zones cannot be used if "
@ -5719,6 +5724,17 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
cfg_aclconfctx_create(mctx, &actx);
if (vclass != dns_rdataclass_in) {
if (check_recursion(config, voptions, dns_rdataclass_in,
options, logctx, actx, mctx))
{
cfg_obj_log(opts, logctx, ISC_LOG_WARNING,
"recursion will be disabled for "
"non-IN view '%s'",
viewname);
}
}
if (voptions != NULL) {
(void)cfg_map_get(voptions, "zone", &zones);
} else {

View file

@ -42,6 +42,7 @@
#include <dns/dispatch.h>
#include <dns/dnstap.h>
#include <dns/edns.h>
#include <dns/enumclass.h>
#include <dns/message.h>
#include <dns/peer.h>
#include <dns/rcode.h>
@ -2041,7 +2042,9 @@ ns_client_request(isc_nmhandle_t *handle, isc_result_t eresult,
}
}
if (client->message->rdclass == 0) {
char classbuf[DNS_RDATACLASS_FORMATSIZE];
switch (client->message->rdclass) {
case dns_rdataclass_reserved0:
if ((client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0 &&
client->message->opcode == dns_opcode_query &&
client->message->counts[DNS_SECTION_QUESTION] == 0U)
@ -2060,12 +2063,46 @@ ns_client_request(isc_nmhandle_t *handle, isc_result_t eresult,
return;
}
ns_client_dumpmessage(client,
"message class could not be determined");
ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
return;
case dns_rdataclass_in:
break;
case dns_rdataclass_chaos:
break;
case dns_rdataclass_hs:
break;
case dns_rdataclass_none:
if (client->message->opcode != dns_opcode_update) {
ns_client_dumpmessage(client,
"message class NONE can be only "
"used in DNS updates");
ns_client_error(client, DNS_R_FORMERR);
return;
}
break;
case dns_rdataclass_any:
/*
* Required for TKEY negotiation.
*/
if (client->message->tkey == 0) {
ns_client_dumpmessage(client,
"message class ANY can be only "
"used for TKEY negotiation");
ns_client_error(client, DNS_R_FORMERR);
return;
}
break;
default:
dns_rdataclass_format(client->message->rdclass, classbuf,
sizeof(classbuf));
ns_client_dumpmessage(client, NULL);
ns_client_log(client, NS_LOGCATEGORY_CLIENT,
NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
"message class could not be determined");
ns_client_dumpmessage(client, "message class could not be "
"determined");
ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
"invalid message class: %s", classbuf);
ns_client_error(client, DNS_R_NOTIMP);
return;
}
@ -2149,9 +2186,6 @@ ns_client_request_continue(void *arg) {
"SIG(0) checks quota reached");
if (can_log_sigchecks_quota()) {
ns_client_log(client, NS_LOGCATEGORY_CLIENT,
NS_LOGMODULE_CLIENT, ISC_LOG_INFO,
"SIG(0) checks quota reached");
ns_client_dumpmessage(
client, "SIG(0) checks quota reached");
}
@ -2161,12 +2195,11 @@ ns_client_request_continue(void *arg) {
dns_rdataclass_format(client->message->rdclass,
classname, sizeof(classname));
ns_client_dumpmessage(client, NULL);
ns_client_log(client, NS_LOGCATEGORY_CLIENT,
NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
"no matching view in class '%s'",
classname);
ns_client_dumpmessage(client,
"no matching view in class");
}
dns_ede_add(&client->edectx, DNS_EDE_PROHIBITED, NULL);
@ -2413,6 +2446,10 @@ ns_client_request_continue(void *arg) {
break;
case dns_opcode_update:
CTRACE("update");
if (client->view->rdclass != dns_rdataclass_in) {
ns_client_error(client, DNS_R_NOTIMP);
break;
}
#ifdef HAVE_DNSTAP
dns_dt_send(client->view, DNS_DTTYPE_UQ, &client->peeraddr,
&client->destsockaddr, transport_type, NULL,
@ -2423,6 +2460,10 @@ ns_client_request_continue(void *arg) {
break;
case dns_opcode_notify:
CTRACE("notify");
if (client->view->rdclass != dns_rdataclass_in) {
ns_client_error(client, DNS_R_NOTIMP);
break;
}
ns_client_settimeout(client, 60);
ns_notify_start(client, client->handle);
break;
@ -2796,7 +2837,7 @@ ns_client_dumpmessage(ns_client_t *client, const char *reason) {
int len = 1024;
isc_result_t result;
if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) {
if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1)) || reason == NULL) {
return;
}

View file

@ -997,7 +997,9 @@ ssu_checkrr(void *data, rr_t *rr) {
RUNTIME_CHECK(result == ISC_R_SUCCESS);
target = &ptr.ptr;
}
if (rr->rdata.type == dns_rdatatype_srv) {
if (rr->rdata.rdclass == dns_rdataclass_in &&
rr->rdata.type == dns_rdatatype_srv)
{
result = dns_rdata_tostruct(&rr->rdata, &srv, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
target = &srv.target;
@ -1352,7 +1354,10 @@ replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
return true;
}
}
if (db_rr->type == dns_rdatatype_wks) {
if (db_rr->rdclass == dns_rdataclass_in &&
db_rr->type == dns_rdatatype_wks)
{
/*
* Compare the address and protocol fields only. These
* form the first five bytes of the RR data. Do a
@ -1495,8 +1500,7 @@ cleanup:
* 'rdata', and 'ttl', respectively.
*/
static void
get_current_rr(dns_message_t *msg, dns_section_t section,
dns_rdataclass_t zoneclass, dns_name_t **name,
get_current_rr(dns_message_t *msg, dns_section_t section, dns_name_t **name,
dns_rdata_t *rdata, dns_rdatatype_t *covers, dns_ttl_t *ttl,
dns_rdataclass_t *update_class) {
dns_rdataset_t *rdataset;
@ -1512,7 +1516,7 @@ get_current_rr(dns_message_t *msg, dns_section_t section,
dns_rdataset_current(rdataset, rdata);
INSIST(dns_rdataset_next(rdataset) == ISC_R_NOMORE);
*update_class = rdata->rdclass;
rdata->rdclass = zoneclass;
rdata->rdclass = dns_rdataclass_in;
}
/*%
@ -1612,7 +1616,6 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
dns_message_t *request = client->message;
isc_mem_t *mctx = client->manager->mctx;
dns_aclenv_t *env = client->manager->aclenv;
dns_rdataclass_t zoneclass;
dns_rdatatype_t covers;
dns_name_t *zonename = NULL;
unsigned int *maxbytype = NULL;
@ -1624,11 +1627,13 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
CHECK(dns_zone_getdb(zone, &db));
zonename = dns_db_origin(db);
zoneclass = dns_db_class(db);
dns_zone_getssutable(zone, &ssutable);
options = dns_zone_getoptions(zone);
dns_db_currentversion(db, &ver);
/* Updates are only supported for class IN. */
INSIST(dns_zone_getclass(zone) == dns_rdataclass_in);
/*
* Update message processing can leak record existence information
* so check that we are allowed to query this zone. Additionally,
@ -1678,13 +1683,13 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
INSIST(ssutable == NULL || update < maxbytypelen);
get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
&rdata, &covers, &ttl, &update_class);
get_current_rr(request, DNS_SECTION_UPDATE, &name, &rdata,
&covers, &ttl, &update_class);
if (!dns_name_issubdomain(name, zonename)) {
FAILC(DNS_R_NOTZONE, "update RR is outside zone");
}
if (update_class == zoneclass) {
if (update_class == dns_rdataclass_in) {
/*
* Check for meta-RRs. The RFC2136 pseudocode says
* check for ANY|AXFR|MAILA|MAILB, but the text adds
@ -1698,6 +1703,7 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
CHECK(DNS_R_REFUSED);
}
if ((options & DNS_ZONEOPT_CHECKSVCB) != 0 &&
rdata.rdclass == dns_rdataclass_in &&
rdata.type == dns_rdatatype_svcb)
{
result = dns_rdata_checksvcb(name, &rdata);
@ -1785,7 +1791,6 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
}
if (update_class == dns_rdataclass_any &&
zoneclass == dns_rdataclass_in &&
(rdata.type == dns_rdatatype_ptr ||
rdata.type == dns_rdatatype_srv))
{
@ -2717,7 +2722,6 @@ update_action(void *arg) {
isc_mem_t *mctx = client->manager->mctx;
dns_rdatatype_t covers;
dns_message_t *request = client->message;
dns_rdataclass_t zoneclass;
dns_name_t *zonename = NULL;
dns_fixedname_t tmpnamefixed;
dns_name_t *tmpname = NULL;
@ -2734,9 +2738,10 @@ update_action(void *arg) {
CHECK(dns_zone_getdb(zone, &db));
zonename = dns_db_origin(db);
zoneclass = dns_db_class(db);
options = dns_zone_getoptions(zone);
INSIST(dns_zone_getclass(zone) == dns_rdataclass_in);
is_inline = (!dns_zone_israw(zone) && dns_zone_issecure(zone));
is_maintain = ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_MAINTAIN) != 0);
is_signing = is_inline || (!is_inline && is_maintain);
@ -2761,8 +2766,8 @@ update_action(void *arg) {
dns_rdataclass_t update_class;
bool flag;
get_current_rr(request, DNS_SECTION_PREREQUISITE, zoneclass,
&name, &rdata, &covers, &ttl, &update_class);
get_current_rr(request, DNS_SECTION_PREREQUISITE, &name, &rdata,
&covers, &ttl, &update_class);
if (ttl != 0) {
PREREQFAILC(DNS_R_FORMERR,
@ -2825,7 +2830,7 @@ update_action(void *arg) {
"prerequisite not satisfied");
}
}
} else if (update_class == zoneclass) {
} else if (update_class == dns_rdataclass_in) {
/* "temp<rr.name, rr.type> += rr;" */
result = temp_append(&temp, name, &rdata);
if (result != ISC_R_SUCCESS) {
@ -2887,10 +2892,10 @@ update_action(void *arg) {
INSIST(ssutable == NULL || update < maxbytypelen);
get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
&rdata, &covers, &ttl, &update_class);
get_current_rr(request, DNS_SECTION_UPDATE, &name, &rdata,
&covers, &ttl, &update_class);
if (update_class == zoneclass) {
if (update_class == dns_rdataclass_in) {
/*
* RFC1123 doesn't allow MF and MD in master files.
*/