[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.

Closes: https://gitlab.isc.org/isc-projects/bind9/-/issues/5784

Merge branch 'each-security-disable-chaos-recursion' into 'security-main'

See merge request isc-private/bind9!936
This commit is contained in:
Ondřej Surý 2026-05-01 10:13:10 +02:00 committed by Michał Kępień
commit 21c8ba4f0b
No known key found for this signature in database
25 changed files with 514 additions and 127 deletions

View file

@ -1715,6 +1715,7 @@ dlzconfigure_callback(dns_view_t *view, dns_dlzdb_t *dlzdb, dns_zone_t *zone) {
dns_name_t *origin = dns_zone_getorigin(zone);
dns_rdataclass_t zclass = view->rdclass;
dns_zone_setclass(zone, zclass);
RETERR(dns_zonemgr_managezone(named_g_server->zonemgr, zone));
dns_zone_setstats(zone, named_g_server->zonestats);
@ -3926,7 +3927,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));
max_cache_size = configure_max_cache_size(view, maps);
@ -4543,35 +4545,13 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
/*
* 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.
* writes into that view's copy. Other classes don't support
* recursion and don't need hints.
*/
if (view->rdclass == dns_rdataclass_in && view->rootdb == NULL) {
CHECK(configure_rootdb(view, NULL));
}
/*
* If we still have no root 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->rootdb == NULL) {
dns_zone_t *rootzone = NULL;
(void)dns_view_findzone(view, dns_rootname, DNS_ZTFIND_EXACT,
&rootzone);
if (rootzone != NULL) {
dns_zone_detach(&rootzone);
} else if (strcmp(view->name, "_bind") != 0 ||
view->rdclass != dns_rdataclass_chaos)
{
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING,
"no root hints for view '%s'",
view->name);
}
}
/*
* Configure the view's transports (DoT/DoH)
*/
@ -4794,9 +4774,10 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
CHECK(configure_view_acl(vconfig, config, "allow-proxy-on", NULL,
aclctx, isc_g_mctx, &view->proxyonacl));
if (strcmp(view->name, "_bind") != 0 &&
view->rdclass != dns_rdataclass_chaos)
{
if (view->rdclass != dns_rdataclass_in) {
dns_acl_none(isc_g_mctx, &view->recursionacl);
dns_acl_none(isc_g_mctx, &view->recursiononacl);
} else {
CHECK(configure_view_acl(vconfig, config, "allow-recursion",
NULL, aclctx, isc_g_mctx,
&view->recursionacl));

View file

@ -515,6 +515,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
@ -738,5 +739,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

@ -1,3 +1,5 @@
#!/bin/sh -e
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
@ -9,17 +11,9 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
import isctest
# shellcheck source=conf.sh
. ../conf.sh
def test_nohintswarn_bindchaos(ns1):
found = True
try:
with ns1.watch_log_from_start(timeout=1) as watcher:
watcher.wait_for_line("no root hints for view '_bind'")
except isctest.log.watchlog.WatchLogTimeout:
found = False
assert found is False
with ns1.watch_log_from_start() as watcher:
watcher.wait_for_line("no root hints for view 'bar'")
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

@ -47,6 +47,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

@ -1,23 +0,0 @@
options {
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.1; };
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
view _bind {
};
view foo {
};
view bar ch {
};

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

@ -485,8 +485,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 ] || {
@ -497,20 +499,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

@ -783,10 +783,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

@ -566,6 +566,9 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
rdtype = rdataset->type;
REQUIRE(rdataset->rdclass == dns_rdataclass_in);
REQUIRE(dns_rdatatype_isaddr(rdtype));
switch (rdataset->trust) {
case dns_trust_glue:
case dns_trust_additional:
@ -584,8 +587,6 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
rdataset->ttl = ttlclamp(rdataset->ttl);
}
REQUIRE(dns_rdatatype_isaddr(rdtype));
DNS_RDATASET_FOREACH(rdataset) {
/* FIXME: Move to a separate function */
dns_adbnamehooklist_t *hookhead = NULL;

View file

@ -972,6 +972,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

@ -6825,6 +6825,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

@ -2869,13 +2869,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, cfg_aclconfctx_t *aclctx,
isc_mem_t *mctx) {
dns_rdataclass_t vclass, const cfg_obj_t *goptions,
cfg_aclconfctx_t *aclctx, isc_mem_t *mctx) {
dns_acl_t *acl = NULL;
const cfg_obj_t *obj;
isc_result_t result = ISC_R_SUCCESS;
bool retval = true;
if (vclass != dns_rdataclass_in) {
return false;
}
/*
* Check the "recursion" option first.
*/
@ -3827,7 +3831,7 @@ isccfg_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, aclctx, mctx))
!check_recursion(config, voptions, zclass, goptions, aclctx, mctx))
{
cfg_obj_log(zoptions, ISC_LOG_ERROR,
"zone '%s': mirror zones cannot be used if "
@ -5646,6 +5650,17 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
cfg_aclconfctx_create(mctx, &aclctx);
if (vclass != dns_rdataclass_in) {
if (check_recursion(config, voptions, dns_rdataclass_in,
options, aclctx, mctx))
{
cfg_obj_log(opts, 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>
@ -2082,7 +2083,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->inner.attributes & NS_CLIENTATTR_WANTCOOKIE) !=
0 &&
client->message->opcode == dns_opcode_query &&
@ -2102,12 +2105,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;
}
@ -2192,9 +2229,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");
}
@ -2204,12 +2238,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);
@ -2459,6 +2492,10 @@ ns_client_request_continue(void *arg) {
break;
case dns_opcode_update:
CTRACE("update");
if (client->inner.view->rdclass != dns_rdataclass_in) {
ns_client_error(client, DNS_R_NOTIMP);
break;
}
#ifdef HAVE_DNSTAP
dns_dt_send(client->inner.view, DNS_DTTYPE_UQ,
&client->inner.peeraddr,
@ -2472,6 +2509,10 @@ ns_client_request_continue(void *arg) {
break;
case dns_opcode_notify:
CTRACE("notify");
if (client->inner.view->rdclass != dns_rdataclass_in) {
ns_client_error(client, DNS_R_NOTIMP);
break;
}
ns_client_settimeout(client, 60);
ns_notify_start(client, client->inner.handle);
break;
@ -2835,7 +2876,7 @@ ns_client_dumpmessage(ns_client_t *client, const char *reason) {
int len = 1024;
isc_result_t result;
if (!isc_log_wouldlog(ISC_LOG_DEBUG(1))) {
if (!isc_log_wouldlog(ISC_LOG_DEBUG(1)) || reason == NULL) {
return;
}

View file

@ -966,7 +966,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;
@ -1311,7 +1313,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
@ -1451,9 +1456,8 @@ add_rr_prepare_action(void *data, rr_t *rr) {
* 'rdata', and 'ttl', respectively.
*/
static void
get_current_rr(dns_rdataclass_t zoneclass, dns_name_t *name, dns_rdata_t *rdata,
dns_rdatatype_t *covers, dns_ttl_t *ttl,
dns_rdataclass_t *update_class) {
get_current_rr(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;
isc_result_t result;
rdataset = ISC_LIST_HEAD(name->list);
@ -1466,7 +1470,7 @@ get_current_rr(dns_rdataclass_t zoneclass, dns_name_t *name, dns_rdata_t *rdata,
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;
}
/*%
@ -1562,7 +1566,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;
@ -1574,11 +1577,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,
@ -1623,13 +1628,12 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
dns_rdataclass_t update_class;
INSIST(ssutable == NULL || update < maxbytypelen);
get_current_rr(zoneclass, name, &rdata, &covers, &ttl,
&update_class);
get_current_rr(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
@ -1643,6 +1647,7 @@ send_update(ns_client_t *client, dns_zone_t *zone) {
CLEANUP(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);
@ -1731,7 +1736,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))
{
@ -2629,7 +2633,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;
@ -2646,9 +2649,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_getkasp(zone) != NULL) && !dns_zone_israw(zone);
is_signing = is_inline || is_maintain;
@ -2669,8 +2673,7 @@ update_action(void *arg) {
dns_rdataclass_t update_class;
bool flag;
get_current_rr(zoneclass, name, &rdata, &covers, &ttl,
&update_class);
get_current_rr(name, &rdata, &covers, &ttl, &update_class);
if (ttl != 0) {
PREREQFAILC(DNS_R_FORMERR,
@ -2733,7 +2736,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;" */
temp_append(&temp, name, &rdata);
} else {
@ -2784,10 +2787,9 @@ update_action(void *arg) {
INSIST(ssutable == NULL || maxidx < maxbytypelen);
get_current_rr(zoneclass, name, &rdata, &covers, &ttl,
&update_class);
get_current_rr(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.
*/