Implement "DNS-0x20 encoding".

This provides extra protection against reply-spoof attacks.

Since DNS queries are case-insensitive, it's possible to randomly flip
the case of letters in a query and still get the correct answer back.
This adds an extra dimension for a cache-poisoning attacker to guess
when sending replies in-the-blind since it's expected that the
legitimate answer will have the same pattern of upper and lower case
as the query, so any replies which don't can be ignored as
malicious.

The amount of extra entropy clearly depends on the number
of a-z and A-Z characters in the query, and this implementation puts a
hard limit of 32 bits to make rescource allocation easy. This about
doubles entropy over the standard random ID and random port
combination.
This commit is contained in:
Simon Kelley
2025-01-19 21:44:19 +00:00
parent 65f9c1aca1
commit 995a16ca0c
6 changed files with 113 additions and 66 deletions

View File

@@ -191,7 +191,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
/* domain-name, canonicalise */
int len;
if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
@@ -339,7 +339,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
pstart = p;
if (!(res = extract_name(header, plen, &p, name, 0, 10)))
if (!(res = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return 0; /* bad packet */
GETSHORT(stype, p);
@@ -374,14 +374,14 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
if (gotkey)
{
/* If there's more than one SIG, ensure they all have same keyname */
if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
if (extract_name(header, plen, &p, keyname, EXTR_NAME_COMPARE, 0) != 1)
return 0;
}
else
{
gotkey = 1;
if (!extract_name(header, plen, &p, keyname, 1, 0))
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
return 0;
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
@@ -503,7 +503,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
GETLONG(sig_inception, p);
GETSHORT(key_tag, p);
if (!extract_name(header, plen, &p, keyname, 1, 0))
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS;
if (!time_check)
@@ -568,7 +568,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
p = rrset[i];
if (!extract_name(header, plen, &p, name, 1, 10))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS;
name_start = name;
@@ -661,7 +661,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
if (!extract_name(header, plen, &p, name, 1, 0))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS;
if (key)
@@ -727,7 +727,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
static unsigned char **cached_digest;
static size_t cached_digest_size = 0;
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p);
@@ -752,7 +752,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p);
@@ -904,7 +904,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p);
@@ -1024,7 +1024,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
}
p = (unsigned char *)(header+1);
if (!extract_name(header, plen, &p, name, 1, 4))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS;
p += 4; /* qtype, qclass */
@@ -1050,7 +1050,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
{
unsigned char *psave;
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(atype, p);
@@ -1231,12 +1231,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
int sig_labels, name_labels;
p = nsecs[i];
if (!extract_name(header, plen, &p, workspace1, 1, 10))
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 10))
return DNSSEC_FAIL_BADPACKET;
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
psave = p;
if (!extract_name(header, plen, &p, workspace2, 1, 0))
if (!extract_name(header, plen, &p, workspace2, EXTR_NAME_EXTRACT, 0))
return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard
@@ -1400,7 +1400,7 @@ 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, 10) ||
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 10) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0;
@@ -1609,7 +1609,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
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, EXTR_NAME_EXTRACT, 0))
return DNSSEC_FAIL_BADPACKET;
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
@@ -1684,7 +1684,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
{
unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
if (!extract_name(header, plen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 10))
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p);
@@ -1735,7 +1735,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
{
unsigned char *psav;
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, EXTR_NAME_COMPARE, 10)))
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type1, p1);
@@ -1965,7 +1965,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
targets[0] = p1;
targetidx = 1;
if (!extract_name(header, plen, &p1, name, 1, 4))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS;
GETSHORT(qtype, p1);
@@ -2003,7 +2003,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
if (!extract_name(header, plen, &p1, name, 1, 10))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type1, p1);
@@ -2018,7 +2018,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Check if we've done this RRset already */
for (p2 = ans_start, j = 0; j < i; j++)
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p2, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p2);
@@ -2115,7 +2115,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if ((p2 = targets[j]))
{
int rc1;
if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
if (!(rc1 = extract_name(header, plen, &p2, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
@@ -2149,7 +2149,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (neganswer)
*neganswer = 1;
if (!extract_name(header, plen, &p2, name, 1, 10))
if (!extract_name(header, plen, &p2, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */