mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
[CVE-2026-3104] sec: usr: Fix memory leaks in code preparing DNSSEC proofs of non-existence
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. Closes isc-projects/bind9#5742 Merge branch '5742-fix-memory-leak-in-addnoqname-and-addclosest' into 'v9.21.20-release' See merge request isc-private/bind9!913
This commit is contained in:
commit
5e29b24dcd
8 changed files with 254 additions and 6 deletions
29
bin/tests/system/nsec/ns1/named.conf.j2
Normal file
29
bin/tests/system/nsec/ns1/named.conf.j2
Normal 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";
|
||||
};
|
||||
24
bin/tests/system/nsec/ns1/root.db
Normal file
24
bin/tests/system/nsec/ns1/root.db
Normal 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
|
||||
24
bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in
Normal file
24
bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in
Normal 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
|
||||
29
bin/tests/system/nsec/ns2/named.conf.j2
Normal file
29
bin/tests/system/nsec/ns2/named.conf.j2
Normal 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";
|
||||
};
|
||||
35
bin/tests/system/nsec/ns3/named.conf.j2
Normal file
35
bin/tests/system/nsec/ns3/named.conf.j2
Normal 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";
|
||||
1
bin/tests/system/nsec/ns3/trusted.conf.j2
Symbolic link
1
bin/tests/system/nsec/ns3/trusted.conf.j2
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../_common/trusted.conf.j2
|
||||
87
bin/tests/system/nsec/tests_excessive_rrsigs.py
Executable file
87
bin/tests/system/nsec/tests_excessive_rrsigs.py
Executable 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)
|
||||
|
|
@ -2981,7 +2981,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);
|
||||
|
|
@ -3001,6 +3001,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);
|
||||
|
||||
|
|
@ -3014,7 +3022,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);
|
||||
|
|
@ -3034,6 +3042,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;
|
||||
|
|
@ -3106,12 +3122,12 @@ qpcache_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
|
|||
DNS_SLABHEADER_SETATTR(newheader, DNS_SLABHEADERATTR_OPTOUT);
|
||||
}
|
||||
if (rdataset->attributes.noqname) {
|
||||
RETERR(addnoqname(qpnode->mctx, newheader, qpdb->maxrrperset,
|
||||
rdataset));
|
||||
CHECK(addnoqname(qpnode->mctx, newheader, qpdb->maxrrperset,
|
||||
rdataset));
|
||||
}
|
||||
if (rdataset->attributes.closest) {
|
||||
RETERR(addclosest(qpnode->mctx, newheader, qpdb->maxrrperset,
|
||||
rdataset));
|
||||
CHECK(addclosest(qpnode->mctx, newheader, qpdb->maxrrperset,
|
||||
rdataset));
|
||||
}
|
||||
|
||||
nlock = &qpdb->buckets[qpnode->locknum].lock;
|
||||
|
|
@ -3179,6 +3195,9 @@ qpcache_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
|
|||
|
||||
INSIST(tlocktype == isc_rwlocktype_none);
|
||||
|
||||
return result;
|
||||
cleanup:
|
||||
dns_slabheader_destroy(&newheader);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue