Maintained by: NLnet Labs

[Unbound-users] VU#209659 CVE-2011-4528: Unbound denial of service vulnerabilities from nonstandard redirection and denial of existence

W.C.A. Wijngaards
Mon Dec 19 12:25:11 CET 2011

Hash: SHA1

This text is on

Subject: Unbound denial of service vulnerabilities from nonstandard
redirection and denial of existence [ VU#209659 CVE-2011-4528 ]

== Summary

Unbound crashes when confronted with a non-standard response from a
server for a domain. This domain produces duplicate RRs from a certain
type and is DNSSEC signed.  Unbound also crashes when confronted with a
query that eventually, and under specific circumstances, resolves to a
domain that misses expected NSEC3 records.

These two problems were discovered within 24 hours, hence a combined
vulnerability disclosure.

By constructing the non standard responses an attacker can use these
vulnerabilities for a DOS attack.

To our knowledge 'denial of service' is the only type of exploit possible.

NLnet Labs has patched the code.

== Description 1: crash on signed duplicate Resource Records

There are authoritative servers that erroneously send duplicated
redirection Resource Records.  Unbound has had a workaround that deals
with this problem since version 1.0.1 whereby it ignores the duplicate
answers. At the time, likely, no DNSSEC signed versions of such zones
existed and only recently such misbehaving authority servers also
started serving signed duplicate RRs causing improper memory allocation
in a portion of the workaround code.

Referencing this improper allocated data causes a crash which can look
like 'uninitialised lock', segmentation faults or free() errors as it
tries to free memory that was never allocated.

== Description 2: crash on missing NSEC3 Resource Records.

If an authority server sends a reply for an NSEC3-signed zone, and
Unbound is configured to validate that zone, then a malformed response
can trigger an assertion failure: when an authority server sends a
response that contains RR types that trigger special processing and
where part of the non-existence proof is missing then an assertion
failure is triggered in unbound.  This response case was observed in the
wild on authority servers that host zones at different points in the
hierarchy at the same time.

The code crashes at assertion failure validator/val_nsec3.c:1214:
nsec3_do_prove_nodata: assertion ce.nc_rrset failed
Or, if assertions are not compiled it, it crashes soon after as it
attempts to access an expected NSEC3 RR.

== Workaround

If the misbehaving domain is known then it can be blocked block domain
names that exploit this with local-zone: ""
refuse lines in config.

There is no easy way to determine what misbehaving domains, except by
carefully analyzing the unbound log files with a high verbosity.

== Solution

Download patched version of unbound, or apply the patch manually.

+ Downloading Patched Versions

* 1.4.14 is released with the patch, but 1.4.14rc1 is vulnerable.

* 1.4.13p2 can be downloaded, it is 1.4.13 with the patch applied.
sha1 474339a182147ee91ec712057f75f417a455a8de
sha256 2f6814a4cc33883964c2833075990328fa330f966c3804ad20a92807428c22c1

+ Applying the Patch manually

The patch to apply can be downloaded here and is verbatim included at
the end of this description.

For unbound version 1.4.0 - 1.4.13 the patch is:
For unbound version 1.0.1 - 1.3.4 the patch is:
(different patch because surrounding code was changed, same contents).

Apply the patch on unbound source directory with 'patch -p0 <filename'
then run 'make install' to install unbound.


Code patch:

Index: iterator/iter_scrub.c
- --- iterator/iter_scrub.c	(revision 2571)
+++ iterator/iter_scrub.c	(working copy)
@@ -187,11 +187,14 @@
 	size_t* snamelen)
 	if(rrset->rr_count != 1) {
+		struct rr_parse* sig;
 		verbose(VERB_ALGO, "Found CNAME rrset with "
 			"size > 1: %u", (unsigned)rrset->rr_count);
 		/* use the first CNAME! */
 		rrset->rr_count = 1;
 		rrset->size = rrset->rr_first->size;
+		for(sig=rrset->rrsig_first; sig; sig=sig->next)
+			rrset->size += sig->size;
 		rrset->rr_last = rrset->rr_first;
 		rrset->rr_first->next = NULL;
Index: validator/val_nsec3.c
- --- validator/val_nsec3.c	(revision 2573)
+++ validator/val_nsec3.c	(working copy)
@@ -1099,6 +1099,10 @@
 		return sec_status_bogus;

+	if(!ce.nc_rrset) {
+		verbose(VERB_ALGO, "nsec3 nodata proof: no next closer nsec3");
+		return sec_status_bogus;
+	}
 	/* We need to make sure that the covering NSEC3 is opt-out. */
 	if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) {

Before 1.4.0 the val_nsec3.c file has to be patched with:
Index: validator/val_nsec3.c
- --- validator/val_nsec3.c	(revision 2573)
+++ validator/val_nsec3.c	(working copy)
@@ -1227,6 +1227,10 @@
 	 * covering NSEC3 was opt-out -- the proveClosestEncloser step already
 	 * checked to see if the closest encloser was a delegation or DNAME.
+	if(!ce.nc_rrset) {
+		verbose(VERB_ALGO, "nsec3 nodata proof: no next closer nsec3");
+		return sec_status_bogus;
+	}
 	if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) {
 		verbose(VERB_ALGO, "nsec3 provenods: covering NSEC3 was not "

Version: GnuPG v2.0.15 (GNU/Linux)
Comment: Using GnuPG with SUSE -