During clone/destroy stress tests on pseudo-interfaces I found a double
free easily reproducible with dhclient(8) running on top of a trunk(4).

The problem comes from trunk_ether_delmulti() which is almost identical
to carp's version except that it always free "mc".

So when you do "# ifconfig trunk0 destroy" the kernel first brings the
interface down and generates a routing message.  dhclient(8) receives
this message and removes the address it's configured on trunk0 and with
it the "default multicast group".  If dhclient(8) lose the race, it will
fail to remove the multicast address but it will free "mc"!

Then ifconfig's thread which was asleep in trunk_ether_purgemulti() -> 
trunk_ioctl_allports() wakes up and tries to free "mc" a second time:

 uvm_fault(0xd5647bd0, 0x0, 0, 1) -> e
 kernel: page fault trap, code=0
 Stopped at      trunk_ether_purgemulti+0xc2:    movl    0x104(%edx),%eax
 ddb> tr
 trunk_ether_purgemulti(d131b800,d131b800,0,d041d091,0) at trunk_ether_purgemult
 i+0xc2
 trunk_clone_destroy(d131b800,0,f3766dec,d1064e94,f3766ef4) at trunk_clone_destr
 oy+0x16
 ifioctl(d54d10f0,80206979,f3766e84,d54a42dc,2) at ifioctl+0x232
 sys_ioctl(d54a42dc,f3766f60,f3766f80,d0566f17,d54a42dc) at sys_ioctl+0x257
 syscall() at syscall+0x247

Here's a simple fix that also reduces the differences with carp's
version.  ok?

Index: net/if_trunk.c
===================================================================
RCS file: /cvs/src/sys/net/if_trunk.c,v
retrieving revision 1.101
diff -u -p -r1.101 if_trunk.c
--- net/if_trunk.c      9 Jun 2015 14:50:14 -0000       1.101
+++ net/if_trunk.c      10 Jun 2015 15:30:10 -0000
@@ -857,18 +857,20 @@ trunk_ether_delmulti(struct trunk_softc 
        if ((error = ether_delmulti(ifr, &tr->tr_ac)) != ENETRESET)
                return (error);
 
-       if ((error = trunk_ioctl_allports(tr, SIOCDELMULTI,
-           (caddr_t)ifr)) != 0) {
+       /* We no longer use this multicast address.  Tell parent so. */
+       error = trunk_ioctl_allports(tr, SIOCDELMULTI, (caddr_t)ifr);
+       if (error == 0) {
+               SLIST_REMOVE(&tr->tr_mc_head, mc, trunk_mc, mc_entries);
+               free(mc, M_DEVBUF, sizeof(*mc));
+       } else {
                /* XXX At least one port failed to remove the address */
                if (tr->tr_ifflags & IFF_DEBUG) {
                        printf("%s: failed to remove multicast address "
-                           "on all ports\n", tr->tr_ifname);
+                           "on all ports (%d)\n", tr->tr_ifname, error);
                }
+               (void)ether_addmulti(ifr, &tr->tr_ac);
        }
 
-       SLIST_REMOVE(&tr->tr_mc_head, mc, trunk_mc, mc_entries);
-       free(mc, M_DEVBUF, 0);
-
        return (0);
 }
 
@@ -886,7 +888,7 @@ trunk_ether_purgemulti(struct trunk_soft
                trunk_ioctl_allports(tr, SIOCDELMULTI, (caddr_t)ifr);
 
                SLIST_REMOVE(&tr->tr_mc_head, mc, trunk_mc, mc_entries);
-               free(mc, M_DEVBUF, 0);
+               free(mc, M_DEVBUF, sizeof(*mc));
        }
 }
 

Reply via email to