From dillon@flea.best.net  Thu Nov 20 04:32:08 1997
Received: from flea.best.net (root@flea.best.net [206.184.139.131])
          by hub.freebsd.org (8.8.7/8.8.7) with ESMTP id EAA02062
          for <FreeBSD-gnats-submit@freebsd.org>; Thu, 20 Nov 1997 04:32:08 -0800 (PST)
          (envelope-from dillon@flea.best.net)
Received: (from dillon@localhost) by flea.best.net (8.8.7/8.7.3) id EAA01449; Thu, 20 Nov 1997 04:31:21 -0800 (PST)
Message-Id: <199711201231.EAA01449@flea.best.net>
Date: Thu, 20 Nov 1997 04:31:21 -0800 (PST)
From: Matt Dillon <dillon@best.net>
Reply-To: dillon@best.net
To: FreeBSD-gnats-submit@freebsd.org
Subject: FreeBSD kernel lockup from spoofed TCP packet
X-Send-Pr-Version: 3.2

>Number:         5103
>Category:       kern
>Synopsis:       It appears to be possible to lockup a FreeBSD box with a spoofed TCP packet.   Two of our shell machines were attacked tonight.
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    security-officer
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Thu Nov 20 04:40:01 PST 1997
>Closed-Date:    Sun Feb 1 13:55:12 PST 1998
>Last-Modified:  Sun Feb  1 13:55:53 PST 1998
>Originator:     Matt Dillon
>Release:        FreeBSD 2.2.5-STABLE i386
>Organization:
Best Internet Communications
>Environment:

	FreeBSD 2.2.5 running on PPro 200's

>Description:

	Two of our machines were locked up tonight by what looks like a
	spoofed TCP packet.  The characteristics of the packet were that
	both the source and destination address were set to the machine's
	ethernet IP address, and the same tcp port was used for both source
	and destination.

	We were able to core both machines from the debugger.  Both kernels
	were stuck in an endless ip_intr loop.  It appeared that the tcp
	stack transmitted a packet which caused the higher level ip_intr
	to loop on tcp_input.  An infinite loop ensued.

>How-To-Repeat:

	Not sure.

>Fix:
	
	not sure about this.  I hacked our kernels to discard any packet
	where ti_src.s_addr == ti_dst.s_addr && ti_sport == ti_dport.  I
	am hoping this will prevent the attack from looping the code.

						-Matt

>Release-Note:
>Audit-Trail:

From: Garrett Wollman <wollman@khavrinen.lcs.mit.edu>
To: freebsd-gnats-submit@freebsd.org
Cc:  Subject: kern/5103: FreeBSD kernel lockup from spoofed TCP packet
Date: Thu, 20 Nov 1997 15:08:52 -0500 (EST)

 <<On Thu, 20 Nov 1997 04:31:21 -0800 (PST), Matt Dillon <dillon@best.net> said:
 
 > 	not sure about this.  I hacked our kernels to discard any packet
 > 	where ti_src.s_addr == ti_dst.s_addr && ti_sport == ti_dport.  I
 > 	am hoping this will prevent the attack from looping the code.
 
 I added this quick hack to tcp_input.c in rev. 1.66, and changed the
 PR's state to `serious'.  There is still an underlying bug, since
 self-connect not only should work, but once did.  The same hack should
 be brought into -stable once it is verified to solve the problem (and
 it certainly should).
 
 -GAWollman
 
 --
 Garrett A. Wollman   | O Siem / We are all family / O Siem / We're all the same
 wollman@lcs.mit.edu  | O Siem / The fires of freedom 
 Opinions not those of| Dance in the burning flame
 MIT, LCS, CRS, or NSA|                     - Susan Aglukark and Chad Irschick

