diff --git a/sys/net/if_tuntap.c b/sys/net/if_tuntap.c --- a/sys/net/if_tuntap.c +++ b/sys/net/if_tuntap.c @@ -649,7 +649,7 @@ error = cv_wait_sig(&tp->tun_cv, &tp->tun_mtx); else cv_wait(&tp->tun_cv, &tp->tun_mtx); - if (error != 0) { + if (error != 0 && tp->tun_busy != 0) { tp->tun_flags &= ~TUN_DYING; TUN_UNLOCK(tp); return (error); @@ -661,10 +661,21 @@ mtx_lock(&tunmtx); TAILQ_REMOVE(&tunhead, tp, tun_list); - mtx_unlock(&tunmtx); - /* destroy_dev will take care of any alias. */ - destroy_dev(tp->tun_dev); + /* + * destroy_dev() will take care of any alias. For transient tunnels, + * we're being called from close(2) so we can't destroy it ourselves + * without deadlocking, but we already know that we can cleanup + * everything else and just continue to prevent it from being reopened. + */ + if ((tp->tun_flags & TUN_TRANSIENT) != 0) { + tp->tun_dev->si_drv1 = tp->tun_dev; + mtx_unlock(&tunmtx); + destroy_dev_sched(tp->tun_dev); + } else { + mtx_unlock(&tunmtx); + destroy_dev(tp->tun_dev); + } seldrain(&tp->tun_rsel); knlist_clear(&tp->tun_rsel.si_note, 0); knlist_destroy(&tp->tun_rsel.si_note); @@ -742,6 +753,7 @@ mtx_unlock(&tunmtx); for (i = 0; i < nitems(tuntap_drivers); ++i) { drv = &tuntap_drivers[i]; + destroy_dev_drain(&drv->cdevsw); delete_unrhdr(drv->unrhdr); clone_cleanup(&drv->clones); } @@ -1116,17 +1128,45 @@ return (error); /* Shouldn't happen */ } + /* + * Transient tunnels do deferred destroy of the tun device but want + * to immediately cleanup state, so they clobber si_drv1 to avoid a + * use-after-free in case someone does happen to open it in the interim. + * We avoid using NULL to be able to distinguish from an uninitialized + * cdev. + * + * tun_destroy() takes the tunmtx to do the swap so that we can safely + * handle a concurrent tunopen(). If it hasn't been swapped out yet, + * then we can safely take its lock and check if it's dying without + * risk as long as we're still holding the tunmtx. + */ + mtx_lock(&tunmtx); + if (dev->si_drv1 == dev) { + mtx_unlock(&tunmtx); + CURVNET_RESTORE(); + return (ENXIO); + } + tp = dev->si_drv1; KASSERT(tp != NULL, ("si_drv1 should have been initialized at creation")); TUN_LOCK(tp); + if ((tp->tun_flags & TUN_DYING) != 0) { + TUN_UNLOCK(tp); + mtx_unlock(&tunmtx); + CURVNET_RESTORE(); + return (EBUSY); + } + + mtx_unlock(&tunmtx); + if ((tp->tun_flags & TUN_INITED) == 0) { TUN_UNLOCK(tp); CURVNET_RESTORE(); return (ENXIO); } - if ((tp->tun_flags & (TUN_OPEN | TUN_DYING)) != 0) { + if ((tp->tun_flags & TUN_OPEN) != 0) { TUN_UNLOCK(tp); CURVNET_RESTORE(); return (EBUSY);