if_wg: do not allow ioctl to race with clone_destroy

This fixes the crash from:

bash -c 'while true; do ifconfig wg0 create; ifconfig wg0 destroy; done&
while true; do wg show wg0 > /dev/null 2>&1; done& wait'

Since we're setting ifp to NULL here, we also have to account for
multicast v6 packets being transmitted during destroy, which can be
triggered by:

  ifconfig wg0 create
  ifconfig wg0 inet6 fe80::1234/120
  ifconfig wg0 up
  route add -inet6 ff02::1:0/120 -iface wg0
  ifconfig wg0 destroy

These are unfixed upstream bug that we're working around.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2021-04-14 14:42:50 -06:00
parent 15df6797da
commit 65c2211c61
1 changed files with 16 additions and 10 deletions

View File

@ -2441,22 +2441,18 @@ free:
static int static int
wg_transmit(struct ifnet *ifp, struct mbuf *m) wg_transmit(struct ifnet *ifp, struct mbuf *m)
{ {
struct wg_softc *sc; struct wg_softc *sc = ifp->if_softc;
sa_family_t family; sa_family_t family;
struct epoch_tracker et; struct epoch_tracker et;
struct wg_peer *peer; struct wg_peer *peer;
struct wg_tag *t; struct wg_tag *t;
uint32_t af; uint32_t af;
int rc; int rc = 0;
/* /* Work around lifetime issue in the ipv6 mld code. */
* Work around lifetime issue in the ipv6 mld code. if (__predict_false((ifp->if_flags & IFF_DYING) || !sc))
*/
if (__predict_false(ifp->if_flags & IFF_DYING))
return (ENXIO); return (ENXIO);
rc = 0;
sc = ifp->if_softc;
if ((t = wg_tag_get(m)) == NULL) { if ((t = wg_tag_get(m)) == NULL) {
rc = ENOBUFS; rc = ENOBUFS;
goto early_out; goto early_out;
@ -2888,9 +2884,16 @@ wg_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{ {
struct wg_data_io *wgd = (struct wg_data_io *)data; struct wg_data_io *wgd = (struct wg_data_io *)data;
struct ifreq *ifr = (struct ifreq *)data; struct ifreq *ifr = (struct ifreq *)data;
struct wg_softc *sc = ifp->if_softc; struct wg_softc *sc;
int ret = 0; int ret = 0;
sx_slock(&wg_sx);
sc = ifp->if_softc;
if (!sc) {
ret = ENXIO;
goto out;
}
switch (cmd) { switch (cmd) {
case SIOCSWG: case SIOCSWG:
ret = priv_check(curthread, PRIV_NET_WG); ret = priv_check(curthread, PRIV_NET_WG);
@ -2908,7 +2911,7 @@ wg_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
*/ */
break; break;
case SIOCSIFFLAGS: case SIOCSIFFLAGS:
if ((ifp->if_flags & IFF_UP) != 0) if (ifp->if_flags & IFF_UP)
ret = wg_up(sc); ret = wg_up(sc);
else else
wg_down(sc); wg_down(sc);
@ -2940,6 +2943,8 @@ wg_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
ret = ENOTTY; ret = ENOTTY;
} }
out:
sx_sunlock(&wg_sx);
return ret; return ret;
} }
@ -3118,6 +3123,7 @@ wg_clone_destroy(struct ifnet *ifp)
struct ucred *cred; struct ucred *cred;
sx_xlock(&wg_sx); sx_xlock(&wg_sx);
ifp->if_softc = NULL;
sx_xlock(&sc->sc_lock); sx_xlock(&sc->sc_lock);
sc->sc_flags |= WGF_DYING; sc->sc_flags |= WGF_DYING;
cred = sc->sc_ucred; cred = sc->sc_ucred;