COMMAND

    nslookup

SYSTEMS AFFECTED

    Many systems

PROBLEM

    Peter van Dijk found following.

        [peter@koek] ~$ nslookup `perl -e 'print "A" x 100;'`
        Server:  zopie.attic.vuurwerk.nl
        Address:  10.10.13.1

        *** zopie.attic.vuurwerk.nl can't find AAA.....AAA: Unspecified error
        [peter@koek] ~$ nslookup `perl -e 'print "A" x 300;'`
        Server:  zopie.attic.vuurwerk.nl
        Address:  10.10.13.1

        *** zopie.attic.vuurwerk.nl can't find AA....AAA: Unspecified error
        Segmentation fault (core dumped)
        [peter@koek] ~$ nslookup `perl -e 'print "A" x 1000;'`
        Server:  zopie.attic.vuurwerk.nl
        Address:  10.10.13.1

        Segmentation fault (core dumped)

    At first, this does not seem a problem: nslookup is not suid  root
    or  anything.   But  several  sites  have  cgi-scripts  that  call
    nslookup...   tests  show  that  these  will  coredump when passed
    enough characters.  Looks exploitable.  The offending line is line
    684 in main.c (BIND 8.1.2 or line 681 in 4.9.7-REL):

        sscanf(string, " %s", host);        /* removes white space */

    Under some  OS's it  may require  a larger  string to overflow the
    buffer.  Same  goes for dig.c!   Willy Tarreau successfully  got a
    shell using  old generic  exploit, with  260 bytes  followed by  a
    pointer to esp-400.

