Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 7 Feb 2010 01:11:37 GMT
From:      Gleb Kurtsou <gleb.kurtsou@gmail.com>
To:        freebsd-gnats-submit@FreeBSD.org
Subject:   kern/143622: [patch] unlock pfil lock while calling firewall hooks
Message-ID:  <201002070111.o171BbEm048024@www.freebsd.org>
Resent-Message-ID: <201002070120.o171K3rG096453@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         143622
>Category:       kern
>Synopsis:       [patch] unlock pfil lock while calling firewall hooks
>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:   Sun Feb 07 01:20:03 UTC 2010
>Closed-Date:
>Last-Modified:
>Originator:     Gleb Kurtsou
>Release:        
>Organization:
>Environment:
FreeBSD sandbox9.vbox 9.0-CURRENT FreeBSD 9.0-CURRENT #2 r201894+71985df: Sun Feb  7 02:39:39 UTC 2010     root@sandbox9.vbox:/usr/obj/usr/src/sys/SANDBOX  amd64
>Description:
Unlock pfil lock while running hooks.

All firewalls in the tree implement locking on there own not expecting to be called with non-sleepable lock held. Besides holding a lock while calling other subsystems is error prone, forbids firewall reentrants, etc.

To grantee consistency add per hook reference count and lazily remove hooks from list.

>How-To-Repeat:

>Fix:


Patch attached with submission follows:

diff --git a/sys/net/pfil.c b/sys/net/pfil.c
index a11950f..36196dc 100644
--- a/sys/net/pfil.c
+++ b/sys/net/pfil.c
@@ -34,6 +34,7 @@
 #include <sys/errno.h>
 #include <sys/lock.h>
 #include <sys/malloc.h>
+#include <sys/refcount.h>
 #include <sys/rmlock.h>
 #include <sys/socket.h>
 #include <sys/socketvar.h>
@@ -56,7 +57,7 @@ static int pfil_list_add(pfil_list_t *, struct packet_filter_hook *, int);
 
 static int pfil_list_remove(pfil_list_t *,
     int (*)(void *, struct mbuf **, struct ifnet *, int, struct inpcb *),
-    void *);
+    void *, struct pfil_head *);
 
 LIST_HEAD(pfilheadhead, pfil_head);
 VNET_DEFINE(struct pfilheadhead, pfil_head_list);
@@ -76,16 +77,24 @@ pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp,
 
 	PFIL_RLOCK(ph, &rmpt);
 	KASSERT(ph->ph_nhooks >= 0, ("Pfil hook count dropped < 0"));
+	refcount_acquire(&ph->ph_refcnt);
 	for (pfh = pfil_hook_get(dir, ph); pfh != NULL;
 	     pfh = TAILQ_NEXT(pfh, pfil_link)) {
-		if (pfh->pfil_func != NULL) {
+		if (pfh->pfil_func != NULL &&
+		    (pfh->pfil_flags & PFIL_HOOK_DOOMED) == 0 ) {
+			PFIL_RUNLOCK(ph, &rmpt);
 			rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir,
 			    inp);
-			if (rv != 0 || m == NULL)
-				break;
+			if (rv != 0 || m == NULL) {
+				refcount_release(&ph->ph_refcnt);
+				goto out;
+			}
+			PFIL_RLOCK(ph, &rmpt);
 		}
 	}
+	refcount_release(&ph->ph_refcnt);
 	PFIL_RUNLOCK(ph, &rmpt);
+out:
 	*mp = m;
 	return (rv);
 }
@@ -107,6 +116,7 @@ pfil_head_register(struct pfil_head *ph)
 			return (EEXIST);
 		}
 	}
+	refcount_init(&ph->ph_refcnt, 0);
 	PFIL_LOCK_INIT(ph);
 	ph->ph_nhooks = 0;
 	TAILQ_INIT(&ph->ph_in);
@@ -186,9 +196,15 @@ pfil_add_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int,
 		}
 	}
 	PFIL_WLOCK(ph);
