Relax per-ifnet cif_vrs list double locking in carp(4).

In all cases where cif_vrs list is modified, two locks are held: per-ifnet
CIF_LOCK and global carp_sx.  It means to read that list only one of them
is enough to be held, so we can skip CIF_LOCK when we already have carp_sx.

This fixes kernel panic, caused by attempts of copyout() to sleep while
holding non-sleepable CIF_LOCK mutex.

Discussed with:	glebius
MFC after:	2 weeks
Sponsored by:	iXsystems, Inc.
This commit is contained in:
Alexander Motin 2017-10-19 09:01:15 +00:00
parent 4074d642d2
commit 81098a018e

View File

@ -175,8 +175,8 @@ static int proto_reg[] = {-1, -1};
* Each softc has a lock sc_mtx. It is used to synchronise carp_input_c(),
* callout-driven events and ioctl()s.
*
* To traverse the list of softcs on an ifnet we use CIF_LOCK(), to
* traverse the global list we use the mutex carp_mtx.
* To traverse the list of softcs on an ifnet we use CIF_LOCK() or carp_sx.
* To traverse the global list we use the mutex carp_mtx.
*
* Known issues with locking:
*
@ -286,7 +286,8 @@ SYSCTL_VNET_PCPUSTAT(_net_inet_carp, OID_AUTO, stats, struct carpstats,
++_i)
#define IFNET_FOREACH_CARP(ifp, sc) \
CIF_LOCK_ASSERT(ifp->if_carp); \
KASSERT(mtx_owned(&ifp->if_carp->cif_mtx) || \
sx_xlocked(&carp_sx), ("cif_vrs not locked")); \
TAILQ_FOREACH((sc), &(ifp)->if_carp->cif_vrs, sc_list)
#define DEMOTE_ADVSKEW(sc) \
@ -1562,6 +1563,8 @@ carp_alloc(struct ifnet *ifp)
struct carp_softc *sc;
struct carp_if *cif;
sx_assert(&carp_sx, SA_XLOCKED);
if ((cif = ifp->if_carp) == NULL)
cif = carp_alloc_if(ifp);
@ -1751,11 +1754,9 @@ carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
}
if (ifp->if_carp) {
CIF_LOCK(ifp->if_carp);
IFNET_FOREACH_CARP(ifp, sc)
if (sc->sc_vhid == carpr.carpr_vhid)
break;
CIF_UNLOCK(ifp->if_carp);
}
if (sc == NULL) {
sc = carp_alloc(ifp);
@ -1826,11 +1827,9 @@ carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
priveleged = (priv_check(td, PRIV_NETINET_CARP) == 0);
if (carpr.carpr_vhid != 0) {
CIF_LOCK(ifp->if_carp);
IFNET_FOREACH_CARP(ifp, sc)
if (sc->sc_vhid == carpr.carpr_vhid)
break;
CIF_UNLOCK(ifp->if_carp);
if (sc == NULL) {
error = ENOENT;
break;
@ -1841,7 +1840,6 @@ carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
int i, count;
count = 0;
CIF_LOCK(ifp->if_carp);
IFNET_FOREACH_CARP(ifp, sc)
count++;
@ -1863,7 +1861,6 @@ carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
}
i++;
}
CIF_UNLOCK(ifp->if_carp);
}
break;
}
@ -1918,11 +1915,9 @@ carp_attach(struct ifaddr *ifa, int vhid)
return (ENOPROTOOPT);
}
CIF_LOCK(cif);
IFNET_FOREACH_CARP(ifp, sc)
if (sc->sc_vhid == vhid)
break;
CIF_UNLOCK(cif);
if (sc == NULL) {
sx_xunlock(&carp_sx);
return (ENOENT);