From: Bill Fenner <fenner@parc.xerox.com>
To: freebsd-gnats-submit@hub.freebsd.org
Cc:  Subject: Re: kern/5103: FreeBSD kernel lockup from spoofed TCP packet 
Date: Thu, 20 Nov 1997 19:40:35 PST

 Garrett Wollman <wollman@khavrinen.lcs.mit.edu> wrote:
 > There is still an underlying bug, since
 > self-connect not only should work, but once did.
 
 Since 2.2.2 was OK, and 2.2.5 wasn't, it was pretty easy to determine
 that it's the part of 1.52 that was removed in 1.63 and 1.54.2.5 that
 causes 2.2.2 to be OK.  It's clear that we don't want to just re-revert
 1.63/1.54.2.5, since it caused other problems.
 
 One path to go down to understand exactly what's going on might be to
 build with TCPDEBUG and send one of these nastygrams to a server that
 has SO_DEBUG set.  You'd need a serial console, since constantly
 printing messages seems to eliminate your ability to use the scrollback
 (at least, hitting scroll lock to pause the messages flying by didn't
 do anything other than make it start beeping after a few seconds).  I
 don't have a convenient serial console, unfortunately, or I'd pursue
 this.
 
   Bill
Responsible-Changed-From-To: freebsd-bugs->security-officer 
Responsible-Changed-By: jkh 
Responsible-Changed-When: Thu Nov 20 20:23:02 PST 1997 
Responsible-Changed-Why:  
The security officer should own this one until resolution. 