SOLUTION

    Theo de  Raadt fixed  'a bucketload  of sscanf's'.   One patch for
    dig is available at:

        http://www.nrw.net/uwe/dig-8.1.2.patch

    And here's Theos's patch.  This patch should get people started at
    repairing their distributions.   This patch doesn't apply  to bind
    8.1.2.  Revisited patch follows it after:

    Index: dig/dig.c
    ===================================================================
    RCS file: /cvs/src/usr.sbin/named/dig/dig.c,v
    retrieving revision 1.4
    retrieving revision 1.5
    diff -u -r1.4 -r1.5
    --- dig.c       1997/07/21 02:10:56     1.4
    +++ dig.c       1998/08/30 03:39:18     1.5
    @@ -765,9 +765,11 @@
         char       option[NAME_LEN];
         char       type[NAME_LEN];
         char       *ptr;
    +    char       get[80];
         int        i;

    -    i = sscanf(string, " %s", option);
    +    snprintf(get, sizeof get, " %%%ds", sizeof option-1);
    +    i = sscanf(string, get, option);
         if (i != 1) {
            fprintf(stderr, ";*** Invalid option: %s\n",  option);
            return(ERROR);
    @@ -800,7 +802,8 @@
            } else if (strncmp(option, "do", 2) == 0) {     /* domain */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", _res.defdname);
    +               snprintf(get, sizeof get, "%%%ds", sizeof _res.defdname-1);
    +               sscanf(++ptr, get, _res.defdname);
                }
              } else if (strncmp(option, "ti", 2) == 0) {      /* timeout */
                ptr = strchr(option, '=');
    Index: nslookup/list.c
    ===================================================================
    RCS file: /cvs/src/usr.sbin/named/nslookup/list.c,v
    retrieving revision 1.2
    diff -u -r1.2 list.c
    --- list.c      1997/03/12 10:42:47     1.2
    +++ list.c      1998/08/31 17:39:27
    @@ -156,13 +156,16 @@
            char    *namePtr;
            char    name[NAME_LEN];
            char    option[NAME_LEN];
    +       char    get[80];

            /*
             *  Parse the command line. It maybe of the form "ls -t domain"
             *  or "ls -t type domain".
             */

    -       i = sscanf(string, " ls -t %s %s", option, name);
    +       snprintf(get, sizeof get, " ls -t %%%ds %%%ds", sizeof option-1,
    +           sizeof name-1);
    +       i = sscanf(string, get, option, name);
            if (putToFile && i == 2 && name[0] == '>') {
                i--;
            }
    @@ -193,12 +196,15 @@
            char    *namePtr;
            char    name[NAME_LEN];
            char    option[NAME_LEN];
    +       char    get[80];

            /*
             *  Parse the command line. It maybe of the form "ls domain",
             *  "ls -X domain".
             */
    -       i = sscanf(string, " ls %s %s", option, name);
    +       snprintf(get, sizeof get, " ls -t %%%ds %%%ds", sizeof option-1,
    +           sizeof name-1);
    +       i = sscanf(string, get, option, name);
            if (putToFile && i == 2 && name[0] == '>') {
                i--;
            }
    @@ -521,6 +527,8 @@
     }


    +#define NAME_SLOP 80
    +
     PrintListInfo(file, msg, eom, qtype, domain)
         FILE       *file;
         u_char     *msg, *eom;
    @@ -533,7 +541,7 @@
         u_int32_t          ttl;
         int                        n, pref, count;
         struct in_addr     inaddr;
    -    char               name[NAME_LEN];
    +    char               name[NAME_LEN + NAME_SLOP];
         char               name2[NAME_LEN];
         Boolean            stripped;

    @@ -562,7 +570,7 @@
            cp += nameLen + QFIXEDSZ;
         }
         for (count = ntohs(headerPtr->ancount); count > 0; count--) {
    -       nameLen = dn_expand(msg, eom, cp, name, sizeof name);
    +       nameLen = dn_expand(msg, eom, cp, name, NAME_LEN);
            if (nameLen < 0)
                return (ERROR);
            cp += nameLen;
    @@ -592,7 +600,7 @@
                }
            }
            if (!stripped && nameLen < sizeof(name)-1) {
    -           strcat(name, ".");
    +           strncat(name, ".", sizeof(name) - strlen(name) - 1);
            }

            fprintf(file, NAME_FORMAT, name);
    @@ -901,8 +909,10 @@
     {
         char file[PATH_MAX];
         char command[PATH_MAX];
    +    char get[80];

    -    sscanf(string, " view %s", file);
    +    snprintf(get, sizeof get, " view %%%ds", sizeof file-1);
    +    sscanf(string, get, file);
         (void)sprintf(command, "grep \"^ \" %s | sort | %s", file, pager);
         system(command);
     }
    @@ -936,6 +946,7 @@
            register int            lastc;
            char                    name[NAME_LEN];
            char                    file[NAME_LEN];
    +       char                    get[80];

            /*
             *  We need a valid current host info to get an inet address.
    @@ -945,7 +956,8 @@
                return (ERROR);
            }

    -       if (sscanf(string, " finger %s", name) == 1) {
    +       snprintf(get, sizeof get, " finger %%%ds", sizeof name-1);
    +       if (sscanf(string, get, name) == 1) {
                if (putToFile && (name[0] == '>')) {
                    name[0] = '\0';
                }
    Index: nslookup/main.c
    ===================================================================
    RCS file: /cvs/src/usr.sbin/named/nslookup/main.c,v
    retrieving revision 1.3
    diff -u -r1.3 main.c
    --- main.c      1997/03/12 10:42:48     1.3
    +++ main.c      1998/08/31 17:43:19
    @@ -465,6 +465,7 @@
         struct in_addr     *servAddrPtr;
         struct in_addr     addr;
         char               newServer[NAME_LEN];
    +    char               get[40];
         int                        result;
         int                        i;

    @@ -474,12 +475,15 @@
          */

         if (local) {
    -       i = sscanf(string, " lserver %s", newServer);
    +       snprintf(get, sizeof get, "lserver %%%ds", sizeof newServer-1);
    +       i = sscanf(string, get, newServer);
         } else {
    -       i = sscanf(string, " server %s", newServer);
    +       snprintf(get, sizeof get, "server %%%ds", sizeof newServer-1);
    +       i = sscanf(string, get, newServer);
         }
         if (i != 1) {
    -       i = sscanf(string, " %s", newServer);
    +       snprintf(get, sizeof get, " %%%ds", sizeof newServer-1);
    +       i = sscanf(string, get, newServer);
            if (i != 1) {
                fprintf(stderr,"SetDefaultServer: invalid name: %s\n",  string);
                return(ERROR);
    @@ -669,6 +673,7 @@
     {
         char       host[NAME_LEN];
         char       file[PATH_MAX];
    +    char       get[20];
         int                result;

         /*
    @@ -684,7 +689,8 @@
          *
          */

    -    sscanf(string, " %s", host);       /* removes white space */
    +    snprintf(get, sizeof get, " %%%ds", sizeof host-1);
    +    sscanf(string, get, host); /* removes white space */
         if (!putToFile) {
            filePtr = stdout;
         } else {
    @@ -740,12 +746,15 @@
         char       file[PATH_MAX];
         char       host[NAME_LEN];
         char       server[NAME_LEN];
    +    char       get[80];
         int                result;
    +
         static HostInfo serverInfo;

         curHostValid = FALSE;

    -    sscanf(string, " %s %s", host, server);
    +    snprintf(get, sizeof get, " %%%ds %%%ds", sizeof host-1, sizeof server-1);
    +    sscanf(string, get, host, server);
         if (!putToFile) {
            filePtr = stdout;
         } else {
    @@ -824,6 +833,7 @@
         register char *option;
     {
         char       type[NAME_LEN];
    +    char       get[40];
         char       *ptr;
         int                tmp;

    @@ -854,7 +864,8 @@
            } else if (strncmp(option, "do", 2) == 0) {     /* domain */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", _res.defdname);
    +               snprintf(get, sizeof get, "%%%ds", sizeof _res.defdname-1);
    +               sscanf(++ptr, get, _res.defdname);
                    res_re_init();
                }
            } else if (strncmp(option, "deb", 1) == 0) {    /* debug */
    @@ -880,13 +891,15 @@
              strncmp(option, "ty", 2) == 0) {              /* type */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", type);
    +               snprintf(get, sizeof get, "%%%ds", sizeof type-1);
    +               sscanf(++ptr, get, type);
                    queryType = StringToType(type, queryType, stderr);
                }
            } else if (strncmp(option, "cl", 2) == 0) {     /* query class */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", type);
    +               snprintf(get, sizeof get, "%%%ds", sizeof type-1);
    +               sscanf(++ptr, get, type);
                    queryClass = StringToClass(type, queryClass, stderr);
                }
            } else if (strncmp(option, "rec", 3) == 0) {    /* recurse */
    @@ -904,7 +917,8 @@
            } else if (strncmp(option, "ro", 2) == 0) {     /* root */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", rootServerName);
    +               snprintf(get, sizeof get, "%%%ds", sizeof rootServerName-1);
    +               sscanf(++ptr, get, rootServerName);
                }
            } else if (strncmp(option, "sea", 3) == 0) {    /* search list */
                _res.options |= RES_DNSRCH;
    @@ -967,6 +981,7 @@
         int n;

         (void)strncpy(_res.defdname, cp, sizeof(_res.defdname) - 1);
    +    _res.defdname[sizeof(_res.defdname) - 1] = '\0';
         if ((cp = strchr(_res.defdname, '\n')) != NULL)
                *cp = '\0';
         /*
    @@ -1111,8 +1126,9 @@
         if ((cp = getenv("HOME")) != NULL &&
            (strlen(cp) + strlen(_PATH_NSLOOKUPRC)) < sizeof(buf)) {

    -       (void) strcpy(buf, cp);
    -       (void) strcat(buf, _PATH_NSLOOKUPRC);
    +       (void) strncpy(buf, cp, sizeof buf - 1);
    +       buf[sizeof buf - 1] = '\0';
    +       (void) strncat(buf, _PATH_NSLOOKUPRC, sizeof buf - strlen(buf) - 1);

            if ((fp = fopen(buf, "r")) != NULL) {
                while (fgets(buf, sizeof(buf), fp) != NULL) {
    Index: nslookup/subr.c
    ===================================================================
    RCS file: /cvs/src/usr.sbin/named/nslookup/subr.c,v
    retrieving revision 1.2
    retrieving revision 1.3
    diff -u -r1.2 -r1.3
    --- subr.c      1997/03/12 10:42:51     1.2
    +++ subr.c      1998/08/30 03:39:21     1.3
    @@ -343,6 +343,7 @@
     {
            char    *redirect;
            FILE    *tmpPtr;
    +       char    get[80];

            /*
             *  Open an output file if we see '>' or >>'.
    @@ -354,10 +355,12 @@
                return(NULL);
            }
            if (redirect[1] == '>') {
    -           sscanf(redirect, ">> %s", file);
    +           snprintf(get, sizeof get, ">> %%%ds", sizeof file-1);
    +           sscanf(redirect, get, file);
                tmpPtr = fopen(file, "a+");
            } else {
    -           sscanf(redirect, "> %s", file);
    +           snprintf(get, sizeof get, "> %%%ds", sizeof file-1);
    +           sscanf(redirect, get, file);
                tmpPtr = fopen(file, "w");
            }

    bind 8.1.2 version:

    diff -ru org/bind-8.1.2/bin/dig/dig.c bind-8.1.2/bin/dig/dig.c
    --- org/bind-8.1.2/bin/dig/dig.c        Thu Mar 19 20:30:18 1998
    +++ bind-8.1.2/bin/dig/dig.c    Tue Sep  1 17:03:21 1998
    @@ -745,9 +745,11 @@
     static int
     SetOption(const char *string) {
            char option[NAME_LEN], type[NAME_LEN], *ptr;
    +       char get[80];
            int i;

    -       i = sscanf(string, " %s", option);
    +       snprintf(get, sizeof(get), " %%%ds", sizeof(option-1));
    +       i = sscanf(string, get, option);
            if (i != 1) {
                    fprintf(stderr, ";*** Invalid option: %s\n",  option);
                    return (ERROR);
    @@ -779,8 +781,10 @@
                    _res.options &= ~RES_DNSRCH;
            } else if (strncmp(option, "do", 2) == 0) { /* domain */
                    ptr = strchr(option, '=');
    -               if (ptr != NULL)
    -                       sscanf(++ptr, "%s", _res.defdname);
    +               if (ptr != NULL) {
    +                       snprintf(get, sizeof(get),"%%%ds", sizeof(_res.defdname));
    +                       sscanf(++ptr, get, _res.defdname);
    +               }
            } else if (strncmp(option, "ti", 2) == 0) {      /* timeout */
                    ptr = strchr(option, '=');
                    if (ptr != NULL)
    diff -ru org/bind-8.1.2/bin/nslookup/list.c bind-8.1.2/bin/nslookup/list.c
    --- org/bind-8.1.2/bin/nslookup/list.c  Tue Nov 18 01:32:33 1997
    +++ bind-8.1.2/bin/nslookup/list.c      Tue Sep  1 17:09:09 1998
    @@ -152,13 +152,16 @@
     ListHostsByType(char *string, int putToFile) {
            char *namePtr, name[NAME_LEN], option[NAME_LEN];
            int i, qtype, result;
    +       char get[80];

            /*
             * Parse the command line. It maybe of the form "ls -t domain"
             * or "ls -t type domain".
             */

    -       i = sscanf(string, " ls -t %s %s", option, name);
    +       snprintf(get, sizeof get, " ls -t %%%ds %%%ds", sizeof option-1,
    +                               sizeof name-1);
    +       i = sscanf(string, get, option, name);
            if (putToFile && i == 2 && name[0] == '>')
                    i--;
            if (i == 2) {
    @@ -183,12 +186,15 @@
     ListHosts(char *string, int putToFile) {
            char *namePtr, name[NAME_LEN], option[NAME_LEN];
            int i, qtype, result;
    +       char get[80];
    
            /*
             *  Parse the command line. It maybe of the form "ls domain",
             *  "ls -X domain".
             */
    -       i = sscanf(string, " ls %s %s", option, name);
    +       snprintf(get, sizeof get, " ls -t %%%ds %%%ds", sizeof option-1,
    +                       sizeof name-1);
    +       i = sscanf(string, get, option, name);
            if (putToFile && i == 2 && name[0] == '>')
                i--;
            if (i == 2) {
    @@ -481,8 +487,10 @@
     {
         char file[PATH_MAX];
         char command[PATH_MAX];
    +       char get[80];

    -    sscanf(string, " view %s", file);
    +       snprintf(get, sizeof get, " view %%%ds", sizeof file-1);
    +    sscanf(string, get, file);
         (void)sprintf(command, "grep \"^ \" %s | sort | %s", file, pager);
         system(command);
     }
    @@ -516,6 +524,7 @@
            int             lastc;
            char                    name[NAME_LEN];
            char                    file[NAME_LEN];
    +       char            get[80];

            /*
             *  We need a valid current host info to get an inet address.
    @@ -525,7 +534,8 @@
                return (ERROR);
            }

    -       if (sscanf(string, " finger %s", name) == 1) {
    +       snprintf(get, sizeof get, " finger %%%ds", sizeof name-1);
    +       if (sscanf(string, get, name) == 1) {
                if (putToFile && (name[0] == '>')) {
                    name[0] = '\0';
                }
    diff -ru org/bind-8.1.2/bin/nslookup/main.c bind-8.1.2/bin/nslookup/main.c
    --- org/bind-8.1.2/bin/nslookup/main.c  Fri Apr 25 02:27:18 1997
    +++ bind-8.1.2/bin/nslookup/main.c      Tue Sep  1 21:15:25 1998
    @@ -464,6 +464,7 @@
         char               newServer[NAME_LEN];
         int                        result;
         int                        i;
    +    char               get[40];

         /*
          *  Parse the command line. It maybe of the form "server name",
    @@ -471,12 +472,15 @@
          */

         if (local) {
    -       i = sscanf(string, " lserver %s", newServer);
    +       snprintf(get, sizeof get, "lserver %%%ds", sizeof newServer-1);
    +       i = sscanf(string, get, newServer);
         } else {
    -       i = sscanf(string, " server %s", newServer);
    +       snprintf(get, sizeof get, "server %%%ds", sizeof newServer-1);
    +       i = sscanf(string, get, newServer);
         }
         if (i != 1) {
    -       i = sscanf(string, " %s", newServer);
    +       snprintf(get, sizeof get, " %%%ds", sizeof newServer-1);
    +       i = sscanf(string, get, newServer);
            if (i != 1) {
                fprintf(stderr,"SetDefaultServer: invalid name: %s\n",  string);
                return(ERROR);
    @@ -667,6 +671,7 @@
         char       host[NAME_LEN];
         char       file[PATH_MAX];
         int                result;
    +    char       get[80];
    
         /*
          *  Invalidate the current host information to prevent Finger=20
    @@ -681,7 +686,8 @@
          *
          */

    -    sscanf(string, " %s", host);       /* removes white space */
    +    snprintf(get, sizeof get, " %%%ds", sizeof host-1);
    +    sscanf(string, get, host); /* removes white space */
         if (!putToFile) {
            filePtr = stdout;
         } else {
    @@ -738,11 +744,13 @@
         char       host[NAME_LEN];
         char       server[NAME_LEN];
         int                result;
    +    char       get[80];
         static HostInfo serverInfo;

         curHostValid = FALSE;

    -    sscanf(string, " %s %s", host, server);
    +    snprintf(get, sizeof get, " %%%ds %%%ds", sizeof host-1, sizeof server=
    -1);
    +    sscanf(string, get, host, server);
         if (!putToFile) {
            filePtr = stdout;
         } else {
    @@ -823,6 +831,7 @@
         char       type[NAME_LEN];
         char       *ptr;
         int                tmp;
    +    char       get[40];

         while (isspace(*option))
            ++option;
    @@ -851,7 +860,8 @@
            } else if (strncmp(option, "do", 2) == 0) { /* domain */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", _res.defdname);
    +               snprintf(get, sizeof get, "%%%ds", sizeof _res.defdname-1);
    +               sscanf(++ptr, get, _res.defdname);
                    res_re_init();
                }
            } else if (strncmp(option, "deb", 1) == 0) {        /* debug */
    @@ -877,13 +887,15 @@
              strncmp(option, "ty", 2) == 0) {          /* type */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", type);
    +               snprintf(get, sizeof get, "%%%ds", sizeof type-1);
    +               sscanf(++ptr, get, type);
                    queryType = StringToType(type, queryType, stderr);
                }
            } else if (strncmp(option, "cl", 2) == 0) { /* query class */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", type);
    +               snprintf(get, sizeof get, "%%%ds", sizeof type-1);
    +               sscanf(++ptr, get, type);
                    queryClass = StringToClass(type, queryClass, stderr);
                }
            } else if (strncmp(option, "rec", 3) == 0) {        /* recurse */
    @@ -901,7 +913,8 @@
            } else if (strncmp(option, "ro", 2) == 0) { /* root */
                ptr = strchr(option, '=');
                if (ptr != NULL) {
    -               sscanf(++ptr, "%s", rootServerName);
    +               snprintf(get, sizeof get, "%%%ds", sizeof rootServerName-1);
    +               sscanf(++ptr, get, rootServerName);
                }
            } else if (strncmp(option, "sea", 3) == 0) {        /* search list */
                _res.options |= RES_DNSRCH;
    @@ -964,6 +977,7 @@
         int n;

         (void)strncpy(_res.defdname, cp, sizeof(_res.defdname) - 1);
    +    _res.defdname[sizeof(_res.defdname) - 1] = '\0';
         if ((cp = strchr(_res.defdname, '\n')) != NULL)
                *cp = '\0';
         /*
    @@ -1109,8 +1123,9 @@
         if ((cp = getenv("HOME")) != NULL &&
           (strlen(cp) + strlen(_PATH_NSLOOKUPRC)) < sizeof(buf)) {

    -       (void) strcpy(buf, cp);
    -       (void) strcat(buf, _PATH_NSLOOKUPRC);
    +       (void) strncpy(buf, cp, sizeof buf - 1);
    +       buf[sizeof buf - 1] = '\0';
    +       (void) strncat(buf, _PATH_NSLOOKUPRC, sizeof buf - strlen(buf) - 1);

            if ((fp = fopen(buf, "r")) != NULL) {
                while (fgets(buf, sizeof(buf), fp) != NULL) {
    diff -ru org/bind-8.1.2/bin/nslookup/subr.c bind-8.1.2/bin/nslookup/subr.c
    --- org/bind-8.1.2/bin/nslookup/subr.c  Fri Apr 25 02:27:19 1997
    +++ bind-8.1.2/bin/nslookup/subr.c      Tue Sep  1 21:14:39 1998
    @@ -341,6 +341,7 @@
     {
            char    *redirect;
            FILE    *tmpPtr;
    +       char    get[80];
    
            /*
             *  Open an output file if we see '>' or >>'.
    @@ -352,10 +353,12 @@
                return(NULL);
            }
            if (redirect[1] == '>') {
    -           sscanf(redirect, ">> %s", file);
    +               snprintf(get, sizeof get, ">> %%%ds", sizeof file-1);
    +           sscanf(redirect, get, file);
                tmpPtr = fopen(file, "a+");
            } else {
    -           sscanf(redirect, "> %s", file);
    +               snprintf(get, sizeof get, "> %%%ds", sizeof file-1);
    +           sscanf(redirect, get, file);
                tmpPtr = fopen(file, "w");
            }