Merge tag 'v9.20.21' into bind-9.20

This commit is contained in:
Michał Kępień 2026-03-25 14:24:13 +00:00
commit b040b566fe
31 changed files with 1070 additions and 47 deletions

View file

@ -24,6 +24,8 @@
#include <sys/types.h>
#include <unistd.h>
#include <dns/acl.h>
#ifdef HAVE_DNSTAP
#include <fstrm.h>
#endif
@ -280,10 +282,10 @@ struct zonelistentry {
* asynchronously.
*/
typedef struct matching_view_ctx {
isc_netaddr_t *srcaddr;
isc_netaddr_t *destaddr;
isc_netaddr_t srcaddr;
isc_netaddr_t destaddr;
dns_message_t *message;
dns_aclenv_t *env;
dns_aclenv_t *aclenv;
ns_server_t *sctx;
isc_loop_t *loop;
isc_job_cb cb;
@ -10358,6 +10360,8 @@ get_matching_view_done(void *cbarg) {
mvctx->cb(mvctx->cbarg);
dns_aclenv_detach(&mvctx->aclenv);
if (mvctx->quota_result == ISC_R_SUCCESS) {
isc_quota_release(&mvctx->sctx->sig0checksquota);
}
@ -10399,10 +10403,10 @@ get_matching_view_continue(void *cbarg, isc_result_t result) {
tsig = dns_tsigkey_identity(mvctx->message->tsigkey);
}
if (dns_acl_allowed(mvctx->srcaddr, tsig, mvctx->view->matchclients,
mvctx->env) &&
dns_acl_allowed(mvctx->destaddr, tsig,
mvctx->view->matchdestinations, mvctx->env) &&
if (dns_acl_allowed(&mvctx->srcaddr, tsig, mvctx->view->matchclients,
mvctx->aclenv) &&
dns_acl_allowed(&mvctx->destaddr, tsig,
mvctx->view->matchdestinations, mvctx->aclenv) &&
!(mvctx->view->matchrecursiveonly &&
(mvctx->message->flags & DNS_MESSAGEFLAG_RD) == 0))
{
@ -10474,9 +10478,9 @@ get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
matching_view_ctx_t *mvctx = isc_mem_get(message->mctx, sizeof(*mvctx));
*mvctx = (matching_view_ctx_t){
.srcaddr = srcaddr,
.destaddr = destaddr,
.env = env,
.srcaddr = *srcaddr,
.destaddr = *destaddr,
.aclenv = dns_aclenv_ref(env),
.cb = cb,
.cbarg = cbarg,
.sigresult = sigresult,

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
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; };
recursion no;
notify yes;
};
zone "." {
type primary;
file "root.db";
};

View file

@ -0,0 +1,24 @@
; 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.
$TTL 300
. IN SOA . a.root.servers.nil. (
2000042100 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
. NS a.root-servers.nil.
a.root-servers.nil. A 10.53.0.1
excessive-nsec-rrsigs. NS ns2.excessive-nsec-rrsigs.
ns2.excessive-nsec-rrsigs. A 10.53.0.2

View file

@ -0,0 +1,24 @@
; 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.
$TTL 300
@ IN SOA mname1. . (
1 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
@ NS ns2
ns2 A 10.53.0.2
* A 127.0.0.1

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
options {
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; };
recursion no;
notify yes;
};
zone "excessive-nsec-rrsigs" {
type primary;
file "excessive-nsec-rrsigs.db.signed";
};

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
// validating resolver
options {
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; };
recursion yes;
dnssec-validation yes;
max-records-per-type 2;
};
zone "." {
type hint;
file "../../_common/root.hint";
};
include "trusted.conf";

View file

@ -0,0 +1 @@
../../_common/trusted.conf.j2

View file

@ -0,0 +1,87 @@
#!/usr/bin/python3
# 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.rdataclass
import dns.rdatatype
import dns.rdtypes.ANY.RRSIG
import dns.zone
from isctest.run import EnvCmd
import isctest
def duplicate_rrsig(rdata, i):
return dns.rdtypes.ANY.RRSIG.RRSIG(
rdclass=rdata.rdclass,
rdtype=rdata.rdtype,
type_covered=rdata.type_covered,
algorithm=rdata.algorithm,
labels=rdata.labels,
# increment orig TTL so the rdataset isn't treated as identical record by dnspython
original_ttl=rdata.original_ttl + i,
expiration=rdata.expiration,
inception=rdata.inception,
key_tag=rdata.key_tag,
signer=rdata.signer,
signature=rdata.signature,
)
def bootstrap():
keygen = EnvCmd("KEYGEN", "-a ECDSA256 -Kns2 -q")
signer = EnvCmd("SIGNER", "-S -g")
zone = "excessive-nsec-rrsigs"
infile = f"{zone}.db.in"
signedfile = f"{zone}.db.signed"
isctest.log.info(f"{zone}: generate ksk and zsk")
ksk_name = keygen(f"-f KSK {zone}").out.strip()
keygen(f"{zone}").out.strip()
ksk = isctest.kasp.Key(ksk_name, keydir="ns2")
isctest.log.info(f"{zone}: sign zone")
signer(f"-P -x -O full -o {zone} -f {signedfile} {infile}", cwd="ns2")
isctest.log.info(
f"{zone}: duplicate the RRSIG(NSEC) to exceed max-records-per-type"
)
zone = dns.zone.from_file(f"ns2/{signedfile}", origin=zone)
for node in zone.values():
rrsig_rdataset = node.find_rdataset(
dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC
)
orig = rrsig_rdataset[0]
rrsig_rdataset.add(duplicate_rrsig(orig, 1))
rrsig_rdataset.add(duplicate_rrsig(orig, 2))
zone.to_file(f"ns2/{signedfile}", sorted=True)
return {
"trust_anchors": [
ksk.into_ta("static-key"),
],
}
# reproducer for CVE-2026-3104 [GL#5742]
def test_excessive_rrsigs(ns3):
# the real test is that there is no crash on shutdown - checked by the test
# framework when the test finishes
# multiple queries seem more reliable to reproduce the memory leak, using a
# single query sometimes didn't cause a crash on shutdown
for i in range(10):
msg = isctest.query.create(f"x{i}.excessive-nsec-rrsigs", "A")
res = isctest.query.udp(msg, ns3.ip, attempts=1)
isctest.check.servfail(res)

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
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; };
recursion no;
dnssec-validation no;
};
controls {
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
include "../../_common/rndc.key";
zone "." {
type primary;
file "root.db";
};

View file

@ -0,0 +1,25 @@
; 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.
$TTL 300
. IN SOA . . (
2025063000 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
. NS a.root-servers.nil.
a.root-servers.nil A 10.53.0.1
iter-too-many. NS ns2.iter-too-many.
ns2.iter-too-many. A 10.53.0.2

View file

@ -0,0 +1,31 @@
; 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.
{% raw %}
$TTL 300
@ IN SOA ns2.iter-too-many. hostmaster.iter-too-many. (
2026020300 ; serial
20 ; refresh (20 seconds)
20 ; retry (20 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
@ IN NS ns2.iter-too-many.
ns2 IN A 10.53.0.2
sub IN NS ns2.sub.iter-too-many.
ns2.sub IN A 10.53.0.2
{% endraw %}
{% for dnskey in dnskeys %}
@dnskey@
{% endfor %}

View file

@ -0,0 +1,40 @@
/*
* 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.
*/
options {
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; };
recursion no;
dnssec-validation no;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
include "../../_common/rndc.key";
zone "iter-too-many" {
type primary;
file "iter-too-many.signed.db";
};
zone "sub.iter-too-many" {
type primary;
file "sub.iter-too-many.db";
};

View file

@ -0,0 +1,24 @@
; 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.
$TTL 300
@ IN SOA ns2.sub.iter-too-many. hostmaster.sub.iter-too-many. (
2026020300 ; serial
20 ; refresh (20 seconds)
20 ; retry (20 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
@ IN NS ns2.sub.iter-too-many.
ns2 IN A 10.53.0.2
example IN A 127.0.0.1

View file

@ -0,0 +1,37 @@
/*
* 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.
*/
options {
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; };
recursion yes;
dnssec-validation yes;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
include "../../_common/rndc.key";
zone "." {
type hint;
file "../../_common/root.hint";
};
include "trusted.conf";

View file

@ -0,0 +1 @@
../../_common/trusted.conf.j2

View file

@ -0,0 +1,61 @@
# 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.
from isctest.run import EnvCmd
import isctest
def bootstrap():
templates = isctest.template.TemplateEngine(".")
keygen = EnvCmd("KEYGEN", "-a ECDSA256")
signer = EnvCmd("SIGNER")
isctest.log.info("setup iter-too-many.")
zonename = "iter-too-many."
ksk_name = keygen(f"-f KSK {zonename}", cwd="ns2").out.strip()
zsk_name = keygen(f"{zonename}", cwd="ns2").out.strip()
ksk = isctest.kasp.Key(ksk_name, keydir="ns2")
zsk = isctest.kasp.Key(zsk_name, keydir="ns2")
dnskeys = [ksk.dnskey, zsk.dnskey]
tdata = {
"dnskeys": dnskeys,
}
templates.render(f"ns2/{zonename}db", tdata, template=f"ns2/{zonename}db.j2.manual")
signer(
f"-P -o {zonename} -f {zonename}signed.db -3 A1B2C3D4 -H too-many -H 51 -S {zonename}db",
cwd="ns2",
)
return {
"trust_anchors": [
ksk.into_ta("static-key"),
],
}
def test_excessive_nsec3_iterations_delegation(ns3):
# reproducer for CVE-2026-1519 [GL#5708]
zone = "example.sub.iter-too-many"
msg = isctest.query.create(zone, "A")
res = isctest.query.tcp(msg, ns3.ip)
# an insecure response is expected regardless of the NSEC3 iteration limit,
# because the sub.iter-too-many. zone is unsigned. the real difference is
# in the CPU usage required for generating such response, but that can't be
# easily and reliably tested in an automated fashion
isctest.check.noerror(res)
with ns3.watch_log_from_start() as watcher:
watcher.wait_for_line(
f"validating {zone}/A: validator_callback_ds: too many iterations"
)

View file

@ -0,0 +1,41 @@
/*
* 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.
*/
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; };
recursion no;
notify no;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
view "v1" {
match-clients { any; };
zone "." {
type hint;
file "/dev/null";
};
};

View file

@ -0,0 +1,17 @@
#!/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
key=$($KEYGEN -q -a RSASHA256 -b 2048 sig0.)

View file

@ -0,0 +1,119 @@
# 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 base64
import glob
import os
import struct
import time
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
import dns.flags
import dns.message
import dns.name
import dns.rdata
import dns.rdataclass
import dns.rdatatype
import dns.renderer
import dns.rrset
import isctest
def load_bind_private_key(filename):
"""Parses a BIND 9 .private key file."""
with open(filename, "r", encoding="utf-8") as f:
lines = f.readlines()
data = {}
for line in lines:
if ":" in line:
key, value = line.split(":", 1)
data[key.strip()] = value.strip()
def b64int(k):
return int.from_bytes(base64.b64decode(data[k]), byteorder="big")
rsa_key = rsa.RSAPrivateNumbers(
p=b64int("Prime1"),
q=b64int("Prime2"),
d=b64int("PrivateExponent"),
dmp1=b64int("Exponent1"),
dmq1=b64int("Exponent2"),
iqmp=b64int("Coefficient"),
public_numbers=rsa.RSAPublicNumbers(
e=b64int("PublicExponent"), n=b64int("Modulus")
),
).private_key(default_backend())
return rsa_key
def make_sig0_query(key_file, key_name_str):
private_key = load_bind_private_key(key_file)
qname = dns.name.from_text(".")
query = dns.message.make_query(qname, dns.rdatatype.SOA)
query.flags |= dns.flags.RD
# Render message to bytes (needed for signing)
renderer = dns.renderer.Renderer()
query.to_wire(renderer)
msg_bytes = renderer.get_wire()
# SIG(0) Constants
basename = os.path.basename(key_file)
key_tag = int(basename.split("+")[2].split(".")[0])
now = int(time.time())
expiration = now + 3600
inception = now - 3600
signer_name = dns.name.from_text(key_name_str)
# Construct SIG RDATA header (0=SIG(0), 8=RSASHA256, 0=Labels)
sig_rdata_header = struct.pack(
"!HBBIIIH", 0, 8, 0, 0, expiration, inception, key_tag
)
sig_rdata_pre_sig = sig_rdata_header + signer_name.to_wire()
# Sign: ( SIG RDATA sans signature ) + ( Message )
signature = private_key.sign(
sig_rdata_pre_sig + msg_bytes, padding.PKCS1v15(), hashes.SHA256()
)
# Create the SIG RR
full_sig_rdata = sig_rdata_pre_sig + signature
sig_rr = dns.rdata.from_wire(
dns.rdataclass.ANY,
dns.rdatatype.SIG,
full_sig_rdata,
0,
len(full_sig_rdata),
)
sig_rrset = dns.rrset.from_rdata(qname, 0, sig_rr)
query.additional.append(sig_rrset)
return query
def test_sig0_acl_bypass():
key_files = glob.glob("Ksig0.+*.private")
assert len(key_files) == 1
query = make_sig0_query(key_files[0], "sig0.")
# Send the query
res = isctest.query.tcp(query, "10.53.0.1")
isctest.check.servfail(res)

View file

@ -0,0 +1,23 @@
; 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.
$ORIGIN .
$TTL 300 ; 5 minutes
example.nil IN SOA ns1.example.nil. hostmaster.example.nil. (
1 ; serial
2000 ; refresh (2000 seconds)
2000 ; retry (2000 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
example.nil. NS ns1.example.nil.
ns1.example.nil. A 10.53.0.1
a.example.nil. A 10.53.0.1

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
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; };
recursion no;
dnssec-validation no;
notify no;
};
key "test-key" {
algorithm "hmac-sha256";
secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=";
};
zone "example.nil" {
type primary;
file "example.db";
};

View file

@ -0,0 +1,62 @@
#!/usr/bin/python3
# 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.
# pylint: disable=unused-variable
import time
import dns.message
import dns.rdataclass
import dns.rdatatype
import dns.rdtypes.ANY.TKEY
import dns.rrset
import dns.tsigkeyring
import pytest
import isctest
pytestmark = pytest.mark.extra_artifacts([])
def create_tkey_msg(qname, mode, alg="hmac-sha256"):
msg = dns.message.make_query(qname, "TKEY")
now = int(time.time())
rdata = dns.rdtypes.ANY.TKEY.TKEY(
rdclass=dns.rdataclass.ANY,
rdtype=dns.rdatatype.TKEY,
algorithm=alg,
inception=now - 3600,
expiration=now + 86400,
mode=mode,
error=0,
key=b"",
)
rrset = dns.rrset.from_rdata(qname, dns.rdatatype.TKEY, rdata)
msg.additional.append(rrset)
return msg
def test_tkey_cve_2026_3119(ns1):
keyring = dns.tsigkeyring.from_text(
{
"test-key": "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=",
}
)
msg_delete = create_tkey_msg("a.example.nil.", 5)
msg_delete.use_tsig(keyring, keyname="test-key")
isctest.query.tcp(msg_delete, ns1.ip, attempts=1)
msg_unsupported = create_tkey_msg("a.example.nil.", 99)
msg_unsupported.use_tsig(keyring, keyname="test-key")
isctest.query.tcp(msg_unsupported, ns1.ip, attempts=1)

View file

@ -18,6 +18,7 @@ Changelog
development. Regular users should refer to :ref:`Release Notes <relnotes>`
for changes relevant to them.
.. include:: ../changelog/changelog-9.20.21.rst
.. include:: ../changelog/changelog-9.20.20.rst
.. include:: ../changelog/changelog-9.20.19.rst
.. include:: ../changelog/changelog-9.20.18.rst

View file

@ -45,6 +45,7 @@ The list of known issues affecting the latest version in the 9.20 branch can be
found at
https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.20
.. include:: ../notes/notes-9.20.21.rst
.. include:: ../notes/notes-9.20.20.rst
.. include:: ../notes/notes-9.20.19.rst
.. include:: ../notes/notes-9.20.18.rst

View file

@ -0,0 +1,77 @@
.. 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.
BIND 9.20.21
------------
Security Fixes
~~~~~~~~~~~~~~
- [CVE-2026-1519] Fix unbounded NSEC3 iterations when validating
referrals to unsigned delegations. ``5af03a06066``
DNSSEC-signed zones may contain high iteration-count NSEC3 records,
which prove that certain delegations are insecure. Previously, a
validating resolver encountering such a delegation processed these
iterations up to the number given, which could be a maximum of 65,535.
This has been addressed by introducing a processing limit, set at 50.
Now, if such an NSEC3 record is encountered, the delegation will be
treated as insecure.
ISC would like to thank Samy Medjahed/Ap4sh for bringing this
vulnerability to our attention. :gl:`#5708`
- [CVE-2026-3104] Fix memory leaks in code preparing DNSSEC proofs of
non-existence. ``13215b9cbbf``
An attacker controlling a DNSSEC-signed zone could trigger a memory
leak in the logic preparing DNSSEC proofs of non-existence, by
creating more than :any:`max-records-per-type` RRSIGs for NSEC
records. These memory leaks have been fixed.
ISC would like to thank Vitaly Simonovich for bringing this
vulnerability to our attention. :gl:`#5742`
- [CVE-2026-3119] Prevent a crash in code processing queries containing
a TKEY record. ``308baa89105``
The :iscman:`named` process could terminate unexpectedly when
processing a correctly signed query containing a TKEY record. This has
been fixed.
ISC would like to thank Vitaly Simonovich for bringing this
vulnerability to our attention. :gl:`#5748`
- [CVE-2026-3591] Fix a stack use-after-return flaw in SIG(0) handling
code. ``aaaae0fd97e``
A stack use-after-return flaw in SIG(0) handling code could enable ACL
bypass and/or assertion failures in certain circumstances. This flaw
has been fixed.
ISC would like to thank Mcsky23 for bringing this vulnerability to our
attention. :gl:`#5754`
Bug Fixes
~~~~~~~~~
- Resolve "key defined in view is not found" ``819fe452745``
Commit `2956e4fc` hardened the `key` name check when used in
`primaries` to reject the configuration if the key was not defined,
rather than simply checking whether the key name was correctly formed.
However, the key name check didn't include the view configuration,
causing keys not to be recognized if they were defined inside the view
and not at the global level. This regression is now fixed.
:gl:`#5761` :gl:`!11613`

View file

@ -0,0 +1,75 @@
.. 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.
Notes for BIND 9.20.21
----------------------
Security Fixes
~~~~~~~~~~~~~~
- Fix unbounded NSEC3 iterations when validating referrals to unsigned
delegations. :cve:`2026-1519`
DNSSEC-signed zones may contain high iteration-count NSEC3 records,
which prove that certain delegations are insecure. Previously, a
validating resolver encountering such a delegation processed these
iterations up to the number given, which could be a maximum of 65,535.
This has been addressed by introducing a processing limit, set at 50.
Now, if such an NSEC3 record is encountered, the delegation will be
treated as insecure.
ISC would like to thank Samy Medjahed/Ap4sh for bringing this
vulnerability to our attention. :gl:`#5708`
- Fix memory leaks in code preparing DNSSEC proofs of non-existence.
:cve:`2026-3104`
An attacker controlling a DNSSEC-signed zone could trigger a memory
leak in the logic preparing DNSSEC proofs of non-existence, by
creating more than :any:`max-records-per-type` RRSIGs for NSEC
records. These memory leaks have been fixed.
ISC would like to thank Vitaly Simonovich for bringing this
vulnerability to our attention. :gl:`#5742`
- Prevent a crash in code processing queries containing a TKEY record.
:cve:`2026-3119`
The :iscman:`named` process could terminate unexpectedly when
processing a correctly signed query containing a TKEY record. This has
been fixed.
ISC would like to thank Vitaly Simonovich for bringing this
vulnerability to our attention. :gl:`#5748`
- Fix a stack use-after-return flaw in SIG(0) handling code.
:cve:`2026-3591`
A stack use-after-return flaw in SIG(0) handling code could enable ACL
bypass and/or assertion failures in certain circumstances. This flaw
has been fixed.
ISC would like to thank Mcsky23 for bringing this vulnerability to our
attention. :gl:`#5754`
Bug Fixes
~~~~~~~~~
- Fix the handling of :namedconf:ref:`key` statements defined inside
views.
A recent change introduced in BIND 9.20.17 hardened the
:namedconf:ref:`key` name check when used in :any:`primaries`, to
immediately reject the configuration if the key was not defined
(rather than only checking whether the key name was correctly formed).
However, that change introduced a regression that prevented the use of
a :namedconf:ref:`key` defined in a view. This has now been fixed.
:gl:`#5761`

View file

@ -393,6 +393,7 @@ enum {
((x) == dns_trust_additional || (x) == dns_trust_pending_additional)
#define DNS_TRUST_GLUE(x) ((x) == dns_trust_glue)
#define DNS_TRUST_ANSWER(x) ((x) == dns_trust_answer)
#define DNS_TRUST_SECURE(x) ((x) >= dns_trust_secure)
/*%
* Name checking severities.

View file

@ -3279,7 +3279,7 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_slabheader_proof_t *noqname = NULL;
dns_name_t name = DNS_NAME_INITEMPTY;
dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT;
isc_region_t r1, r2;
isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL };
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
@ -3305,6 +3305,14 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
newheader->noqname = noqname;
cleanup:
if (result != ISC_R_SUCCESS) {
if (r1.base != NULL) {
isc_mem_put(mctx, r1.base, r1.length);
}
if (r2.base != NULL) {
isc_mem_put(mctx, r2.base, r2.length);
}
}
dns_rdataset_disassociate(&neg);
dns_rdataset_disassociate(&negsig);
@ -3318,7 +3326,7 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_slabheader_proof_t *closest = NULL;
dns_name_t name = DNS_NAME_INITEMPTY;
dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT;
isc_region_t r1, r2;
isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL };
result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
@ -3344,6 +3352,14 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
newheader->closest = closest;
cleanup:
if (result != ISC_R_SUCCESS) {
if (r1.base != NULL) {
isc_mem_put(mctx, r1.base, r1.length);
}
if (r2.base != NULL) {
isc_mem_put(mctx, r2.base, r2.length);
}
}
dns_rdataset_disassociate(&neg);
dns_rdataset_disassociate(&negsig);
return result;

View file

@ -3180,7 +3180,7 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_slabheader_proof_t *noqname = NULL;
dns_name_t name = DNS_NAME_INITEMPTY;
dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT;
isc_region_t r1, r2;
isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL };
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
@ -3206,6 +3206,14 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
newheader->noqname = noqname;
cleanup:
if (result != ISC_R_SUCCESS) {
if (r1.base != NULL) {
isc_mem_put(mctx, r1.base, r1.length);
}
if (r2.base != NULL) {
isc_mem_put(mctx, r2.base, r2.length);
}
}
dns_rdataset_disassociate(&neg);
dns_rdataset_disassociate(&negsig);
@ -3219,7 +3227,7 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_slabheader_proof_t *closest = NULL;
dns_name_t name = DNS_NAME_INITEMPTY;
dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT;
isc_region_t r1, r2;
isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL };
result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
@ -3245,6 +3253,14 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
newheader->closest = closest;
cleanup:
if (result != ISC_R_SUCCESS) {
if (r1.base != NULL) {
isc_mem_put(mctx, r1.base, r1.length);
}
if (r2.base != NULL) {
isc_mem_put(mctx, r2.base, r2.length);
}
}
dns_rdataset_disassociate(&neg);
dns_rdataset_disassociate(&negsig);
return result;

View file

@ -420,7 +420,8 @@ dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx,
/*
* A delete operation uses the fully specified qname.
*/
CHECK(process_deletetkey(signer, qname, &tkeyin, &tkeyout,
keyname = qname;
CHECK(process_deletetkey(signer, keyname, &tkeyin, &tkeyout,
ring));
break;
case DNS_TKEYMODE_GSSAPI:
@ -463,6 +464,10 @@ dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx,
result = DNS_R_NOTIMP;
goto cleanup;
default:
/*
* For unrecognized modes also use the fully specified qname.
*/
keyname = qname;
tkeyout.error = dns_tsigerror_badmode;
}

View file

@ -230,7 +230,8 @@ markanswer(dns_validator_t *val, const char *where, const char *mbstext) {
* Mark the RRsets in val->vstat with trust level secure.
*/
static void
marksecure(dns_validator_t *val) {
marksecure(dns_validator_t *val, const char *where) {
validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (%s)", where);
dns_rdataset_settrust(val->rdataset, dns_trust_secure);
if (val->sigrdataset != NULL) {
dns_rdataset_settrust(val->sigrdataset, dns_trust_secure);
@ -257,12 +258,25 @@ validator_done(dns_validator_t *val, isc_result_t result) {
}
/*%
* Look in the NSEC record returned from a DS query to see if there is
* a NS RRset at this name. If it is found we are at a delegation point.
* The isdelegation() function is called as part of seeking the DS record.
* Look in the NSEC or NSEC3 record returned from a DS query to see if the
* record has the NS bitmap set. If so, we are at a delegation point.
*
* If the response contains NSEC3 records with too high iterations, we cannot
* (or rather we are not going to) validate the insecurity proof. Instead we
* are going to treat the message as insecure and just assume the DS was at
* the delegation.
*
* Returns:
*\li #ISC_R_SUCCESS the NS bitmap was set in the NSEC or NSEC3 record, or
* the NSEC3 covers the name (in case of opt-out), or
* we cannot validate the insecurity proof and are going
* to treat the message as isnecure.
*\li #ISC_R_NOTFOUND the NS bitmap was not set,
*/
static bool
isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
isc_result_t dbresult) {
static isc_result_t
isdelegation(dns_validator_t *val, dns_name_t *name, dns_rdataset_t *rdataset,
isc_result_t dbresult, const char *caller) {
dns_fixedname_t fixed;
dns_label_t hashlabel;
dns_name_t nsec3name;
@ -290,7 +304,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
goto trynsec3;
}
if (result != ISC_R_SUCCESS) {
return false;
return ISC_R_NOTFOUND;
}
}
@ -304,7 +318,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
dns_rdata_reset(&rdata);
}
dns_rdataset_disassociate(&set);
return found;
return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND;
trynsec3:
/*
@ -343,6 +357,18 @@ trynsec3:
if (nsec3.next_length > NSEC3_MAX_HASH_LENGTH) {
continue;
}
/*
* If there are too many iterations assume bad things
* are happening and bail out early. Treat as if the
* DS was at the delegation.
*/
if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) {
validator_log(val, ISC_LOG_DEBUG(3),
"%s: too many iterations",
caller);
dns_rdataset_disassociate(&set);
return ISC_R_SUCCESS;
}
length = isc_iterated_hash(
hash, nsec3.hash, nsec3.iterations, nsec3.salt,
nsec3.salt_length, name->ndata, name->length);
@ -354,7 +380,7 @@ trynsec3:
found = dns_nsec3_typepresent(&rdata,
dns_rdatatype_ns);
dns_rdataset_disassociate(&set);
return found;
return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND;
}
if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) {
continue;
@ -370,12 +396,12 @@ trynsec3:
memcmp(hash, nsec3.next, length) < 0)))
{
dns_rdataset_disassociate(&set);
return true;
return ISC_R_SUCCESS;
}
}
dns_rdataset_disassociate(&set);
}
return found;
return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND;
}
static void
@ -589,9 +615,10 @@ fetch_callback_ds(void *arg) {
break;
case DNS_R_NXRRSET:
case DNS_R_NCACHENXRRSET:
if (isdelegation(resp->foundname, &val->frdataset,
eresult))
{
result = isdelegation(val, resp->foundname,
&val->frdataset, eresult,
"fetch_callback_ds");
if (result == ISC_R_SUCCESS) {
/*
* Failed to find a DS while trying to prove
* insecurity. If this is a zone cut, that
@ -707,10 +734,13 @@ validator_callback_ds(void *arg) {
dns_trust_totext(val->frdataset.trust));
have_dsset = (val->frdataset.type == dns_rdatatype_ds);
name = dns_fixedname_name(&val->fname);
if ((val->attributes & VALATTR_INSECURITY) != 0 &&
val->frdataset.covers == dns_rdatatype_ds &&
NEGATIVE(&val->frdataset) &&
isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET))
isdelegation(val, name, &val->frdataset,
DNS_R_NCACHENXRRSET,
"validator_callback_ds") == ISC_R_SUCCESS)
{
result = markanswer(val, "validator_callback_ds",
"no DS and this is a delegation");
@ -1487,11 +1517,19 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
bool ignore = false;
dns_name_t *wild;
if (DNS_TRUST_SECURE(val->rdataset->trust)) {
/*
* This RRset was already verified before.
*/
return ISC_R_SUCCESS;
}
val->attributes |= VALATTR_TRIEDVERIFY;
wild = dns_fixedname_initname(&fixed);
if (over_max_validations(val)) {
return ISC_R_QUOTA;
}
wild = dns_fixedname_initname(&fixed);
again:
result = dns_dnssec_verify(val->name, val->rdataset, key, ignore,
val->view->maxbits, val->view->mctx, rdata,
@ -1846,9 +1884,7 @@ validate_answer_finish(void *arg) {
}
if (val->result == ISC_R_SUCCESS) {
marksecure(val);
validator_log(val, ISC_LOG_DEBUG(3),
"marking as secure, noqname proof not needed");
marksecure(val, "noqname proof not needed");
validate_async_done(val, val->result);
return;
}
@ -2057,8 +2093,7 @@ validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) {
/* Abort, abort, abort! */
break;
case ISC_R_SUCCESS:
marksecure(val);
validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
marksecure(val, "validate_dnskey (DS)");
break;
case ISC_R_NOMORE:
if (val->unsupported_algorithm != 0 ||
@ -2769,7 +2804,19 @@ validate_neg_rrset(dns_validator_t *val, dns_name_t *name,
}
}
if (rdataset->type != dns_rdatatype_nsec &&
DNS_TRUST_SECURE(rdataset->trust))
{
/*
* The negative response data is already verified.
* We skip NSEC records, because they require special
* processing in validator_callback_nsec().
*/
return DNS_R_CONTINUE;
}
val->nxset = rdataset;
result = create_validator(val, name, rdataset->type, rdataset,
sigrdataset, validator_callback_nsec,
"validate_neg_rrset");
@ -2879,11 +2926,9 @@ validate_ncache(dns_validator_t *val, bool resume) {
}
result = validate_neg_rrset(val, name, rdataset, sigrdataset);
if (result == DNS_R_CONTINUE) {
continue;
if (result != DNS_R_CONTINUE) {
return result;
}
return result;
}
if (result == ISC_R_NOMORE) {
result = ISC_R_SUCCESS;
@ -2932,7 +2977,8 @@ validate_nx(dns_validator_t *val, bool resume) {
result = findnsec3proofs(val);
if (result == DNS_R_NSEC3ITERRANGE) {
validator_log(val, ISC_LOG_DEBUG(3),
"too many iterations");
"%s: too many iterations",
__func__);
markanswer(val, "validate_nx (3)", NULL);
return ISC_R_SUCCESS;
}
@ -2940,9 +2986,7 @@ validate_nx(dns_validator_t *val, bool resume) {
if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val))
{
validator_log(val, ISC_LOG_DEBUG(3),
"marking as secure, noqname proof found");
marksecure(val);
marksecure(val, "validate_nx (noqname proof found)");
return ISC_R_SUCCESS;
} else if (FOUNDOPTOUT(val) &&
dns_name_countlabels(
@ -2968,7 +3012,7 @@ validate_nx(dns_validator_t *val, bool resume) {
result = findnsec3proofs(val);
if (result == DNS_R_NSEC3ITERRANGE) {
validator_log(val, ISC_LOG_DEBUG(3),
"too many iterations");
"%s: too many iterations", __func__);
markanswer(val, "validate_nx (4)", NULL);
return ISC_R_SUCCESS;
}
@ -2996,7 +3040,8 @@ validate_nx(dns_validator_t *val, bool resume) {
validator_log(val, ISC_LOG_DEBUG(3),
"nonexistence proof(s) found");
if (val->message == NULL) {
marksecure(val);
marksecure(val,
"validate_nx (nonexistence proofs found)");
} else {
val->secure = true;
}
@ -3181,7 +3226,9 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) {
return ISC_R_COMPLETE;
}
if (isdelegation(tname, &val->frdataset, result)) {
result = isdelegation(val, tname, &val->frdataset, result,
"seek_ds");
if (result == ISC_R_SUCCESS) {
*resp = markanswer(val, "seek_ds (3)",
"this is a delegation");
return ISC_R_COMPLETE;