From: Bill Paul <wpaul@skynet.ctr.columbia.edu>
To: fenner@parc.xerox.com
Cc: freebsd-gnats@freebsd.org, jkh@freebsd.org, security-officer@freebsd.org
Subject: Re: kern/5103: FreeBSD kernel lockup from spoofed TCP packet
Date: Sat, 22 Nov 1997 08:57:00 PST

 Of all the gin joints in all the towns in all the world, Bill Fenner had 
 to walk into mine and say:
 
 [chop]
   
 >  One path to go down to understand exactly what's going on might be to
 >  build with TCPDEBUG and send one of these nastygrams to a server that
 >  has SO_DEBUG set.  You'd need a serial console, since constantly
 >  printing messages seems to eliminate your ability to use the scrollback
 >  (at least, hitting scroll lock to pause the messages flying by didn't
 >  do anything other than make it start beeping after a few seconds).  I
 >  don't have a convenient serial console, unfortunately, or I'd pursue
 >  this.
 >  
 >    Bill
 
 Well I was able to set up a machine with a serial console. Okay,
 I may not be a networking expert, but every now and then I get
 lucky. Here's my take on all this.
 
 First of all, this is not the same as doing a self-connect. If you
 were really trying to connect a socket to itself, the connection would
 at some point pass through the SYN_SENT state. In this scenario, the
 connection goes from LISTEN to SYN_RECEIVED without ever passing
 through SYN_SENT. (Actually, connect()ing to a bound socker that's
 been listen()ed is not allowed, so I don't think you'll ever see
 a self-connect where the connection goes from LISTEN to SYN_SENT
 to SYN_RECEIVED.)
 
 I did some anaylsis using a kernel with 'options TCPDEBUG' and
 'options DDB' in the config file. Here's the steps I took and the
 results:
 
 - Compiled kernel with options DDB and options TCPDEBUG and reboot.
 
 - Used gdb to turn on the tcpconsdebug flag to enable the debugging
   printf()s. (There's no sysctl for this, so you have to do it the
   old fashioned way.)
 
 # gdb -q --wcore -k /vmunix /dev/mem
 (no debugging symbols found)...
 IdlePTD 23e000
 current pcb at 37d2000
 #0  0xf0118557 in mi_switch ()
 (kgdb) set tcpconsdebug = 1
 (kgdb) quit
 
 - Killed syslogd to stop the console from getting cluttered.
 
 # ps -auxwww | grep sys
 root       156  0.0  0.8   240  476  d0  S+    5:22PM    0:00.01 grep sys
 root        77  0.0  0.8   204  496  ??  Is    5:22PM    0:00.10 syslogd
 # kill -TERM 77
 Nov 21 17:23:02 voice syslogd: exiting on signal 15
 Nov 21 17:23:02 voice syslogd: exiting on signal 15
 Nov 21 17:23:02 voice syslogd: exiting on signal 15
 # 
 
 - Dropped into the kernel debugger and set a breakpoint at tcp_output.
   This lets you step through each cycle in the loop; if you don't do
   this you just get a torrent of printf()s that are impossible to follow.
  
 # Debugger("manual escape to debugger")
 Stopped at      _Debugger+0x35: movb    $0,_in_Debugger.122
 db> break tcp_output
 db> continue
 
 - Ran a small program that creates an AF_INET socket, bind()s it to
   port 8000, turns on the SO_DEBUG flag, and does a listen.
 
 # ./a.out &
 [1] 157
 # 0xf06cda00 CLOSED:user BIND -> CLOSED
 	rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 0xf06cda00 CLOSED:user LISTEN -> LISTEN
 	rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 
 - Now I transmitted a 'loopback SYN' to the listening program. (Yes, you
   can do this on the local host via the loopback network.) Note that
   my program transmits the SYN with a TCP sequence number of 0xdead.
   From here on, we watch what the kernel does.
 
 # ./lose2 voice 8000
 0xf0725900 CLOSED:user ATTACH -> CLOSED
 	rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 
 - The first step is that the SYN is received and the connection goes into
   the SYN received state. Since we're in the LISTEN state, tcp_input()
   allocates a fresh initial sequence number for the receive side of the
   connection.
 
 0xf0725900 LISTEN:input dead@0, urp=0<SYN> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4000,deae) snd_(una,nxt,max) (e6ab6b,e6ab6b,e6ab6b)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 Breakpoint at   _tcp_output:    pushl   %ebp
 
 - Next, tcp_input() initiates the second part of the 3-way handshake by
   sending a SYN,ACK, which gets sent back to itself.
 
 db> continue
 0xf0725900 SYN_RCVD:output [e6ab6b..e6ab6f)@deae, urp=0<SYN,ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4000,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 
 - Now tcp_input() gets the SYN,ACK, however it decides to drop the
   segment. It does this when it arrives at the code detailed on
   page 958 of _TCP/IP Illustrated Vol. 2_. This is near line 1030
   in our version of tcp_input.c. According to the commentary on page
   959, "The entire segment lies outside the window and is not a window
   probe, so the segment is discarded and an ACK is sent. This ACK will
   contain the expected sequence number."
 
   This is where the problem starts: this code does a 'goto dropafterack'
   which causes an ACK to be sent.
 
 0xf0725900 SYN_RCVD:drop e6ab6b@deae, urp=0<SYN,ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 Breakpoint at   _tcp_output:    pushl   %ebp
 db> continue
 0xf0725900 SYN_RCVD:output e6ab6c@deae, urp=0<ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 0xf0725900 SYN_RCVD:drop e6ab6c@deae, urp=0<ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 Breakpoint at   _tcp_output:    pushl   %ebp
 
 - Now we are trapped in a loop: tcp_input() receives the ACK but the same
   conditions are true (the segment lies outside the window) so it once
   again jumps to dropafterack.
 
 db> continue
 0xf0725900 SYN_RCVD:output e6ab6c@deae, urp=0<ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 0xf0725900 SYN_RCVD:drop e6ab6c@deae, urp=0<ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c)
 	snd_(wl1,wl2,wnd) (dead,0,0)
 Breakpoint at   _tcp_output:    pushl   %ebp
 
 - An infinite cycle of ACKing ACKs ensues. Interrupts are masked, so the 
   machine stops responding to pretty much everything, except maybe other
   network interrupts and the kernel debugger.
 
 
 The question now is: how to fix this. The only thing I can come up
 with is the following one line patch:
 
 
 *** tcp_input.c.orig	Sat Nov 22 11:14:33 1997
 --- tcp_input.c	Sat Nov 22 11:20:19 1997
 ***************
 *** 1047,1053 ****
   			if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) {
   				tp->t_flags |= TF_ACKNOW;
   				tcpstat.tcps_rcvwinprobe++;
 ! 			} else
   				goto dropafterack;
   		} else
   			tcpstat.tcps_rcvbyteafterwin += todrop;
 --- 1047,1053 ----
   			if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) {
   				tp->t_flags |= TF_ACKNOW;
   				tcpstat.tcps_rcvwinprobe++;
 ! 			} else if (tp->t_state != TCPS_SYN_RECEIVED)
   				goto dropafterack;
   		} else
   			tcpstat.tcps_rcvbyteafterwin += todrop;
 
 
 Note that this is against tcp_input.c from my 2.1.0 machine at home,
 but it should apply to pretty much any version of tcp_input.c, including
 those from OpenBSD and NetBSD. This breaks the loop, though I'm not
 sure if this also breaks TCP behavior in other cases.
 
 So, do I get a cookie?
 
 -Bill
 
 -- 
 =============================================================================
 -Bill Paul            (212) 854-6020 | System Manager, Master of Unix-Fu
 Work:         wpaul@ctr.columbia.edu | Center for Telecommunications Research
 Home:  wpaul@skynet.ctr.columbia.edu | Columbia University, New York City
 =============================================================================
  "It is not I who am crazy; it is I who am mad!" - Ren Hoek, "Space Madness"
 =============================================================================

