Fix deadlock in vlan detach

John Baldwin jhb at freebsd.org
Mon May 3 14:40:25 UTC 2010


When a vlan interface is torn down in vlan_unconfig(), it removes references
to any multicast addresses from the parent device.  If any of those attempts
fail, then vlan_unconfig() fails and doesn't tear down the device.  However,
when a "normal" ifnet is detached, it destroys all its multicast addresses in
if_detach() before it invokes the ifnet_departure eventhandler.  This means
that when the vlan eventhandler tries to call vlan_unconfig(), it will fail
if multicast has ever been used on the vlan interface as the attempts to
release a reference on the multicast address on the parent interface will
fail with ENOENT.  However, the code does not expect vlan_unconfig() to ever
fail, and in fact it will loop forever here:

restart:
	for (i = 0; i < (1 << ifp->if_vlantrunk->hwidth); i++)
		if ((ifv = LIST_FIRST(&ifp->if_vlantrunk->hash[i]))) {
			vlan_unconfig_locked(ifv->ifv_ifp);
			if (ifp->if_vlantrunk)
				goto restart;	/* trunk->hwidth can change */
			else
				break;
		}

due to the 'goto restart'.

The fix I came up with was to make vlan_unconfig() simply ignore errors from
removing a multicast reference from the parent device and always succeed.  I
think this is probably more robust anyway.  None of the callers of
vlan_unconfig() ever check the return value to handle failure anyway.  You
can trigger this hang by kldunload'ing a network driver where at least one
instance has a sub-interface with a multicast address registered.  Thoughts?

Index: if_vlan.c
===================================================================
--- if_vlan.c	(revision 207329)
+++ if_vlan.c	(working copy)
@@ -187,8 +187,8 @@
     int (*func)(struct ifnet *, int));
 static	int vlan_setflags(struct ifnet *ifp, int status);
 static	int vlan_setmulti(struct ifnet *ifp);
-static	int vlan_unconfig(struct ifnet *ifp);
-static	int vlan_unconfig_locked(struct ifnet *ifp);
+static	void vlan_unconfig(struct ifnet *ifp);
+static	void vlan_unconfig_locked(struct ifnet *ifp);
 static	int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag);
 static	void vlan_link_state(struct ifnet *ifp);
 static	void vlan_capabilities(struct ifvlan *ifv);
@@ -1128,25 +1128,22 @@
 	return (error);
 }
 
-static int
+static void
 vlan_unconfig(struct ifnet *ifp)
 {
-	int ret;
 
 	VLAN_LOCK();
-	ret = vlan_unconfig_locked(ifp);
+	vlan_unconfig_locked(ifp);
 	VLAN_UNLOCK();
-	return (ret);
 }
 
-static int
+static void
 vlan_unconfig_locked(struct ifnet *ifp)
 {
 	struct ifvlantrunk *trunk;
 	struct vlan_mc_entry *mc;
 	struct ifvlan *ifv;
 	struct ifnet  *parent;
-	int error;
 
 	VLAN_LOCK_ASSERT();
 
@@ -1175,9 +1172,15 @@
 		while ((mc = SLIST_FIRST(&ifv->vlan_mc_listhead)) != NULL) {
 			bcopy((char *)&mc->mc_addr, LLADDR(&sdl),
 			    ETHER_ADDR_LEN);
-			error = if_delmulti(parent, (struct sockaddr *)&sdl);
-			if (error)
-				return (error);
+
+			/*
+			 * This may fail if the parent interface is
+			 * being detached.  Regardless, we should do a
+			 * best effort to free this interface as much
+			 * as possible as all callers expect vlan
+			 * destruction to succeed.
+			 */
+			(void)if_delmulti(parent, (struct sockaddr *)&sdl);
 			SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries);
 			free(mc, M_VLAN);
 		}
@@ -1223,8 +1226,6 @@
 	 */
 	if (parent != NULL)
 		EVENTHANDLER_INVOKE(vlan_unconfig, parent, ifv->ifv_tag);
-
-	return (0);
 }
 
 /* Handle a reference counted flag that should be set on the parent as well */


-- 
John Baldwin


More information about the freebsd-net mailing list