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:
Hans Petter Selasky 2019-10-16 09:11:49 +00:00
parent 786c532a8f
commit a55383e720
2 changed files with 100 additions and 2 deletions

View File

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

View File

@ -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