Module Name:    src
Committed By:   bouyer
Date:           Sun Apr 23 21:05:09 UTC 2017

Modified Files:
        src/sys/netcan [bouyer-socketcan]: can.c can_pcb.c can_pcb.h

Log Message:
Add locking and refcounting to canpcb.
Store the canpcb in the in the mbuf tag on send instead of the socket's address.
This should protect against a race where the socket cloud be closed before
we get back the mbuf from the adapter's driver.


To generate a diff of this commit:
cvs rdiff -u -r1.1.2.12 -r1.1.2.13 src/sys/netcan/can.c
cvs rdiff -u -r1.1.2.3 -r1.1.2.4 src/sys/netcan/can_pcb.c \
    src/sys/netcan/can_pcb.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/netcan/can.c
diff -u src/sys/netcan/can.c:1.1.2.12 src/sys/netcan/can.c:1.1.2.13
--- src/sys/netcan/can.c:1.1.2.12	Thu Apr 20 17:29:10 2017
+++ src/sys/netcan/can.c	Sun Apr 23 21:05:09 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: can.c,v 1.1.2.12 2017/04/20 17:29:10 bouyer Exp $	*/
+/*	$NetBSD: can.c,v 1.1.2.13 2017/04/23 21:05:09 bouyer Exp $	*/
 
 /*-
  * Copyright (c) 2003, 2017 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: can.c,v 1.1.2.12 2017/04/20 17:29:10 bouyer Exp $");
+__KERNEL_RCSID(0, "$NetBSD: can.c,v 1.1.2.13 2017/04/23 21:05:09 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -235,7 +235,10 @@ can_output(struct mbuf *m, struct canpcb
 		ifp->if_oerrors++;
 		return ENOMEM;
 	}
-	*(struct socket **)(sotag + 1) = canp->canp_socket;
+	mutex_enter(&canp->canp_mtx);
+	canp_ref(canp);
+	mutex_exit(&canp->canp_mtx);
+	*(struct canpcb **)(sotag + 1) = canp;
 	m_tag_prepend(m, sotag);
 
 	if (m->m_len <= ifp->if_mtu) {
@@ -302,7 +305,6 @@ canintr(void)
 	struct sockaddr_can from;
 	struct canpcb   *canp;
 	struct m_tag	*sotag;
-	struct socket	*so;
 	struct canpcb	*sender_canp;
 
 	mutex_enter(softnet_lock);
@@ -319,13 +321,13 @@ canintr(void)
 #endif
 		sotag = m_tag_find(m, PACKET_TAG_SO, NULL);
 		if (sotag) {
-			so = *(struct socket **)(sotag + 1);
-			sender_canp = sotocanpcb(so);
+			sender_canp = *(struct canpcb **)(sotag + 1);
 			m_tag_delete(m, sotag);
+			KASSERT(sender_canp != NULL);
 			/* if the sender doesn't want loopback, don't do it */
-			if (sender_canp &&
-			    (sender_canp->canp_flags & CANP_NO_LOOPBACK) != 0) {
+			if ((sender_canp->canp_flags & CANP_NO_LOOPBACK) != 0) {
 				m_freem(m);
+				canp_unref(sender_canp);
 				continue;
 			}
 		} else {
@@ -340,19 +342,31 @@ canintr(void)
 		TAILQ_FOREACH(canp, &cbtable.canpt_queue, canp_queue) {
 			struct mbuf *mc;
 
+			mutex_enter(&canp->canp_mtx);
+			/* skip if we're detached */
+			if (canp->canp_state == CANP_DETACHED) {
+				mutex_exit(&canp->canp_mtx);
+				continue;
+			}
+
 			/* don't loop back to sockets on other interfaces */
 			if (canp->canp_ifp != NULL &&
 			    canp->canp_ifp->if_index != rcv_ifindex) {
+				mutex_exit(&canp->canp_mtx);
 				continue;
 			}
 			/* don't loop back to myself if I don't want it */
 			if (canp == sender_canp &&
-			    (canp->canp_flags & CANP_RECEIVE_OWN) == 0)
+			    (canp->canp_flags & CANP_RECEIVE_OWN) == 0) {
+				mutex_exit(&canp->canp_mtx);
 				continue;
+			}
 
 			/* skip if the accept filter doen't match this pkt */
-			if (!can_pcbfilter(canp, m))
+			if (!can_pcbfilter(canp, m)) {
+				mutex_exit(&canp->canp_mtx);
 				continue;
+			}
 
 			if (TAILQ_NEXT(canp, canp_queue) != NULL) {
 				/*
@@ -375,9 +389,13 @@ canintr(void)
 				m_freem(mc);
 			} else
 				sorwakeup(canp->canp_socket);
+			mutex_exit(&canp->canp_mtx);
 			if (m == NULL)
 				break;
 		}
+		if (sender_canp) {
+			canp_unref(sender_canp);
+		}
 		/* If it didn't go anywhere just delete it */
 		if (m) {
 			m_freem(m);
@@ -492,7 +510,6 @@ can_disconnect(struct socket *so)
 	/*soisdisconnected(so);*/
 	so->so_state &= ~SS_ISCONNECTED;	/* XXX */
 	can_pcbdisconnect(canp);
-	can_pcbstate(canp, CANP_BOUND);		/* XXX */
 	return 0;
 }
 
@@ -877,7 +894,9 @@ can_raw_setop(struct canpcb *canp, struc
 		int nfilters = sopt->sopt_size / sizeof(struct can_filter);
 		if (sopt->sopt_size % sizeof(struct can_filter) != 0)
 			return EINVAL;
+		mutex_enter(&canp->canp_mtx);
 		error = can_pcbsetfilter(canp, sopt->sopt_data, nfilters);
+		mutex_exit(&canp->canp_mtx);
 		break;
 		}
 	default:

Index: src/sys/netcan/can_pcb.c
diff -u src/sys/netcan/can_pcb.c:1.1.2.3 src/sys/netcan/can_pcb.c:1.1.2.4
--- src/sys/netcan/can_pcb.c:1.1.2.3	Sun Feb  5 19:44:53 2017
+++ src/sys/netcan/can_pcb.c	Sun Apr 23 21:05:09 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: can_pcb.c,v 1.1.2.3 2017/02/05 19:44:53 bouyer Exp $	*/
+/*	$NetBSD: can_pcb.c,v 1.1.2.4 2017/04/23 21:05:09 bouyer Exp $	*/
 
 /*-
  * Copyright (c) 2003, 2017 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: can_pcb.c,v 1.1.2.3 2017/02/05 19:44:53 bouyer Exp $");
+__KERNEL_RCSID(0, "$NetBSD: can_pcb.c,v 1.1.2.4 2017/04/23 21:05:09 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -106,12 +106,14 @@ can_pcballoc(struct socket *so, void *v)
 	canp->canp_socket = so;
 	canp->canp_filters = can_init_filter;
 	canp->canp_nfilters = 1;
+	mutex_init(&canp->canp_mtx, MUTEX_DEFAULT, IPL_NET);
+	canp->canp_refcount = 1;
 
 	so->so_pcb = canp;
-	s = splnet();
+	mutex_enter(&canp->canp_mtx);
 	TAILQ_INSERT_HEAD(&table->canpt_queue, canp, canp_queue);
 	can_pcbstate(canp, CANP_ATTACHED);
-	splx(s);
+	mutex_exit(&canp->canp_mtx);
 	return (0);
 }
 
@@ -122,6 +124,7 @@ can_pcbbind(void *v, struct sockaddr_can
 
 	if (scan->can_family != AF_CAN)
 		return (EAFNOSUPPORT);
+	mutex_enter(&canp->canp_mtx);
 	if (scan->can_ifindex != 0) {
 		canp->canp_ifp = if_byindex(scan->can_ifindex);
 		if (canp->canp_ifp == NULL)
@@ -132,6 +135,7 @@ can_pcbbind(void *v, struct sockaddr_can
 		canp->canp_socket->so_state &= ~SS_ISCONNECTED;	/* XXX */
 	}
 	can_pcbstate(canp, CANP_BOUND);
+	mutex_exit(&canp->canp_mtx);
 	return 0;
 }
 
@@ -150,8 +154,10 @@ can_pcbconnect(void *v, struct sockaddr_
 	if (scan->can_family != AF_CAN)
 		return (EAFNOSUPPORT);
 #if 0
+	mutex_enter(&canp->canp_mtx);
 	memcpy(&canp->canp_dst, scan, sizeof(struct sockaddr_can));
 	can_pcbstate(canp, CANP_CONNECTED);
+	mutex_exit(&canp->canp_mtx);
 	return 0;
 #endif
 	return EOPNOTSUPP;
@@ -162,7 +168,9 @@ can_pcbdisconnect(void *v)
 {
 	struct canpcb *canp = v;
 
-	can_pcbstate(canp, CANP_BOUND);
+	mutex_enter(&canp->canp_mtx);
+	can_pcbstate(canp, CANP_DETACHED);
+	mutex_exit(&canp->canp_mtx);
 	if (canp->canp_socket->so_state & SS_NOFDREF)
 		can_pcbdetach(canp);
 }
@@ -172,28 +180,51 @@ can_pcbdetach(void *v)
 {
 	struct canpcb *canp = v;
 	struct socket *so = canp->canp_socket;
-	int s;
 
 	KASSERT(mutex_owned(softnet_lock));
 	so->so_pcb = NULL;
-	s =  splnet();
-	can_pcbstate(canp, CANP_ATTACHED);
-	TAILQ_REMOVE(&canp->canp_table->canpt_queue, canp, canp_queue);
-	splx(s);
-	sofree(so); /* sofree drops the lock */
+	mutex_enter(&canp->canp_mtx);
+	can_pcbstate(canp, CANP_DETACHED);
 	can_pcbsetfilter(canp, NULL, 0);
-	pool_put(&canpcb_pool, canp);
+	mutex_exit(&canp->canp_mtx);
+	TAILQ_REMOVE(&canp->canp_table->canpt_queue, canp, canp_queue);
+	sofree(so); /* sofree drops the softnet_lock */
+	canp_unref(canp);
 	mutex_enter(softnet_lock);
 }
 
 void
+canp_ref(struct canpcb *canp)
+{
+	KASSERT(mutex_owned(&canp->canp_mtx));
+	canp->canp_refcount++;
+}
+
+void
+canp_unref(struct canpcb *canp)
+{
+	mutex_enter(&canp->canp_mtx);
+	canp->canp_refcount--;
+	KASSERT(canp->canp_refcount >= 0);
+	if (canp->canp_refcount > 0) {
+		mutex_exit(&canp->canp_mtx);
+		return;
+	}
+	mutex_exit(&canp->canp_mtx);
+	mutex_destroy(&canp->canp_mtx);
+	pool_put(&canpcb_pool, canp);
+}
+
+void
 can_setsockaddr(struct canpcb *canp, struct sockaddr_can *scan)
 {
 
+	mutex_enter(&canp->canp_mtx);
 	memset(scan, 0, sizeof (*scan));
 	scan->can_family = AF_CAN;
 	scan->can_len = sizeof(*scan);
 	scan->can_ifindex = canp->canp_ifp->if_index;
+	mutex_exit(&canp->canp_mtx);
 }
 
 int
@@ -201,6 +232,7 @@ can_pcbsetfilter(struct canpcb *canp, st
 {
 
 	struct can_filter *newf;
+	KASSERT(mutex_owned(&canp->canp_mtx));
 
 	if (nfilters > 0) {
 		newf =
@@ -303,6 +335,7 @@ void
 can_pcbstate(struct canpcb *canp, int state)
 {
 	int ifindex = canp->canp_ifp ? canp->canp_ifp->if_index : 0;
+	KASSERT(mutex_owned(&canp->canp_mtx));
 
 	if (canp->canp_state > CANP_ATTACHED)
 		LIST_REMOVE(canp, canp_hash);
@@ -332,6 +365,7 @@ can_pcbfilter(struct canpcb *canp, struc
 	struct can_frame *fmp;
 	struct can_filter *fip;
 
+	KASSERT(mutex_owned(&canp->canp_mtx));
 	KASSERT((m->m_flags & M_PKTHDR) != 0);
 	KASSERT(m->m_len == m->m_pkthdr.len);
 
Index: src/sys/netcan/can_pcb.h
diff -u src/sys/netcan/can_pcb.h:1.1.2.3 src/sys/netcan/can_pcb.h:1.1.2.4
--- src/sys/netcan/can_pcb.h:1.1.2.3	Sun Feb  5 10:56:12 2017
+++ src/sys/netcan/can_pcb.h	Sun Apr 23 21:05:09 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: can_pcb.h,v 1.1.2.3 2017/02/05 10:56:12 bouyer Exp $	*/
+/*	$NetBSD: can_pcb.h,v 1.1.2.4 2017/04/23 21:05:09 bouyer Exp $	*/
 
 /*-
  * Copyright (c) 2003, 2017 The NetBSD Foundation, Inc.
@@ -48,6 +48,7 @@ struct canpcb {
 	LIST_ENTRY(canpcb) canp_hash;
 	LIST_ENTRY(canpcb) canp_lhash;
 	TAILQ_ENTRY(canpcb) canp_queue;
+	kmutex_t 	canp_mtx;	/* protect states and refcount */
 	int		canp_state;
 	int		canp_flags;
 	struct		socket *canp_socket;	/* back pointer to socket */
@@ -56,6 +57,8 @@ struct canpcb {
 	struct		canpcbtable *canp_table;
 	struct		can_filter *canp_filters; /* filter array */
 	int		canp_nfilters; /* size of canp_filters */
+
+	int		canp_refcount;
 };
 
 LIST_HEAD(canpcbhead, canpcb);
@@ -71,9 +74,10 @@ struct canpcbtable {
 };
 
 /* states in canp_state: */
-#define	CANP_ATTACHED		0
-#define	CANP_BOUND		1
-#define	CANP_CONNECTED		2
+#define	CANP_DETACHED		0
+#define	CANP_ATTACHED		1
+#define	CANP_BOUND		2
+#define	CANP_CONNECTED		3
 
 /* flags in canp_flags: */
 #define CANP_NO_LOOPBACK	0x0001 /* local loopback disabled */
@@ -100,6 +104,10 @@ void	can_pcbstate(struct canpcb *, int);
 void	can_setsockaddr(struct canpcb *, struct sockaddr_can *);
 int	can_pcbsetfilter(struct canpcb *, struct can_filter *, int);
 bool	can_pcbfilter(struct canpcb *, struct mbuf *);
+
+/* refcount management */
+void	canp_ref(struct canpcb *);
+void	canp_unref(struct canpcb *);
 #endif
 
 #endif /* _NETCAN_CAN_PCB_H_ */

Reply via email to