File bind-9.11-CVE-2024-1737.patch of Package bind.35132
commit be06e6215cb2a2891d0035e296a7ec1cda66ce8f
Author: Nicki Křížek <nicki@isc.org>
Date: Wed Jul 24 08:55:54 2024 +0000
[9.11] [CVE-2024-1737]
Previously, the number of RRs in the RRSets were internally unlimited.
As the data structure that holds the RRs is just a linked list, and
there are places where we just walk through all of the RRs, adding an
RRSet with huge number of RRs inside would slow down processing of said
RRSets.
The fix for end-of-life branches make the limit compile-time only for
simplicity and the limit can be changed at the compile time by adding
following define to CFLAGS:
-DDNS_RDATASET_MAX_RECORDS=<limit>
Backport of https://gitlab.isc.org/isc-private/bind9/-/merge_requests/698
See isc-projects/bind9#3405
Merge branch '3405-limit-the-number-of-resource-records-in-rrset-9.11' into 'bind-9.11'
See merge request isc-projects/bind9!9169
[9.11][CVE-2024-1737 (part 2)] Be smarter about refusing to add many RR types to the database
Add HTTPS, SVCB, SRV, PTR, NAPTR, DNSKEY and TXT records to the list of
the priority types that are put at the beginning of the slabheader list
for faster access and to avoid eviction when there are more types than
the max-types-per-name limit.
(cherry picked from commit b27c6bcce894786a8e082eafd59eccbf6f2731cb)
---
Backport of MR https://gitlab.isc.org/isc-private/bind9/-/merge_requests/712
Related https://gitlab.isc.org/isc-projects/bind9/-/issues/3405
Merge branch '3405-limit-the-number-of-resource-records-in-rrset-nxdomain-9.11' into 'bind-9.11'
See merge request isc-projects/bind9!9171
diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh
index 51ea530ebe..5a6513aea3 100644
--- a/bin/tests/system/dnssec/tests.sh
+++ b/bin/tests/system/dnssec/tests.sh
@@ -1765,15 +1765,17 @@ awk '/IN *SOA/ {if (NF != 7) exit(1)}' signer/signer.out.4 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
-echo_i "checking TTLs are capped by dnssec-signzone -M ($n)"
-ret=0
-(
-cd signer
-$SIGNER -O full -f signer.out.8 -S -M 30 -o example example.db > /dev/null 2>&1
-) || ret=1
-awk '/^;/ { next; } $2 > 30 { exit 1; }' signer/signer.out.8 || ret=1
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=`expr $status + $ret`
+# Skip test because it doesn't work with the maximum records of 100 for CVE-2024-1737
+# bsc#1229002
+#echo_i "checking TTLs are capped by dnssec-signzone -M ($n)"
+#ret=0
+#(
+#cd signer
+#$SIGNER -O full -f signer.out.8 -S -M 30 -o example example.db > /dev/null 2>&1
+#) || ret=1
+#awk '/^;/ { next; } $2 > 30 { exit 1; }' signer/signer.out.8 || ret=1
+#if [ $ret != 0 ]; then echo_i "failed"; fi
+#status=`expr $status + $ret`
echo_i "checking dnssec-signzone -N date ($n)"
ret=0
diff --git a/bin/tests/system/limits/prereq.sh b/bin/tests/system/limits/prereq.sh
new file mode 100644
index 0000000000..3ae82b8191
--- /dev/null
+++ b/bin/tests/system/limits/prereq.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Skip test because it doesn't work with the maximum records of 100 for CVE-2024-1737
+# bsc#1229002
+exit 255
diff --git a/bin/tests/system/masterformat/tests.sh b/bin/tests/system/masterformat/tests.sh
index 6383fbddb1..17ec4a3102 100755
--- a/bin/tests/system/masterformat/tests.sh
+++ b/bin/tests/system/masterformat/tests.sh
@@ -183,21 +183,23 @@ n=$((n+1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status+ret))
-echo_i "checking that large rdatasets loaded ($n)"
-for i in 0 1 2 3 4 5 6 7 8 9
-do
-ret=0
-for a in a b c
-do
- $DIG +tcp txt "${a}.large" @10.53.0.2 -p "${PORT}" > "dig.out.ns2.test$n"
- grep "status: NOERROR" "dig.out.ns2.test$n" > /dev/null || ret=1
-done
-[ $ret -eq 0 ] && break
-sleep 1
-done
-n=$((n+1))
-[ $ret -eq 0 ] || echo_i "failed"
-status=$((status+ret))
+# Skip test because it doesn't work with the maximum records of 100 for CVE-2024-1737
+# bsc#1229002
+#echo_i "checking that large rdatasets loaded ($n)"
+#for i in 0 1 2 3 4 5 6 7 8 9
+#do
+#ret=0
+#for a in a b c
+#do
+# $DIG +tcp txt "${a}.large" @10.53.0.2 -p "${PORT}" > "dig.out.ns2.test$n"
+# grep "status: NOERROR" "dig.out.ns2.test$n" > /dev/null || ret=1
+#done
+#[ $ret -eq 0 ] && break
+#sleep 1
+#done
+#n=$((n+1))
+#[ $ret -eq 0 ] || echo_i "failed"
+#status=$((status+ret))
echo_i "checking format transitions: text->raw->map->text ($n)"
ret=0
diff --git a/configure b/configure
index 887787faef..dac8cfe03b 100755
--- a/configure
+++ b/configure
@@ -12169,7 +12169,7 @@ fi
XTARGETS=
case "$enable_developer" in
yes)
- STD_CDEFINES="$STD_CDEFINES -DISC_LIST_CHECKINIT=1"
+ STD_CDEFINES="$STD_CDEFINES -DISC_LIST_CHECKINIT=1 -DDNS_RDATASET_MAX_RECORDS=5000 -DDNS_RBTDB_MAX_RTYPES=5000"
test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
test "${enable_querytrace+set}" = set || enable_querytrace=yes
test "${enable_filter_aaaa+set}" = set || enable_filter_aaaa=yes
diff --git a/configure.ac b/configure.ac
index 33bd76ac3b..f42b4f8fd0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -94,7 +94,7 @@ AC_ARG_ENABLE(developer,
XTARGETS=
case "$enable_developer" in
yes)
- STD_CDEFINES="$STD_CDEFINES -DISC_LIST_CHECKINIT=1"
+ STD_CDEFINES="$STD_CDEFINES -DISC_LIST_CHECKINIT=1 -DDNS_RDATASET_MAX_RECORDS=5000 -DDNS_RBTDB_MAX_RTYPES=5000"
test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
test "${enable_querytrace+set}" = set || enable_querytrace=yes
test "${enable_filter_aaaa+set}" = set || enable_filter_aaaa=yes
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index 7a622103a7..55ad7fa946 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -1127,6 +1127,44 @@ set_ttl(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, dns_ttl_t newttl) {
isc_heap_decreased(heap, header->heap_index);
}
+static bool
+prio_type(rbtdb_rdatatype_t type) {
+ switch (type) {
+ case dns_rdatatype_soa:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_soa):
+ case dns_rdatatype_a:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_a):
+ case dns_rdatatype_mx:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_mx):
+ case dns_rdatatype_aaaa:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_aaaa):
+ case dns_rdatatype_nsec:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec):
+ case dns_rdatatype_nsec3:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec3):
+ case dns_rdatatype_ns:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ns):
+ case dns_rdatatype_ds:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ds):
+ case dns_rdatatype_cname:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_cname):
+ case dns_rdatatype_dname:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_dname):
+ case dns_rdatatype_dnskey:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_dnskey):
+ case dns_rdatatype_srv:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_srv):
+ case dns_rdatatype_txt:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_txt):
+ case dns_rdatatype_ptr:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ptr):
+ case dns_rdatatype_naptr:
+ case RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_naptr):
+ return (true);
+ }
+ return (false);
+}
+
/*%
* These functions allow the heap code to rank the priority of each
* element. It returns true if v1 happens "sooner" than v2.
@@ -6127,6 +6165,30 @@ update_recordsandbytes(bool add, rbtdb_version_t *rbtversion,
RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write);
}
+#ifndef DNS_RBTDB_MAX_RTYPES
+#define DNS_RBTDB_MAX_RTYPES 100
+#endif /* DNS_RBTDB_MAX_RTYPES */
+
+static bool
+overmaxtype(dns_rbtdb_t *rbtdb, uint32_t ntypes) {
+ UNUSED(rbtdb);
+
+ if (DNS_RBTDB_MAX_RTYPES == 0) {
+ return (false);
+ }
+
+ return (ntypes >= DNS_RBTDB_MAX_RTYPES);
+}
+
+static bool
+prio_header(rdatasetheader_t *header) {
+ if (NEGATIVE(header) && prio_type(RBTDB_RDATATYPE_EXT(header->type))) {
+ return (true);
+ }
+
+ return (prio_type(header->type));
+}
+
/*
* write lock on rbtnode must be held.
*/
@@ -6137,6 +6199,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
{
rbtdb_changed_t *changed = NULL;
rdatasetheader_t *topheader, *topheader_prev, *header, *sigheader;
+ rdatasetheader_t *prioheader = NULL, *expireheader = NULL;
unsigned char *merged;
isc_result_t result;
bool header_nx;
@@ -6146,6 +6209,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
rbtdb_rdatatype_t negtype, sigtype;
dns_trust_t trust;
int idx;
+ uint32_t ntypes = 0;
/*
* Add an rdatasetheader_t to a node.
@@ -6278,6 +6342,15 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
for (topheader = rbtnode->data;
topheader != NULL;
topheader = topheader->next) {
+ if (IS_CACHE(rbtdb) && ACTIVE(topheader, now)) {
+ ++ntypes;
+ expireheader = topheader;
+ } else if (!IS_CACHE(rbtdb)) {
+ ++ntypes;
+ }
+ if (prio_header(topheader)) {
+ prioheader = topheader;
+ }
if (topheader->type == newheader->type ||
topheader->type == negtype)
break;
@@ -6633,9 +6706,46 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
/*
* No rdatasets of the given type exist at the node.
*/
- newheader->next = rbtnode->data;
+ if (!IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) {
+ free_rdataset(rbtdb, rbtdb->common.mctx,
+ newheader);
+ return (ISC_R_QUOTA);
+ }
+
newheader->down = NULL;
- rbtnode->data = newheader;
+
+ if (prio_header(newheader)) {
+ /* This is a priority type, prepend it */
+ newheader->next = rbtnode->data;
+ rbtnode->data = newheader;
+ } else if (prioheader != NULL) {
+ /* Append after the priority headers */
+ newheader->next = prioheader->next;
+ prioheader->next = newheader;
+ } else {
+ /* There were no priority headers */
+ newheader->next = rbtnode->data;
+ rbtnode->data = newheader;
+ }
+
+ if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) {
+ if (expireheader == NULL) {
+ expireheader = newheader;
+ }
+ if (NEGATIVE(newheader) &&
+ !prio_header(newheader))
+ {
+ /*
+ * Add the new non-priority negative
+ * header to the database only
+ * temporarily.
+ */
+ expireheader = newheader;
+ }
+
+ set_ttl(rbtdb, expireheader, 0);
+ mark_stale_header(rbtdb, expireheader);
+ }
}
}
diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c
index 7cd3855d77..22fe63e063 100644
--- a/lib/dns/rdataslab.c
+++ b/lib/dns/rdataslab.c
@@ -115,6 +115,10 @@ fillin_offsets(unsigned char *offsetbase, unsigned int *offsettable,
}
#endif
+#ifndef DNS_RDATASET_MAX_RECORDS
+#define DNS_RDATASET_MAX_RECORDS 100
+#endif /* DNS_RDATASET_MAX_RECORDS */
+
isc_result_t
dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
isc_region_t *region, unsigned int reservelen)
@@ -161,6 +165,10 @@ dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
return (ISC_R_SUCCESS);
}
+ if (nitems > DNS_RDATASET_MAX_RECORDS) {
+ return (DNS_R_TOOMANYRECORDS);
+ }
+
if (nitems > 0xffff)
return (ISC_R_NOSPACE);
@@ -654,6 +662,10 @@ dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
#endif
INSIST(ocount > 0 && ncount > 0);
+ if (ocount + ncount > DNS_RDATASET_MAX_RECORDS) {
+ return (DNS_R_TOOMANYRECORDS);
+ }
+
#if DNS_RDATASET_FIXED
oncount = ncount;
#endif