Fix NSEC bitmap parsing infinite loop. CVE-2026-4890

Report from Royce M <royce@xchglabs.com>.

Location: dnssec.c:1290-1306, dnssec.c:1450-1463

The bitmap window iteration advances by p[1] instead of p[1]+2
(missing the 2-byte window header). With bitmap_length=0, both rdlen and p are
unchanged, causing an infinite loop and dnsmasq stops responding to all queries.

Bug is reachable before RRSIG validation
(confirmed by the source comment at line 2125), so no valid
DNSSEC signatures are needed.
This commit is contained in:
Simon Kelley committed 2026-05-10 21:49:40 +01:00
1 parent 014e909f78
commit 7b151eb606
1 file changed
+58 -81
+58 -81
View File
@@ -1243,15 +1243,23 @@ static int hostname_cmp(const char *a, const char *b)
}
}
static int check_type_bitmap(unsigned char *p, int type)
{
int offset = (type & 0xff) >> 3;
if (p[0] == type >> 8 && p[1] > offset && (p[offset + 2] & (0x80 >> (type & 0x07))))
return 1;
return 0;
}
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
{
int i, rc, rdlen;
unsigned char *p, *psave;
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
if (nons)
*nons = 1;
@@ -1295,17 +1303,6 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* rdlen is now length of type map, and p points to it
packet checked to be as long as rdlen implies in prove_non_existence() */
/* check that the first typemap is complete. */
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
/* RFC 6672 5.3.4.1. */
#define DNAME_OFFSET (T_DNAME >> 3)
#define DNAME_MASK (0x80 >> (T_DNAME & 0x07))
if (p[0] == 0 && (p[1] >= DNAME_OFFSET + 1) && (p[2 + DNAME_OFFSET] & DNAME_MASK) != 0 &&
hostname_issubdomain(name, workspace1) == 1)
return DNSSEC_FAIL_NONSEC;
rc = hostname_cmp(workspace1, name);
if (rc == 0)
@@ -1314,42 +1311,37 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
if (type == T_NSEC || type == T_RRSIG)
return 0;
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
if (p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return DNSSEC_FAIL_NONSEC;
}
while (rdlen > 0)
{
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
{
/* Does the NSEC say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return DNSSEC_FAIL_NONSEC;
break; /* finished checking */
}
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
if (check_type_bitmap(p, type))
return DNSSEC_FAIL_NONSEC;
/* If we can prove that there's no NS record, return that information. */
if (nons && check_type_bitmap(p, T_NS))
*nons = 0;
rdlen -= p[1];
p += p[1];
/* A CNAME answer would also be valid, so if there's a CNAME it should
have been returned. */
if (check_type_bitmap(p, T_CNAME))
return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && check_type_bitmap(p, T_DS))
return DNSSEC_FAIL_NONSEC;
/* RFC 6672 5.3.4.1. */
if (check_type_bitmap(p, T_DNAME) && hostname_issubdomain(name, workspace1) == 1)
return DNSSEC_FAIL_NONSEC;
rdlen -= p[1] + 2;
p += p[1] + 2;
}
return 0;
@@ -1473,50 +1465,35 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
we just need to check the type map. p points to the RR data for the record.
Note we have packet length up to rdlen bytes checked. */
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
p += hash_len; /* skip next-domain hash */
rdlen -= p - psave;
/* check that the first typemap is complete. */
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
return 0;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return 0;
}
while (rdlen > 0)
{
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
return 0;
/* Does the NSEC3 say our type exists? */
if (check_type_bitmap(p, type))
return 0;
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return 0;
break; /* finished checking */
}
rdlen -= p[1];
p += p[1];
/* If we can prove that there's no NS record, return that information. */
if (nons && check_type_bitmap(p, T_NS))
*nons = 0;
/* A CNAME answer would also be valid, so if there's a CNAME it should
have been returned. */
if (check_type_bitmap(p, T_CNAME))
return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && check_type_bitmap(p, T_DS))
return 0;
rdlen -= p[1] + 2;
p += p[1] + 2;
}
return 1;