Module Name:    src
Committed By:   ozaki-r
Date:           Thu Feb 15 04:27:24 UTC 2018

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

Log Message:
Fix kernel panic (assertion failure) on receiving an IPv6 packet with large 
options

If an IPv6 packet has large options, a necessary space for evacuation can
exceed the expected size (ah_pool_item_size). Give up using the pool_cache
if it happens.

Pointed out by maxv@


To generate a diff of this commit:
cvs rdiff -u -r1.78 -r1.79 src/sys/netipsec/xform_ah.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/xform_ah.c
diff -u src/sys/netipsec/xform_ah.c:1.78 src/sys/netipsec/xform_ah.c:1.79
--- src/sys/netipsec/xform_ah.c:1.78	Thu Feb 15 04:24:32 2018
+++ src/sys/netipsec/xform_ah.c	Thu Feb 15 04:27:24 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: xform_ah.c,v 1.78 2018/02/15 04:24:32 ozaki-r Exp $	*/
+/*	$NetBSD: xform_ah.c,v 1.79 2018/02/15 04:27:24 ozaki-r Exp $	*/
 /*	$FreeBSD: src/sys/netipsec/xform_ah.c,v 1.1.4.1 2003/01/24 05:11:36 sam Exp $	*/
 /*	$OpenBSD: ip_ah.c,v 1.63 2001/06/26 06:18:58 angelos Exp $ */
 /*
@@ -39,7 +39,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: xform_ah.c,v 1.78 2018/02/15 04:24:32 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: xform_ah.c,v 1.79 2018/02/15 04:27:24 ozaki-r Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_inet.h"
@@ -55,6 +55,7 @@ __KERNEL_RCSID(0, "$NetBSD: xform_ah.c,v
 #include <sys/sysctl.h>
 #include <sys/pool.h>
 #include <sys/pserialize.h>
+#include <sys/kmem.h>
 
 #include <net/if.h>
 
@@ -621,6 +622,7 @@ ah_input(struct mbuf *m, struct secasvar
 	int hl, rplen, authsize, error, stat = AH_STAT_HDROPS;
 	struct cryptodesc *crda;
 	struct cryptop *crp = NULL;
+	bool pool_used;
 
 	IPSEC_SPLASSERT_SOFTNET(__func__);
 
@@ -693,9 +695,14 @@ ah_input(struct mbuf *m, struct secasvar
 	size_t extra = skip + rplen + authsize;
 	size += extra;
 
-	KASSERTMSG(size <= ah_pool_item_size,
-	    "size=%zu > ah_pool_item_size=%zu\n", size, ah_pool_item_size);
-	tc = pool_cache_get(ah_tdb_crypto_pool_cache, PR_NOWAIT);
+	if (__predict_true(size <= ah_pool_item_size)) {
+		tc = pool_cache_get(ah_tdb_crypto_pool_cache, PR_NOWAIT);
+		pool_used = true;
+	} else {
+		/* size can exceed on IPv6 packets with large options.  */
+		tc = kmem_intr_zalloc(size, KM_NOSLEEP);
+		pool_used = false;
+	}
 	if (tc == NULL) {
 		DPRINTF(("%s: failed to allocate tdb_crypto\n", __func__));
 		stat = AH_STAT_CRYPTO;
@@ -767,8 +774,12 @@ ah_input(struct mbuf *m, struct secasvar
 	return crypto_dispatch(crp);
 
 bad:
-	if (tc != NULL)
-		pool_cache_put(ah_tdb_crypto_pool_cache, tc);
+	if (tc != NULL) {
+		if (__predict_true(pool_used))
+			pool_cache_put(ah_tdb_crypto_pool_cache, tc);
+		else
+			kmem_intr_free(tc, size);
+	}
 	if (crp != NULL)
 		crypto_freereq(crp);
 	if (m != NULL)
@@ -808,6 +819,8 @@ ah_input_cb(struct cryptop *crp)
 	int authsize;
 	uint16_t dport;
 	uint16_t sport;
+	bool pool_used;
+	size_t size;
 	IPSEC_DECLARE_LOCK_VARIABLE;
 
 	KASSERT(crp->crp_opaque != NULL);
@@ -829,6 +842,16 @@ ah_input_cb(struct cryptop *crp)
 	    saidx->dst.sa.sa_family == AF_INET6,
 	    "unexpected protocol family %u", saidx->dst.sa.sa_family);
 
+	/* Figure out header size. */
+	rplen = HDRSIZE(sav);
+	authsize = AUTHSIZE(sav);
+
+	size = sizeof(*tc) + skip + rplen + authsize;
+	if (__predict_true(size <= ah_pool_item_size))
+		pool_used = true;
+	else
+		pool_used = false;
+
 	/* Check for crypto errors. */
 	if (crp->crp_etype) {
 		if (sav->tdb_cryptoid != 0)
@@ -849,10 +872,6 @@ ah_input_cb(struct cryptop *crp)
 		crp = NULL;
 	}
 
-	/* Figure out header size. */
-	rplen = HDRSIZE(sav);
-	authsize = AUTHSIZE(sav);
-
 	if (ipsec_debug)
 		memset(calc, 0, sizeof(calc));
 
@@ -890,7 +909,10 @@ ah_input_cb(struct cryptop *crp)
 	/* Copyback the saved (uncooked) network headers. */
 	m_copyback(m, 0, skip, ptr);
 
-	pool_cache_put(ah_tdb_crypto_pool_cache, tc);
+	if (__predict_true(pool_used))
+		pool_cache_put(ah_tdb_crypto_pool_cache, tc);
+	else
+		kmem_intr_free(tc, size);
 	tc = NULL;
 
 	/*
@@ -937,8 +959,12 @@ bad:
 	IPSEC_RELEASE_GLOBAL_LOCKS();
 	if (m != NULL)
 		m_freem(m);
-	if (tc != NULL)
-		pool_cache_put(ah_tdb_crypto_pool_cache, tc);
+	if (tc != NULL) {
+		if (pool_used)
+			pool_cache_put(ah_tdb_crypto_pool_cache, tc);
+		else
+			kmem_intr_free(tc, size);
+	}
 	if (crp != NULL)
 		crypto_freereq(crp);
 	return error;

Reply via email to