Module Name: src Committed By: ozaki-r Date: Wed Jul 17 07:07:59 UTC 2019
Modified Files: src/sys/netipsec: key.c Log Message: Avoid a race condition between SA (sav) manipulations An sav can be removed from belonging list(s) twice resulting in an assertion failure of pslist. It can occur if the following two operations interleave: (i) a deletion or a update of an SA via the API, and (ii) a state change (key_sa_chgstate) of the same SA by the timer. Note that even (ii) removes an sav once from its list(s) on a update. The cause of the race condition is that the two operations are not serialized and (i) doesn't get and remove an sav from belonging list(s) atomically. So (ii) can be inserted between an acquisition and a removal of (i). Avoid the race condition by making (i) atomic. To generate a diff of this commit: cvs rdiff -u -r1.263 -r1.264 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.263 src/sys/netipsec/key.c:1.264 --- src/sys/netipsec/key.c:1.263 Wed Jun 12 22:23:06 2019 +++ src/sys/netipsec/key.c Wed Jul 17 07:07:59 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: key.c,v 1.263 2019/06/12 22:23:06 christos Exp $ */ +/* $NetBSD: key.c,v 1.264 2019/07/17 07:07:59 ozaki-r Exp $ */ /* $FreeBSD: 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.263 2019/06/12 22:23:06 christos Exp $"); +__KERNEL_RCSID(0, "$NetBSD: key.c,v 1.264 2019/07/17 07:07:59 ozaki-r Exp $"); /* * This code is referred to RFC 2367 @@ -696,8 +696,8 @@ static bool key_sah_has_sav(struct secas static void key_sah_ref(struct secashead *); static void key_sah_unref(struct secashead *); static void key_init_sav(struct secasvar *); +static void key_wait_sav(struct secasvar *); static void key_destroy_sav(struct secasvar *); -static void key_destroy_sav_with_ref(struct secasvar *); static struct secasvar *key_newsav(struct mbuf *, const struct sadb_msghdr *, int *, const char*, int); #define KEY_NEWSAV(m, sadb, e) \ @@ -1598,30 +1598,20 @@ key_destroy_sav(struct secasvar *sav) } /* - * Destroy sav with holding its reference. + * Wait for references of a passed sav to go away. */ static void -key_destroy_sav_with_ref(struct secasvar *sav) +key_wait_sav(struct secasvar *sav) { ASSERT_SLEEPABLE(); mutex_enter(&key_sad.lock); - sav->state = SADB_SASTATE_DEAD; - SAVLIST_WRITER_REMOVE(sav); - SAVLUT_WRITER_REMOVE(sav); - mutex_exit(&key_sad.lock); - - /* We cannot unref with holding key_sad.lock */ - KEY_SA_UNREF(&sav); - - mutex_enter(&key_sad.lock); + KASSERT(sav->state == SADB_SASTATE_DEAD); KDASSERT(mutex_ownable(softnet_lock)); key_sad_pserialize_perform(); localcount_drain(&sav->localcount, &key_sad.cv_lc, &key_sad.lock); mutex_exit(&key_sad.lock); - - key_destroy_sav(sav); } /* %%% SPD management */ @@ -3534,6 +3524,38 @@ out: } /* + * Search SAD litmited alive SA by an SPI and remove it from a list. + * OUT: + * NULL : not found + * others : found, pointer to a SA. + */ +static struct secasvar * +key_lookup_and_remove_sav(struct secashead *sah, u_int32_t spi) +{ + struct secasvar *sav = NULL; + u_int state; + + /* search all status */ + mutex_enter(&key_sad.lock); + SASTATE_ALIVE_FOREACH(state) { + SAVLIST_WRITER_FOREACH(sav, sah, state) { + KASSERT(sav->state == state); + + if (sav->spi == spi) { + sav->state = SADB_SASTATE_DEAD; + SAVLIST_WRITER_REMOVE(sav); + SAVLUT_WRITER_REMOVE(sav); + goto out; + } + } + } +out: + mutex_exit(&key_sad.lock); + + return sav; +} + +/* * Free allocated data to member variables of sav: * sav->replay, sav->key_* and sav->lft_*. */ @@ -5628,7 +5650,7 @@ key_api_update(struct socket *so, struct const struct sockaddr *src, *dst; struct secasindex saidx; struct secashead *sah; - struct secasvar *sav, *newsav; + struct secasvar *sav, *newsav, *oldsav; u_int16_t proto; u_int8_t mode; u_int16_t reqid; @@ -5781,12 +5803,25 @@ key_api_update(struct socket *so, struct mutex_exit(&key_sad.lock); key_validate_savlist(sah, SADB_SASTATE_MATURE); + /* + * We need to lookup and remove the sav atomically, so get it again + * here by a special API while we have a reference to it. + */ + oldsav = key_lookup_and_remove_sav(sah, sa0->sadb_sa_spi); + /* We can release the reference because of oldsav */ + KEY_SA_UNREF(&sav); + if (oldsav == NULL) { + /* Someone has already removed the sav. Nothing to do. */ + } else { + key_wait_sav(oldsav); + key_destroy_sav(oldsav); + oldsav = NULL; + } + sav = NULL; + key_sah_unref(sah); sah = NULL; - key_destroy_sav_with_ref(sav); - sav = NULL; - { struct mbuf *n; @@ -6187,7 +6222,7 @@ key_api_delete(struct socket *so, struct sah = key_getsah_ref(&saidx, CMP_HEAD); if (sah != NULL) { /* get a SA with SPI. */ - sav = key_getsavbyspi(sah, sa0->sadb_sa_spi); + sav = key_lookup_and_remove_sav(sah, sa0->sadb_sa_spi); key_sah_unref(sah); } @@ -6196,7 +6231,8 @@ key_api_delete(struct socket *so, struct return key_senderror(so, m, ENOENT); } - key_destroy_sav_with_ref(sav); + key_wait_sav(sav); + key_destroy_sav(sav); sav = NULL; {