Fix panic in network stack due to use after free when receiving
partial fragmented packets before a network interface is detached. When sending IPv4 or IPv6 fragmented packets and a fragment is lost before the network device is freed, the mbuf making up the fragment will remain in the temporary hashed fragment list and cause a panic when it times out due to accessing a freed network interface structure. 1) Make sure the m_pkthdr.rcvif always points to a valid network interface. Else the rcvif field should be set to NULL. 2) Use the rcvif of the last received fragment as m_pkthdr.rcvif for the fully defragged packet, instead of the first received fragment. Panic backtrace for IPv6: panic() icmp6_reflect() # tries to access rcvif->if_afdata[AF_INET6]->xxx icmp6_error() frag6_freef() frag6_slowtimo() pfslowtimo() softclock_call_cc() softclock() ithread_loop() Reviewed by: bz Differential Revision: https://reviews.freebsd.org/D19622 MFC after: 1 week Sponsored by: Mellanox Technologies
This commit is contained in:
parent
786c532a8f
commit
a55383e720
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=353635
@ -47,7 +47,10 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <net/if_var.h>
|
||||
#include <net/rss_config.h>
|
||||
#include <net/netisr.h>
|
||||
#include <net/vnet.h>
|
||||
@ -181,6 +184,7 @@ ip_reass(struct mbuf *m)
|
||||
struct ip *ip;
|
||||
struct mbuf *p, *q, *nq, *t;
|
||||
struct ipq *fp;
|
||||
struct ifnet *srcifp;
|
||||
struct ipqhead *head;
|
||||
int i, hlen, next, tmpmax;
|
||||
u_int8_t ecn, ecn0;
|
||||
@ -240,6 +244,11 @@ ip_reass(struct mbuf *m)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store receive network interface pointer for later.
|
||||
*/
|
||||
srcifp = m->m_pkthdr.rcvif;
|
||||
|
||||
/*
|
||||
* Attempt reassembly; if it succeeds, proceed.
|
||||
* ip_reass() will return a different mbuf.
|
||||
@ -490,8 +499,11 @@ ip_reass(struct mbuf *m)
|
||||
m->m_len += (ip->ip_hl << 2);
|
||||
m->m_data -= (ip->ip_hl << 2);
|
||||
/* some debugging cruft by sklower, below, will go away soon */
|
||||
if (m->m_flags & M_PKTHDR) /* XXX this should be done elsewhere */
|
||||
if (m->m_flags & M_PKTHDR) { /* XXX this should be done elsewhere */
|
||||
m_fixhdr(m);
|
||||
/* set valid receive interface pointer */
|
||||
m->m_pkthdr.rcvif = srcifp;
|
||||
}
|
||||
IPSTAT_INC(ips_reassembled);
|
||||
IPQ_UNLOCK(hash);
|
||||
|
||||
@ -607,6 +619,43 @@ ipreass_drain(void)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Drain off all datagram fragments belonging to
|
||||
* the given network interface.
|
||||
*/
|
||||
static void
|
||||
ipreass_cleanup(void *arg __unused, struct ifnet *ifp)
|
||||
{
|
||||
struct ipq *fp, *temp;
|
||||
struct mbuf *m;
|
||||
int i;
|
||||
|
||||
KASSERT(ifp != NULL, ("%s: ifp is NULL", __func__));
|
||||
|
||||
/*
|
||||
* Skip processing if IPv4 reassembly is not initialised or
|
||||
* torn down by ipreass_destroy().
|
||||
*/
|
||||
if (V_ipq_zone == NULL)
|
||||
return;
|
||||
|
||||
CURVNET_SET_QUIET(ifp->if_vnet);
|
||||
for (i = 0; i < IPREASS_NHASH; i++) {
|
||||
IPQ_LOCK(i);
|
||||
/* Scan fragment list. */
|
||||
TAILQ_FOREACH_SAFE(fp, &V_ipq[i].head, ipq_list, temp) {
|
||||
for (m = fp->ipq_frags; m != NULL; m = m->m_nextpkt) {
|
||||
/* clear no longer valid rcvif pointer */
|
||||
if (m->m_pkthdr.rcvif == ifp)
|
||||
m->m_pkthdr.rcvif = NULL;
|
||||
}
|
||||
}
|
||||
IPQ_UNLOCK(i);
|
||||
}
|
||||
CURVNET_RESTORE();
|
||||
}
|
||||
EVENTHANDLER_DEFINE(ifnet_departure_event, ipreass_cleanup, NULL, 0);
|
||||
|
||||
#ifdef VIMAGE
|
||||
/*
|
||||
* Destroy IP reassembly structures.
|
||||
@ -617,6 +666,7 @@ ipreass_destroy(void)
|
||||
|
||||
ipreass_drain();
|
||||
uma_zdestroy(V_ipq_zone);
|
||||
V_ipq_zone = NULL;
|
||||
for (int i = 0; i < IPREASS_NHASH; i++)
|
||||
mtx_destroy(&V_ipq[i].lock);
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ frag6_freef(struct ip6q *q6, uint32_t bucket)
|
||||
* Return ICMP time exceeded error for the 1st fragment.
|
||||
* Just free other fragments.
|
||||
*/
|
||||
if (af6->ip6af_off == 0) {
|
||||
if (af6->ip6af_off == 0 && m->m_pkthdr.rcvif != NULL) {
|
||||
|
||||
/* Adjust pointer. */
|
||||
ip6 = mtod(m, struct ip6_hdr *);
|
||||
@ -271,6 +271,43 @@ frag6_freef(struct ip6q *q6, uint32_t bucket)
|
||||
atomic_subtract_int(&V_frag6_nfragpackets, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drain off all datagram fragments belonging to
|
||||
* the given network interface.
|
||||
*/
|
||||
static void
|
||||
frag6_cleanup(void *arg __unused, struct ifnet *ifp)
|
||||
{
|
||||
struct ip6q *q6, *q6n, *head;
|
||||
struct ip6asfrag *af6;
|
||||
struct mbuf *m;
|
||||
int i;
|
||||
|
||||
KASSERT(ifp != NULL, ("%s: ifp is NULL", __func__));
|
||||
|
||||
CURVNET_SET_QUIET(ifp->if_vnet);
|
||||
for (i = 0; i < IP6REASS_NHASH; i++) {
|
||||
IP6QB_LOCK(i);
|
||||
head = IP6QB_HEAD(i);
|
||||
/* Scan fragment list. */
|
||||
for (q6 = head->ip6q_next; q6 != head; q6 = q6n) {
|
||||
q6n = q6->ip6q_next;
|
||||
|
||||
for (af6 = q6->ip6q_down; af6 != (struct ip6asfrag *)q6;
|
||||
af6 = af6->ip6af_down) {
|
||||
m = IP6_REASS_MBUF(af6);
|
||||
|
||||
/* clear no longer valid rcvif pointer */
|
||||
if (m->m_pkthdr.rcvif == ifp)
|
||||
m->m_pkthdr.rcvif = NULL;
|
||||
}
|
||||
}
|
||||
IP6QB_UNLOCK(i);
|
||||
}
|
||||
CURVNET_RESTORE();
|
||||
}
|
||||
EVENTHANDLER_DEFINE(ifnet_departure_event, frag6_cleanup, NULL, 0);
|
||||
|
||||
/*
|
||||
* Like in RFC2460, in RFC8200, fragment and reassembly rules do not agree with
|
||||
* each other, in terms of next header field handling in fragment header.
|
||||
@ -307,6 +344,7 @@ int
|
||||
frag6_input(struct mbuf **mp, int *offp, int proto)
|
||||
{
|
||||
struct ifnet *dstifp;
|
||||
struct ifnet *srcifp;
|
||||
struct in6_ifaddr *ia6;
|
||||
struct ip6_hdr *ip6;
|
||||
struct ip6_frag *ip6f;
|
||||
@ -338,6 +376,11 @@ frag6_input(struct mbuf **mp, int *offp, int proto)
|
||||
return (IPPROTO_DONE);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Store receive network interface pointer for later.
|
||||
*/
|
||||
srcifp = m->m_pkthdr.rcvif;
|
||||
|
||||
dstifp = NULL;
|
||||
/* Find the destination interface of the packet. */
|
||||
ia6 = in6ifa_ifwithaddr(&ip6->ip6_dst, 0 /* XXX */);
|
||||
@ -534,6 +577,9 @@ frag6_input(struct mbuf **mp, int *offp, int proto)
|
||||
frag6_deq(af6, bucket);
|
||||
free(af6, M_FRAG6);
|
||||
|
||||
/* Set a valid receive interface pointer. */
|
||||
merr->m_pkthdr.rcvif = srcifp;
|
||||
|
||||
/* Adjust pointer. */
|
||||
ip6err = mtod(merr, struct ip6_hdr *);
|
||||
|
||||
@ -720,6 +766,8 @@ frag6_input(struct mbuf **mp, int *offp, int proto)
|
||||
for (t = m; t; t = t->m_next)
|
||||
plen += t->m_len;
|
||||
m->m_pkthdr.len = plen;
|
||||
/* Set a valid receive interface pointer. */
|
||||
m->m_pkthdr.rcvif = srcifp;
|
||||
}
|
||||
|
||||
#ifdef RSS
|
||||
|
Loading…
Reference in New Issue
Block a user