# Harden against unseemly large queries.
# harden-large-queries: no
+ # Harden against out of zone rrsets, to avoid spoofing attempts.
+ # harden-glue: yes
+
# Do not query the following addresses. No DNS queries are sent there.
# List one address per entry. To block other ports than the default
# DNS port, use "1.2.3.4@123" to block port 123 for 1.2.3.4.
Very large queries are ignored. Default is off, since it is legal protocol
wise to send these, and could be necessary for operation if TSIG or EDNS
payload is very large.
+.It \fBharden-glue:\fR <yes or no>
+Will trust glue only if it is within the servers authority. Default is on.
.It \fBdo-not-query-address:\fR <IP address>
Do not query the given IP address. Can be IP4 or IP6. By default the
DNS port is blocked for that address. Appending "@portnumber" will block
*/
#include "config.h"
#include "iterator/iter_scrub.h"
+#include "services/cache/rrset.h"
#include "util/log.h"
#include "util/net_help.h"
#include "util/region-allocator.h"
+#include "util/config_file.h"
+#include "util/module.h"
#include "util/data/msgparse.h"
#include "util/data/dname.h"
#include "util/data/msgreply.h"
+#include "util/alloc.h"
/** RRset flag used during scrubbing. The RRset is OK. */
#define RRSET_SCRUB_OK 0x80
return 1;
}
+/**
+ * Store potential poison in the cache (only if hardening disabled).
+ * The rrset is stored in the cache but removed from the message.
+ * So that it will be used for infrastructure purposes, but not be
+ * returned to the client.
+ * @param pkt: packet
+ * @param msg: message parsed
+ * @param env: environment with cache
+ * @param rrset: to store.
+ */
+static void
+store_rrset(ldns_buffer* pkt, struct msg_parse* msg, struct module_env* env,
+ struct rrset_parse* rrset)
+{
+ struct ub_packed_rrset_key* k;
+ struct packed_rrset_data* d;
+ struct rrset_ref ref;
+ uint32_t now = time(NULL);
+
+ k = alloc_special_obtain(env->alloc);
+ if(!k)
+ return;
+ k->entry.data = NULL;
+ if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) {
+ alloc_special_release(env->alloc, k);
+ return;
+ }
+ d = (struct packed_rrset_data*)k->entry.data;
+ packed_rrset_ttl_add(d, now);
+ ref.key = k;
+ ref.id = k->id;
+ /*ignore ret: it was in the cache, ref updated */
+ (void)rrset_cache_update(env->rrset_cache, &ref,
+ env->alloc, now);
+}
+
/**
* Given a response event, remove suspect RRsets from the response.
* "Suspect" rrsets are potentially poison. Note that this routine expects
* @param pkt: packet.
* @param msg: msg to normalize.
* @param zonename: name of server zone.
+ * @param env: module environment with config and cache.
* @return 0 on error.
*/
static int
-scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, uint8_t* zonename)
+scrub_sanitize(ldns_buffer* pkt, struct msg_parse* msg, uint8_t* zonename,
+ struct module_env* env)
{
struct rrset_parse* rrset, *prev;
prev = NULL;
* same check can be used */
if(!pkt_sub(pkt, rrset->dname, zonename)) {
- remove_rrset("sanitize: removing potential poison "
- "RRset:", pkt, msg, prev, &rrset);
+ if(!env->cfg->harden_glue) {
+ /* store in cache! Since it is relevant
+ * (from normalize) it will be picked up
+ * from the cache to be used later */
+ store_rrset(pkt, msg, env, rrset);
+ remove_rrset("sanitize: storing potential "
+ "poison RRset:", pkt, msg, prev, &rrset);
+ } else
+ remove_rrset("sanitize: removing potential "
+ "poison RRset:", pkt, msg, prev, &rrset);
continue;
}
prev = rrset;
int
scrub_message(ldns_buffer* pkt, struct msg_parse* msg,
- struct query_info* qinfo, uint8_t* zonename, struct region* region)
+ struct query_info* qinfo, uint8_t* zonename, struct region* region,
+ struct module_env* env)
{
/* basic sanity checks */
log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
if(!scrub_normalize(pkt, msg, qinfo, region))
return 0;
/* delete all out-of-zone information */
- if(!scrub_sanitize(pkt, msg, zonename))
+ if(!scrub_sanitize(pkt, msg, zonename, env))
return 0;
return 1;
}
struct msg_parse;
struct query_info;
struct region;
+struct module_env;
/**
* Cleanup the passed dns message.
* @param zonename: the name of the last delegation point.
* Used to determine out of bailiwick information.
* @param region: where to allocate (new) parts of the message.
+ * @param env: module environment with config settings and cache.
* @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 region* region);
+ struct query_info* qinfo, uint8_t* zonename, struct region* region,
+ struct module_env* env);
#endif /* ITERATOR_ITER_SCRUB_H */
/* 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->scratch, qstate->env))
goto handle_it;
/* allocate response dns_msg in region */
cfg->forwards = NULL;
cfg->harden_short_bufsize = 0;
cfg->harden_large_queries = 0;
+ cfg->harden_glue = 1;
cfg->hide_identity = 0;
cfg->hide_version = 0;
cfg->identity = NULL;
int harden_short_bufsize;
/** harden against very large query sizes */
int harden_large_queries;
+ /** harden against spoofed glue (out of zone data) */
+ int harden_glue;
/** chrootdir, if not "" or chroot will be done */
char* chrootdir;
target-fetch-policy{COLON} { YDOUT; return VAR_TARGET_FETCH_POLICY;}
harden-short-bufsize{COLON} { YDOUT; return VAR_HARDEN_SHORT_BUFSIZE;}
harden-large-queries{COLON} { YDOUT; return VAR_HARDEN_LARGE_QUERIES;}
+harden-glue{COLON} { YDOUT; return VAR_HARDEN_GLUE;}
stub-zone{COLON} { YDOUT; return VAR_STUB_ZONE;}
name{COLON} { YDOUT; return VAR_NAME;}
stub-addr{COLON} { YDOUT; return VAR_STUB_ADDR;}
%token VAR_HARDEN_SHORT_BUFSIZE VAR_HARDEN_LARGE_QUERIES
%token VAR_FORWARD_ZONE VAR_FORWARD_HOST VAR_FORWARD_ADDR
%token VAR_DO_NOT_QUERY_ADDRESS VAR_HIDE_IDENTITY VAR_HIDE_VERSION
-%token VAR_IDENTITY VAR_VERSION
+%token VAR_IDENTITY VAR_VERSION VAR_HARDEN_GLUE
%%
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
server_infra_cache_numlame | server_target_fetch_policy |
server_harden_short_bufsize | server_harden_large_queries |
server_do_not_query_address | server_hide_identity |
- server_hide_version | server_identity | server_version
+ server_hide_version | server_identity | server_version |
+ server_harden_glue
;
stubstart: VAR_STUB_ZONE
{
free($2);
}
;
+server_harden_glue: VAR_HARDEN_GLUE STRING
+ {
+ OUTYY(("P(server_harden_glue:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+ else cfg_parser->cfg->harden_glue =
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
+ ;
server_do_not_query_address: VAR_DO_NOT_QUERY_ADDRESS STRING
{
OUTYY(("P(server_do_not_query_address:%s)\n", $2));
/** get trust value for rrset */
static enum rrset_trust
-get_rrset_trust(struct reply_info* rep, size_t i)
+get_rrset_trust(struct msg_parse* msg, struct rrset_parse* rrset)
{
- uint16_t AA = rep->flags & BIT_AA;
- /* TODO: need scrubber that knows what zone the server serves, so that
- * it can check if AA bit is warranted.
- * it can check if rrset_trust_nonauth_ans_AA should be used */
- if(i < rep->an_numrrsets) {
- /* answer section */
+ uint16_t AA = msg->flags & BIT_AA;
+ if(rrset->section == LDNS_SECTION_ANSWER) {
if(AA) return rrset_trust_ans_AA;
else return rrset_trust_ans_noAA;
-
- } else if(i < rep->an_numrrsets+rep->ns_numrrsets) {
- /* authority section */
+ } else if(rrset->section == LDNS_SECTION_AUTHORITY) {
if(AA) return rrset_trust_auth_AA;
else return rrset_trust_auth_noAA;
} else {
return rrset_trust_none;
}
+int
+parse_copy_decompress_rrset(ldns_buffer* pkt, struct msg_parse* msg,
+ struct rrset_parse *pset, struct region* region,
+ struct ub_packed_rrset_key* pk)
+{
+ struct packed_rrset_data* data;
+ pk->rk.flags = pset->flags;
+ pk->rk.dname_len = pset->dname_len;
+ if(region)
+ pk->rk.dname = (uint8_t*)region_alloc(
+ region, pset->dname_len);
+ else pk->rk.dname =
+ (uint8_t*)malloc(pset->dname_len);
+ if(!pk->rk.dname)
+ return 0;
+ /** copy & decompress dname */
+ dname_pkt_copy(pkt, pk->rk.dname, pset->dname);
+ /** copy over type and class */
+ pk->rk.type = htons(pset->type);
+ pk->rk.rrset_class = pset->rrset_class;
+ /** read data part. */
+ if(!parse_create_rrset(pkt, pset, &data, region))
+ return 0;
+ pk->entry.data = (void*)data;
+ pk->entry.key = (void*)pk;
+ pk->entry.hash = pset->hash;
+ data->trust = get_rrset_trust(msg, pset);
+ return 1;
+}
+
/**
* Copy and decompress rrs
* @param pkt: the packet for compression pointer resolution.
rep->ttl = NORR_TTL;
for(i=0; i<rep->rrset_count; i++) {
- rep->rrsets[i]->rk.flags = pset->flags;
- rep->rrsets[i]->rk.dname_len = pset->dname_len;
- if(region)
- rep->rrsets[i]->rk.dname = (uint8_t*)region_alloc(
- region, pset->dname_len);
- else rep->rrsets[i]->rk.dname =
- (uint8_t*)malloc(pset->dname_len);
- if(!rep->rrsets[i]->rk.dname)
- return 0;
- /** copy & decompress dname */
- dname_pkt_copy(pkt, rep->rrsets[i]->rk.dname, pset->dname);
- /** copy over type and class */
- rep->rrsets[i]->rk.type = htons(pset->type);
- rep->rrsets[i]->rk.rrset_class = pset->rrset_class;
- /** read data part. */
- if(!parse_create_rrset(pkt, pset, &data, region))
+ if(!parse_copy_decompress_rrset(pkt, msg, pset, region,
+ rep->rrsets[i]))
return 0;
- rep->rrsets[i]->entry.data = (void*)data;
- rep->rrsets[i]->entry.key = (void*)rep->rrsets[i];
- rep->rrsets[i]->entry.hash = pset->hash;
- data->trust = get_rrset_trust(rep, i);
+ data = (struct packed_rrset_data*)rep->rrsets[i]->entry.data;
if(data->ttl < rep->ttl)
rep->ttl = data->ttl;
struct region;
struct edns_data;
struct msg_parse;
+struct rrset_parse;
/**
* Structure to store query information that makes answers to queries
struct reply_info* reply_info_copy(struct reply_info* rep,
struct alloc_cache* alloc, struct region* region);
+/**
+ * Copy a parsed rrset into given key, decompressing and allocating rdata.
+ * @param pkt: packet for decompression
+ * @param msg: the parser message (for flags for trust).
+ * @param pset: the parsed rrset to copy.
+ * @param region: if NULL - malloc, else data is allocated in this region.
+ * @param pk: a freshly obtained rrsetkey structure. No dname is set yet,
+ * will be set on return.
+ * Note that TTL will still be relative on return.
+ * @return false on alloc failure.
+ */
+int parse_copy_decompress_rrset(ldns_buffer* pkt, struct msg_parse* msg,
+ struct rrset_parse *pset, struct region* region,
+ struct ub_packed_rrset_key* pk);
+
/**
* Find answer rrset in reply, the one matching qinfo. Follows CNAMEs, so the
* result may have a different owner name.