From: Simon Kelley Date: Fri, 5 Jan 2024 22:56:47 +0000 (+0000) Subject: Overhaul data checking in NSEC code. X-Git-Tag: v2.90~1^2~4 X-Git-Url: https://thekelleys.org.uk/gitweb/?a=commitdiff_plain;h=76bceb06c46618797bbc2c24f73ce1e650c76b31;p=dnsmasq.git Overhaul data checking in NSEC code. --- diff --git a/src/dnssec.c b/src/dnssec.c index 036d560..c3246cb 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -918,7 +918,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch a.key.flags = flags; if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)) - return STAT_BOGUS; + { + blockdata_free(key); + return STAT_BOGUS; + } a.log.keytag = keytag; a.log.algo = algo; @@ -1019,6 +1022,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char for (i = 0; i < ntohs(header->ancount); i++) { + unsigned char *psave; + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) return STAT_BOGUS; /* bad packet */ @@ -1029,15 +1034,16 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad packet */ + + psave = p; if (aclass == class && atype == T_DS && rc == 1) { int algo, digest, keytag; - unsigned char *psave = p; struct blockdata *key; - if (rdlen < 4) - return STAT_BOGUS; /* bad packet */ + if (rdlen < 5) + return STAT_BOGUS; /* min 1 byte digest! */ GETSHORT(keytag, p); algo = *p++; @@ -1073,12 +1079,9 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char found_supported = 1; } } - - p = psave; } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_BOGUS; /* bad packet */ + + p = psave + rdlen; } cache_end_insert(); @@ -1201,11 +1204,11 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi p = nsecs[i]; if (!extract_name(header, plen, &p, workspace1, 1, 10)) - return 0; + return DNSSEC_FAIL_BADPACKET; p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); psave = p; - if (!extract_name(header, plen, &p, workspace2, 1, 10)) + if (!extract_name(header, plen, &p, workspace2, 1, 0)) return DNSSEC_FAIL_BADPACKET; /* If NSEC comes from wildcard expansion, use original wildcard @@ -1239,7 +1242,8 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi /* NSEC with the same name as the RR we're testing, check that the type in question doesn't appear in the type map */ rdlen -= p - psave; - /* rdlen is now length of type map, and p points to it */ + /* 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() */ /* If we can prove that there's no NS record, return that information. */ if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) @@ -1368,23 +1372,23 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige for (i = 0; i < nsec_count; i++) if ((p = nsecs[i])) { - if (!extract_name(header, plen, &p, workspace1, 1, 0) || + if (!extract_name(header, plen, &p, workspace1, 1, 10) || !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) return 0; p += 8; /* class, type, TTL */ GETSHORT(rdlen, p); + psave = p; + + /* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */ p++; /* algo */ flags = *p++; /* flags */ p += 2; /* iterations */ salt_len = *p++; /* salt_len */ p += salt_len; /* salt */ hash_len = *p++; /* p now points to next hashed name */ - - if (!CHECK_LEN(header, p, plen, hash_len)) - return 0; - + if (digest_len == base32_len && hash_len == base32_len) { int rc = memcmp(workspace2, digest, digest_len); @@ -1392,7 +1396,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige if (rc == 0) { /* We found an NSEC3 whose hashed name exactly matches the query, so - we just need to check the type map. p points to the RR data for the record. */ + 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); @@ -1400,15 +1405,12 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige p += hash_len; /* skip next-domain hash */ rdlen -= p - psave; - if (!CHECK_LEN(header, p, plen, rdlen)) - return 0; - if (rdlen >= 2 && p[0] == 0) { /* 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) @@ -1511,9 +1513,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns salt_len = *p++; salt = p; - if (!CHECK_LEN(header, salt, plen, salt_len)) - return DNSSEC_FAIL_BADPACKET; /* bad packet */ - + /* Now prune so we only have NSEC3 records with same iterations, salt and algo */ for (i = 0; i < nsec_count; i++) { @@ -1543,9 +1543,6 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (salt_len != *p++) continue; - if (!CHECK_LEN(header, p, plen, salt_len)) - return DNSSEC_FAIL_BADPACKET; /* bad packet */ - if (memcmp(p, salt, salt_len) != 0) continue; @@ -1666,7 +1663,10 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key GETSHORT(class, p); GETLONG(ttl, p); GETSHORT(rdlen, p); - + + if (!CHECK_LEN(header, p, plen, rdlen)) + return DNSSEC_FAIL_BADPACKET; + if (class == qclass && (type == T_NSEC || type == T_NSEC3)) { if (nsec_ttl) @@ -1705,22 +1705,25 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key for (j = ntohs(header->nscount); j != 0; j--) { + unsigned char *psav; + if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) return DNSSEC_FAIL_BADPACKET; - + GETSHORT(type1, p1); GETSHORT(class1, p1); p1 += 4; /* TTL */ GETSHORT(rdlen1, p1); + psav = p1; + if (!CHECK_LEN(header, p1, plen, rdlen1)) return DNSSEC_FAIL_BADPACKET; if (res == 1 && class1 == qclass && type1 == T_RRSIG) { int type_covered; - unsigned char *psav = p1; - + if (rdlen1 < 18) return DNSSEC_FAIL_BADPACKET; /* bad packet */ @@ -1735,24 +1738,47 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key rrsig_labels[nsecs_found] = p1; else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ return DNSSEC_FAIL_NONSEC; - } - p1 = psav; + } } - if (!ADD_RDLEN(header, p1, plen, rdlen1)) - return DNSSEC_FAIL_BADPACKET; + p1 = psav + rdlen1; } /* Must have found at least one sig. */ if (!rrsig_labels[nsecs_found]) return DNSSEC_FAIL_NONSEC; } + else if (type == T_NSEC3) + { + /* Decode the packet structure enough to check that rdlen is big enough + to contain everything other than the type bitmap. + (packet checked to be long enough to contain rdlen above) + We don't need to do any further length checks in check_nes3_coverage() + or prove_non_existence_nsec3() */ + + int salt_len, hash_len; + unsigned char *psav = p; + + if (rdlen < 5) + return DNSSEC_FAIL_BADPACKET; + + p += 4; /* algo, flags, iterations */ + salt_len = *p++; /* salt_len */ + if (rdlen < (6 + salt_len)) + return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */ + + p += salt_len; /* salt */ + hash_len = *p++; + if (rdlen < (6 + salt_len + hash_len)) + return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */ + + p = psav; + } nsecset[nsecs_found++] = pstart; } - if (!ADD_RDLEN(header, p, plen, rdlen)) - return DNSSEC_FAIL_BADPACKET; + p += rdlen; } if (type_found == T_NSEC) diff --git a/src/metrics.c b/src/metrics.c index da40614..e59e762 100644 --- a/src/metrics.c +++ b/src/metrics.c @@ -24,7 +24,9 @@ const char * metric_names[] = { "dns_local_answered", "dns_stale_answered", "dns_unanswered", - "max_crypto_use", + "dnssec_max_crypto_use", + "dnssec_max_sig_fail", + "dnssec_max_work", "bootp", "pxe", "dhcp_ack", diff --git a/src/metrics.h b/src/metrics.h index 3ca2173..cd85e53 100644 --- a/src/metrics.h +++ b/src/metrics.h @@ -23,7 +23,9 @@ enum { METRIC_DNS_LOCAL_ANSWERED, METRIC_DNS_STALE_ANSWERED, METRIC_DNS_UNANSWERED_QUERY, - METRIC_CRYTO_HWM, + METRIC_CRYTO_HWM, + METRIC_SIG_FAIL_HWM, + METRIC_WORK_HWM, METRIC_BOOTP, METRIC_PXE, METRIC_DHCPACK,