Date: Mon, 17 Apr 2017 06:58:48 -0700 From: Chris Torek <torek@torek.net> To: freebsd-net@freebsd.org Subject: three multicast fixes Message-ID: <201704171358.v3HDwmF3071624@elf.torek.net>
next in thread | raw e-mail | index | archive | help
The first is mostly cosmetic, it's just something I observed when I turned on multicast debug to find the bug that the second patch is for. The second patch is a kludge and if anyone has a better fix, have at it. :-) Note that it's also hard to provoke the bug. The only way I know to observe it somewhat reliably is to create and destroy bridge interfaces, using a debug kernel with memory debug enabled, while adding and deleting hardware interfaces to the bridge so that they repeatedly join and leave multicast groups. Eventually you hit the race and crash. The third is simple enough to observe. It may affect only the lagg driver, which implements SIOCDELMULTI by removing and re-adding all the multicast addresses other than the one actually deleted (which is of course really-removed by then). If running lagg on a VIMAGE kernel, leaving a multicast group causes this occasional crash: Fatal trap 12: page fault while in kernel mode cpuid =3D 0; apic id =3D 00 fault virtual address =3D 0x28 fault code =3D supervisor read data, page not present instruction pointer =3D 0x20:0xffffffff80adf6ea stack pointer =3D 0x28:0xfffffe009117e810 frame pointer =3D 0x28:0xfffffe009117e8b0 code segment =3D base 0x0, limit 0xfffff, type 0x1b =3D DPL 0, pres 1, long 1, def32 0, gran 1 processor eflags =3D interrupt enabled, resume, IOPL =3D 0 current process =3D 0 (thread taskq) rt_newmaddrmsg() at rt_newmaddrmsg+0x2a/frame 0xfffffe009117e8b0 if_addmulti() at if_addmulti+0x24c/frame 0xfffffe009117e940 lagg_ether_cmdmulti() at lagg_ether_cmdmulti+0x168/frame 0xfffffe009117e= 980 lagg_ioctl() at lagg_ioctl+0xad/frame 0xfffffe009117ea60 in_leavegroup() at in_leavegroup+0xb9/frame 0xfffffe009117eab0 inp_gcmoptions() at inp_gcmoptions+0x1a8/frame 0xfffffe009117eb20 taskqueue_run_locked() at taskqueue_run_locked+0x117/frame 0xfffffe00911= 7eb80 taskqueue_thread_loop() at taskqueue_thread_loop+0xc8/frame 0xfffffe0091= 17ebb0 fork_exit() at fork_exit+0x85/frame 0xfffffe009117ebf0 fork_trampoline() at fork_trampoline+0xe/frame 0xfffffe009117ebf0 --- trap 0, rip =3D 0, rsp =3D 0, rbp =3D 0 --- Here, "curvnet" is NULL in the lagg_ioctl(), so that when rt_newmaddrmsg() does: if (V_route_cb.any_count =3D=3D 0) we crash with the offset of the per-vnet route_cb data. (The patches are in Git format, I applied them to the read-only freebsd master tree on GitHub.) Chris =46rom 65145b74d3dae68feddcf8a2aad235c3f0a981e9 Mon Sep 17 00:00:00 2001 From: Chris Torek <chris.torek@gmail.com> Date: Mon, 8 Feb 2016 18:55:30 -0800 Subject: [PATCH 1/3] ip multicast debug: fix strings vs defines Turning on multicast debug made multicast failure worse because the strings and #define values no longer matched up. Fix them, and make sure they stay matched-up. --- sys/netinet/in_mcast.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c index c7fff9463128..eef560e7aca7 100644 --- a/sys/netinet/in_mcast.c +++ b/sys/netinet/in_mcast.c @@ -2920,7 +2920,14 @@ sysctl_ip_mcast_filters(SYSCTL_HANDLER_ARGS) = #if defined(KTR) && (KTR_COMPILE & KTR_IGMPV3) = -static const char *inm_modestrs[] =3D { "un", "in", "ex" }; +static const char *inm_modestrs[] =3D { + [MCAST_UNDEFINED] =3D "un", + [MCAST_INCLUDE] =3D "in", + [MCAST_EXCLUDE] =3D "ex", +}; +_Static_assert(MCAST_UNDEFINED =3D=3D 0 && + MCAST_EXCLUDE + 1 =3D=3D nitems(inm_modestrs), + "inm_modestrs: no longer matches #defines"); = static const char * inm_mode_str(const int mode) @@ -2932,16 +2939,20 @@ inm_mode_str(const int mode) } = static const char *inm_statestrs[] =3D { - "not-member", - "silent", - "idle", - "lazy", - "sleeping", - "awakening", - "query-pending", - "sg-query-pending", - "leaving" + [IGMP_NOT_MEMBER] =3D "not-member", + [IGMP_SILENT_MEMBER] =3D "silent", + [IGMP_REPORTING_MEMBER] =3D "reporting", + [IGMP_IDLE_MEMBER] =3D "idle", + [IGMP_LAZY_MEMBER] =3D "lazy", + [IGMP_SLEEPING_MEMBER] =3D "sleeping", + [IGMP_AWAKENING_MEMBER] =3D "awakening", + [IGMP_G_QUERY_PENDING_MEMBER] =3D "query-pending", + [IGMP_SG_QUERY_PENDING_MEMBER] =3D "sg-query-pending", + [IGMP_LEAVING_MEMBER] =3D "leaving", }; +_Static_assert(IGMP_NOT_MEMBER =3D=3D 0 && + IGMP_LEAVING_MEMBER + 1 =3D=3D nitems(inm_statestrs), + "inm_statetrs: no longer matches #defines"); = static const char * inm_state_str(const int state) -- = 2.12.1 =46rom 099e1b2c059fe962c1dd91af0a6735f02f611ac3 Mon Sep 17 00:00:00 2001 From: Chris Torek <torek@ixsystems.com> Date: Wed, 10 Feb 2016 18:41:39 -0800 Subject: [PATCH 2/3] ip multicast: fix use-after-free During if_detach(), we get a race where a closing socket is releasing multicast data (via inp_freemoptions()) at the same time as igmp_ifdetach() is releasing all multicast data for the interface, resulting in a potential double teardown and double free. This bug has been present since late 2011: Author: jhb <jhb@FreeBSD.org> Defer the work of freeing IPv4 multicast options from a socket to an asychronous task. ... It is very hard to trip over. You must create and delete interfaces (bridges that join real interfaces are good candidates) repeatedly, and even then, if M_IPMADDR (in_multi data structure) memory is not reused for something else during the race, the reference count in inm->inm_refcount is an unsigned int, so it decrements from the left-over 0 to 4294967295, avoiding a second free. Turning on the memory debug options (scribbling values over freed memory) catches the problem more quickly, though you still must create and destroy interfaces that partcipate in multicast to see it. The fix here is a kludge, but should serve until the entire network locking code and up/downcall system is reworked. --- sys/netinet/in.c | 4 ++++ sys/netinet/in_mcast.c | 11 +++++++++++ sys/netinet/ip_var.h | 1 + 3 files changed, 16 insertions(+) diff --git a/sys/netinet/in.c b/sys/netinet/in.c index e1611d50b4f0..50cfb188c442 100644 --- a/sys/netinet/in.c +++ b/sys/netinet/in.c @@ -1013,6 +1013,8 @@ in_purgemaddrs(struct ifnet *ifp) struct in_multi *inm, *tinm; struct ifmultiaddr *ifma; = + inp_freemopt_wait(); /* XXX kludge */ + LIST_INIT(&purgeinms); IN_MULTI_LOCK(); = @@ -1043,6 +1045,8 @@ in_purgemaddrs(struct ifnet *ifp) igmp_ifdetach(ifp); = IN_MULTI_UNLOCK(); + + inp_freemopt_wait(); /* XXX kludge */ } = struct in_llentry { diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c index eef560e7aca7..1d1258701fa2 100644 --- a/sys/netinet/in_mcast.c +++ b/sys/netinet/in_mcast.c @@ -1585,6 +1585,17 @@ inp_freemoptions(struct ip_moptions *imo) taskqueue_enqueue(taskqueue_thread, &imo_gc_task); } = +/* + * Wait for inp_freemoptions() to complete any current work. + * Used because inp_freemoptions is no longer synchronous. + */ +void +inp_freemopt_wait(void) +{ + + taskqueue_drain(taskqueue_thread, &imo_gc_task); +} + static void inp_freemoptions_internal(struct ip_moptions *imo) { diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h index f7e58d18635a..e51bcffc4fc2 100644 --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -200,6 +200,7 @@ extern struct pr_usrreqs rip_usrreqs; #define V_drop_redirect VNET(drop_redirect) = void inp_freemoptions(struct ip_moptions *); +void inp_freemopt_wait(void); int inp_getmoptions(struct inpcb *, struct sockopt *); int inp_setmoptions(struct inpcb *, struct sockopt *); = -- = 2.12.1 =46rom e27ff26bd086c46f2b1cce8eb95f4625cc4333fc Mon Sep 17 00:00:00 2001 From: Chris Torek <torek@ixsystems.com> Date: Sat, 5 Mar 2016 15:02:33 -0800 Subject: [PATCH 3/3] ip_multicast: defer vnet restore in in_leavegroup Note: in_leavegroup() does all its real work in in_leavegroup_locked(). For discussion here the two] functions might as well be equivalent. In in_leavegroup_locked(), when we're shedding a multicast group, we may (or may not) delete it from an interface via the igmp_change_state() call. This is where we currently set the multicast's vnet, and then restore the old vnet on return. However, a few lines later we use inm_release_locked() to release the inet multicast data structure, and that in turn may -- not necessarily will, only if the inm really is being freed -- call if_delmulti_ifma(), which may -- not necessarily will, again -- call the interface's SIOCDELMULTI ioctl (if and only if there is an interface and this was the last ref to this multicast address). For (at least) the lagg interface, we still need the current vnet to be valid during the SIOCDELMULTI. So, don't restore the old vnet until we've not only finished the IGMP code but also inm_release_locked(). --- sys/netinet/in_mcast.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c index 1d1258701fa2..4549c952e058 100644 --- a/sys/netinet/in_mcast.c +++ b/sys/netinet/in_mcast.c @@ -1275,12 +1275,12 @@ in_leavegroup_locked(struct in_multi *inm, /*const= */ struct in_mfilter *imf) CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__); CURVNET_SET(inm->inm_ifp->if_vnet); error =3D igmp_change_state(inm); - CURVNET_RESTORE(); if (error) CTR1(KTR_IGMPV3, "%s: failed igmp downcall", __func__); = CTR2(KTR_IGMPV3, "%s: dropping ref on %p", __func__, inm); inm_release_locked(inm); + CURVNET_RESTORE(); = return (error); } -- = 2.12.1
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201704171358.v3HDwmF3071624>