xorl %eax, %eax

CVE-2009-0696: ISC BIND 9 Remote RRset Crash

with one comment

This vulnerability was reported to ISC by Matthias Urlichs and the affected releases are ISC BIND 9.4 before 9.4.3-P3, 9.5 before 9.5.1-P3 and 9.6 before 9.6.1-P1 according to the assigned CVE ID. It also states that this was actively exploited in the wild in July 2009. Anyway, here is some code from lib/dns/db.c of 9.4.3-P2 release of the popular DNS implementation.

/***
 *** Rdataset Methods
 ***/

isc_result_t
dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
                    dns_rdatatype_t type, dns_rdatatype_t covers,
                    isc_stdtime_t now, dns_rdataset_t *rdataset,
                    dns_rdataset_t *sigrdataset)
{
        /*
         * Search for an rdataset of type 'type' at 'node' that are in version
         * 'version' of 'db'.  If found, make 'rdataset' refer to it.
         */

        REQUIRE(DNS_DB_VALID(db));
        REQUIRE(node != NULL);
        REQUIRE(DNS_RDATASET_VALID(rdataset));
        REQUIRE(! dns_rdataset_isassociated(rdataset));
        REQUIRE(covers == 0 || type == dns_rdatatype_rrsig);
        REQUIRE(type != dns_rdatatype_any);
        REQUIRE(sigrdataset == NULL ||
                (DNS_RDATASET_VALID(sigrdataset) &&
                 ! dns_rdataset_isassociated(sigrdataset)));

        return ((db->methods->findrdataset)(db, node, version, type, covers,
                                            now, rdataset, sigrdataset));
}

This routine is used to search for an rdataset of type ‘type’ as the comment says. However, before calling the db->methods->findrdataset function pointer, it has a few checks using REQUIRE() macro which can be found at lib/lwres/assert_p.h like this:

#include <assert.h>             /* Required for assert() prototype. */

#define REQUIRE(x)              assert(x)
#define INSIST(x)               assert(x)

So, all of the checks inside dns_db_findrdataset() are common assert(3) expressions. Among others, you can see the “REQUIRE(type != dns_rdatatype_any)” check which will trigger the assert() if the type of the DNS packet is that of “dns_rdatatype_any”. Consequently, a packet with record type of “ANY” would trigger this bug. This code could be reached through at least one Resource Record Set (RRset) for the requested Fully Qualified Domain Name (FQDN). The function that checks if there are any RRset in the DB can be found at bin/named/update.c like this:

/*%
 * Check the "RRset exists (value dependent)" prerequisite information
 * in 'temp' against the contents of the database 'db'.
 *
 * Return ISC_R_SUCCESS if the prerequisites are satisfied,
 * rcode(dns_rcode_nxrrset) if not.
 *
 * 'temp' must be pre-sorted.
 */

static isc_result_t
temp_check(isc_mem_t *mctx, dns_diff_t *temp, dns_db_t *db,
           dns_dbversion_t *ver, dns_name_t *tmpname, dns_rdatatype_t *typep)
{
        isc_result_t result;
        dns_name_t *name;
        dns_dbnode_t *node;
        dns_difftuple_t *t;
        dns_diff_t trash;
   ...
                        *typep = type = t->rdata.type;
                        if (type == dns_rdatatype_rrsig ||
                            type == dns_rdatatype_sig)
                                covers = dns_rdata_covers(&t->rdata);
                        else
                                covers = 0;
   ...
        return (ISC_R_SUCCESS);
}

As you can see in the above snippet, packet’s type is checked against RRsig or DNS sig types and if it is either one of these, it will perform the dns_rdata_covers(). Otherwise it will set covers to zero. However, this allows packets with “ANY” type (defined as dns_rdatatype_any) to pass if there is a RRset for the FQDN in the DNS server. This will lead to triggering of the above assert(3). To fix this bug, they applied the following patch:

                                covers = dns_rdata_covers(&t->rdata);
-                       else
+                       else if (type == dns_rdatatype_any) {
+                               dns_db_detachnode(db, &node);
+                               dns_diff_clear(&trash);
+                               return (DNS_R_NXRRSET);
+                       } else
                                covers = 0;

Now, in case of an “ANY” type packet, the node will be detached from the database using dns_db_detachnode() the trash will be cleared and the function will return with DNS_R_NXRRSET (aka. “The desired name exists, but the desired type does not.”). This might seem an innocent DoS only bug, but if BIND is compiled with no debugging information (NDEBUG flag), as it should, this REQUIRE() check will not be included in the final binary and this bug could lead to something much more interesting than a common assert(3) trigger.

Written by xorl

July 30, 2009 at 16:29

Posted in bugs

One Response

Subscribe to comments with RSS.

  1. The fix for this (by debian, anyway) was an iptable rule which blocks all dnsupdate packets:

    iptables -A INPUT -p udp –dport 53 -j DROP -m u32 –u32 ’30>>27&0xF=5′

    blacky

    August 3, 2009 at 04:55


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s