From missnglnk@sneakerz.org  Fri Sep  8 07:45:41 2000
Return-Path: <missnglnk@sneakerz.org>
Received: from sneakerz.org (sneakerz.org [207.154.226.254])
	by hub.freebsd.org (Postfix) with ESMTP id EBF2137B42C
	for <FreeBSD-gnats-submit@freebsd.org>; Fri,  8 Sep 2000 07:45:40 -0700 (PDT)
Received: by sneakerz.org (Postfix, from userid 1023)
	id 988115D006; Fri,  8 Sep 2000 09:45:35 -0500 (CDT)
Message-Id: <20000908144535.988115D006@sneakerz.org>
Date: Fri,  8 Sep 2000 09:45:35 -0500 (CDT)
From: missnglnk@sneakerz.org
Reply-To: missnglnk@sneakerz.org
To: FreeBSD-gnats-submit@freebsd.org
Subject: Multiple problems in ipfw's stateful code 
X-Send-Pr-Version: 3.2

>Number:         21118
>Category:       kern
>Synopsis:       Multiple problems in ipfw's stateful code
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    luigi
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Fri Sep 08 07:50:00 PDT 2000
>Closed-Date:    Mon Sep 3 13:09:13 PDT 2001
>Last-Modified:  Mon Sep 03 13:23:08 PDT 2001
>Originator:     Lawrence N. King
>Release:        FreeBSD 4.1-FEARSOME-20000818 i386
>Organization:
>Environment:
	FreeBSD 4.0-RELEASE
	FreeBSD 4.1-STABLE
	FreeBSD 5.0-CURRENT
	Above version with IPFIREWALL defined in kernel

>Description:

	When a connection is made and kept track of (keep-state):
	1) Connections expire prematurely if dynamic ACK lifetime
	   is is greater than the keepalive interval.

	2) Expired connections aren't actually expired, but are
	   allowed to continue (TCP connections are extended by
	   the dynamic RST lifetime even though they have expired,
	   and all other protocol connections are extended by
	   the dynamic short lifetime).

	3) Expired connections aren't cleaned from the state table
	   unless the next dynamic rule added reaches the maximum
	   amount of dynamic rules.

	4) A new state is installed for any packet which matches
	   the rule (with keep-state), regardless of whether its
	   not a TCP packet with the SYN flag set, or if its an
	   ICMP query packet.

>How-To-Repeat:

	ipfw -f flush
	ipfw add check-state
	ipfw add allow ip from any to any via fxp0 out keep-state
	ipfw add deny ip from any to any

	Now issue a couple of connections, I did SSH, a new state
	was installed, the rule expired, but I still could continue
	my SSH connection since its expiration time was being extended
	by the dynamic RST lifetime.

	Instead of issuing a TCP packet with the SYN flag set, issue
	a TCP packet with any other flag set, and a new state is
	installed for this packet.

	Let a couple of dynamic rules timeout (you can check via ipfw
	show), and watch as they linger around, they aren't cleaned
	out until the amount of dynamic rules is equal to the maximum
	and a new rule is being installed.

>Fix:

	Nothing for problem 1, but solutions for 2-4 are in the below
	patch:

-- snip --
--- sys/netinet/ip_fw.c.orig	Wed Sep  6 21:01:07 2000
+++ sys/netinet/ip_fw.c	Wed Sep  6 21:40:55 2000
@@ -735,4 +735,3 @@
 	    break ;
-	default:
-#if 0
+	case TH_RST | (TH_RST << 8) :
 	    /*
@@ -741,7 +740,18 @@
 	     */
-	    if ( (q->state & ((TH_RST << 8)|TH_RST)) == 0)
-		printf("invalid state: 0x%x\n", q->state);
-#endif
+	    printf("invalid state: 0x%x\n", q->state);
 	    q->expire = time_second + dyn_rst_lifetime ;
 	    break ;
+	default:
+	    /*
+             * A TCP packet found in unknown state, drop it.
+	     */
+	    DEB(printf("packet should be dropped (state: 0x%x)\n", q->state));
+            old_q = q ;
+            if (prev != NULL)
+                prev->next = q = q->next ;
+            else
+                ipfw_dyn_v[i] = q = q->next ;
+            dyn_count-- ;
+            free(old_q, M_IPFW);
+	    break ;
 	}
@@ -838,4 +848,7 @@
     }
-    if (dyn_count >= dyn_max) /* try remove old ones... */
-	remove_dyn_rule(NULL, 0 /* expire */);
+    /*
+     * Unconditionally remove expired states.
+     */
+    remove_dyn_rule(NULL, 0 /* expire */);
+
     if (dyn_count >= dyn_max) {
@@ -1277,4 +1290,43 @@
 		 */
-		if (q == NULL && f->fw_flg & IP_FW_F_KEEP_S)
-		    install_state(chain);
+		if (q == NULL && f->fw_flg & IP_FW_F_KEEP_S) {
+		    /*
+		     * Instead of unconditionally adding a new state,
+		     * check the protocol and flags, and add a new state
+		     * or ignore packet.
+		     */
+		    switch(proto) {
+		        case IPPROTO_TCP:
+		            if (flags & TH_SYN) {
+		                DEB(printf("-- installing state for TCP packet\n"));
+		                install_state(chain);
+		            } else {
+		                DEB(printf("-- invalid TCP connection state\n"));
+		            }
+                            break;
+		        case IPPROTO_UDP:
+		            DEB(printf("-- installing state for UDP packet\n"));
+		            install_state(chain);
+                            break;
+		        case IPPROTO_ICMP:
+		            if (is_icmp_query(ip)) {
+		                DEB(printf("-- installing state for ICMP packet\n"));
+		                install_state(chain);
+		            } else {
+		                DEB(printf("-- invalid ICMP connection state\n"));
+		            }
+                            break;
+		        default:
+		            /*
+		             * Unknown packet, if default is to accept all
+		             * packets, add a new state, otherwise ignore.
+			     */
+#ifdef IPFIREWALL_DEFAULT_TO_ACCEPT
+		            DEB(printf("-- installing state for unknown packet\n"));
+		            install_state(chain);
+#else
+		            DEB(printf("invalid unknown protocol connection state\n"));
+#endif
+                            break;
+                    }
+		}
 #endif
-- snip --

>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: freebsd-bugs->luigi 
Responsible-Changed-By: johan 
Responsible-Changed-When: Sat Sep 16 05:10:52 PDT 2000 
Responsible-Changed-Why:  
Over to ipfw maintainer. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=21118 
State-Changed-From-To: open->closed 
State-Changed-By: luigi 
State-Changed-When: Mon Sep 3 13:09:13 PDT 2001 
State-Changed-Why:  
The PR addresses 4 issues: 
#1 This is the way the code currently works. There is no way out other 
than setting the timeout larger than the keepalive interval, or 
having ipfw do its own keepalives. The latter is the right way 
to handle this problem but it requires relatively large changes. 

#2 and #4 This is correct behaviour for the rules used in the PR. If 
you want connections to expire (more correctly, if you want 
dynamic rules to be created only on SYN packets) you should use 
the "setup" option in the rule e.g. 

ipfw add allow ip from any to any setup keep-state 
ipfw add deny ip from any to any. 

The "problem" is entirely caused by the use of wrong rules. 

#3 Again this is a design decision to reduce the overhead of 
garbage collection. 
Expired rules are removed only when you run out of space, 
or you happen to hit one of them during a lookup. 
There is no reason to do it otherwise. 

So, i am inclined to close this PR -- none of the above is a problem 
in the ipfw code. 


http://www.FreeBSD.org/cgi/query-pr.cgi?pr=21118 
>Unformatted:
