From 8a9be9e4931db20a70e14197a17bd80dff62391a Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Sat, 25 Jan 2014 23:17:21 +0000 Subject: [PATCH] Replace CRC32 with SHA1 for spoof detection in DNSSEC builds. --- src/dnsmasq.h | 11 ++++++-- src/dnssec.c | 30 ++++++++++++++++++++ src/forward.c | 78 +++++++++++++++++++++++++++++++++++---------------- 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index c5436f5..68bf9d7 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -539,6 +539,12 @@ struct hostsfile { #define FREC_DNSKEY_QUERY 8 #define FREC_DS_QUERY 16 +#ifdef HAVE_DNSSEC +#define HASH_SIZE 20 /* SHA-1 digest size */ +#else +#define HASH_SIZE sizeof(int) +#endif + struct frec { union mysockaddr source; struct all_addr dest; @@ -550,9 +556,9 @@ struct frec { unsigned int iface; unsigned short orig_id, new_id; int fd, forwardall, flags; - unsigned int crc; time_t time; -#ifdef HAVE_DNSSEC + unsigned char *hash[HASH_SIZE]; +#ifdef HAVE_DNSSEC int class; struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; @@ -1070,6 +1076,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class); int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); +unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); /* util.c */ void rand_init(void); diff --git a/src/dnssec.c b/src/dnssec.c index ec1e6c1..ebd7254 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -1366,5 +1366,35 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i return add_do_bit(header, p - (unsigned char *)header, end); } + +unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) +{ + int q; + unsigned int len; + unsigned char *p = (unsigned char *)(header+1); + const struct nettle_hash *hash; + void *ctx; + unsigned char *digest; + if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) + return NULL; + + for (q = ntohs(header->qdcount); q != 0; q--) + { + if (!extract_name(header, plen, &p, name, 1, 4)) + return digest; /* bad packet */ + + len = to_wire(name); + hash->update(ctx, len, (unsigned char *)name); + /* CRC the class and type as well */ + hash->update(ctx, 4, p); + + p += 4; + if (!CHECK_LEN(header, p, plen, 0)) + return digest; /* bad packet */ + } + + return digest; +} + #endif /* HAVE_DNSSEC */ diff --git a/src/forward.c b/src/forward.c index 6ac71e6..2bc2244 100644 --- a/src/forward.c +++ b/src/forward.c @@ -16,11 +16,11 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, unsigned int crc); +static struct frec *lookup_frec(unsigned short id, void *hash); static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, - unsigned int crc); -static unsigned short get_id(unsigned int crc); + void *hash); +static unsigned short get_id(void); static void free_frec(struct frec *f); static struct randfd *allocate_rfd(int family); @@ -239,18 +239,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, char *domain = NULL; int type = 0, norebind = 0; struct all_addr *addrp = NULL; - unsigned int crc = questions_crc(header, plen, daemon->namebuff); unsigned int flags = 0; - unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); struct server *start = NULL; - +#ifdef HAVE_DNSSEC + void *hash = hash_questions(header, plen, daemon->namebuff); +#else + unsigned int crc = questions_crc(header, plen, daemon->namebuff); + void *hash = &crc; +#endif + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + /* RFC 4035: sect 4.6 para 2 */ header->hb4 &= ~HB4_AD; /* may be no servers available. */ if (!daemon->servers) forward = NULL; - else if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, crc))) + else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) { #ifdef HAVE_DNSSEC /* If we've already got an answer to this query, but we're awaiting keys for vaildation, @@ -320,9 +325,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, forward->dest = *dst_addr; forward->iface = dst_iface; forward->orig_id = ntohs(header->id); - forward->new_id = get_id(crc); + forward->new_id = get_id(); forward->fd = udpfd; - forward->crc = crc; + memcpy(forward->hash, hash, HASH_SIZE); forward->forwardall = 0; forward->flags = 0; if (norebind) @@ -653,7 +658,11 @@ void reply_query(int fd, int family, time_t now) ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen); size_t nn; struct server *server; - + void *hash; +#ifndef HAVE_DNSSEC + unsigned int crc; +#endif + /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -671,10 +680,17 @@ void reply_query(int fd, int family, time_t now) break; header = (struct dns_header *)daemon->packet; + +#ifdef HAVE_DNSSEC + hash = hash_questions(header, n, daemon->namebuff); +#else + hash = &crc; + crc = questions_crc(header, n, daemon->namebuff); +#endif if (!server || n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || - !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) + !(forward = lookup_frec(ntohs(header->id), hash))) return; if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) && @@ -813,8 +829,9 @@ void reply_query(int fd, int family, time_t now) nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz, daemon->keyname, forward->class, T_DS, &server->addr); } - new->crc = questions_crc(header, nn, daemon->namebuff); - new->new_id = get_id(new->crc); + if ((hash = hash_questions(header, nn, daemon->namebuff))) + memcpy(new->hash, hash, HASH_SIZE); + new->new_id = get_id(); header->id = htons(new->new_id); /* Save query for retransmission */ new->stash = blockdata_alloc((char *)header, nn); @@ -1357,8 +1374,13 @@ unsigned char *tcp_request(int confd, time_t now, if (!flags && last_server) { struct server *firstsendto = NULL; +#ifdef HAVE_DNSSEC + unsigned char *newhash, *hash[HASH_SIZE]; + if ((newhash = hash_questions(header, (unsigned int)size, daemon->keyname))) + memcpy(hash, newhash, HASH_SIZE); +#else unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); - +#endif /* Loop round available servers until we succeed in connecting to one. Note that this code subtley ensures that consecutive queries on this connection which can go to the same server, do so. */ @@ -1481,10 +1503,18 @@ unsigned char *tcp_request(int confd, time_t now, /* If the crc of the question section doesn't match the crc we sent, then someone might be attempting to insert bogus values into the cache by sending replies containing questions and bogus answers. */ - if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) - m = process_reply(header, now, last_server, (unsigned int)m, - option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, - cache_secure, check_subnet, &peer_addr); +#ifdef HAVE_DNSSEC + newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); + if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) + break; +#else + if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) + break; +#endif + + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, + cache_secure, check_subnet, &peer_addr); break; } @@ -1674,13 +1704,13 @@ struct frec *get_new_frec(time_t now, int *wait, int force) } /* crc is all-ones if not known. */ -static struct frec *lookup_frec(unsigned short id, unsigned int crc) +static struct frec *lookup_frec(unsigned short id, void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->new_id == id && - (f->crc == crc || crc == 0xffffffff)) + (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) return f; return NULL; @@ -1688,14 +1718,14 @@ static struct frec *lookup_frec(unsigned short id, unsigned int crc) static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, - unsigned int crc) + void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->orig_id == id && - f->crc == crc && + memcmp(hash, f->hash, HASH_SIZE) == 0 && sockaddr_isequal(&f->source, addr)) return f; @@ -1719,13 +1749,13 @@ void server_gone(struct server *server) } /* return unique random ids. */ -static unsigned short get_id(unsigned int crc) +static unsigned short get_id(void) { unsigned short ret = 0; do ret = rand16(); - while (lookup_frec(ret, crc)); + while (lookup_frec(ret, NULL)); return ret; }