mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 10:18:25 +00:00
Nasty cache failure and memory leak with DNSSEC.
This commit is contained in:
@@ -21,20 +21,33 @@
|
||||
static struct blockdata *keyblock_free;
|
||||
static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
|
||||
|
||||
static void blockdata_expand(int n)
|
||||
{
|
||||
struct blockdata *new = whine_malloc(n * sizeof(struct blockdata));
|
||||
|
||||
if (new)
|
||||
{
|
||||
int i;
|
||||
|
||||
new[n-1].next = keyblock_free;
|
||||
keyblock_free = new;
|
||||
|
||||
for (i = 0; i < n - 1; i++)
|
||||
new[i].next = &new[i+1];
|
||||
|
||||
blockdata_alloced += n;
|
||||
}
|
||||
}
|
||||
|
||||
/* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */
|
||||
void blockdata_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
blockdata_alloced = (daemon->cachesize * 100) / sizeof(struct blockdata);
|
||||
|
||||
keyblock_free = safe_malloc(blockdata_alloced * sizeof(struct blockdata));
|
||||
keyblock_free[blockdata_alloced-1].next = NULL;
|
||||
for (i = 0; i < blockdata_alloced - 1; i++)
|
||||
keyblock_free[i].next = &keyblock_free[i+1];
|
||||
|
||||
keyblock_free = NULL;
|
||||
blockdata_alloced = 0;
|
||||
blockdata_count = 0;
|
||||
blockdata_hwm = 0;
|
||||
|
||||
blockdata_expand((daemon->cachesize * 100) / sizeof(struct blockdata));
|
||||
}
|
||||
|
||||
void blockdata_report(void)
|
||||
@@ -51,18 +64,15 @@ struct blockdata *blockdata_alloc(char *data, size_t len)
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
if (!keyblock_free)
|
||||
blockdata_expand(50);
|
||||
|
||||
if (keyblock_free)
|
||||
{
|
||||
block = keyblock_free;
|
||||
keyblock_free = block->next;
|
||||
blockdata_count++;
|
||||
}
|
||||
else if ((block = whine_malloc(sizeof(struct blockdata))))
|
||||
{
|
||||
blockdata_count++;
|
||||
if (blockdata_alloced < blockdata_count)
|
||||
blockdata_alloced = blockdata_count;
|
||||
}
|
||||
|
||||
if (!block)
|
||||
{
|
||||
|
||||
26
src/cache.c
26
src/cache.c
@@ -486,14 +486,32 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
|
||||
insert. Once in this state, all inserts will probably fail. */
|
||||
if (free_avail)
|
||||
{
|
||||
static warned = 0;
|
||||
if (!warned)
|
||||
{
|
||||
my_syslog(LOG_ERR, _("Internal error in cache."));
|
||||
warned = 1;
|
||||
}
|
||||
insert_error = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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. */
|
||||
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++;
|
||||
}
|
||||
else
|
||||
@@ -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.
|
||||
If that fails, give up now. */
|
||||
If that fails, give up now, always succeed for DNSSEC records. */
|
||||
if (name && (strlen(name) > SMALLDNAME-1))
|
||||
{
|
||||
if (big_free)
|
||||
@@ -513,13 +531,13 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
|
||||
big_name = big_free;
|
||||
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))))
|
||||
{
|
||||
insert_error = 1;
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
else if (bignames_left != 0)
|
||||
bignames_left--;
|
||||
|
||||
}
|
||||
|
||||
187
src/dnssec.c
187
src/dnssec.c
@@ -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 */
|
||||
|
||||
cache_start_insert(); /* RRSIGS */
|
||||
|
||||
/* 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);
|
||||
j != 0; j--)
|
||||
@@ -577,33 +575,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
}
|
||||
@@ -612,8 +584,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
cache_end_insert(); /* RRSIGS */
|
||||
|
||||
/* RRset empty, no RRSIGs */
|
||||
if (rrsetidx == 0 || sigidx == 0)
|
||||
return STAT_INSECURE;
|
||||
@@ -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.
|
||||
Leave name of query in name.
|
||||
Put all DNSKEYs in the answer which are valid into the cache.
|
||||
return codes:
|
||||
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
|
||||
{
|
||||
unsigned char *psave, *p = (unsigned char *)(header+1);
|
||||
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 all_addr a;
|
||||
|
||||
if (ntohs(header->qdcount) != 1 ||
|
||||
!extract_name(header, plen, &p, name, 1, 4))
|
||||
{
|
||||
strcpy(name, "<none>");
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
GETSHORT(qtype, p);
|
||||
GETSHORT(qclass, p);
|
||||
@@ -821,7 +787,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
p = psave;
|
||||
|
||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
||||
{
|
||||
if (key)
|
||||
blockdata_free(key);
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
}
|
||||
|
||||
/* No zone key flag or malloc failure */
|
||||
if (!key)
|
||||
@@ -865,7 +835,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
GETLONG(ttl, p);
|
||||
GETSHORT(rdlen, p);
|
||||
|
||||
if (qclass != class || qtype != T_DNSKEY || rc == 2)
|
||||
if (!CHECK_LEN(header, p, plen, rdlen))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
if (qclass == class && rc == 1)
|
||||
{
|
||||
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 */
|
||||
|
||||
psave = p;
|
||||
|
||||
if (qtype == T_DNSKEY)
|
||||
{
|
||||
if (rdlen < 4)
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
GETSHORT(flags, p);
|
||||
if (*p++ != 3)
|
||||
return STAT_INSECURE;
|
||||
@@ -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 */
|
||||
a.addr.dnssec.class = class;
|
||||
|
||||
if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
|
||||
(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
|
||||
if ((key = blockdata_alloc((char*)p, rdlen - 4)))
|
||||
{
|
||||
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;
|
||||
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
|
||||
recp1->addr.key.flags = flags;
|
||||
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;
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p, plen, rdlen))
|
||||
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
|
||||
}
|
||||
|
||||
/* 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.
|
||||
return codes:
|
||||
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
|
||||
|
||||
if (ntohs(header->qdcount) != 1 ||
|
||||
!extract_name(header, plen, &p, name, 1, 4))
|
||||
{
|
||||
strcpy(name, "<none>");
|
||||
return STAT_INSECURE;
|
||||
}
|
||||
|
||||
GETSHORT(qtype, p);
|
||||
GETSHORT(qclass, p);
|
||||
@@ -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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
@@ -1082,6 +1088,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
/* Not done, validate now */
|
||||
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 (class)
|
||||
@@ -1089,18 +1101,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 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 RRsigs in answer section, and if we just validated a DS RRset, cache it */
|
||||
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)))
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
@@ -1110,9 +1114,18 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
GETLONG(ttl, 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;
|
||||
|
||||
if (type1 == T_DS && type2 == T_DS)
|
||||
{
|
||||
if (rdlen2 < 4)
|
||||
return STAT_INSECURE; /* bad packet */
|
||||
|
||||
GETSHORT(keytag, p2);
|
||||
algo = *p2++;
|
||||
digest = *p2++;
|
||||
@@ -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 */
|
||||
a.addr.dnssec.class = class2;
|
||||
|
||||
if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)) &&
|
||||
(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
|
||||
if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
|
||||
{
|
||||
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
|
||||
blockdata_free(key);
|
||||
else
|
||||
{
|
||||
a.addr.keytag = keytag;
|
||||
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
|
||||
crecp->uid = class2;
|
||||
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;
|
||||
}
|
||||
@@ -1143,7 +1195,6 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
||||
cache_end_insert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
||||
return STAT_INSECURE;
|
||||
|
||||
Reference in New Issue
Block a user