diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 771bec1..7d65cd0 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -118,6 +118,8 @@ int main (int argc, char **argv) daemon->namebuff = safe_malloc(MAXDNAME * 2); daemon->keyname = safe_malloc(MAXDNAME * 2); daemon->workspacename = safe_malloc(MAXDNAME * 2); + /* one char flag per possible RR in answer section. */ + daemon->rr_status = safe_malloc(256); } #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 563443e..e0184a9 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1025,6 +1025,7 @@ extern struct daemon { #ifdef HAVE_DNSSEC char *keyname; /* MAXDNAME size buffer */ char *workspacename; /* ditto */ + char *rr_status; /* 256 bytes as flags for individual RRs */ #endif unsigned int local_answer, queries_forwarded, auth_answer; struct frec *frec_list; @@ -1139,7 +1140,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, unsigned long ttl); int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, char **ipsets, int is_sign, int check_rebind, - int no_cache_dnssec, int secure, int *doctored); + int no_cache_dnssec, int secure, int *doctored, char *rr_status); size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct in_addr local_addr, struct in_addr local_netmask, time_t now, int ad_reqd, int do_bit, int have_pseudoheader); @@ -1172,7 +1173,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); 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 check_unsigned, int *neganswer, int *nons); + int check_unsigned, int *neganswer, int *nons, char *rr_status); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); diff --git a/src/dnssec.c b/src/dnssec.c index a74d01a..3819eda 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -1180,8 +1180,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char if (qtype != T_DS || qclass != class) rc = STAT_BOGUS; else - rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons); - /* Note dnssec_validate_reply() will have cached positive answers */ + rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, NULL); if (rc == STAT_INSECURE) rc = STAT_BOGUS; @@ -1965,18 +1964,25 @@ static int zone_status(char *name, int class, char *keyname, time_t now) STAT_INSECURE at least one RRset not validated, because in unsigned zone. STAT_BOGUS signature is wrong, bad packet, no validation where there should be. STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) - STAT_NEED_DS need DS to complete validation (name is returned in keyname) + STAT_NEED_DS need DS to complete validation (name is returned in keyname) + + If non-NULL, rr_status points to a char array which corressponds to the RRs in the + answer section (only). This is set to 1 for each RR which is validated, and 0 for any which aren't. */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, - int *class, int check_unsigned, int *neganswer, int *nons) + int *class, int check_unsigned, int *neganswer, int *nons, char *rr_status) { static unsigned char **targets = NULL; static int target_sz = 0; unsigned char *ans_start, *p1, *p2; - int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx; + int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; int i, j, rc; + int secure = STAT_SECURE; + if (rr_status) + memset(rr_status, 0, ntohs(header->ancount)); + if (neganswer) *neganswer = 0; @@ -2033,7 +2039,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) { - if (!extract_name(header, plen, &p1, name, 1, 10)) + if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1)) + return STAT_BOGUS; + + if (!extract_name(header, plen, &p1, name, 1, 10)) return STAT_BOGUS; /* bad packet */ GETSHORT(type1, p1); @@ -2042,106 +2051,125 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch GETSHORT(rdlen1, p1); /* Don't try and validate RRSIGs! */ - if (type1 != T_RRSIG) + if (type1 == T_RRSIG) + continue; + + /* Check if we've done this RRset already */ + for (p2 = ans_start, j = 0; j < i; j++) { - /* 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))) - return STAT_BOGUS; /* bad packet */ - - GETSHORT(type2, p2); - GETSHORT(class2, p2); - p2 += 4; /* TTL */ - GETSHORT(rdlen2, p2); - - if (type2 == type1 && class2 == class1 && rc == 1) - break; /* Done it before: name, type, class all match. */ - - if (!ADD_RDLEN(header, p2, plen, rdlen2)) - return STAT_BOGUS; - } + if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + GETSHORT(type2, p2); + GETSHORT(class2, p2); + p2 += 4; /* TTL */ + GETSHORT(rdlen2, p2); + + if (type2 == type1 && class2 == class1 && rc == 1) + break; /* Done it before: name, type, class all match. */ + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; + } + + if (j != i) + { + /* Done already: copy the validation status */ + if (rr_status && (i < ntohs(header->ancount))) + rr_status[i] = rr_status[j]; + } + else + { /* Not done, validate now */ - if (j == i) + int sigcnt, rrcnt; + char *wildname; + + if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) + return STAT_BOGUS; + + /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */ + if (sigcnt == 0) { - int sigcnt, rrcnt; - char *wildname; - - if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) - return STAT_BOGUS; - - /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */ - if (sigcnt == 0) + if (check_unsigned) { - if (check_unsigned) - { - rc = zone_status(name, class1, keyname, now); - if (rc == STAT_SECURE) - rc = STAT_BOGUS; - if (class) - *class = class1; /* Class for NEED_DS or NEED_KEY */ - } - else - rc = STAT_INSECURE; - - return rc; + rc = zone_status(name, class1, keyname, now); + if (rc == STAT_SECURE) + rc = STAT_BOGUS; + if (class) + *class = class1; /* Class for NEED_DS or NEED_KEY */ } + else + rc = STAT_INSECURE; + if (rc != STAT_INSECURE) + return rc; + } + else + { /* explore_rrset() gives us key name from sigs in keyname. Can't overwrite name here. */ strcpy(daemon->workspacename, keyname); rc = zone_status(daemon->workspacename, class1, keyname, now); - - if (rc != STAT_SECURE) + + if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) { /* Zone is insecure, don't need to validate RRset */ if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ return rc; - } + } - rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); - - if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) - { - if (class) - *class = class1; /* Class for DS or DNSKEY */ - return rc; - } - else + /* Zone is insecure, don't need to validate RRset */ + if (rc == STAT_SECURE) { + rc = validate_rrset(now, header, plen, class1, type1, sigcnt, + rrcnt, name, keyname, &wildname, NULL, 0, 0, 0); + + if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + { + if (class) + *class = class1; /* Class for DS or DNSKEY */ + return rc; + } + /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ - + + /* Note that RR is validated */ + if (rr_status && (i < ntohs(header->ancount))) + rr_status[i] = 1; + /* Note if we've validated either the answer to the question or the target of a CNAME. Any not noted will need NSEC or to be in unsigned space. */ - for (j = 0; j flags |= SERV_WARNED_RECURSIVE; } +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + rr_status = daemon->rr_status; +#endif + if (daemon->bogus_addr && RCODE(header) != NXDOMAIN && check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) { @@ -675,7 +681,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server cache_secure = 0; } - if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) + if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored, rr_status)) { my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; @@ -859,7 +865,7 @@ void reply_query(int fd, int family, time_t now) (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) { int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; - + if (option_bool(OPT_NO_REBIND)) check_rebind = !(forward->flags & FREC_NOREBIND); @@ -899,7 +905,8 @@ void reply_query(int fd, int family, time_t now) status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, - option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), NULL, NULL); + option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), + NULL, NULL, daemon->rr_status); } /* Can't validate, as we're missing key data. Put this @@ -1483,7 +1490,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si new_status = dnssec_validate_ds(now, header, n, name, keyname, class); else new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, - option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), NULL, NULL); + option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), + NULL, NULL, daemon->rr_status); if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) break; diff --git a/src/rfc1035.c b/src/rfc1035.c index 22d94b4..10e710e 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -584,7 +584,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc expired and cleaned out that way. Return 1 if we reject an address because it look like part of dns-rebinding attack. */ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, - char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored) + char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, + int secure, int *doctored, char *rr_status) { unsigned char *p, *p1, *endrr, *namep; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; @@ -595,6 +596,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t #else (void)ipsets; /* unused */ #endif + cache_start_insert(); @@ -603,10 +605,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t { searched_soa = 1; ttl = find_soa(header, qlen, name, doctored); -#ifdef HAVE_DNSSEC - if (*doctored && secure) - return 0; -#endif + + if (*doctored) + { + if (secure) + return 0; + if (rr_status) + for (i = 0; i < ntohs(header->ancount); i++) + if (rr_status[i]) + return 0; + } } /* go through the questions. */ @@ -617,7 +625,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t int found = 0, cname_count = CNAME_CHAIN; struct crec *cpp = NULL; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; - int secflag = secure ? F_DNSSECOK : 0; + int cname_short = 0; unsigned long cttl = ULONG_MAX, attl; namep = p; @@ -645,8 +653,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!(p1 = skip_questions(header, qlen))) return 0; - for (j = ntohs(header->ancount); j != 0; j--) + for (j = 0; j < ntohs(header->ancount); j++) { + int secflag = 0; unsigned char *tmp = namep; /* the loop body overwrites the original name, so get it back here. */ if (!extract_name(header, qlen, &tmp, name, 1, 0) || @@ -672,11 +681,21 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t { if (!extract_name(header, qlen, &p1, name, 1, 0)) return 0; - + + if (rr_status && rr_status[j]) + { + /* validated RR anywhere in CNAME chain, don't cache. */ + if (cname_short || aqtype == T_CNAME) + return 0; + + secflag = F_DNSSECOK; + } + if (aqtype == T_CNAME) { - if (!cname_count-- || secure) - return 0; /* looped CNAMES, or DNSSEC, which we can't cache. */ + if (!cname_count--) + return 0; /* looped CNAMES, we can't cache. */ + cname_short = 1; goto cname_loop; } @@ -698,7 +717,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t ttl = find_soa(header, qlen, NULL, doctored); } if (ttl) - cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag); + cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ? F_DNSSECOK : 0)); } } else @@ -726,8 +745,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!(p1 = skip_questions(header, qlen))) return 0; - for (j = ntohs(header->ancount); j != 0; j--) + for (j = 0; j < ntohs(header->ancount); j++) { + int secflag = 0; + if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) return 0; /* bad packet */ @@ -744,6 +765,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) { +#ifdef HAVE_DNSSEC + if (rr_status && rr_status[j]) + secflag = F_DNSSECOK; +#endif if (aqtype == T_CNAME) { if (!cname_count--) @@ -835,7 +860,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t pointing at this, inherit its TTL */ if (ttl || cpp) { - newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag); + newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); if (newc && cpp) { cpp->addr.cname.target.cache = newc; @@ -950,7 +975,7 @@ int check_for_local_domain(char *name, time_t now) /* Note: the call to cache_find_by_name is intended to find any record which matches ie A, AAAA, CNAME. */ - if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME |F_NO_RR)) && + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) && (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) return 1; @@ -1736,8 +1761,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (qtype == T_CNAME || qtype == T_ANY) { - if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) && - (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | (dryrun ? F_NO_RR : 0))))) + if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) && + (qtype == T_CNAME || (crecp->flags & F_CONFIG)) && + ((crecp->flags & F_CONFIG) || !do_bit || !(crecp->flags & F_DNSSECOK))) { if (!(crecp->flags & F_DNSSECOK)) sec_data = 0;