Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 31 Aug 1998 11:00:03 -0700 (PDT)
From:      Archie Cobbs <archie@whistle.com>
To:        freebsd-hackers@FreeBSD.ORG
Subject:   Re: FreeBSD's RST validation
Message-ID:  <199808311800.LAA06751@bubba.whistle.com>
In-Reply-To: <199808311306.GAA27281@salsa.gv.tsc.tdk.com> from Don Lewis at "Aug 31, 98 06:06:36 am"

next in thread | previous in thread | raw e-mail | index | archive | help
Would some -hackers comment on this bug (posted to freebsd-security)?

Thanks,
-Archie

Don Lewis writes:
> On Aug 30,  5:21pm, Tristan Horn wrote:
> } Subject: FreeBSD's RST validation
> } --ZRyEpB+iJ+qUx0kp
> } Content-Type: multipart/mixed; boundary=qGV0fN9tzfkG3CxV
> } 
> } 
> } --qGV0fN9tzfkG3CxV
> } Content-Type: text/plain; charset=us-ascii
> } 
> } RFC 793, pages 36-39 (chapter 3.5) describes closing connections with
> } TCP.  Page 37 is of particular interest:
> } 
> }   Reset Processing
> } 
> }   In all states except SYN-SENT, all reset (RST) segments are validated
> }   by checking their SEQ-fields.  A reset is valid if its sequence number
> }   is in the window.  In the SYN-SENT state (a RST received in response
> }   to an initial SYN), the RST is acceptable if the ACK field
> }   acknowledges the SYN.
> } 
> } Unfortunately, FreeBSD (2.2.5, 2.2.6, 2.2.7, 3.0) does not appear to
> } validate RST segments to this extent.  In other words, only the packets'
> } IP/port pairs are checked.
> 
> Back in December 1997, I posted the following patch for the LAND attack
> and also implemented stricter RST validation.  The variation of the
> LAND fix in the first two chunks of this patch was implemented (you'll
> have to look carefully at the code to find the second chunk), but I don't
> believe the rest of the fixes in this patch were applied.
> 
> I've been running a version of this patch altered for 2.1.x since December
> without problems.  If you remove the first two chunks of this patch, it
> will apply cleanly to the 2.2-stable version of tcp_input.c, though I have
> no idea if it will work ...
> 
> -----------------  Cut Here --------------------------
> --- tcp_input.c.2_2	Mon Dec  1 16:49:21 1997
> +++ tcp_input.c	Wed Dec  3 02:21:45 1997
> @@ -318,19 +318,6 @@
>  #endif /* TUBA_INCLUDE */
>  
>  	/*
> -	 * Reject attempted self-connects.  XXX This actually masks
> -	 * a bug elsewhere, since self-connect should work.
> -	 * However, a urrently-active DoS attack in the Internet
> -	 * sends a phony self-connect request which causes an infinite
> -	 * loop.
> -	 */
> -	if (ti->ti_src.s_addr == ti->ti_dst.s_addr
> -	    && ti->ti_sport == ti->ti_dport) {
> -		tcpstat.tcps_badsyn++;
> -		goto drop;
> -	}
> -
> -	/*
>  	 * Check that TCP offset makes sense,
>  	 * pull out TCP options and adjust length.		XXX
>  	 */
> @@ -654,6 +641,24 @@
>  		if (m->m_flags & (M_BCAST|M_MCAST) ||
>  		    IN_MULTICAST(ntohl(ti->ti_dst.s_addr)))
>  			goto drop;
> +
> +		/*
> +		 * Reject attempted self-connects.
> +		 *
> +		 * Doing the test here should prevent the "LAND" DoS
> +		 * attack without affecting legitimate self-connects
> +		 * which will occur in the SYN-SENT state.
> +		 * 
> +		 * In the dropafterack code below we'll also fix the real
> +		 * bug in the SYN-RECEIVED state that causes the infinite
> +		 * loop since it can also be used to generate ACK storms.
> +		 */
> +		if (ti->ti_src.s_addr == ti->ti_dst.s_addr
> +		    && ti->ti_sport == ti->ti_dport) {
> +			tcpstat.tcps_badsyn++;
> +			goto drop;
> +		}
> +
>  		am = m_get(M_DONTWAIT, MT_SONAME);	/* XXX */
>  		if (am == NULL)
>  			goto drop;
> @@ -962,17 +967,99 @@
>  
>  	/*
>  	 * States other than LISTEN or SYN_SENT.
> -	 * First check timestamp, if present.
> +	 * First check the RST flag and sequence number since reset segments
> +	 * are exempt from the timestamp and connection count tests.  This
> +	 * fixes a bug introduced by the Stevens, vol. 2, p. 960 bugfix
> +	 * below which allowed reset segments in half the sequence space
> +	 * to fall though and be processed (which gives forged reset
> +	 * segments with a random sequence number a 50 percent chance of
> +	 * killing a connection).
> +	 * Then check timestamp, if present.
>  	 * Then check the connection count, if present.
>  	 * Then check that at least some bytes of segment are within
>  	 * receive window.  If segment begins before rcv_nxt,
>  	 * drop leading data (and SYN); if nothing left, just ack.
>  	 *
> +	 *
> +	 * If the RST bit is set, check the sequence number to see
> +	 * if this is a valid reset segment.
> +	 * RFC 793 page 37:
> +	 *   In all states except SYN-SENT, all reset (RST) segments
> +	 *   are validated by checking their SEQ-fields.  A reset is
> +	 *   valid if its sequence number is in the window.
> +	 * Note: this does not take into account delayed ACKs, so
> +	 *   we should test against last_ack_sent instead of rcv_nxt.
> +	 *   Also, it does not make sense to allow reset segments with
> +	 *   sequence numbers greater than last_ack_sent to be processed
> +	 *   since these sequence numbers are just the acknowledgement
> +	 *   numbers in our outgoing packets being echoed back at us,
> +	 *   and these acknowledgement numbers are monotonically
> +	 *   increasing.
> +	 * If we have multiple segments in flight, the intial reset
> +	 * segment sequence numbers will be to the left of last_ack_sent,
> +	 * but they will eventually catch up.
> +	 * In any case, it never made sense to trim reset segments to
> +	 * fit the receive window since RFC 1122 says:
> +	 *   4.2.2.12  RST Segment: RFC-793 Section 3.4
> +	 *
> +	 *    A TCP SHOULD allow a received RST segment to include data.
> +	 *
> +	 *    DISCUSSION
> +	 *         It has been suggested that a RST segment could contain
> +	 *         ASCII text that encoded and explained the cause of the
> +	 *         RST.  No standard has yet been established for such
> +	 *         data.
> +	 *
> +	 * If the reset segment passes the sequence number test examine
> +	 * the state:
> +	 *    SYN_RECEIVED STATE:
> +	 *	If passive open, return to LISTEN state.
> +	 *	If active open, inform user that connection was refused.
> +	 *    ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES:
> +	 *	Inform user that connection was reset, and close tcb.
> +	 *    CLOSING, LAST_ACK, TIME_WAIT STATES
> +	 *	Close the tcb.
> +	 *    TIME_WAIT state:
> +	 *	Drop the segment - see Stevens, vol. 2, p. 964 and
> +	 *      RFC 1337.
> +	 */
> +	if (tiflags&TH_RST) {
> +		if (tp->last_ack_sent == ti->ti_seq) {
> +			switch (tp->t_state) {
> +
> +			case TCPS_SYN_RECEIVED:
> +				so->so_error = ECONNREFUSED;
> +				goto close;
> +
> +			case TCPS_ESTABLISHED:
> +			case TCPS_FIN_WAIT_1:
> +			case TCPS_FIN_WAIT_2:
> +			case TCPS_CLOSE_WAIT:
> +				so->so_error = ECONNRESET;
> +			close:
> +				tp->t_state = TCPS_CLOSED;
> +				tcpstat.tcps_drops++;
> +				tp = tcp_close(tp);
> +				break;
> +
> +			case TCPS_CLOSING:
> +			case TCPS_LAST_ACK:
> +				tp = tcp_close(tp);
> +				break;
> +
> +			case TCPS_TIME_WAIT:
> +				break;
> +			}
> +		}
> +		goto drop;
> +	}
> +
> +	/*
>  	 * RFC 1323 PAWS: If we have a timestamp reply on this segment
>  	 * and it's less than ts_recent, drop it.
>  	 */
> -	if ((to.to_flag & TOF_TS) != 0 && (tiflags & TH_RST) == 0 &&
> -	    tp->ts_recent && TSTMP_LT(to.to_tsval, tp->ts_recent)) {
> +	if ((to.to_flag & TOF_TS) != 0 && tp->ts_recent &&
> +	    TSTMP_LT(to.to_tsval, tp->ts_recent)) {
>  
>  		/* Check to see if ts_recent is over 24 days old.  */
>  		if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) {
> @@ -1003,10 +1090,19 @@
>  	 *   RST segments do not have to comply with this.
>  	 */
>  	if ((tp->t_flags & (TF_REQ_CC|TF_RCVD_CC)) == (TF_REQ_CC|TF_RCVD_CC) &&
> -	    ((to.to_flag & TOF_CC) == 0 || tp->cc_recv != to.to_cc) &&
> -	    (tiflags & TH_RST) == 0)
> +	    ((to.to_flag & TOF_CC) == 0 || tp->cc_recv != to.to_cc))
>   		goto dropafterack;
>  
> +	/*
> +	 * In the SYN-RECEIVED state, validate that the packet belongs to
> +	 * this connection before trimming the data to fit the receive
> +	 * window.  Check the sequence number versus IRS since we know
> +	 * the sequence numbers haven't wrapped.  This is a partial fix
> +	 * for the "LAND" DoS attack.
> +	 */
> +	if (tp->t_state == TCPS_SYN_RECEIVED && SEQ_LT(ti->ti_seq, tp->irs))
> +		goto dropwithreset;
> +
>  	todrop = tp->rcv_nxt - ti->ti_seq;
>  	if (todrop > 0) {
>  		if (tiflags & TH_SYN) {
> @@ -1118,40 +1214,6 @@
>  	}
>  
>  	/*
> -	 * If the RST bit is set examine the state:
> -	 *    SYN_RECEIVED STATE:
> -	 *	If passive open, return to LISTEN state.
> -	 *	If active open, inform user that connection was refused.
> -	 *    ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES:
> -	 *	Inform user that connection was reset, and close tcb.
> -	 *    CLOSING, LAST_ACK, TIME_WAIT STATES
> -	 *	Close the tcb.
> -	 */
> -	if (tiflags&TH_RST) switch (tp->t_state) {
> -
> -	case TCPS_SYN_RECEIVED:
> -		so->so_error = ECONNREFUSED;
> -		goto close;
> -
> -	case TCPS_ESTABLISHED:
> -	case TCPS_FIN_WAIT_1:
> -	case TCPS_FIN_WAIT_2:
> -	case TCPS_CLOSE_WAIT:
> -		so->so_error = ECONNRESET;
> -	close:
> -		tp->t_state = TCPS_CLOSED;
> -		tcpstat.tcps_drops++;
> -		tp = tcp_close(tp);
> -		goto drop;
> -
> -	case TCPS_CLOSING:
> -	case TCPS_LAST_ACK:
> -	case TCPS_TIME_WAIT:
> -		tp = tcp_close(tp);
> -		goto drop;
> -	}
> -
> -	/*
>  	 * If a SYN is in the window, then this is an
>  	 * error and we send an RST and drop the connection.
>  	 */
> @@ -1660,9 +1722,22 @@
>  	/*
>  	 * Generate an ACK dropping incoming segment if it occupies
>  	 * sequence space, where the ACK reflects our state.
> -	 */
> -	if (tiflags & TH_RST)
> -		goto drop;
> +	 *
> +	 * We can now skip the test for the RST flag since all
> +	 * paths to this code happen after packets containing
> +	 * RST have been dropped.
> +	 *
> +	 * In the SYN-RECEIVED state, don't send an ACK unless the
> +	 * segment we received passes the SYN-RECEIVED ACK test.
> +	 * If it fails send a RST.  This breaks the loop in the
> +	 * "LAND" DoS attack, and also prevents an ACK storm
> +	 * between two listening ports that have been sent forged
> +	 * SYN segments, each with the source address of the other.
> +	 */
> +	if (tp->t_state == TCPS_SYN_RECEIVED && (tiflags & TH_ACK) &&
> +	    (SEQ_GT(tp->snd_una, ti->ti_ack) ||
> +	     SEQ_GT(ti->ti_ack, tp->snd_max)) )
> +		goto dropwithreset;
>  #ifdef TCPDEBUG
>  	if (so->so_options & SO_DEBUG)
>  		tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0);
> -----------------  Cut Here --------------------------
> 
> To Unsubscribe: send mail to majordomo@FreeBSD.org
> with "unsubscribe freebsd-security" in the body of the message
> 

___________________________________________________________________________
Archie Cobbs   *   Whistle Communications, Inc.  *   http://www.whistle.com

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199808311800.LAA06751>