From: Bill Fenner <fenner@parc.xerox.com>
To: Bill Paul <wpaul@skynet.ctr.columbia.edu>
Cc: fenner@parc.xerox.com, freebsd-gnats-submit@freebsd.org, jkh@freebsd.org,
        security-officer@freebsd.org
Subject: Re: kern/5103: FreeBSD kernel lockup from spoofed TCP packet 
Date: Sat, 22 Nov 1997 10:38:54 PST

 Bill Paul <wpaul@skynet.ctr.columbia.edu> wrote:
 >This breaks the loop, though I'm not
 >sure if this also breaks TCP behavior in other cases.
 
 I think it breaks the case where the SYN/ACK is lost and the originator
 retransmits the SYN.  Then we'll be in SYN_RECEIVED and have received
 a packet that's completely outside the window (immediately before it).
 
 Also, using the window to break this loop doesn't work when the attacker
 has guessed your new ISS.  In this case, the connection could even move
 to ESTABLISHED, and the result would look like a self-connect.  I think
 telnetd would be pretty confused to be talking to itself.
 
 Thanks for the analysis, though, Bill - it'll definitely help.  If you
 feel like making your ISS predictable and forging a packet with the right
 ISS it'd be nice to see what happens in that case as well.
 
 I wonder if we could RST if we get a SYN/ACK in SYN_RECEIVED state (Don
 Lewis suggested this in freebsd-security).  We can get a SYN-only, if
 the other end lost our SYN/ACK so is retransmitting, or we can get an
 ACK-only, if the other end is ACK'ing our SYN, but I'm pretty sure that
 you can't get a SYN/ACK.  Unfortunately, the SYN/ACK is likely to be
 outside the window, so we can't do the window checks.  Without the
 window checks, we're vulnerable to old packets from the same connection
 that happened earlier coming back from la-la-land in the network
 (unlikely, but one of the things that TCP was designed to handle).
 
   Bill

From: Guido van Rooij <guido@gvr.org>
To: fenner@parc.xerox.com (Bill Fenner)
Cc: wpaul@skynet.ctr.columbia.edu, fenner@parc.xerox.com,
        freebsd-gnats-submit@freebsd.org, jkh@freebsd.org,
        security-officer@freebsd.org
Subject: Re: kern/5103: FreeBSD kernel lockup from spoofed TCP packet
Date: Sat, 22 Nov 1997 21:16:45 +0100 (MET)

 Just FYI: I've asked Rich Stevens for his opinion on this matter.
 He promised to take a look. I'll forward the appropriate messages to him.
 
 -Guido

From: Bill Fenner <fenner@parc.xerox.com>
To: freebsd-gnats-submit@freebsd.org
Cc:  Subject: kern/5103
Date: Sun, 23 Nov 1997 02:38:21 PST

 This paragraph from RFC793 implies that we should be sending a RST when
 we get the SYN/ACK unless the ACK is valid (because we're in an
 unsynchronized state and we get an ACK for something we haven't sent):
 
     2.  If the connection is in any non-synchronized state (LISTEN,
     SYN-SENT, SYN-RECEIVED), and the incoming segment acknowledges
     something not yet sent (the segment carries an unacceptable ACK), or
     if an incoming segment has a security level or compartment which
     does not exactly match the level and compartment requested for the
     connection, a reset is sent.
 
 This is part of what pst's code in rev1.52 did (which is why 2.2.2-RELEASE
 was not vulnerable).  The reason it was backed out was because it did too
 much checking on RST's, but it was at least partially correct.  Doing this
 prevents this attack from working unless the attacker guesses the TCP ISS
 (in which case I believe the connection would succeed and the new endpoint
 would be connected with itself).
 
   Bill

From: Bill Paul <wpaul@skynet.ctr.columbia.edu>
To: fenner@parc.xerox.com (Bill Fenner)
Cc: freebsd-gnats-submit@freebsd.org, jkh@freebsd.org,
        security-officer@freebsd.org
