DragonFlyBSD Kernel Audit
DF-0691 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/netinet6/mld6.c b/sys/netinet6/mld6.c
--- a/sys/netinet6/mld6.c
+++ b/sys/netinet6/mld6.c
@@ -289,6 +289,16 @@
 				continue;
 			in6m = (struct in6_multi *)ifma->ifma_protospec;
 
+			/*
+			 * in6_delmulti() tears the membership down under crit_enter()
+			 * WITHOUT the ifnet serializer: it NULLs ifma_protospec (and
+			 * later kfrees in6m) before if_delmulti() removes ifma from
+			 * this list. While ifma is still on the list we can therefore
+			 * observe a NULL (or about-to-be-freed) protospec. Skip it
+			 * instead of dereferencing in6m->in6m_addr below. */
+			if (in6m == NULL)
+				continue;
+
 			if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr,
 					&mld6_all_nodes_linklocal) ||
 			    IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c
--- a/sys/netinet6/in6.c
+++ b/sys/netinet6/in6.c
@@ -1763,19 +1763,29 @@
 	struct ifmultiaddr *ifma = in6m->in6m_ifma;
 
 	crit_enter();
-
+	int do_free = 0;
 	if (ifma->ifma_refcount == 1) {
 		/*
 		 * No remaining claims to this record; let MLD6 know
 		 * that we are leaving the multicast group.
 		 */
 		mld6_stop_listening(in6m);
+		/*
+		 * NULL the protospec first so a concurrent mld6_input() reader
+		 * (which holds the ifnet serializer and NULL-checks protospec)
+		 * skips this entry, but DEFER the kfree() until AFTER
+		 * if_delmulti() removes ifma from the interface list -- so that
+		 * while ifma is still on the list, in6m stays allocated and only
+		 * protospec is NULL. This closes both the NULL-deref and the
+		 * use-after-free window. */
 		ifma->ifma_protospec = NULL;
 		LIST_REMOVE(in6m, in6m_entry);
-		kfree(in6m, M_IPMADDR);
+		do_free = 1;
 	}
 	/* XXX - should be separate API for when we have an ifma? */
 	if_delmulti(ifma->ifma_ifp, ifma->ifma_addr);
+	if (do_free)
+		kfree(in6m, M_IPMADDR);
 	crit_exit();
 }