Commit 8d718cbb authored by Simon Kelley's avatar Simon Kelley

Nasty cache failure and memory leak with DNSSEC.

parent f6a2b793
...@@ -21,20 +21,33 @@ ...@@ -21,20 +21,33 @@
static struct blockdata *keyblock_free; static struct blockdata *keyblock_free;
static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced; static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
/* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */ static void blockdata_expand(int n)
void blockdata_init(void)
{ {
struct blockdata *new = whine_malloc(n * sizeof(struct blockdata));
if (new)
{
int i; int i;
blockdata_alloced = (daemon->cachesize * 100) / sizeof(struct blockdata); new[n-1].next = keyblock_free;
keyblock_free = new;
keyblock_free = safe_malloc(blockdata_alloced * sizeof(struct blockdata)); for (i = 0; i < n - 1; i++)
keyblock_free[blockdata_alloced-1].next = NULL; new[i].next = &new[i+1];
for (i = 0; i < blockdata_alloced - 1; i++)
keyblock_free[i].next = &keyblock_free[i+1];
blockdata_alloced += n;
}
}
/* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */
void blockdata_init(void)
{
keyblock_free = NULL;
blockdata_alloced = 0;
blockdata_count = 0; blockdata_count = 0;
blockdata_hwm = 0; blockdata_hwm = 0;
blockdata_expand((daemon->cachesize * 100) / sizeof(struct blockdata));
} }
void blockdata_report(void) void blockdata_report(void)
...@@ -51,18 +64,15 @@ struct blockdata *blockdata_alloc(char *data, size_t len) ...@@ -51,18 +64,15 @@ struct blockdata *blockdata_alloc(char *data, size_t len)
while (len > 0) while (len > 0)
{ {
if (!keyblock_free)
blockdata_expand(50);
if (keyblock_free) if (keyblock_free)
{ {
block = keyblock_free; block = keyblock_free;
keyblock_free = block->next; keyblock_free = block->next;
blockdata_count++; blockdata_count++;
} }
else if ((block = whine_malloc(sizeof(struct blockdata))))
{
blockdata_count++;
if (blockdata_alloced < blockdata_count)
blockdata_alloced = blockdata_count;
}
if (!block) if (!block)
{ {
......
...@@ -486,14 +486,32 @@ struct crec *cache_insert(char *name, struct all_addr *addr, ...@@ -486,14 +486,32 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
insert. Once in this state, all inserts will probably fail. */ insert. Once in this state, all inserts will probably fail. */
if (free_avail) if (free_avail)
{ {
static warned = 0;
if (!warned)
{
my_syslog(LOG_ERR, _("Internal error in cache."));
warned = 1;
}
insert_error = 1; insert_error = 1;
return NULL; return NULL;
} }
if (freed_all) if (freed_all)
{ {
struct all_addr free_addr = new->addr.addr;;
#ifdef HAVE_DNSSEC
/* For DNSSEC records, addr holds class and type_covered for RRSIG */
if (new->flags & (F_DS | F_DNSKEY))
{
free_addr.addr.dnssec.class = new->uid;
if ((new->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY))
free_addr.addr.dnssec.type = new->addr.sig.type_covered;
}
#endif
free_avail = 1; /* Must be free space now. */ free_avail = 1; /* Must be free space now. */
cache_scan_free(cache_get_name(new), &new->addr.addr, now, new->flags); cache_scan_free(cache_get_name(new), &free_addr, now, new->flags);
cache_live_freed++; cache_live_freed++;
} }
else else
...@@ -505,7 +523,7 @@ struct crec *cache_insert(char *name, struct all_addr *addr, ...@@ -505,7 +523,7 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
} }
/* Check if we need to and can allocate extra memory for a long name. /* Check if we need to and can allocate extra memory for a long name.
If that fails, give up now. */ If that fails, give up now, always succeed for DNSSEC records. */
if (name && (strlen(name) > SMALLDNAME-1)) if (name && (strlen(name) > SMALLDNAME-1))
{ {
if (big_free) if (big_free)
...@@ -513,13 +531,13 @@ struct crec *cache_insert(char *name, struct all_addr *addr, ...@@ -513,13 +531,13 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
big_name = big_free; big_name = big_free;
big_free = big_free->next; big_free = big_free->next;
} }
else if (!bignames_left || else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
!(big_name = (union bigname *)whine_malloc(sizeof(union bigname)))) !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
{ {
insert_error = 1; insert_error = 1;
return NULL; return NULL;
} }
else else if (bignames_left != 0)
bignames_left--; bignames_left--;
} }
......
...@@ -500,8 +500,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -500,8 +500,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
name_labels = count_labels(name); /* For 4035 5.3.2 check */ name_labels = count_labels(name); /* For 4035 5.3.2 check */
cache_start_insert(); /* RRSIGS */
/* look for RRSIGs for this RRset and get pointers to each RR in the set. */ /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);
j != 0; j--) j != 0; j--)
...@@ -577,33 +575,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -577,33 +575,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
} }
sigs[sigidx++] = pdata; sigs[sigidx++] = pdata;
/* If it's a type we're going to cache, cache the RRISG too */
if (type_covered == T_A || type_covered == T_AAAA ||
type_covered == T_CNAME || type_covered == T_DS ||
type_covered == T_DNSKEY || type_covered == T_PTR)
{
struct all_addr a;
struct blockdata *block;
a.addr.dnssec.class = class;
a.addr.dnssec.type = type_covered;
algo = *p++;
p += 13; /* labels, orig_ttl, expiration, inception */
GETSHORT(key_tag, p);
if ((block = blockdata_alloc((char*)pdata + 2, rdlen)) &&
(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS)))
{
crecp->uid = class;
crecp->addr.sig.keydata = block;
crecp->addr.sig.keylen = rdlen;
crecp->addr.sig.keytag = key_tag;
crecp->addr.sig.type_covered = type_covered;
crecp->addr.sig.algo = algo;
} }
}
}
p = pdata + 2; /* restore for ADD_RDLEN */ p = pdata + 2; /* restore for ADD_RDLEN */
} }
} }
...@@ -612,8 +584,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -612,8 +584,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
return STAT_INSECURE; return STAT_INSECURE;
} }
cache_end_insert(); /* RRSIGS */
/* RRset empty, no RRSIGs */ /* RRset empty, no RRSIGs */
if (rrsetidx == 0 || sigidx == 0) if (rrsetidx == 0 || sigidx == 0)
return STAT_INSECURE; return STAT_INSECURE;
...@@ -747,7 +717,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -747,7 +717,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
} }
/* The DNS packet is expected to contain the answer to a DNSKEY query. /* The DNS packet is expected to contain the answer to a DNSKEY query.
Leave name of query in name.
Put all DNSKEYs in the answer which are valid into the cache. Put all DNSKEYs in the answer which are valid into the cache.
return codes: return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply. STAT_INSECURE bad packet, no DNSKEYs in reply.
...@@ -760,16 +729,13 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -760,16 +729,13 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
{ {
unsigned char *psave, *p = (unsigned char *)(header+1); unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1; struct crec *crecp, *recp1;
int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered;
struct blockdata *key; struct blockdata *key;
struct all_addr a; struct all_addr a;
if (ntohs(header->qdcount) != 1 || if (ntohs(header->qdcount) != 1 ||
!extract_name(header, plen, &p, name, 1, 4)) !extract_name(header, plen, &p, name, 1, 4))
{
strcpy(name, "<none>");
return STAT_INSECURE; return STAT_INSECURE;
}
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
...@@ -821,7 +787,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -821,7 +787,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
p = psave; p = psave;
if (!ADD_RDLEN(header, p, plen, rdlen)) if (!ADD_RDLEN(header, p, plen, rdlen))
{
if (key)
blockdata_free(key);
return STAT_INSECURE; /* bad packet */ return STAT_INSECURE; /* bad packet */
}
/* No zone key flag or malloc failure */ /* No zone key flag or malloc failure */
if (!key) if (!key)
...@@ -865,7 +835,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -865,7 +835,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
if (valid) if (valid)
{ {
/* DNSKEY RRset determined to be OK, now cache it. */ /* DNSKEY RRset determined to be OK, now cache it and the RRsigs that sign it. */
cache_start_insert(); cache_start_insert();
p = skip_questions(header, plen); p = skip_questions(header, plen);
...@@ -881,19 +851,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -881,19 +851,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (qclass != class || qtype != T_DNSKEY || rc == 2) if (!CHECK_LEN(header, p, plen, rdlen))
{
if (ADD_RDLEN(header, p, plen, rdlen))
continue;
return STAT_INSECURE; /* bad packet */
}
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
return STAT_INSECURE; /* bad packet */ return STAT_INSECURE; /* bad packet */
if (qclass == class && rc == 1)
{
psave = p; psave = p;
if (qtype == T_DNSKEY)
{
if (rdlen < 4)
return STAT_INSECURE; /* bad packet */
GETSHORT(flags, p); GETSHORT(flags, p);
if (*p++ != 3) if (*p++ != 3)
return STAT_INSECURE; return STAT_INSECURE;
...@@ -903,11 +872,12 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -903,11 +872,12 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
/* Cache needs to known class for DNSSEC stuff */ /* Cache needs to known class for DNSSEC stuff */
a.addr.dnssec.class = class; a.addr.dnssec.class = class;
if ((key = blockdata_alloc((char*)p, rdlen - 4)) && if ((key = blockdata_alloc((char*)p, rdlen - 4)))
(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) {
if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
blockdata_free(key);
else
{ {
struct all_addr a;
a.addr.keytag = keytag; a.addr.keytag = keytag;
log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
...@@ -918,8 +888,44 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -918,8 +888,44 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
recp1->addr.key.flags = flags; recp1->addr.key.flags = flags;
recp1->uid = class; recp1->uid = class;
} }
}
}
else if (qtype == T_RRSIG)
{
/* RRSIG, cache if covers DNSKEY RRset */
if (rdlen < 18)
return STAT_INSECURE; /* bad packet */
GETSHORT(type_covered, p);
if (type_covered == T_DNSKEY)
{
a.addr.dnssec.class = class;
a.addr.dnssec.type = type_covered;
algo = *p++;
p += 13; /* labels, orig_ttl, expiration, inception */
GETSHORT(keytag, p);
if ((key = blockdata_alloc((char*)psave, rdlen)))
{
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS)))
blockdata_free(key);
else
{
crecp->uid = class;
crecp->addr.sig.keydata = key;
crecp->addr.sig.keylen = rdlen;
crecp->addr.sig.keytag = keytag;
crecp->addr.sig.type_covered = type_covered;
crecp->addr.sig.algo = algo;
}
}
}
}
p = psave; p = psave;
}
if (!ADD_RDLEN(header, p, plen, rdlen)) if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */ return STAT_INSECURE; /* bad packet */
} }
...@@ -934,7 +940,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -934,7 +940,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
} }
/* The DNS packet is expected to contain the answer to a DS query /* The DNS packet is expected to contain the answer to a DS query
Leave name of DS query in name.
Put all DSs in the answer which are valid into the cache. Put all DSs in the answer which are valid into the cache.
return codes: return codes:
STAT_INSECURE bad packet, no DS in reply. STAT_INSECURE bad packet, no DS in reply.
...@@ -950,10 +955,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -950,10 +955,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (ntohs(header->qdcount) != 1 || if (ntohs(header->qdcount) != 1 ||
!extract_name(header, plen, &p, name, 1, 4)) !extract_name(header, plen, &p, name, 1, 4))
{
strcpy(name, "<none>");
return STAT_INSECURE; return STAT_INSECURE;
}
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
...@@ -964,7 +966,11 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -964,7 +966,11 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL); val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
if (val == STAT_BOGUS) if (val == STAT_BOGUS)
{
p = (unsigned char *)(header+1);
extract_name(header, plen, &p, name, 1, 4);
log_query(F_UPSTREAM, name, NULL, "BOGUS DS"); log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
}
return val; return val;
} }
...@@ -1082,6 +1088,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1082,6 +1088,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Not done, validate now */ /* Not done, validate now */
if (j == i) if (j == i)
{ {
int ttl, keytag, algo, digest, type_covered;
unsigned char *psave;
struct all_addr a;
struct blockdata *key;
struct crec *crecp;
if ((rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE) if ((rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
{ {
if (class) if (class)
...@@ -1089,18 +1101,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1089,18 +1101,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return rc; return rc;
} }
/* If we just validated a DS RRset, cache it */ /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
if (type1 == T_DS)
{
int ttl, keytag, algo, digest;
unsigned char *psave;
struct all_addr a;
struct blockdata *key;
struct crec *crecp;
cache_start_insert(); cache_start_insert();
for (p2 = ans_start, j = 0; j < ntohs(header->ancount) + ntohs(header->nscount); j++) for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
{ {
if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
return STAT_INSECURE; /* bad packet */ return STAT_INSECURE; /* bad packet */
...@@ -1110,9 +1114,18 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1110,9 +1114,18 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
GETLONG(ttl, p2); GETLONG(ttl, p2);
GETSHORT(rdlen2, p2); GETSHORT(rdlen2, p2);
if (type2 == T_DS && class2 == class1 && rc == 1) if (!CHECK_LEN(header, p2, plen, rdlen2))
return STAT_INSECURE; /* bad packet */
if (class2 == class1 && rc == 1)
{ {
psave = p2; psave = p2;
if (type1 == T_DS && type2 == T_DS)
{
if (rdlen2 < 4)
return STAT_INSECURE; /* bad packet */
GETSHORT(keytag, p2); GETSHORT(keytag, p2);
algo = *p2++; algo = *p2++;
digest = *p2++; digest = *p2++;
...@@ -1120,8 +1133,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1120,8 +1133,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Cache needs to known class for DNSSEC stuff */ /* Cache needs to known class for DNSSEC stuff */
a.addr.dnssec.class = class2; a.addr.dnssec.class = class2;
if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)) && if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) {
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
blockdata_free(key);
else
{ {
a.addr.keytag = keytag; a.addr.keytag = keytag;
log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
...@@ -1132,6 +1148,42 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1132,6 +1148,42 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
crecp->uid = class2; crecp->uid = class2;
crecp->addr.ds.keylen = rdlen2 - 4; crecp->addr.ds.keylen = rdlen2 - 4;
} }
}
}
else if (type2 == T_RRSIG)
{
if (rdlen2 < 18)
return STAT_INSECURE; /* bad packet */
GETSHORT(type_covered, p2);
if (type_covered == type1 &&
(type_covered == T_A || type_covered == T_AAAA ||
type_covered == T_CNAME || type_covered == T_DS ||
type_covered == T_DNSKEY || type_covered == T_PTR))
{
a.addr.dnssec.type = type_covered;
algo = *p2++;
p2 += 13; /* labels, orig_ttl, expiration, inception */
GETSHORT(keytag, p2);
if ((key = blockdata_alloc((char*)psave, rdlen2)))
{
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS)))
blockdata_free(key);
else
{
crecp->uid = class1;
crecp->addr.sig.keydata = key;
crecp->addr.sig.keylen = rdlen2;
crecp->addr.sig.keytag = keytag;
crecp->addr.sig.type_covered = type_covered;
crecp->addr.sig.algo = algo;
}
}
}
}
p2 = psave; p2 = psave;
} }
...@@ -1143,7 +1195,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1143,7 +1195,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
cache_end_insert(); cache_end_insert();
} }
} }
}
if (!ADD_RDLEN(header, p1, plen, rdlen1)) if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_INSECURE; return STAT_INSECURE;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment