[9.18] chg: dev: Invalid NSEC3 can cause OOB read of the isdelegation() stack

When .next_length is longer than NSEC3_MAX_HASH_LENGTH, it causes a
harmless out-of-bound read of the isdelegation() stack.  This has been
fixed.

Closes #5749

Backport of MR !11553

Merge branch 'backport-5749-fix-OOB-read-in-isdelegation-9.18' into 'bind-9.18'

See merge request isc-projects/bind9!11595
This commit is contained in:
Ondřej Surý 2026-02-24 17:29:38 +01:00
commit 97fd0c56e4
6 changed files with 69 additions and 22 deletions

View file

@ -47,7 +47,6 @@ FQ7RBG86KRMACA1NAAKP2KQRQALBA0C7.dyn.example.net. 7200 RRSIG NSEC3 7 4 7200 201
577WZnTQemStx+diON9rEGXAGnU7C0KLjrFL
VyhocnBnNtxJS8eRMSWvb9XuYCMNhYKOurtt
Ar4qh4VW1+unmA== )
I7A7A184GGMI35K1E3IR650LKO7NOB5R.dyn.example.net. 7200 IN NSEC3 1 0 10 76931F IMQ912BREQP1POLAH3RMONG;UED541AS A RRSIG
IMQ912BREQP1POLAH3RMONG3UED541AS.dyn.example.net. 7200 IN NSEC3 1 0 10 76931F S3USV4M1HLVJ8F88EDSG8N9PVQRQ20N7 A RRSIG
7200 RRSIG NSEC3 7 4 7200 20100227180048 (
20100221180048 30323 dyn.example.net.

View file

@ -28,6 +28,12 @@
#define DNS_NSEC3_SALTSIZE 255
#define DNS_NSEC3_MAXITERATIONS 150U
/*
* The maximum hash that can be encoded in a single label using
* base32hexnp. floor(63*5/8)
*/
#define NSEC3_MAX_HASH_LENGTH 39
/*
* hash = 1, flags =1, iterations = 2, salt length = 1, salt = 255 (max)
* hash length = 1, hash = 255 (max), bitmap = 8192 + 512 (max)

View file

@ -35,6 +35,8 @@
#include <isc/base32.h>
#include <isc/iterated_hash.h>
#include <dns/nsec3.h>
#define RRTYPE_NSEC3_ATTRIBUTES DNS_RDATATYPEATTR_DNSSEC
static isc_result_t
@ -96,8 +98,17 @@ fromtext_nsec3(ARGS_FROMTEXT) {
false));
isc_buffer_init(&b, buf, sizeof(buf));
RETTOK(isc_base32hexnp_decodestring(DNS_AS_STR(token), &b));
if (isc_buffer_usedlength(&b) > 0xffU) {
RETTOK(ISC_R_RANGE);
switch (hashalg) {
case dns_hash_sha1:
if (isc_buffer_usedlength(&b) != ISC_SHA1_DIGESTLENGTH) {
RETTOK(ISC_R_RANGE);
}
break;
default:
if (isc_buffer_usedlength(&b) > NSEC3_MAX_HASH_LENGTH) {
RETTOK(ISC_R_RANGE);
}
break;
}
RETERR(uint8_tobuffer(isc_buffer_usedlength(&b), target));
RETERR(mem_tobuffer(target, &buf, isc_buffer_usedlength(&b)));
@ -184,7 +195,7 @@ totext_nsec3(ARGS_TOTEXT) {
static isc_result_t
fromwire_nsec3(ARGS_FROMWIRE) {
isc_region_t sr, rr;
unsigned int saltlen, hashlen;
unsigned int hash, saltlen, hashlen;
REQUIRE(type == dns_rdatatype_nsec3);
@ -200,6 +211,7 @@ fromwire_nsec3(ARGS_FROMWIRE) {
if (sr.length < 5U) {
RETERR(DNS_R_FORMERR);
}
hash = sr.base[0];
saltlen = sr.base[4];
isc_region_consume(&sr, 5);
@ -214,8 +226,19 @@ fromwire_nsec3(ARGS_FROMWIRE) {
hashlen = sr.base[0];
isc_region_consume(&sr, 1);
if (hashlen < 1 || sr.length < hashlen) {
RETERR(DNS_R_FORMERR);
switch (hash) {
case dns_hash_sha1:
if (hashlen != ISC_SHA1_DIGESTLENGTH || sr.length < hashlen) {
RETERR(DNS_R_FORMERR);
}
break;
default:
if (hashlen < 1 || hashlen > NSEC3_MAX_HASH_LENGTH ||
sr.length < hashlen)
{
RETERR(DNS_R_FORMERR);
}
break;
}
isc_region_consume(&sr, hashlen);
@ -265,7 +288,6 @@ fromstruct_nsec3(ARGS_FROMSTRUCT) {
REQUIRE(nsec3->common.rdtype == type);
REQUIRE(nsec3->common.rdclass == rdclass);
REQUIRE(nsec3->typebits != NULL || nsec3->len == 0);
REQUIRE(nsec3->hash == dns_hash_sha1);
UNUSED(type);
UNUSED(rdclass);
@ -324,6 +346,7 @@ tostruct_nsec3(ARGS_TOSTRUCT) {
}
nsec3->mctx = mctx;
return ISC_R_SUCCESS;
cleanup:

View file

@ -339,6 +339,9 @@ trynsec3:
if (nsec3.hash != 1) {
continue;
}
if (nsec3.next_length > NSEC3_MAX_HASH_LENGTH) {
continue;
}
length = isc_iterated_hash(
hash, nsec3.hash, nsec3.iterations, nsec3.salt,
nsec3.salt_length, name->ndata, name->length);

View file

@ -15,18 +15,6 @@
#include <isc/lang.h>
/*
* The maximal hash length that can be encoded in a name
* using base32hex. floor(255/8)*5
*/
#define NSEC3_MAX_HASH_LENGTH 155
/*
* The maximum has that can be encoded in a single label using
* base32hex. floor(63/8)*5
*/
#define NSEC3_MAX_LABEL_HASH 35
ISC_LANG_BEGINDECLS
int

View file

@ -2374,8 +2374,7 @@ ISC_RUN_TEST_IMPL(nsec) {
* RFC 5155.
*/
ISC_RUN_TEST_IMPL(nsec3) {
text_ok_t text_ok[] = { TEXT_INVALID(""),
TEXT_INVALID("."),
text_ok_t text_ok[] = { TEXT_INVALID(""), TEXT_INVALID("."),
TEXT_INVALID(". RRSIG"),
TEXT_INVALID("1 0 10 76931F"),
TEXT_INVALID("1 0 10 76931F "
@ -2391,9 +2390,38 @@ ISC_RUN_TEST_IMPL(nsec3) {
"AJHVGTICN6K0VDA53GCHFMT219SRRQLM"),
TEXT_VALID("1 0 10 - "
"AJHVGTICN6K0VDA53GCHFMT219SRRQLM"),
/* 123456789012345678901234567890123456789 */
TEXT_VALID("2 0 10 - "
"64P36D1L6ORJGE9G64P36D1L6ORJGE9G64P"
"36D1L6ORJGE9G64P36D1L6ORJGE8"),
/* 1234567890123456789012345678901234567890 */
TEXT_INVALID("2 0 10 - "
"64P36D1L6ORJGE9G64P36D1L6ORJGE9G6"
"4P36D1L6ORJGE9G64P36D1L6ORJGE9G"),
TEXT_SENTINEL() };
wire_ok_t wire_ok[] = {
WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00),
/* maximal hash */
WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09),
/* Too big hash */
WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x00),
/*
* Sentinel.
*/
WIRE_SENTINEL()
};
check_rdata(text_ok, NULL, NULL, false, dns_rdataclass_in,
check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
dns_rdatatype_nsec3, sizeof(dns_rdata_nsec3_t));
}