by RSS Dehui Yin  |  Nov 08, 2016  |  Filed in: Security Research

A defect in BIND's handling of a DNAME answer was fixed in a critical update from the Internet Systems Consortium (ISC) several days ago. This defect affects all BIND recursive servers, and can be exploited to remotely take down recursive servers by sending a simple DNAME answer thereby causing a denial of service (DoS.)

This potential DoS vulnerability is caused by an assertion failure in Resolver.c or Db.c when caching the DNS response with DNAME Record. In this post we will examine the underlying code and expose the root cause of this vulnerability.

The DNAME record is used to redirect a DNS name to another domain. It has the following format:

                               <owner> <ttl> <class> DNAME <target>

The DNAME record is stored by BIND in a separate record because its name field "" is different from other records whose name fields always equal the DNS query name in the QUESTION section.

The DNAME answer handling vulnerability is the result of an assertion failure when caching the DNS records in the ANSWER section of the DNS response. The assertion failure occurs in function "valcreate()" of Resolver.c or  function "dns_db_attach()" of Db.c.

The following code snippet was taken from BIND version 9.10.4-P3. Comments added by me have been highlighted.

Resolver.c:

static inline isc_result_t

cache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now)

{

....

 

            for (section = DNS_SECTION_ANSWER;

                 section <= DNS_SECTION_ADDITIONAL;

                 section++) {

                        result = dns_message_firstname(fctx->rmessage, section);

                        while (result == ISC_R_SUCCESS) {

                                    name = NULL;

                                    dns_message_currentname(fctx->rmessage, section,

                                                                        &name);

                                    if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) {

                                                //caching the DNS Record the first time, which is not the DNAME record in the ANSWER section.

                                                result = cache_name(fctx, name, addrinfo, now);

                                                if (result != ISC_R_SUCCESS)

                                                            break;

                                    }

                                    result = dns_message_nextname(fctx->rmessage, section);

                        }

....

}

The function cache_message is used to cache DNS records from the ANSWER section to the ADDITIONAL section. It caches different records according to their name field. If the DNAME record is cached later than another record, the assertion error occurs. There are two ways to trigger the vulnerable code according to the "dnssec-validation" option in the configuration file.

If the "dnssec-validation" option is enabled, the assertion failure will happen in function "valcreate()".

 

Resolver.c:

static inline isc_result_t

cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo,

               isc_stdtime_t now)

{

....

            if (valrdataset != NULL) {

                        dns_rdatatype_t vtype = fctx->type;

....

                        //call valcreate() to create the new validator the first time

                        result = valcreate(fctx, addrinfo, name, vtype, valrdataset,

                                                   valsigrdataset, valoptions, task);

            }

....

}

Resolver.c:

static isc_result_t

valcreate(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, dns_name_t *name,

              dns_rdatatype_t type, dns_rdataset_t *rdataset,

              dns_rdataset_t *sigrdataset, unsigned int valoptions,

              isc_task_t *task)

{

....

    //fctx->validators is NULL here, skip the following "INSIST".

            if (!ISC_LIST_EMPTY(fctx->validators))  

                        INSIST((valoptions & DNS_VALIDATOR_DEFER) != 0);

    //create validator

            result = dns_validator_create(fctx->res->view, name, type, rdataset,

                                                      sigrdataset, fctx->rmessage,

                                                      valoptions, task, validated, valarg,

                                                      &validator);

            if (result == ISC_R_SUCCESS) {

                        inc_stats(fctx->res, dns_resstatscounter_val);

                        if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {

                                    INSIST(fctx->validator == NULL);

                                    //set fctx->validator, it is no longer NULL.

                                    fctx->validator = validator;

....

}

 

Resolver.c:

 

static inline isc_result_t

cache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now)

{

....

 

            for (section = DNS_SECTION_ANSWER;

                 section <= DNS_SECTION_ADDITIONAL;

                 section++) {

                        result = dns_message_firstname(fctx->rmessage, section);

                        while (result == ISC_R_SUCCESS) {

                                    name = NULL;

                                    dns_message_currentname(fctx->rmessage, section,

                                                                        &name);

                                    if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) {

                                                //Cache the DNS Record on the second instance, which is a DNAME Record. DNAME has a different name field from the first DNS Record.

                                                result = cache_name(fctx, name, addrinfo, now);

....

}

Resolver.c:

static isc_result_t

valcreate(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, dns_name_t *name,

              dns_rdatatype_t type, dns_rdataset_t *rdataset,

              dns_rdataset_t *sigrdataset, unsigned int valoptions,

              isc_task_t *task)

{

....

    //fctx->validators is no longer NULL, execute INSIST, valoptions is NULL, then assertion failure occurs.

            if (!ISC_LIST_EMPTY(fctx->validators))  

                        INSIST((valoptions & DNS_VALIDATOR_DEFER) != 0);

 

}

If the "dnssec-validation" option is disabled, the assertion failure will happen in function "dns_db_attach()".

Resolver.c:

static inline isc_result_t

cache_name(fetchctx_t *fctx, dns_name_t *name, dns_adbaddrinfo_t *addrinfo,

               isc_stdtime_t now)

{

....

            //if "dnssec-validation" is disabled, have_answer will be set as "true," and it will enter into the "if" clause.

            if (result == ISC_R_SUCCESS && have_answer) {

....

                                    //assertion failure happens in dns_db_attach when it is called the second time.

                                    dns_db_attach(fctx->cache, adbp);

                                    dns_db_transfernode(fctx->cache, &node, anodep);

                                    clone_results(fctx);

                        }

            }

....

}

 

Db.c:

void

dns_db_attach(dns_db_t *source, dns_db_t **targetp) {

....

            //*targetp is not NULL when called the second time, causing the assertion failure to happen.

            REQUIRE(targetp != NULL && *targetp == NULL);

        //Set *targetp as "source" when called the first time and it is no longer NULL.

            (source->methods->attach)(source, targetp);

            ENSURE(*targetp == source);

}

Following are images showing the abortion of the affected DNS server using the above two conditions:

"dnssec-validation" is enabled:

 "dnssec-validation" is disabled:

Please note that authentication is NOT required to exploit this vulnerability.

Fortinet released IPS signature ISC.BIND.DNAME.answer.Response.Handling.DoS to address this vulnerability.

by RSS Dehui Yin  |  Nov 08, 2016  |  Filed in: Security Research