diff options
| author | Gleb Smirnoff <glebius@FreeBSD.org> | 2026-01-23 22:18:18 +0000 |
|---|---|---|
| committer | Gleb Smirnoff <glebius@FreeBSD.org> | 2026-01-23 22:18:18 +0000 |
| commit | f3de667137e90679cd20fa5c1dcd93a4c51ad848 (patch) | |
| tree | 7f5170a1a61598a90cecac27f92d46a9554f0515 | |
| parent | cba9f88105c268f73fa2a92ca4479cc15b8a3338 (diff) | |
netinet6: free in6_ifextra with epoch_call(9)
This is expected to fix the old in6_selecthlim() panics. The nature of
the panic is that a packet sending thread will obtain the struct ifnet
pointer locklessly and then pick the if_inet6 pointer from it and
dereference it. While the struct ifnet is freed via epoch_call(9), the
struct in6_ifextra until this change was not. For the forwarded packets,
or locally originated non-TCP packets we were probably safe due to the old
if_dead trick. But locally originated TCP packets may dereference
in6_ifextra via direct call into in6_selecthlim() from the tcp_output(),
before ip6_output().
NB: hypothetically a similar problem also applies to IPv4's if_inet pointer,
but there are no known panics, yet.
PR: 279653
Reviewed by: tuexen
Differential Revision: https://reviews.freebsd.org/D54728
| -rw-r--r-- | sys/netinet6/in6_ifattach.c | 25 | ||||
| -rw-r--r-- | sys/netinet6/in6_var.h | 2 |
2 files changed, 22 insertions, 5 deletions
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c index b9ce64e5b86b..c38fe90632f8 100644 --- a/sys/netinet6/in6_ifattach.c +++ b/sys/netinet6/in6_ifattach.c @@ -900,6 +900,19 @@ in6_ifdetach(struct ifnet *ifp) } static void +in6_ifextra_free(epoch_context_t ctx) +{ + struct in6_ifextra *ext = + __containerof(ctx, struct in6_ifextra, epoch_ctx); + + COUNTER_ARRAY_FREE(ext->in6_ifstat, + sizeof(struct in6_ifstat) / sizeof(uint64_t)); + COUNTER_ARRAY_FREE(ext->icmp6_ifstat, + sizeof(struct icmp6_ifstat) / sizeof(uint64_t)); + free(ext, M_IFADDR); +} + +static void in6_ifdeparture(void *arg __unused, struct ifnet *ifp) { struct in6_ifextra *ext = ifp->if_inet6; @@ -916,14 +929,16 @@ in6_ifdeparture(void *arg __unused, struct ifnet *ifp) if (!VNET_IS_SHUTTING_DOWN(ifp->if_vnet)) #endif _in6_ifdetach(ifp, 1); + /* + * XXXGL: mld and nd bits are left in a consistent state after + * destructors, but I'm not sure if it safe to call lltable_free() here. + * Individual lle entries are epoch(9) protected, but the table itself + * isn't. + */ mld_domifdetach(ifp); nd6_ifdetach(ifp); lltable_free(ext->lltable); - COUNTER_ARRAY_FREE(ext->in6_ifstat, - sizeof(struct in6_ifstat) / sizeof(uint64_t)); - COUNTER_ARRAY_FREE(ext->icmp6_ifstat, - sizeof(struct icmp6_ifstat) / sizeof(uint64_t)); - free(ext, M_IFADDR); + NET_EPOCH_CALL(in6_ifextra_free, &ext->epoch_ctx); } EVENTHANDLER_DEFINE(ifnet_departure_event, in6_ifdeparture, NULL, EVENTHANDLER_PRI_ANY); diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h index 894628b796d1..057cd84b6ea7 100644 --- a/sys/netinet6/in6_var.h +++ b/sys/netinet6/in6_var.h @@ -531,6 +531,8 @@ struct in6_ifextra { } scope6_id; struct lltable *lltable; + + struct epoch_context epoch_ctx; }; #define LLTABLE6(ifp) ((ifp)->if_inet6->lltable) |
