On Tue, Feb 15, 2022 at 02:35:33PM +1000, David Gwynne wrote:
> if an open tun (or tap) device is destroyed via ifconfig destroy,
> there is a window while the open device is being revoked on the vfs
> side that a third thread can come and open it again. this in turn
> triggers a kassert in the ifconfig destroy path where it expects the
> device to be closed.
>
> i think this diff fixes it by having the open code refuse to work with a
> device that's in the process of being destroyed.
>
> this was found by syzkaller. the detail is at
> https://syzkaller.appspot.com/bug?id=1d1ea7860c36e5066edea1145cf2d56715d5042b.
>
> i was able to reproduce the problem with this code. it was pretty hard
> to hit, but ive had no luck since adding the TUN_DEAD handling to
> tun_dev_open.
The diff looks good to me.
OK visa@
> Index: if_tun.c
> ===================================================================
> RCS file: /cvs/src/sys/net/if_tun.c,v
> retrieving revision 1.233
> diff -u -p -r1.233 if_tun.c
> --- if_tun.c 15 Feb 2022 04:19:52 -0000 1.233
> +++ if_tun.c 15 Feb 2022 04:21:53 -0000
> @@ -218,6 +218,8 @@ tun_create(struct if_clone *ifc, int uni
> KERNEL_ASSERT_LOCKED();
>
> sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
> + refcnt_init(&sc->sc_refs);
> +
> ifp = &sc->sc_if;
> snprintf(ifp->if_xname, sizeof(ifp->if_xname),
> "%s%d", ifc->ifc_name, unit);
> @@ -267,7 +269,6 @@ tun_create(struct if_clone *ifc, int uni
> }
>
> sigio_init(&sc->sc_sigio);
> - refcnt_init(&sc->sc_refs);
>
> /* tell tun_dev_open we're initialised */
>
> @@ -381,7 +382,7 @@ tun_dev_open(dev_t dev, const struct if_
> rdomain = rtable_l2(p->p_p->ps_rtableid);
>
> /* let's find or make an interface to work with */
> - while ((ifp = if_unit(name)) == NULL) {
> + while ((sc = tun_name_lookup(name)) == NULL) {
> error = if_clone_create(name, rdomain);
> switch (error) {
> case 0: /* it's probably ours */
> @@ -394,34 +395,45 @@ tun_dev_open(dev_t dev, const struct if_
> }
> }
>
> - sc = ifp->if_softc;
> + refcnt_take(&sc->sc_refs);
> +
> /* wait for it to be fully constructed before we use it */
> - while (!ISSET(sc->sc_flags, TUN_INITED)) {
> + for (;;) {
> + if (ISSET(sc->sc_flags, TUN_DEAD)) {
> + error = ENXIO;
> + goto done;
> + }
> +
> + if (ISSET(sc->sc_flags, TUN_INITED))
> + break;
> +
> error = tsleep_nsec(sc, PCATCH, "tuninit", INFSLP);
> if (error != 0) {
> /* XXX if_clone_destroy if stayup? */
> - if_put(ifp);
> - return (error);
> + goto done;
> }
> }
>
> if (sc->sc_dev != 0) {
> /* aww, we lost */
> - if_put(ifp);
> - return (EBUSY);
> + error = EBUSY;
> + goto done;
> }
> /* it's ours now */
> sc->sc_dev = dev;
> CLR(sc->sc_flags, stayup);
>
> /* automatically mark the interface running on open */
> + ifp = &sc->sc_if;
> NET_LOCK();
> SET(ifp->if_flags, IFF_UP | IFF_RUNNING);
> NET_UNLOCK();
> tun_link_state(ifp, LINK_STATE_FULL_DUPLEX);
> - if_put(ifp);
> + error = 0;
>
> - return (0);
> +done:
> + tun_put(sc);
> + return (error);
> }
>
> /*
>