Module Name:    src
Committed By:   ozaki-r
Date:           Wed Aug  9 04:29:36 UTC 2017

Modified Files:
        src/sys/netipsec: key.c

Log Message:
Fix deadlock between key_sendup_mbuf called from key_acquire and 
localcount_drain

If we call key_sendup_mbuf from key_acquire that is called on packet
processing, a deadlock can happen like this:
- At key_acquire, a reference to an SP (and an SA) is held
- key_sendup_mbuf will try to take key_so_mtx
- Some other thread may try to localcount_drain to the SP with
  holding key_so_mtx in say key_api_spdflush
- In this case localcount_drain never return because key_sendup_mbuf
  that has stuck on key_so_mtx never release a reference to the SP

Fix the deadlock by deferring key_sendup_mbuf to the timer
(key_timehandler).


To generate a diff of this commit:
cvs rdiff -u -r1.219 -r1.220 src/sys/netipsec/key.c

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

Modified files:

Index: src/sys/netipsec/key.c
diff -u src/sys/netipsec/key.c:1.219 src/sys/netipsec/key.c:1.220
--- src/sys/netipsec/key.c:1.219	Wed Aug  9 03:41:11 2017
+++ src/sys/netipsec/key.c	Wed Aug  9 04:29:36 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: key.c,v 1.219 2017/08/09 03:41:11 ozaki-r Exp $	*/
+/*	$NetBSD: key.c,v 1.220 2017/08/09 04:29:36 ozaki-r Exp $	*/
 /*	$FreeBSD: src/sys/netipsec/key.c,v 1.3.2.3 2004/02/14 22:23:23 bms Exp $	*/
 /*	$KAME: key.c,v 1.191 2001/06/27 10:46:49 sakane Exp $	*/
 
@@ -32,7 +32,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: key.c,v 1.219 2017/08/09 03:41:11 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: key.c,v 1.220 2017/08/09 04:29:36 ozaki-r Exp $");
 
 /*
  * This code is referd to RFC 2367
@@ -735,6 +735,8 @@ static struct mbuf *key_getcomb_ipcomp (
 static struct mbuf *key_getprop (const struct secasindex *);
 
 static int key_acquire (const struct secasindex *, struct secpolicy *);
+static void key_acquire_sendup_mbuf_later(struct mbuf *);
+static void key_acquire_sendup_pending_mbuf(void);
 #ifndef IPSEC_NONBLOCK_ACQUIRE
 static struct secacq *key_newacq (const struct secasindex *);
 static struct secacq *key_getacq (const struct secasindex *);
@@ -4867,6 +4869,8 @@ key_timehandler_spacq(time_t now)
 #endif
 }
 
+static unsigned int key_timehandler_work_enqueued = 0;
+
 /*
  * time handler.
  * scanning SPD and SAD to check status for each entries,
@@ -4878,6 +4882,9 @@ key_timehandler_work(struct work *wk, vo
 	time_t now = time_uptime;
 	IPSEC_DECLARE_LOCK_VARIABLE;
 
+	/* We can allow enqueuing another work at this point */
+	atomic_swap_uint(&key_timehandler_work_enqueued, 0);
+
 	IPSEC_ACQUIRE_GLOBAL_LOCKS();
 
 	key_timehandler_spd(now);
@@ -4885,6 +4892,8 @@ key_timehandler_work(struct work *wk, vo
 	key_timehandler_acq(now);
 	key_timehandler_spacq(now);
 
+	key_acquire_sendup_pending_mbuf();
+
 	/* do exchange to tick time !! */
 	callout_reset(&key_timehandler_ch, hz, key_timehandler, NULL);
 
@@ -4896,6 +4905,10 @@ static void
 key_timehandler(void *arg)
 {
 
+	/* Avoid enqueuing another work when one is already enqueued */
+	if (atomic_swap_uint(&key_timehandler_work_enqueued, 1) == 1)
+		return;
+
 	workqueue_enqueue(key_timehandler_wq, &key_timehandler_wk, NULL);
 }
 
@@ -6631,7 +6644,20 @@ key_acquire(const struct secasindex *sai
 	mtod(result, struct sadb_msg *)->sadb_msg_len =
 	    PFKEY_UNIT64(result->m_pkthdr.len);
 
-	return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED);
+	/*
+	 * XXX we cannot call key_sendup_mbuf directly here because
+	 * it can cause a deadlock:
+	 * - We have a reference to an SP (and an SA) here
+	 * - key_sendup_mbuf will try to take key_so_mtx
+	 * - Some other thread may try to localcount_drain to the SP with
+	 *   holding key_so_mtx in say key_api_spdflush
+	 * - In this case localcount_drain never return because key_sendup_mbuf
+	 *   that has stuck on key_so_mtx never release a reference to the SP
+	 *
+	 * So defer key_sendup_mbuf to the timer.
+	 */
+	key_acquire_sendup_mbuf_later(result);
+	return 0;
 
  fail:
 	if (result)
@@ -6639,6 +6665,57 @@ key_acquire(const struct secasindex *sai
 	return error;
 }
 
+static struct mbuf *key_acquire_mbuf_head = NULL;
+
+static void
+key_acquire_sendup_pending_mbuf(void)
+{
+	struct mbuf *m, *prev = NULL;
+	int error;
+
+again:
+	mutex_enter(&key_misc.lock);
+	m = key_acquire_mbuf_head;
+	/* Get an earliest mbuf (one at the tail of the list) */
+	while (m != NULL) {
+		if (m->m_nextpkt == NULL) {
+			if (prev != NULL)
+				prev->m_nextpkt = NULL;
+			if (m == key_acquire_mbuf_head)
+				key_acquire_mbuf_head = NULL;
+			break;
+		}
+		prev = m;
+		m = m->m_nextpkt;
+	}
+	mutex_exit(&key_misc.lock);
+
+	if (m == NULL)
+		return;
+
+	error = key_sendup_mbuf(NULL, m, KEY_SENDUP_REGISTERED);
+	if (error != 0)
+		IPSECLOG(LOG_WARNING, "key_sendup_mbuf failed (error=%d)\n",
+		    error);
+
+	if (prev != NULL)
+		goto again;
+}
+
+static void
+key_acquire_sendup_mbuf_later(struct mbuf *m)
+{
+
+	mutex_enter(&key_misc.lock);
+	/* Enqueue mbuf at the head of the list */
+	m->m_nextpkt = key_acquire_mbuf_head;
+	key_acquire_mbuf_head = m;
+	mutex_exit(&key_misc.lock);
+
+	/* Kick the timer */
+	key_timehandler(NULL);
+}
+
 #ifndef IPSEC_NONBLOCK_ACQUIRE
 static struct secacq *
 key_newacq(const struct secasindex *saidx)

Reply via email to