From: Wouter Wijngaards Date: Thu, 4 Sep 2008 12:25:15 +0000 (+0000) Subject: private-addresses. X-Git-Tag: release-1.1.0~127 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=72904a3366f9bd879f1f22d1ddf4e7f5361dcf68;p=thirdparty%2Funbound.git private-addresses. git-svn-id: file:///svn/unbound/trunk@1224 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 44375a106..d2674ee77 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +4 September 2008: Wouter + - scrubber scrubs away private addresses. + - test for private addresses. man page entry. + 3 September 2008: Wouter - options for 'DNS Rebinding' protection: private-address and private-domain. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 11a3ec82d..49f23c189 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -412,6 +412,29 @@ support this. It is known that some authority servers do not support 0x20, and resolution will fail for them. A solution is on the TODO list. This feature is an experimental implementation of draft dns\-0x20. .TP +.B private\-address: \fI +Give IPv4 of IPv6 addresses or classless subnets. These are addresses +on your private network, and are not allowed to be returned for public +internet names. Any occurence of such addresses are removed from +DNS answers. Additionally, the DNSSEC validator may mark the answers +bogus. This protects against so-called DNS Rebinding, where a user browser +is turned into a network proxy, allowing remote access through the browser +to other parts of your private network. Some names can be allowed to +contain your private addresses, by default all the \fBlocal\-data\fR +that you configured is allowed to, and you can specify additional +names using \fBprivate\-domain\fR. No private addresses are enabled +by default. We consider to enable this for the RFC1918 private IP +address space by default in later releases. That would enable private +addresses for 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 192.254.0.0/16 +fd00::/8 and fe80::/10, since the RFC standards say these addresses +should not be visible on the public internet. Turning on 127.0.0.0/8 +would hinder many spamblocklists as they use that. +.TP +.B private\-domain: \fI +Allow this domain, and all its subdomains to contain private addresses. +Give multiple times to allow multiple domain names to contain private +addresses. Default is none. +.TP .B do\-not\-query\-address: \fI Do not query the given IP address. Can be IP4 or IP6. Append /num to indicate a classless delegation netblock, for example like diff --git a/iterator/iter_priv.c b/iterator/iter_priv.c index cb7cc186f..ccd530b87 100644 --- a/iterator/iter_priv.c +++ b/iterator/iter_priv.c @@ -46,6 +46,7 @@ #include "util/log.h" #include "util/config_file.h" #include "util/data/dname.h" +#include "util/data/msgparse.h" #include "util/net_help.h" #include "util/storage/dnstree.h" @@ -113,7 +114,7 @@ static int read_names(struct iter_priv* priv, struct config_file* cfg) int nm_labs; ldns_rdf* rdf; - for(p = cfg->private_address; p; p = p->next) { + for(p = cfg->private_domain; p; p = p->next) { log_assert(p->str); rdf = ldns_dname_new_frm_str(p->str); if(!rdf) { @@ -162,13 +163,29 @@ int priv_apply_cfg(struct iter_priv* priv, struct config_file* cfg) return 1; } -int priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr, +/** + * See if an address is blocked. + * @param priv: structure for address storage. + * @param addr: address to check + * @param addrlen: length of addr. + * @return: true if the address must not be queried. false if unlisted. + */ +static int +priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr, socklen_t addrlen) { return addr_tree_lookup(&priv->a, addr, addrlen) != NULL; } -int priv_lookup_name(struct iter_priv* priv, uint8_t* name, uint16_t dclass) +/** + * See if a name is whitelisted. + * @param priv: structure for address storage. + * @param name: name to check. + * @param dclass: class to check. + * @return: true if the name is OK. false if unlisted. + */ +static int +priv_lookup_name(struct iter_priv* priv, uint8_t* name, uint16_t dclass) { size_t len; int labs = dname_count_size_labels(name, &len); @@ -180,3 +197,48 @@ size_t priv_get_mem(struct iter_priv* priv) if(!priv) return 0; return sizeof(*priv) + regional_get_mem(priv->region); } + +int priv_rrset_bad(struct iter_priv* priv, struct rrset_parse* rrset) +{ + /* see if it is a private name, that is allowed to have any */ + if(priv_lookup_name(priv, rrset->dname, ntohs(rrset->rrset_class))) { + return 0; + } else { + /* so its a public name, check the address */ + struct sockaddr_storage addr; + socklen_t len; + struct rr_parse* rr; + if(rrset->type == LDNS_RR_TYPE_A) { + struct sockaddr_in* sa = (struct sockaddr_in*)&addr; + len = (socklen_t)sizeof(*sa); + memset(sa, 0, len); + sa->sin_family = AF_INET; + sa->sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); + for(rr = rrset->rr_first; rr; rr = rr->next) { + if(ldns_read_uint16(rr->ttl_data+4) + != INET_SIZE) + continue; + memmove(&sa->sin_addr, rr->ttl_data+4+2, + INET_SIZE); + if(priv_lookup_addr(priv, &addr, len)) + return 1; + } + } else if(rrset->type == LDNS_RR_TYPE_AAAA) { + struct sockaddr_in6* sa = (struct sockaddr_in6*)&addr; + len = (socklen_t)sizeof(*sa); + memset(sa, 0, len); + sa->sin6_family = AF_INET6; + sa->sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); + for(rr = rrset->rr_first; rr; rr = rr->next) { + if(ldns_read_uint16(rr->ttl_data+4) + != INET6_SIZE) + continue; + memmove(&sa->sin6_addr, rr->ttl_data+4+2, + INET6_SIZE); + if(priv_lookup_addr(priv, &addr, len)) + return 1; + } + } + } + return 0; +} diff --git a/iterator/iter_priv.h b/iterator/iter_priv.h index 9f2689e12..b7177ccad 100644 --- a/iterator/iter_priv.h +++ b/iterator/iter_priv.h @@ -46,6 +46,7 @@ struct iter_env; struct config_file; struct regional; +struct rrset_parse; /** * Iterator priv structure @@ -89,23 +90,12 @@ void priv_delete(struct iter_priv* priv); int priv_apply_cfg(struct iter_priv* priv, struct config_file* cfg); /** - * See if an address is blocked. - * @param priv: structure for address storage. - * @param addr: address to check - * @param addrlen: length of addr. - * @return: true if the address must not be queried. false if unlisted. - */ -int priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr, - socklen_t addrlen); - -/** - * See if a name is whitelisted. - * @param priv: structure for address storage. - * @param name: name to check. - * @param dclass: class to check. - * @return: true if the name is OK. false if unlisted. + * See if rrset is bad. + * @param priv: structure for private address storage. + * @param rrset: the rrset to examine, A or AAAA. + * @return true if the rrset is bad and should be removed. */ -int priv_lookup_name(struct iter_priv* priv, uint8_t* name, uint16_t dclass); +int priv_rrset_bad(struct iter_priv* priv, struct rrset_parse* rrset); /** * Get memory used by priv structure. diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c index 27972fd97..09abb219a 100644 --- a/iterator/iter_scrub.c +++ b/iterator/iter_scrub.c @@ -41,6 +41,8 @@ */ #include "config.h" #include "iterator/iter_scrub.h" +#include "iterator/iterator.h" +#include "iterator/iter_priv.h" #include "services/cache/rrset.h" #include "util/log.h" #include "util/net_help.h" @@ -558,11 +560,13 @@ static int sanitize_nsec_is_overreach(struct rrset_parse* rrset, * @param qinfo: the question originally asked. * @param zonename: name of server zone. * @param env: module environment with config and cache. + * @param ie: iterator environment with private address data. * @return 0 on error. */ static int scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, - struct query_info* qinfo, uint8_t* zonename, struct module_env* env) + struct query_info* qinfo, uint8_t* zonename, struct module_env* env, + struct iter_env* ie) { struct rrset_parse* rrset, *prev; prev = NULL; @@ -606,6 +610,18 @@ scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, prev = NULL; rrset = msg->rrset_first; while(rrset) { + + /* remove private addresses */ + if( (rrset->type == LDNS_RR_TYPE_A || + rrset->type == LDNS_RR_TYPE_AAAA) && + priv_rrset_bad(ie->priv, rrset)) { + /* set servfail, so the classification becomes + * THROWAWAY, instead of LAME or other unwanted */ + FLAGS_SET_RCODE(msg->flags, LDNS_RCODE_SERVFAIL); + remove_rrset("sanitize: removing public name with " + "private address", pkt, msg, prev, &rrset); + continue; + } /* skip DNAME records -- they will always be followed by a * synthesized CNAME, which will be relevant. @@ -653,7 +669,7 @@ scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, int scrub_message(ldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct regional* region, - struct module_env* env) + struct module_env* env, struct iter_env* ie) { /* basic sanity checks */ log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, @@ -684,7 +700,7 @@ scrub_message(ldns_buffer* pkt, struct msg_parse* msg, if(!scrub_normalize(pkt, msg, qinfo, region)) return 0; /* delete all out-of-zone information */ - if(!scrub_sanitize(pkt, msg, qinfo, zonename, env)) + if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie)) return 0; return 1; } diff --git a/iterator/iter_scrub.h b/iterator/iter_scrub.h index 7ec9b76e1..8bceb0059 100644 --- a/iterator/iter_scrub.h +++ b/iterator/iter_scrub.h @@ -46,6 +46,7 @@ struct msg_parse; struct query_info; struct regional; struct module_env; +struct iter_env; /** * Cleanup the passed dns message. @@ -57,10 +58,11 @@ struct module_env; * Used to determine out of bailiwick information. * @param regional: where to allocate (new) parts of the message. * @param env: module environment with config settings and cache. + * @param ie: iterator module environment data. * @return: false if the message is total waste. true if scrubbed with success. */ int scrub_message(ldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct regional* regional, - struct module_env* env); + struct module_env* env, struct iter_env* ie); #endif /* ITERATOR_ITER_SCRUB_H */ diff --git a/iterator/iterator.c b/iterator/iterator.c index de440c721..d63412261 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -1773,7 +1773,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq, /* normalize and sanitize: easy to delete items from linked lists */ if(!scrub_message(pkt, prs, &iq->qchase, iq->dp->name, - qstate->env->scratch, qstate->env)) + qstate->env->scratch, qstate->env, ie)) goto handle_it; /* allocate response dns_msg in region */ diff --git a/testdata/iter_privaddr.rpl b/testdata/iter_privaddr.rpl new file mode 100644 index 000000000..603f5b1cc --- /dev/null +++ b/testdata/iter_privaddr.rpl @@ -0,0 +1,223 @@ +; config options +server: + private-address: 10.0.0.0/8 + private-address: 172.16.0.0/12 + private-address: 192.168.0.0/16 + private-address: 192.254.0.0/16 + private-address: fd00::/8 + private-address: fe80::/10 + + private-domain: "example.net" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. + +CONFIG_END + +SCENARIO_BEGIN Test iterator scrubber with private addresses. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +; root server authoritative for example.net too. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +mail.example.net. IN A +SECTION ANSWER +mail.example.net. IN A 10.20.30.40 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 192.20.30.40 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +mail.example.com. IN AAAA +SECTION ANSWER +mail.example.com. IN AAAA fe80::15 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +foo.example.com. IN A +SECTION ANSWER +foo.example.com. IN A 10.20.30.40 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; public address is not scrubbed +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; recursion happens here. +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 192.20.30.40 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +; IPv4 address is scrubbed +STEP 3 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +foo.example.com. IN A +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +foo.example.com. IN A +SECTION ANSWER +; scrubbed away +;foo.example.com. IN A 10.20.30.40 +ENTRY_END + +; IPv6 address is scrubbed +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +mail.example.com. IN AAAA +ENTRY_END + +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +mail.example.com. IN AAAA +SECTION ANSWER +ENTRY_END + +; allowed domain is not scrubbed. +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +mail.example.net. IN A +ENTRY_END + +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +mail.example.net. IN A +SECTION ANSWER +mail.example.net. IN A 10.20.30.40 +ENTRY_END + +SCENARIO_END