+	if (ph->ph_refcnt == 0) {
+		/* Remove doomed hooks */
+		pfil_list_remove(&ph->ph_in, NULL, NULL, ph);
+		pfil_list_remove(&ph->ph_out, NULL, NULL, ph);
+	}
 	if (flags & PFIL_IN) {
 		pfh1->pfil_func = func;
 		pfh1->pfil_arg = arg;
+		pfh1->pfil_flags = 0;
 		err = pfil_list_add(&ph->ph_in, pfh1, flags & ~PFIL_OUT);
 		if (err)
 			goto locked_error;
@@ -197,10 +213,11 @@ pfil_add_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int,
 	if (flags & PFIL_OUT) {
 		pfh2->pfil_func = func;
 		pfh2->pfil_arg = arg;
+		pfh2->pfil_flags = 0;
 		err = pfil_list_add(&ph->ph_out, pfh2, flags & ~PFIL_IN);
 		if (err) {
 			if (flags & PFIL_IN)
-				pfil_list_remove(&ph->ph_in, func, arg);
+				pfil_list_remove(&ph->ph_in, func, arg, ph);
 			goto locked_error;
 		}
 		ph->ph_nhooks++;
@@ -229,12 +246,12 @@ pfil_remove_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int,
 
 	PFIL_WLOCK(ph);
 	if (flags & PFIL_IN) {
-		err = pfil_list_remove(&ph->ph_in, func, arg);
+		err = pfil_list_remove(&ph->ph_in, func, arg, ph);
 		if (err == 0)
 			ph->ph_nhooks--;
 	}
 	if ((err == 0) && (flags & PFIL_OUT)) {
-		err = pfil_list_remove(&ph->ph_out, func, arg);
+		err = pfil_list_remove(&ph->ph_out, func, arg, ph);
 		if (err == 0)
 			ph->ph_nhooks--;
 	}
@@ -250,11 +267,13 @@ pfil_list_add(pfil_list_t *list, struct packet_filter_hook *pfh1, int flags)
 	/*
 	 * First make sure the hook is not already there.
 	 */
-	TAILQ_FOREACH(pfh, list, pfil_link)
+	TAILQ_FOREACH(pfh, list, pfil_link) {
 		if (pfh->pfil_func == pfh1->pfil_func &&
-		    pfh->pfil_arg == pfh1->pfil_arg)
+		    pfh->pfil_arg == pfh1->pfil_arg &&
+		    (pfh->pfil_flags & PFIL_HOOK_DOOMED) == 0) {
 			return (EEXIST);
-
+		}
+	}
 	/*
 	 * Insert the input list in reverse order of the output list so that
 	 * the same path is followed in or out of the kernel.
@@ -273,14 +292,19 @@ pfil_list_add(pfil_list_t *list, struct packet_filter_hook *pfh1, int flags)
 static int
 pfil_list_remove(pfil_list_t *list,
     int (*func)(void *, struct mbuf **, struct ifnet *, int, struct inpcb *),
-    void *arg)
+    void *arg, struct pfil_head *ph)
 {
 	struct packet_filter_hook *pfh;
 
 	TAILQ_FOREACH(pfh, list, pfil_link)
-		if (pfh->pfil_func == func && pfh->pfil_arg == arg) {
-			TAILQ_REMOVE(list, pfh, pfil_link);
-			free(pfh, M_IFADDR);
+		if ((pfh->pfil_func == func && pfh->pfil_arg == arg) ||
+		    (pfh->pfil_flags & PFIL_HOOK_DOOMED) != 0) {
+			if (ph->ph_refcnt == 0) {
+				TAILQ_REMOVE(list, pfh, pfil_link);
+				free(pfh, M_IFADDR);
+			} else {
+				pfh->pfil_flags |= PFIL_HOOK_DOOMED;
+			}
 			return (0);
 		}
 	return (ENOENT);
diff --git a/sys/net/pfil.h b/sys/net/pfil.h
index 6ac750a..56d44d9 100644
--- a/sys/net/pfil.h
+++ b/sys/net/pfil.h
@@ -47,11 +47,15 @@ struct inpcb;
  * The packet filter hooks are designed for anything to call them to
  * possibly intercept the packet.
  */
+
+#define PFIL_HOOK_DOOMED	0x00000001
+
 struct packet_filter_hook {
         TAILQ_ENTRY(packet_filter_hook) pfil_link;
 	int	(*pfil_func)(void *, struct mbuf **, struct ifnet *, int,
 		    struct inpcb *);
 	void	*pfil_arg;
+	int	pfil_flags;
 };
 
 #define PFIL_IN		0x00000001
@@ -69,6 +73,7 @@ struct pfil_head {
 	pfil_list_t	ph_out;
 	int		ph_type;
 	int		ph_nhooks;
+	volatile u_int	ph_refcnt;
 	struct rmlock	ph_lock;
 	union {
 		u_long		phu_val;


>Release-Note:
>Audit-Trail:
>Unformatted:



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