Friday, September 7, 2007

Making MaraDNS even more secure

Now that MaraDNS has had over five years of real-world testing, and a few minor security problems (nothing worse than denial of service) have been found, here are some thoughts on how to make a program have the minimum number of security problems possible:

  • Do not add features to a program after a design/requirement spec for the program has been written. All three MaraDNS security problems existed because I added features to the program after the basic program was done. Namely:

    1. I added round-robin rotation to the codebase after the basic authoritative-only DNS server was written. This added a denial of service security bug to the code.

    2. I changed the authoritative code to give out nicer messages when invalid DNS packets were received. This added another denial of service security bug to the code.

    3. I added ipv6 to MaraDNS. This was the cause of a third, and final (so far; knock on wood) denial of service security bug to MaraDNS.

    It also looks like the one known denial of service security bug in djbdns was the cause of a similar feature creep; basically, the existence of TCP code in dnscache caused the bug.

  • Code in a style that make memory leaks difficult. For example, make sure there are a limited number of strings allocated at the beginning of a function, and use a variable to store the return value. Use gotos for error trapping if programming in C, since C does not have real Java-style error trapping, nor C++'s automatic destructors.

    If a block of code needs a temporary string, declare the string at the beginning of the block and don't leave the block without zapping the string. For example:

    function() {
    /* This is *always* the first part of a
    * block that allocates
    * strings. Yes, we allocate every last
    * string *before*
    * doing anything else */
    overflow_resistant_string *s1, *s2, *s3;
    if(allocate_memory(s1) == 0) {
    return ERROR;
    } else if(allocate_memory(s2) == 0) {
    return ERROR;
    } else if(allocate_memory(s3) == 0) {
    return ERROR;

    int return_code = OK; /* If you're using
    * GCC2 still, upgrade */

    /* Now we run normal code */

    /* Lots of snipped code here */

    if(do_something_important == FAILS) {
    return_code = ERROR;
    goto leave_function;

    /* Lots more snipped code */

    if(weve_already_funished == YES) {
    return_code = YIPEE;
    goto leave_function;

    /* More snipped code */

    return return_code;

  • Use buffer-overflow resistant strings. Check; this is why MaraDNS (knock on wood) hasn't had a security problem worse than denial of service.

  • Use as many operating system features to sandbox the code as possible. Check; this is why MaraDNS is in a chroot() environemt running as the "nobody" user (it is also possible to have MaraDNS run as a maradns-specific user if desired)