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(); }