Subject: Re: kern/5103: FreeBSD kernel lockup from spoofed TCP packet
Date: Sun, 23 Nov 1997 13:27:20 -0500 (EST)

 Of all the gin joints in all the towns in all the world, Bill Fenner had 
 to walk into mine and say:
 
 > Bill Paul <wpaul@skynet.ctr.columbia.edu> wrote:
 > >This breaks the loop, though I'm not
 > >sure if this also breaks TCP behavior in other cases.
 > 
 > I think it breaks the case where the SYN/ACK is lost and the originator
 > retransmits the SYN.  Then we'll be in SYN_RECEIVED and have received
 > a packet that's completely outside the window (immediately before it).
 
 Well... what about this then:
 
 *** tcp_input.c.orig	Sat Nov 22 11:14:33 1997
 --- tcp_input.c	Sun Nov 23 12:53:48 1997
 ***************
 *** 1047,1053 ****
   			if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) {
   				tp->t_flags |= TF_ACKNOW;
   				tcpstat.tcps_rcvwinprobe++;
 ! 			} else
   				goto dropafterack;
   		} else
   			tcpstat.tcps_rcvbyteafterwin += todrop;
 --- 1047,1054 ----
   			if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) {
   				tp->t_flags |= TF_ACKNOW;
   				tcpstat.tcps_rcvwinprobe++;
 ! 			} else if (tp->t_state != TCPS_SYN_RECEIVED &&
 ! 				   !(tiflags & TH_ACK))
   				goto dropafterack;
   		} else
   			tcpstat.tcps_rcvbyteafterwin += todrop;
 
 If the other end retransmits the SYN because our SYN,ACK got lost, the 
 retransmission shouldn't have the ACK bit set, correct?
 
 > Also, using the window to break this loop doesn't work when the attacker
 > has guessed your new ISS.  In this case, the connection could even move
 > to ESTABLISHED, and the result would look like a self-connect.  I think
 > telnetd would be pretty confused to be talking to itself.
 > 
 > Thanks for the analysis, though, Bill - it'll definitely help.  If you
 > feel like making your ISS predictable and forging a packet with the right
 > ISS it'd be nice to see what happens in that case as well.
 
 Okay, I've done this. I hacked tcp_input.c so that it has a 
 tcp_iss_predictable (in additon to tcp_iss; didn't want to break
 the existing counter for no reason) which is only incremented at each 
 connection rather than at each connection and every second (which is
 what my 2.1.0 box at home does). After observing the chosen sequence
 number after few connects, I modified the transmitter program to
 use the next sequence number in line, which happened to be
 384000.
 
 Incidentally, for this test I used the same little program I used
 previously; this program listens on port 8000 for a connect, accept()s
 the connection, write()s a few bytes to the new socket, then close()s
 the new socket and loops around again waiting for a new connection.
 
 Here's what happened:
 
 0xf06af000 CLOSED:user ATTACH -> CLOSED
 	rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 0xf06af000 LISTEN:input 5dc00@0, urp=0<SYN> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (5dc01,4000,5dc01) snd_(una,nxt,max) (5dc00,5dc00,5dc00)
 	snd_(wl1,wl2,wnd) (5dc00,0,0)
 0xf06af000 SYN_RCVD:output [5dc00..5dc04)@5dc01, urp=0<SYN,ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (5dc01,4000,5dc01) snd_(una,nxt,max) (5dc00,5dc01,5dc01)
 	snd_(wl1,wl2,wnd) (5dc00,0,0)
 /* XXXXXXXXXXXXX */
 0xf06af000 SYN_RCVD:input 5dc00@5dc01, urp=0<SYN,ACK> -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc01,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc01,5dc01)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,4000)
 /* XXXXXXXXXXXXX */
 0xf06af000 ESTABLISHED:output 5dc01@5dc01, urp=0<ACK> -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc01,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc01,5dc01)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,4000)
 0xf06af000 ESTABLISHED:input 5dc01@5dc01, urp=0<ACK> -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc01,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc01,5dc01)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 ESTABLISHED:user ACCEPT -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc01,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc01,5dc01)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 ESTABLISHED:output [5dc01..5dc65)@5dc01, urp=0<ACK,PUSH> -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc01,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc65,5dc65)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 ESTABLISHED:user SEND -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc01,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc65,5dc65)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 ESTABLISHED:user SEND -> ESTABLISHED
 	rcv_(nxt,wnd,up) (5dc65,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc65,5dc65)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 FIN_WAIT_1:output [5dc65..5dc69)@5dc65, urp=0<ACK,FIN,PUSH> -> FIN_WAIT_1
 	rcv_(nxt,wnd,up) (5dc65,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc6a,5dc6a)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 ESTABLISHED:user DISCONNECT -> FIN_WAIT_1
 	rcv_(nxt,wnd,up) (5dc65,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc6a,5dc6a)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 0xf06af000 FIN_WAIT_1:user DETACH -> FIN_WAIT_1
 	rcv_(nxt,wnd,up) (5dc65,7000,5dc01) snd_(una,nxt,max) (5dc01,5dc6a,5dc6a)
 	snd_(wl1,wl2,wnd) (5dc01,5dc01,7000)
 ???????? drop [5dc65..5dc69)@5dc65, urp=0<ACK,FIN,PUSH>
 ???????? output 5dc65@0, urp=0<RST>
 0xf06ae100 CLOSED:user ATTACH -> CLOSED
 	rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 0xf06ae100 LISTEN:drop 5dc65@0, urp=0<RST> -> LISTEN
 	rcv_(nxt,wnd,up) (0,4000,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 
 Looks like you were right: if the forged SYN datagram has the same
 sequence number as the receiver's ISS, the connection will get all
 the way to the ESTABLISHED state. (Fortunately, my program only talks
 and never listens, so it can't get caught in a conversation with itself.)
 The section between the XXXXXXXX's shows where the reception of the 
 SYN,ACK causes the connection to move to ESTABLISHED. Looking at a log of 
 a normal connection to the same program (telnet localhost 8000), the 
 corresponding section shows an ACK, not a SYN,ACK:
 
 0xf06aee00 CLOSED:user ATTACH -> CLOSED
 	rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0)
 	snd_(wl1,wl2,wnd) (0,0,0)
 0xf06aee00 LISTEN:input 8e17001@0, urp=0<SYN> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (8e17002,4000,8e17002) snd_(una,nxt,max) (6d600,6d600,6d600)
 	snd_(wl1,wl2,wnd) (8e17001,0,4000)
 0xf06aee00 SYN_RCVD:output [6d600..6d624)@8e17002, urp=0<SYN,ACK> -> SYN_RCVD
 	rcv_(nxt,wnd,up) (8e17002,4000,8e17002) snd_(una,nxt,max) (6d600,6d601,6d601)
 	snd_(wl1,wl2,wnd) (8e17001,0,4000)
 /* XXXXXXXXXXXXX */
 0xf06aee00 SYN_RCVD:input 8e17002@6d601, urp=0<ACK> -> ESTABLISHED
 	rcv_(nxt,wnd,up) (8e17002,7000,8e17002) snd_(una,nxt,max) (6d601,6d601,6d601)
 	snd_(wl1,wl2,wnd) (8e17002,6d601,7000)
 /* XXXXXXXXXXXXX */
 0xf06aee00 ESTABLISHED:user ACCEPT -> ESTABLISHED
 	rcv_(nxt,wnd,up) (8e17002,7000,8e17002) snd_(una,nxt,max) (6d601,6d601,6d601)
 	snd_(wl1,wl2,wnd) (8e17002,6d601,7000)
 0xf06aee00 ESTABLISHED:output [6d601..6d679)@8e17002, urp=0<ACK,PUSH> -> ESTABLISHED
 	rcv_(nxt,wnd,up) (8e17002,7000,8e17002) snd_(una,nxt,max) (6d601,6d665,6d665)
 	snd_(wl1,wl2,wnd) (8e17002,6d601,7000)
 [rest deleted]
  
 > I wonder if we could RST if we get a SYN/ACK in SYN_RECEIVED state (Don
 > Lewis suggested this in freebsd-security).  We can get a SYN-only, if
 > the other end lost our SYN/ACK so is retransmitting, or we can get an
 > ACK-only, if the other end is ACK'ing our SYN, but I'm pretty sure that
 > you can't get a SYN/ACK.  Unfortunately, the SYN/ACK is likely to be
 > outside the window, so we can't do the window checks.  Without the
 > window checks, we're vulnerable to old packets from the same connection
 > that happened earlier coming back from la-la-land in the network
 > (unlikely, but one of the things that TCP was designed to handle).
 > 
 >   Bill
 
 How about a combination of his patch and my patch?
 
 On a somewhat separate note, just out of curiosity, is it possible
 for the loop condition brought about by the forged 'loopback SYN'
 to occur between two different machines? In other words, is it
 possible for an ACK war to break out between two hosts rather that
 just inside the TCP stack of one? If so, would my hack break the
 cycle?
 
 -Bill
 
 -- 
 =============================================================================
 -Bill Paul            (212) 854-6020 | System Manager, Master of Unix-Fu
 Work:         wpaul@ctr.columbia.edu | Center for Telecommunications Research
 Home:  wpaul@skynet.ctr.columbia.edu | Columbia University, New York City
 =============================================================================
  "It is not I who am crazy; it is I who am mad!" - Ren Hoek, "Space Madness"
 =============================================================================

From: Bill Fenner <fenner@parc.xerox.com>
To: Bill Paul <wpaul@skynet.ctr.columbia.edu>
Cc: fenner@parc.xerox.com (Bill Fenner), freebsd-gnats-submit@freebsd.org,
        security-officer@freebsd.org
Subject: Re: kern/5103: FreeBSD kernel lockup from spoofed TCP packet 
Date: Sun, 23 Nov 1997 19:31:08 PST

 Bill Paul <wpaul@skynet.ctr.columbia.edu> wrote:
 >Okay, I've done this.
 
 Totally awesome, Bill.  Thanks for verifying my guess.
 
 >How about a combination of his patch and my patch?
 
 Since we need to check for SYN, we need for the sequence trimming to
 not trim it off.  Therefore, what I suggest is moving the check to
 before the trimming step, as in the following patch.
 
 >On a somewhat separate note, just out of curiosity, is it possible
 >for the loop condition brought about by the forged 'loopback SYN'
 >to occur between two different machines?
 
 I believe so, but you have to get the SYN's to cross before the SYN/ACK's.
 Not too hard the two machines are far apart and you're in the middle.
 I've been trying to do this on a single multi-homed machine and it's
 pretty hard to get two SYN's in before the SYN/ACK, and the only far-away
 machine I have access to is 2.2.2 so isn't vulnerable.
 
 >If so, would my hack break the cycle?
 
 Once again, it depends on whether or not the attacker guessed (both)
 sequence numbers.  If he did, the connection could go to ESTABLISHED.
 
   Bill
 
 The following patch is threefold:
 1. Drop packets that appear to be from this endpoint while in LISTEN state.
 2. Check the ACK early in SYN_RECEIEVED state.  This is more or less equivalent
    to Bill's patch.  Checking the ACK in SYN_RECEIVED is required by RFC793.
 3. RST a SYN/ACK received in SYN_RECEIVED state.
 
 I'm not in love with the move of the ACK checking; I only moved it because
 I had already made a new case to check the presence of SYN/ACK early and
 it made sense to move it.
 
 
 Index: tcp_input.c
 ===================================================================
 RCS file: /home/ncvs/src/sys/netinet/tcp_input.c,v
 retrieving revision 1.65
 diff -u -r1.65 tcp_input.c
 --- tcp_input.c	1997/11/07 08:53:21	1.65
 +++ tcp_input.c	1997/11/23 20:54:17
 @@ -613,6 +613,7 @@
  	 * If the state is LISTEN then ignore segment if it contains an RST.
  	 * If the segment contains an ACK then it is bad and send a RST.
  	 * If it does not contain a SYN then it is not interesting; drop it.
 +	 * If it is from this socket, drop it, it must be forged.
  	 * Don't bother responding if the destination was a broadcast.
  	 * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial
  	 * tp->iss, and send a segment:
 @@ -631,6 +632,9 @@
  			goto dropwithreset;
  		if ((tiflags & TH_SYN) == 0)
  			goto drop;
 +		if ((ti->ti_dport == ti->ti_sport) &&
 +		    (ti->ti_dst.s_addr == ti->ti_src.s_addr))
 +			goto drop;
  		/*
  		 * RFC1122 4.2.3.10, p. 104: discard bcast/mcast SYN
  		 * in_broadcast() should never return true on a received
 @@ -749,6 +753,24 @@
  		}
  
  	/*
 +	 * If the state is SYN_RECEIVED:
 +	 *	if seg contains SYN/ACK, RST it and drop the connection.
 +	 *	if seg contains an ACK, but not for our SYN/ACK, drop the input.
 +	 */
 +	case TCPS_SYN_RECEIVED:
 +		if (tiflags & TH_ACK) {
 +			if (tiflags & TH_SYN) {
 +				tp = tcp_drop(tp, ECONNREFUSED);
 +				tcpstat.tcps_badsyn++;
 +				goto dropwithreset;
 +			}
 +			if (SEQ_LEQ(ti->ti_ack, tp->snd_una) ||
 +			    SEQ_GT(ti->ti_ack, tp->snd_max))
 +				goto dropwithreset;
 +		}
 +		break;
 +
 +	/*
  	 * If the state is SYN_SENT:
  	 *	if seg contains an ACK, but not for our SYN, drop the input.
  	 *	if seg contains a RST, then drop the connection.
 @@ -1163,14 +1185,11 @@
  	switch (tp->t_state) {
  
  	/*
 -	 * In SYN_RECEIVED state if the ack ACKs our SYN then enter
 -	 * ESTABLISHED state and continue processing, otherwise
 -	 * send an RST.
 +	 * In SYN_RECEIVED state, the ack ACKs our SYN, so enter
 +	 * ESTABLISHED state and continue processing.
 +	 * The ACK was checked above.
  	 */
  	case TCPS_SYN_RECEIVED:
 -		if (SEQ_GT(tp->snd_una, ti->ti_ack) ||
 -		    SEQ_GT(ti->ti_ack, tp->snd_max))
 -			goto dropwithreset;
  
  		tcpstat.tcps_connects++;
  		soisconnected(so);
State-Changed-From-To: open->closed 
State-Changed-By: fenner 
State-Changed-When: Sun Feb 1 13:55:12 PST 1998 
State-Changed-Why:  
Fixed in rev 1.68 (-current) and 1.54.2.6 (-stable) 
>Unformatted:
