>Number:         176763
>Category:       kern
>Synopsis:       Removing pf Source entries locks kernel.
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Fri Mar 08 19:40:01 UTC 2013
>Closed-Date:
>Last-Modified:
>Originator:     Kajetan Staszkiewicz
>Release:        9.1-RELEASE
>Organization:
InnoGames GmbH
>Environment:
FreeBSD xxxxxxx 9.1-RELEASE FreeBSD 9.1-RELEASE #10 r247265M: Mon Feb 25 
14:58:39 CET 2013     root@xxxxxxx:/usr/obj/usr/src/sys/IGLB3  amd64
>Description:
The case will happen on a FreeBSD router with large amount of pf State and 
Source entries. Use pfctl to remove some Sources. For each matching source 
whole State table is searched element by element.

With hundreds of matching Sources (and hundreds of thousands of States to 
search through) this can freeze the kernel for a few seconds. Under some 
conditions (e.g. a DDoS attack hitting the IP address for which the Source 
entries will be removed), kernel will freeze permanently.
>How-To-Repeat:
`pfctl -K`
>Fix:
Create a list of State entries and attach it to Source. This way only this 
short list must be searched when Source is removed. List is maintained during 
State creation and removal.

Patch attached with submission follows:

--- sys/contrib/pf/net/pf.c.orig        2013-03-05 11:27:01.000000000 +0100
+++ sys/contrib/pf/net/pf.c     2013-03-08 14:14:39.000000000 +0100
@@ -1517,6 +1517,19 @@
        u_int32_t timeout;
 
        if (s->src_node != NULL) {
+
+               /* Remove this pf_state from the list of states linked to 
pf_src_node */
+               if (s->prev_state) /* not the first pf_state in list */
+                       s->prev_state->next_state = s->next_state;
+               else /* the fist pf_state in the list, modify list head in 
pf_src_node */
+                       s->src_node->linked_states = s->next_state;
+       
+               if (s->next_state) /* not the last pf_state in list */
+                       s->next_state->prev_state = s->prev_state;
+
+               s->prev_state = NULL;
+               s->next_state = NULL;
+
                if (s->src.tcp_est)
                        --s->src_node->conn;
                if (--s->src_node->states <= 0) {
@@ -1532,6 +1545,19 @@
                }
        }
        if (s->nat_src_node != s->src_node && s->nat_src_node != NULL) {
+
+               /* Remove this pf_state from the list of states linked to 
pf_src_node */
+               if (s->prev_state) /* not the first pf_state in list */
+                       s->prev_state->next_state = s->next_state;
+               else /* the fist pf_state in the list, modify list head in 
pf_src_node */
+                       s->nat_src_node->linked_states = s->next_state;
+       
+               if (s->next_state) /* not the last pf_state in list */
+                       s->next_state->prev_state = s->prev_state;
+
+               s->prev_state = NULL;
+               s->next_state = NULL;
+
                if (--s->nat_src_node->states <= 0) {
                        timeout = s->rule.ptr->timeout[PFTM_SRC_NODE];
                        if (!timeout)
@@ -3895,12 +3921,24 @@
        if (sn != NULL) {
                s->src_node = sn;
                s->src_node->states++;
+
+               /* attach this state to head of list */
+               s->next_state = sn->linked_states;
+               if (s->next_state)
+                       s->next_state->prev_state = s;
+               sn->linked_states = s;
        }
        if (nsn != NULL) {
                /* XXX We only modify one side for now. */
                PF_ACPY(&nsn->raddr, &nk->addr[1], pd->af);
                s->nat_src_node = nsn;
                s->nat_src_node->states++;
+
+               /* attach this state to head of list */
+               s->next_state = nsn->linked_states;
+               if (s->next_state)
+                       s->next_state->prev_state = s;
+               nsn->linked_states = s;
        }
        if (pd->proto == IPPROTO_TCP) {
                if ((pd->flags & PFDESC_TCP_NORM) && pf_normalize_tcp_init(m,
--- sys/contrib/pf/net/pf_ioctl.c.orig  2013-03-05 11:26:44.000000000 +0100
+++ sys/contrib/pf/net/pf_ioctl.c       2013-03-08 14:10:08.000000000 +0100
@@ -3790,6 +3790,7 @@
        case DIOCKILLSRCNODES: {
                struct pf_src_node      *sn;
                struct pf_state         *s;
+               struct pf_state         **pns; /* pointer to next_state of 
previous state */
                struct pfioc_src_node_kill *psnk =
                    (struct pfioc_src_node_kill *)addr;
                u_int                   killed = 0;
@@ -3808,20 +3809,17 @@
                                &psnk->psnk_dst.addr.v.a.mask,
                                &sn->raddr, sn->af)) {
                                /* Handle state to src_node linkage */
-                               if (sn->states != 0) {
-                                       RB_FOREACH(s, pf_state_tree_id,
-#ifdef __FreeBSD__
-                                           &V_tree_id) {
-#else
-                                           &tree_id) {
-#endif
-                                               if (s->src_node == sn)
-                                                       s->src_node = NULL;
-                                               if (s->nat_src_node == sn)
-                                                       s->nat_src_node = NULL;
-                                       }
-                                       sn->states = 0;
+                               s = NULL; /* make gcc happy */
+                               pns = &sn->linked_states;
+                               for (s = sn->linked_states; s != NULL; s = 
s->next_state) {
+                                       s->src_node = NULL;
+                                       s->nat_src_node = NULL;
+                                       *pns = NULL;
+                                       s->prev_state = NULL;
+                                       pns = &s->next_state;
                                }
+                               *pns = NULL;
+                               sn->states = 0;
                                sn->expire = 1;
                                killed++;
                        }
--- sys/contrib/pf/net/pfvar.h.orig     2013-03-05 11:27:14.000000000 +0100
+++ sys/contrib/pf/net/pfvar.h  2013-03-08 11:02:29.000000000 +0100
@@ -748,6 +748,7 @@
        u_int32_t        expire;
        sa_family_t      af;
        u_int8_t         ruletype;
+       struct pf_state  *linked_states;
 };
 
 #define PFSNODE_HIWAT          10000   /* default source node table size */
@@ -852,6 +853,8 @@
        struct pfi_kif          *rt_kif;
        struct pf_src_node      *src_node;
        struct pf_src_node      *nat_src_node;
+       struct pf_state         *prev_state;
+       struct pf_state         *next_state;
        u_int64_t                packets[2];
        u_int64_t                bytes[2];
        u_int32_t                creation;


>Release-Note:
>Audit-Trail:
>Unformatted:
_______________________________________________
[email protected] mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-bugs
To unsubscribe, send any mail to "[email protected]"

Reply via email to