panic: TCP header not in one mbuf

Matthew Dillon dillon at apollo.backplane.com
Sat Jul 17 11:14:01 PDT 2004


:Hello.
:Encountered this panic while downloading NetBSD source tree with CVSup.
:The kernel was compiled at 12th of July, but as far as I looked at commits@
:list, there's not a relavant fix yet. FWIW, the panicked DragonFly
:machine is behind a FreeBSD-CURRENT ipnat box, and I'm using mssclamp
:option in the ipnat rule file to workaround MTU problem. I'm going to
:do some more tests to see if it's reproducible.

   Try this patch.  It's another check ordering issue in ip_demux.c.
   Fragmented packets are skipping the check.  The TCP and UDP header
   length must be checked within the first fragment and, in fact, it
   is also a very good idea to disallow tcp/ip and udp/ip headers that 
   cross fragment boundaries (there are a ton of attacks that use that
   trick to get through various firewalls).

					-Matt
					Matthew Dillon 
					<dillon at xxxxxxxxxxxxx>

Index: ip_demux.c
===================================================================
RCS file: /cvs/src/sys/netinet/ip_demux.c,v
retrieving revision 1.24
diff -u -r1.24 ip_demux.c
--- ip_demux.c	8 Jul 2004 22:43:01 -0000	1.24
+++ ip_demux.c	17 Jul 2004 18:11:44 -0000
@@ -112,24 +112,73 @@
 	lwkt_port_t port;
 	int cpu;
 
+	/*
+	 * The packet must be at least the size of an IP header
+	 */
 	if (m->m_pkthdr.len < sizeof(struct ip)) {
 		ipstat.ips_tooshort++;
 		return (NULL);
 	}
 
+	/*
+	 * The first mbuf must entirely contain the IP header
+	 */
 	if (m->m_len < sizeof(struct ip) &&
 	    (m = m_pullup(m, sizeof(struct ip))) == NULL) {
 		ipstat.ips_toosmall++;
 		return (NULL);
 	}
-
 	ip = mtod(m, struct ip *);
 
+	/*
+	 * Extract the actual IP header length and do a bounds check.  The
+	 * first mbuf must entirely contain the extended IP header.
+	 */
 	iphlen = ip->ip_hl << 2;
 	if (iphlen < sizeof(struct ip)) {	/* minimum header length */
 		ipstat.ips_badhlen++;
 		return (NULL);
 	}
+	if (m->m_len < iphlen) {
+		m = m_pullup(m, iphlen);
+		if (m == NULL) {
+			ipstat.ips_badhlen++;
+			return (NULL);
+		}
+		ip = mtod(m, struct ip *);
+	}
+
+	/*
+	 * The TCP/IP or UDP/IP header must be entirely contained within
+	 * the first fragment of a packet.  Packet filters will break if they
+	 * aren't.
+	 */
+	if ((ntohs(ip->ip_off) & IP_OFFMASK) == 0) {
+		switch (ip->ip_p) {
+		case IPPROTO_TCP:
+			if (m->m_len < iphlen + sizeof(struct tcphdr)) {
+				m = m_pullup(m, iphlen + sizeof(struct tcphdr));
+				if (m == NULL) {
+					tcpstat.tcps_rcvshort++;
+					return (NULL);
+				}
+				ip = mtod(m, struct ip *);
+			}
+			break;
+		case IPPROTO_UDP:
+			if (m->m_len < iphlen + sizeof(struct udphdr)) {
+				m = m_pullup(m, iphlen + sizeof(struct udphdr));
+				if (m == NULL) {
+					udpstat.udps_hdrops++;
+					return (NULL);
+				}
+				ip = mtod(m, struct ip *);
+			}
+			break;
+		default:
+			break;
+		}
+	}
 
 	/*
 	 * XXX generic packet handling defrag on CPU 0 for now.
@@ -139,11 +188,6 @@
 
 	switch (ip->ip_p) {
 	case IPPROTO_TCP:
-		if (m->m_len < iphlen + sizeof(struct tcphdr) &&
-		    (m = m_pullup(m, iphlen + sizeof(struct tcphdr))) == NULL) {
-			tcpstat.tcps_rcvshort++;
-			return (NULL);
-		}
 		th = (struct tcphdr *)((caddr_t)ip + iphlen);
 		thoff = th->th_off << 2;
 		if (thoff < sizeof(struct tcphdr) ||
@@ -166,14 +210,6 @@
 		port = &tcp_thread[cpu].td_msgport;
 		break;
 	case IPPROTO_UDP:
-		if (m->m_len < iphlen + sizeof(struct udphdr)) {
-			m = m_pullup(m, iphlen + sizeof(struct udphdr));
-			if (m == NULL) {
-				udpstat.udps_hdrops++;
-				return (NULL);
-			}
-			ip = mtod(m, struct ip *);
-		}
 		uh = (struct udphdr *)((caddr_t)ip + iphlen);
 
 		if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
@@ -186,10 +222,6 @@
 		port = &udp_thread[cpu].td_msgport;
 		break;
 	default:
-		if (m->m_len < iphlen && (m = m_pullup(m, iphlen)) == NULL) {
-			ipstat.ips_badhlen++;
-			return (NULL);
-		}
 		port = &netisr_cpu[0].td_msgport;
 		break;
 	}





More information about the Bugs mailing list