The CVE number for this vulnerability is CVE-2014-8602. == Summary
The resolver can be tricked into following an endless series of delegations, this consumes a lot of resources. A patch is available that limits the number of fetches performed for a query. == Description Resolvers fetch the content for domain names by sending queries to authority servers on the internet. One of the responses that authority servers can return is a referral response, which points to further servers to continue the lookup. To continue the lookup, resolvers may have to perform recursion, where new names, called glue, from the referral response have to be looked up to continue the query resolution. The issue here is a lack of limiting on the recursion fetches performed to figure out a particular query. The authority server is a special set-up that responds with an infinite amount of glue. This then causes the resolver to spend a lot of resources diving into the infinite glue looking up names, only find out it needs to look up even more names. == Impact The impact for unbound is fairly low, combined with a tricky to exploit vulnerability. The packet rate, however, can be fairly high. The exploit needs a lot specific glue setup on the authority server, or even a special-purpose trick-authority-server. A trigger query has to be sent to unbound. Unbound will spend a lot of resources on this query, and this will impact unbound's cpu and network resources. Unbound may therefore lose some ability or timeliness for the service of customer queries (a denial of service). Unbound will continue to respond normally for cached queries. == Remote Exploit This is not a remote code execution exploit, this vulnerability consumes CPU and network resources. == Remedy A very simple workaround is to ignore the problem and let existing anti-DoS systems in unbound deal with the issue. It will consume a lot of resources, but other customers will (most likely) continue to get service. If affected, unbound-control flush_requestlist provides temporary relief, but the issue could resume (immediately). Putting the maliciously sent query in local-data, or using access-control to block the malicious query sending IP would workaround that exploit set-up. The config statement do-not-query-address: IPorNetblock can be used to block a specific authority server. The proper fix is a patch, which is available: http://unbound.net/downloads/patch_cve_2014_8602.diff == Solution The solution is a code patch, apply this patch with patch -p0 < the_patch_file. Then recompile and install unbound. == Acknowledgement Florian Maury (ANSSI)
Index: iterator/iterator.c
===================================================================
--- iterator/iterator.c (revision 3272)
+++ iterator/iterator.c (working copy)
@@ -120,6 +120,7 @@
iq->query_restart_count = 0;
iq->referral_count = 0;
iq->sent_count = 0;
+ iq->target_count = NULL;
iq->wait_priming_stub = 0;
iq->refetch_glue = 0;
iq->dnssec_expected = 0;
@@ -453,6 +454,26 @@
return 1;
}
+/** create target count structure for this query */
+static void
+target_count_create(struct iter_qstate* iq)
+{
+ if(!iq->target_count) {
+ iq->target_count = (int*)calloc(2, sizeof(int));
+ /* if calloc fails we simply do not track this number */
+ if(iq->target_count)
+ iq->target_count[0] = 1;
+ }
+}
+
+static void
+target_count_increase(struct iter_qstate* iq, int num)
+{
+ target_count_create(iq);
+ if(iq->target_count)
+ iq->target_count[1] += num;
+}
+
/**
* Generate a subrequest.
* Generate a local request event. Local events are tied to this module, and
@@ -524,6 +545,10 @@
subiq = (struct iter_qstate*)subq->minfo[id];
memset(subiq, 0, sizeof(*subiq));
subiq->num_target_queries = 0;
+ target_count_create(iq);
+ subiq->target_count = iq->target_count;
+ if(iq->target_count)
+ iq->target_count[0] ++; /* extra reference */
subiq->num_current_queries = 0;
subiq->depth = iq->depth+1;
outbound_list_init(&subiq->outlist);
@@ -1350,6 +1375,12 @@
if(iq->depth == ie->max_dependency_depth)
return 0;
+ if(iq->depth > 0 && iq->target_count &&
+ iq->target_count[1] > MAX_TARGET_COUNT) {
+ verbose(VERB_QUERY, "request has exceeded the maximum "
+ "number of glue fetches %d", iq->target_count[1]);
+ return 0;
+ }
iter_mark_cycle_targets(qstate, iq->dp);
missing = (int)delegpt_count_missing_targets(iq->dp);
@@ -1532,6 +1563,7 @@
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
iq->num_target_queries += qs;
+ target_count_increase(iq, qs);
if(qs != 0) {
qstate->ext_state[id] = module_wait_subquery;
return 0; /* and wait for them */
@@ -1541,6 +1573,12 @@
verbose(VERB_QUERY, "maxdepth and need more nameservers, fail");
return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
}
+ if(iq->depth > 0 && iq->target_count &&
+ iq->target_count[1] > MAX_TARGET_COUNT) {
+ verbose(VERB_QUERY, "request has exceeded the maximum "
+ "number of glue fetches %d", iq->target_count[1]);
+ return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
/* mark cycle targets for parent-side lookups */
iter_mark_pside_cycle_targets(qstate, iq->dp);
/* see if we can issue queries to get nameserver addresses */
@@ -1570,6 +1608,7 @@
if(query_count != 0) { /* suspend to await results */
verbose(VERB_ALGO, "try parent-side glue lookup");
iq->num_target_queries += query_count;
+ target_count_increase(iq, query_count);
qstate->ext_state[id] = module_wait_subquery;
return 0;
}
@@ -1725,6 +1764,7 @@
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
iq->num_target_queries += extra;
+ target_count_increase(iq, extra);
if(iq->num_target_queries > 0) {
/* wait to get all targets, we want to try em */
verbose(VERB_ALGO, "wait for all targets for fallback");
@@ -1765,6 +1805,7 @@
/* errors ignored, these targets are not strictly necessary for
* this result, we do not have to reply with SERVFAIL */
iq->num_target_queries += extra;
+ target_count_increase(iq, extra);
}
/* Add the current set of unused targets to our queue. */
@@ -1810,6 +1851,7 @@
return 1;
}
iq->num_target_queries += qs;
+ target_count_increase(iq, qs);
}
/* Since a target query might have been made, we
* need to check again. */
@@ -2921,6 +2963,8 @@
iq = (struct iter_qstate*)qstate->minfo[id];
if(iq) {
outbound_list_clear(&iq->outlist);
+ if(iq->target_count && --iq->target_count[0] == 0)
+ free(iq->target_count);
iq->num_current_queries = 0;
}
qstate->minfo[id] = NULL;
Index: iterator/iterator.h
===================================================================
--- iterator/iterator.h (revision 3272)
+++ iterator/iterator.h (working copy)
@@ -52,6 +52,8 @@
struct iter_prep_list;
struct iter_priv;
+/** max number of targets spawned for a query and its subqueries */
+#define MAX_TARGET_COUNT 32
/** max number of query restarts. Determines max number of CNAME chain. */
#define MAX_RESTART_COUNT 8
/** max number of referrals. Makes sure resolver does not run away */
@@ -251,6 +253,10 @@
/** number of queries fired off */
int sent_count;
+
+ /** number of target queries spawned in [1], for this query and its
+ * subqueries, the malloced-array is shared, [0] refcount. */
+ int* target_count;
/**
* The query must store NS records from referrals as parentside RRs
signature.asc
Description: OpenPGP digital signature
_______________________________________________ Unbound-users mailing list [email protected] http://unbound.nlnetlabs.nl/mailman/listinfo/unbound-users
