diff options
Diffstat (limited to 'package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch')
-rw-r--r-- | package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch b/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch new file mode 100644 index 0000000000..64fb0dcf70 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch @@ -0,0 +1,350 @@ +From 25e63f1e56f5acdcf91893a1b92ad1e0f2f552d8 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Wed, 25 Nov 2020 21:17:52 +0000 +Subject: Handle caching with EDNS options better. + +If we add the EDNS client subnet option, or the client's +MAC address, then the reply we get back may very depending on +that. Since the cache is ignorant of such things, it's not safe to +cache such replies. This patch determines when a dangerous EDNS +option is being added and disables caching. + +Note that for much the same reason, we can't combine multiple +queries for the same question when dangerous EDNS options are +being added, and the code now handles that in the same way. This +query combining is required for security against cache poisoning, +so disabling the cache has a security function as well as a +correctness one. +--- + man/dnsmasq.8 | 4 +-- + src/dnsmasq.h | 3 ++- + src/edns0.c | 75 ++++++++++++++++++++++++++++++++------------------- + src/forward.c | 41 ++++++++++++++++++---------- + 4 files changed, 78 insertions(+), 45 deletions(-) + +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -690,8 +690,8 @@ still marks the request so that no upstr + address information either. The default is zero for both IPv4 and + IPv6. Note that upstream nameservers may be configured to return + different results based on this information, but the dnsmasq cache +-does not take account. If a dnsmasq instance is configured such that +-different results may be encountered, caching should be disabled. ++does not take account. Caching is therefore disabled for such replies, ++unless the subnet address being added is constant. + + For example, + .B --add-subnet=24,96 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -644,6 +644,7 @@ struct hostsfile { + #define FREC_TEST_PKTSZ 256 + #define FREC_HAS_EXTRADATA 512 + #define FREC_HAS_PHEADER 1024 ++#define FREC_NO_CACHE 2048 + + #define HASH_SIZE 32 /* SHA-256 digest size */ + +@@ -1628,7 +1629,7 @@ size_t add_pseudoheader(struct dns_heade + unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace); + size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit); + size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, +- union mysockaddr *source, time_t now, int *check_subnet); ++ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable); + int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); + + /* arp.c */ +--- a/src/edns0.c ++++ b/src/edns0.c +@@ -264,7 +264,8 @@ static void encoder(unsigned char *in, c + out[3] = char64(in[2]); + } + +-static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) ++static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, ++ union mysockaddr *l3, time_t now, int *cacheablep) + { + int maclen, replace = 2; /* can't get mac address, just delete any incoming. */ + unsigned char mac[DHCP_CHADDR_MAX]; +@@ -273,6 +274,7 @@ static size_t add_dns_client(struct dns_ + if ((maclen = find_mac(l3, mac, 1, now)) == 6) + { + replace = 1; ++ *cacheablep = 0; + + if (option_bool(OPT_MAC_HEX)) + print_mac(encode, mac, maclen); +@@ -288,14 +290,18 @@ static size_t add_dns_client(struct dns_ + } + + +-static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) ++static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, ++ union mysockaddr *l3, time_t now, int *cacheablep) + { + int maclen; + unsigned char mac[DHCP_CHADDR_MAX]; + + if ((maclen = find_mac(l3, mac, 1, now)) != 0) +- plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); +- ++ { ++ *cacheablep = 0; ++ plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); ++ } ++ + return plen; + } + +@@ -313,17 +319,18 @@ static void *get_addrp(union mysockaddr + return &addr->in.sin_addr; + } + +-static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) ++static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep) + { + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + void *addrp = NULL; + int sa_family = source->sa.sa_family; +- ++ int cacheable = 0; ++ + opt->source_netmask = 0; + opt->scope_netmask = 0; +- ++ + if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6) + { + opt->source_netmask = daemon->add_subnet6->mask; +@@ -331,6 +338,7 @@ static size_t calc_subnet_opt(struct sub + { + sa_family = daemon->add_subnet6->addr.sa.sa_family; + addrp = get_addrp(&daemon->add_subnet6->addr, sa_family); ++ cacheable = 1; + } + else + addrp = &source->in6.sin6_addr; +@@ -343,6 +351,7 @@ static size_t calc_subnet_opt(struct sub + { + sa_family = daemon->add_subnet4->addr.sa.sa_family; + addrp = get_addrp(&daemon->add_subnet4->addr, sa_family); ++ cacheable = 1; /* Address is constant */ + } + else + addrp = &source->in.sin_addr; +@@ -350,8 +359,6 @@ static size_t calc_subnet_opt(struct sub + + opt->family = htons(sa_family == AF_INET6 ? 2 : 1); + +- len = 0; +- + if (addrp && opt->source_netmask != 0) + { + len = ((opt->source_netmask - 1) >> 3) + 1; +@@ -359,18 +366,26 @@ static size_t calc_subnet_opt(struct sub + if (opt->source_netmask & 7) + opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7)); + } ++ else ++ { ++ cacheable = 1; /* No address ever supplied. */ ++ len = 0; ++ } ++ ++ if (cacheablep) ++ *cacheablep = cacheable; + + return len + 4; + } + +-static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source) ++static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable) + { + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + struct subnet_opt opt; + +- len = calc_subnet_opt(&opt, source); ++ len = calc_subnet_opt(&opt, source, cacheable); + return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0); + } + +@@ -383,18 +398,18 @@ int check_source(struct dns_header *head + unsigned char *p; + int code, i, rdlen; + +- calc_len = calc_subnet_opt(&opt, peer); +- +- if (!(p = skip_name(pseudoheader, header, plen, 10))) +- return 1; +- +- p += 8; /* skip UDP length and RCODE */ ++ calc_len = calc_subnet_opt(&opt, peer, NULL); + +- GETSHORT(rdlen, p); +- if (!CHECK_LEN(header, p, plen, rdlen)) +- return 1; /* bad packet */ +- +- /* check if option there */ ++ if (!(p = skip_name(pseudoheader, header, plen, 10))) ++ return 1; ++ ++ p += 8; /* skip UDP length and RCODE */ ++ ++ GETSHORT(rdlen, p); ++ if (!CHECK_LEN(header, p, plen, rdlen)) ++ return 1; /* bad packet */ ++ ++ /* check if option there */ + for (i = 0; i + 4 < rdlen; i += len + 4) + { + GETSHORT(code, p); +@@ -412,24 +427,28 @@ int check_source(struct dns_header *head + return 1; + } + ++/* Set *check_subnet if we add a client subnet option, which needs to checked ++ in the reply. Set *cacheable to zero if we add an option which the answer ++ may depend on. */ + size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, +- union mysockaddr *source, time_t now, int *check_subnet) ++ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable) + { + *check_subnet = 0; +- ++ *cacheable = 1; ++ + if (option_bool(OPT_ADD_MAC)) +- plen = add_mac(header, plen, limit, source, now); ++ plen = add_mac(header, plen, limit, source, now, cacheable); + + if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) +- plen = add_dns_client(header, plen, limit, source, now); +- ++ plen = add_dns_client(header, plen, limit, source, now, cacheable); ++ + if (daemon->dns_client_id) + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, + (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); + + if (option_bool(OPT_CLIENT_SUBNET)) + { +- plen = add_source_addr(header, plen, limit, source); ++ plen = add_source_addr(header, plen, limit, source, cacheable); + *check_subnet = 1; + } + +--- a/src/forward.c ++++ b/src/forward.c +@@ -344,13 +344,10 @@ static int forward_query(int udpfd, unio + { + /* Query from new source, but the same query may be in progress + from another source. If so, just add this client to the +- list that will get the reply. ++ list that will get the reply.*/ + +- Note that is the EDNS client subnet option is in use, we can't do this, +- as the clients (and therefore query EDNS options) will be different +- for each query. The EDNS subnet code has checks to avoid +- attacks in this case. */ +- if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags))) ++ if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && ++ (forward = lookup_frec_by_query(hash, fwd_flags))) + { + /* Note whine_malloc() zeros memory. */ + if (!daemon->free_frec_src && +@@ -447,18 +444,21 @@ static int forward_query(int udpfd, unio + if (!flags && forward) + { + struct server *firstsentto = start; +- int subnet, forwarded = 0; ++ int subnet, cacheable, forwarded = 0; + size_t edns0_len; + unsigned char *pheader; + + /* If a query is retried, use the log_id for the retry when logging the answer. */ + forward->frec_src.log_id = daemon->log_id; + +- plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet); ++ plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable); + + if (subnet) + forward->flags |= FREC_HAS_SUBNET; +- ++ ++ if (!cacheable) ++ forward->flags |= FREC_NO_CACHE; ++ + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && do_dnssec) + { +@@ -642,7 +642,7 @@ static size_t process_reply(struct dns_h + } + } + #endif +- ++ + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) + { + /* Get extended RCODE. */ +@@ -1244,6 +1244,11 @@ void reply_query(int fd, int family, tim + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; ++ ++ /* Never cache answers which are contingent on the source or MAC address EDSN0 option, ++ since the cache is ignorant of such things. */ ++ if (forward->flags & FREC_NO_CACHE) ++ no_cache_dnssec = 1; + + if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, +@@ -1788,7 +1793,7 @@ unsigned char *tcp_request(int confd, ti + int local_auth = 0; + #endif + int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0; +- int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; ++ int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + size_t m; + unsigned short qtype; + unsigned int gotname; +@@ -1959,7 +1964,7 @@ unsigned char *tcp_request(int confd, ti + char *domain = NULL; + unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); + +- size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); ++ size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable); + + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); +@@ -2122,6 +2127,11 @@ unsigned char *tcp_request(int confd, ti + break; + } + ++ /* Never cache answers which are contingent on the source or MAC address EDSN0 option, ++ since the cache is ignorant of such things. */ ++ if (!cacheable) ++ no_cache_dnssec = 1; ++ + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, + ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); +@@ -2385,10 +2395,13 @@ static struct frec *lookup_frec_by_query + struct frec *f; + + /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below +- ensures that no frec created for internal DNSSEC query can be returned here. */ ++ ensures that no frec created for internal DNSSEC query can be returned here. ++ ++ Similarly FREC_NO_CACHE is never set in flags, so a query which is ++ contigent on a particular source address EDNS0 option will never be matched. */ + + #define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ +- | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY) ++ | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE) + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && |