Nmap Development mailing list archives

[PATCH] IP Packet Validity Checking


From: Kris Katterjohn <katterjohn () gmail com>
Date: Wed, 11 Jun 2008 17:26:05 -0500

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hey everyone,

I've attached a patch to check for IP packet validity in readip_pcap() so that
the caller doesn't need to worry about it, they can assume the packet is good.

readip_pcap() now takes a boolean 'validate' argument, and all calls set it to
true currently.

In a nutshell, it checks to make sure:

1) there is enough room for an IP header in the amount of bytes read
2) the IP version number is correct
3) the IP length fields are at least as big as the standard header
4) the IP packet received isn't a fragment, or is the initial fragment
5) there is enough room for a valid TCP or UDP header for TCP/UDP packets
6) the TCP options are reasonable for TCP packets

With debugging >= 3, it will print a message when it rejects a packet and it
gives the reason why.

I'm limited in my ability to test these sort of patches because I cant just
generate bogus or odd traffic, especially not in the apartment I'm staying in
now for summer classes since I only have one computer with me.

Now that the packet validity can be taken care of this early, I've also
included a fix for a bug I discovered while testing this.  Since readip_pcap()
takes a pointer to set the length of the capture, that's what the callers use
to gauge going passed the end of the packet.  However, things like the
Ethernet CRC trailer were also captured, and thus were counted in the length,
leading the caller to assume there is more packet than there really is.  It
didn't cause any overruns of the buffer, because the buffer is the size of the
return length, but it would give garbage results if it read past the real
packet's end (which was easy to do).  Now, if validity is checked for, the
length is set to the IP's total length field.

So, if you could review and/or test the patch out, I'd appreciate it.

Thanks,
Kris Katterjohn

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iQIVAwUBSFBQ+v9K37xXYl36AQL92xAAsIMt+foS8T7QBKKjj8Loh3xyTrxdi2d3
p/RziLEqkD1urBv0iuDqLYLCmgOmAXv44c65o/SdAr6DysQVPObN6/rUt9IayEJw
DYlJmqKH4Gsgwqa3MuE4PHsmpUkKO9Do6yZiq+hLATjA2tCGNZwb3ar/adOqQGlV
lHsilmMFTBvl2ll7/oTVrDAjEVqJ0hq+1sB3ERKpbMeVVQndsBjTuX8clEWrrkxT
hP2Q3+f217Pu+uOYeAnsjuP0jMbJjhJTV4m3m8XR6e0woC8ohsHD8QIJaE7LwBa/
8QUfSXB0GdCAh6dgtjrXiIWWpe+LCEGK9TWqsWBDLYzD3F4R04Ej81ikJ+kzDiDl
g7q0XWmfLVhRWkQUOJYge8moiPLEVVH4PjyPKRurp2cgUxNpIxtxwpdU7WWPJ/9V
Qz3O4TvQmMQNyI4JNIh3C8cTvgOv7o/0PtGDku+3kk9Rc3G1awoFad54HQt39JnM
5o1e0lWC2yHMcQ4fmgLW59CuOS6MuIN4hrupSIkZD9EEdVPBdWhJQPcgoC4tjjUg
X4ZCIAN1mz3t/QG0omF8x2whzXaX+Wo5I6l5YnEso+IxneK5v0SsdIhW23HS2BsS
J5DrDbfHkX2IgKF1bJrzXDvK10YQVzBDoE1XEhYIUzeFD55eh6CedNnxueKDxnJp
XjU1XnMAvZE=
=7wXD
-----END PGP SIGNATURE-----
Index: osscan2.cc
===================================================================
--- osscan2.cc  (revision 8126)
+++ osscan2.cc  (working copy)
@@ -3426,7 +3426,7 @@
       if(o.debugging > 2)
         log_write(LOG_PLAIN, "pcap wait time is %ld.\n", to_usec);
       
-      ip = (struct ip*) readip_pcap(HOS->pd, &bytes, to_usec, &rcvdtime, &linkhdr);
+      ip = (struct ip*) readip_pcap(HOS->pd, &bytes, to_usec, &rcvdtime, &linkhdr, true);
     
       gettimeofday(&now, NULL);
       
@@ -3443,7 +3443,7 @@
         timedout = true;
       }
       
-      if(bytes < 20 || bytes < (4 * ip->ip_hl) + 4U)
+      if(bytes < (4 * ip->ip_hl) + 4U)
         continue;
 
       memset(&sin, 0, sizeof(sin));
@@ -3595,7 +3595,7 @@
       if(o.debugging > 2)
         log_write(LOG_PLAIN, "pcap wait time is %ld.\n", to_usec);
       
-      ip = (struct ip*) readip_pcap(HOS->pd, &bytes, to_usec, &rcvdtime, &linkhdr);
+      ip = (struct ip*) readip_pcap(HOS->pd, &bytes, to_usec, &rcvdtime, &linkhdr, true);
     
       gettimeofday(&now, NULL);
       
@@ -3612,7 +3612,7 @@
         timedout = true;
       }
       
-      if(bytes < 20 || bytes < (4 * ip->ip_hl) + 4U)
+      if(bytes < (4 * ip->ip_hl) + 4U)
         continue;
 
       memset(&sin, 0, sizeof(sin));
Index: tcpip.cc
===================================================================
--- tcpip.cc    (revision 8126)
+++ tcpip.cc    (working copy)
@@ -1898,6 +1898,149 @@
        return pcap_select(p, &tv);
 }
 
+/* Used by validatepkt() to validate the TCP option lengths.  The options
+ * checked are MSS, WScale, SackOK, Sack, and Timestamp
+ */
+static bool validateTCPoptions(u8 *tcpc, unsigned len)
+{
+       struct tcp_hdr *tcp = (struct tcp_hdr *) tcpc;
+       unsigned hdrlen, optlen;
+
+       hdrlen = tcp->th_off * 4;
+
+       if (hdrlen > len || hdrlen < sizeof(struct tcp_hdr))
+               return false;
+
+       /* Get to the options */
+       tcpc += sizeof(struct tcp_hdr);
+       optlen = hdrlen - sizeof(struct tcp_hdr);
+
+       while (optlen > 0) {
+               switch (*tcpc) {
+               case 2: /* MSS */
+                       if (optlen < 4)
+                               return false;
+                       optlen -= 4;
+                       tcpc += 4;
+                       break;
+               case 3: /* Window Scale */
+                       if (optlen < 3)
+                               return false;
+                       optlen -= 3;
+                       tcpc += 3;
+                       break;
+               case 4: /* SACK Permitted */
+                       if (optlen < 2)
+                               return false;
+                       optlen -= 2;
+                       tcpc += 2;
+                       break;
+               case 5: /* SACK */
+                       if (optlen < *++tcpc)
+                               return false;
+                       if (!(*tcpc - 2) || ((*tcpc - 2) % 8))
+                               return false;
+                       optlen -= *tcpc;
+                       tcpc += (*tcpc - 1);
+                       break;
+               case 8: /* Timestamp */
+                       if (optlen < 10)
+                               return false;
+                       optlen -= 10;
+                       tcpc += 10;
+                       break;
+               default:
+                       optlen--;
+                       tcpc++;
+                       break;
+               }
+       }
+
+       return true;
+}
+
+/* Used by readip_pcap() to validate an IP packet.  It checks to make sure:
+ *
+ * 1) there is enough room for an IP header in the amount of bytes read
+ * 2) the IP version number is correct
+ * 3) the IP length fields are at least as big as the standard header
+ * 4) the IP packet received isn't a fragment, or is the initial fragment
+ * 5) there is enough room for a valid TCP or UDP header for TCP/UDP packets
+ * 6) the TCP options are reasonable for TCP packets (see validateTCPoptions())
+ *
+ * Checking the IP total length (iplen) to see if its at least as large as the
+ * number of bytes read (len) does not work because things like the Ethernet
+ * CRC also get captured and are counted in len.  Therefore, after the IP total
+ * length is considered reasonable, iplen is used instead of len.  readip_pcap
+ * fixes the length on it's end after this is validated.
+ */
+static bool validatepkt(u8 *ipc, unsigned len)
+{
+       struct ip *ip = (struct ip *) ipc;
+       unsigned fragoff, iphdrlen, iplen;
+
+       if (len < sizeof(struct ip)) {
+               if (o.debugging >= 3)
+                       error("Rejecting tiny, supposed IP packet (size %u)", len);
+               return false;
+       }
+
+       if (ip->ip_v != 4) {
+               if (o.debugging >= 3)
+                       error("Rejecting IP packet because of invalid version number %u", ip->ip_v);
+               return false;
+       }
+
+       iphdrlen = ip->ip_hl * 4;
+
+       if (iphdrlen < sizeof(struct ip)) {
+               if (o.debugging >= 3)
+                       error("Rejecting IP packet because of invalid header length %u", iphdrlen);
+               return false;
+       }
+
+       iplen = ntohs(ip->ip_len);
+
+       if (iplen < iphdrlen) {
+               if (o.debugging >= 3)
+                       error("Rejecting IP packet because of invalid total length %u", iplen);
+               return false;
+       }
+
+       fragoff = 8 * (ntohs(ip->ip_off) & IP_OFFMASK);
+
+       if (fragoff) {
+               if (o.debugging >= 3)
+                       error("Rejecting IP fragment (offset %u)", fragoff);
+               return false;
+       }
+
+       switch (ip->ip_p) {
+       case IPPROTO_TCP:
+               if (iphdrlen + sizeof(struct tcp_hdr) > iplen) {
+                       if (o.debugging >= 3)
+                               error("Rejecting TCP packet because of incomplete header");
+                       return false;
+               }
+               if (!validateTCPoptions(ipc + iphdrlen, iplen - iphdrlen)) {
+                       if (o.debugging >= 3)
+                               error("Rejecting TCP packet because of bad TCP options");
+                       return false;
+               }
+               break;
+       case IPPROTO_UDP:
+               if (iphdrlen + sizeof(struct udp_hdr) < iplen)
+                       break;
+               if (o.debugging >= 3)
+                       error("Rejecting UDP packet because of incomplete header");
+               return false;
+       default:
+               break;
+       }
+
+       return true;
+}
+
 /* Read an IP packet using libpcap .  We return the packet and take
    a pcap descriptor and a pointer to the packet length (which we set
    in the function. If you want a maximum length returned, you
@@ -1912,8 +2055,11 @@
    filled with the time that packet was captured from the wire by
    pcap.  If linknfo is not NULL, linknfo->headerlen and
    linknfo->header will be filled with the appropriate values. */
+/* Specifying true for validate will enable validity checks against the
+   received IP packet.  See validatepkt() for a list of checks.
+ */
 char *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, 
-                 struct timeval *rcvdtime, struct link_header *linknfo) {
+                 struct timeval *rcvdtime, struct link_header *linknfo, bool validate) {
 unsigned int offset = 0;
 struct pcap_pkthdr head;
 char *p;
@@ -1923,6 +2069,7 @@
 static char *alignedbuf = NULL;
 static unsigned int alignedbufsz=0;
 static int warning = 0;
+struct ip *iphdr;
 
 if (linknfo) { memset(linknfo, 0, sizeof(*linknfo)); }
 
@@ -2062,6 +2209,23 @@
  }
  memcpy(alignedbuf, p, *len);
 
+ if (validate) {
+   /* Let's see if this packet passes inspection.. */
+   if (!validatepkt((u8 *) alignedbuf, *len)) {
+     *len = 0;
+     return NULL;
+   }
+
+   iphdr = (struct ip *) alignedbuf;
+
+   /* OK, since the IP header has been validated, we don't want to tell
+    * the caller they have more packet than they really have.  This can
+    * be caused by the Ethernet CRC trailer being counted, for example.
+    */
+   if (*len > ntohs(iphdr->ip_len))
+     *len = ntohs(iphdr->ip_len);
+ }
+
  // printf("Just got a packet at %li,%li\n", head.ts.tv_sec, head.ts.tv_usec);
  if (rcvdtime) {
    // FIXME: I eventually need to figure out why Windows head.ts time is sometimes BEFORE the time I
Index: tcpip.h
===================================================================
--- tcpip.h     (revision 8126)
+++ tcpip.h     (working copy)
@@ -713,7 +713,7 @@
    pcap.  If linknfo is not NULL, lnknfo->headerlen and
    lnkinfo->header will be filled with the appropriate values. */
 char *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, 
-                 struct timeval *rcvdtime, struct link_header *linknfo);
+                 struct timeval *rcvdtime, struct link_header *linknfo, bool validate);
 
 /* Attempts to read one IPv4/Ethernet ARP reply packet from the pcap
    descriptor pd.  If it receives one, fills in sendermac (must pass
Index: idle_scan.cc
===================================================================
--- idle_scan.cc        (revision 8126)
+++ idle_scan.cc        (working copy)
@@ -206,7 +206,7 @@
 
       to_usec = proxy->host.to.timeout - TIMEVAL_SUBTRACT(tv_end, tv_sent[tries-1]);
       if (to_usec < 0) to_usec = 0; // Final no-block poll
-      ip = (struct ip *) readip_pcap(proxy->pd, &bytes, to_usec, &rcvdtime, NULL);      
+      ip = (struct ip *) readip_pcap(proxy->pd, &bytes, to_usec, &rcvdtime, NULL, true);      
       gettimeofday(&tv_end, NULL);
       if (ip) {
        if (bytes < ( 4 * ip->ip_hl) + 14U)
@@ -445,7 +445,7 @@
     while(probes_returned < probes_sent && !timedout) {
 
       to_usec = (probes_sent == NUM_IPID_PROBES)? hardtimeout : 1000;
-      ip = (struct ip *) readip_pcap(proxy->pd, &bytes, to_usec, &rcvdtime, NULL);
+      ip = (struct ip *) readip_pcap(proxy->pd, &bytes, to_usec, &rcvdtime, NULL, true);
 
       gettimeofday(&tmptv, NULL);
       
Index: scan_engine.cc
===================================================================
--- scan_engine.cc      (revision 8126)
+++ scan_engine.cc      (working copy)
@@ -3581,7 +3581,7 @@
   do {
     to_usec = TIMEVAL_SUBTRACT(*stime, USI->now);
     if (to_usec < 2000) to_usec = 2000;
-    ip = (struct ip *) readip_pcap(USI->pd, &bytes, to_usec, &rcvdtime, &linkhdr);
+    ip = (struct ip *) readip_pcap(USI->pd, &bytes, to_usec, &rcvdtime, &linkhdr, true);
     gettimeofday(&USI->now, NULL);
     if (!ip && TIMEVAL_SUBTRACT(*stime, USI->now) < 0) {
       timedout = true;
@@ -3595,13 +3595,11 @@
       timedout = true;
     }
 
-    /* OK, we got a packet.  Let's make sure it is well-formed */
+    /* OK, we got a packet.  Most packet validity tests are taken care
+     * of in readip_pcap, so this is simple
+     */
     if (bytes < 28)
       continue;
-    if (ip->ip_v != 4)
-      continue;
-    if (ip->ip_hl < 5)
-      continue;
 
     if (USI->prot_scan) {
       memset(&sin, 0, sizeof(sin));
@@ -3640,8 +3638,6 @@
     }
 
     if (ip->ip_p == IPPROTO_TCP && !USI->prot_scan) {
-      if ((unsigned) ip->ip_hl * 4 + 20 > bytes)
-       continue;
       struct tcp_hdr *tcp = (struct tcp_hdr *) ((u8 *) ip + ip->ip_hl * 4);
       /* Now ensure this host is even in the incomplete list */
       memset(&sin, 0, sizeof(sin));
@@ -3832,8 +3828,6 @@
        }
       }
     } else if (ip->ip_p == IPPROTO_UDP && !USI->prot_scan) {
-      if ((unsigned) ip->ip_hl * 4 + 8 > bytes)
-       continue;
       struct udp_hdr *udp = (struct udp_hdr *) ((u8 *) ip + ip->ip_hl * 4);
       /* Search for this host on the incomplete list */
       memset(&sin, 0, sizeof(sin));
@@ -3970,7 +3964,7 @@
   do {
     to_usec = TIMEVAL_SUBTRACT(*stime, USI->now);
     if (to_usec < 2000) to_usec = 2000;
-    ip = (struct ip *) readip_pcap(USI->pd, &bytes, to_usec, &rcvdtime, &linkhdr);
+    ip = (struct ip *) readip_pcap(USI->pd, &bytes, to_usec, &rcvdtime, &linkhdr, true);
     gettimeofday(&USI->now, NULL);
     if (!ip) {
       if (TIMEVAL_SUBTRACT(*stime, USI->now) < 0) {
@@ -3987,17 +3981,11 @@
       timedout = true;
     }
 
-    /* OK, we got a packet.  Let's make sure it is well-formed */
+    /* OK, we got a packet.  Most packet validity tests are taken care
+     * of in readip_pcap, so this is simple
+     */
     if (bytes == 0)
       continue;
-    if (bytes <= 20) {  
-      error("%d byte micro packet received in %s", bytes, __func__);
-      continue;
-    }  
-    if (ip->ip_v != 4)
-      continue;
-    if (ip->ip_hl < 5)
-      continue;
 
     if (USI->ptech.rawprotoscan) {
       memset(&sin, 0, sizeof(sin));
@@ -4328,10 +4316,6 @@
       if (!USI->ptech.rawtcpscan) {
         continue;
       }
-      if (bytes < 4 * ip->ip_hl + 16U) {
-          error("TCP packet is only %d bytes, we can't get enough information from it", bytes);
-          continue;
-      }
       struct tcp_hdr *tcp = (struct tcp_hdr *) (((u8 *) ip) + 4 * ip->ip_hl);
       /* Check that the packet has useful flags. */
       if (!(tcp->th_flags & TH_RST)
Index: traceroute.cc
===================================================================
--- traceroute.cc       (revision 8126)
+++ traceroute.cc       (working copy)
@@ -417,7 +417,7 @@
     u32 ipaddr;
 
     /* Got to look into readip_pcap's timeout value, perhaps make it dynamic */
-    ip = (struct ip *) readip_pcap (pd, &bytes, 10000, &rcvdtime, &linkhdr);
+    ip = (struct ip *) readip_pcap (pd, &bytes, 10000, &rcvdtime, &linkhdr, true);
 
     if (ip == NULL)
         return finished ();
@@ -524,9 +524,6 @@
        }
         break;
     case IPPROTO_TCP:
-        if ((unsigned) ip->ip_hl * 4 + 20 > bytes)
-            break;
-
         tcp = (struct tcp_hdr *) ((char *) ip + 4 * ip->ip_hl);
 
         if (TraceGroups.find (ip->ip_src.s_addr) != TraceGroups.end ())
@@ -569,8 +566,6 @@
         }
         break;
     case IPPROTO_UDP:
-        if ((unsigned) ip->ip_hl * 4 + 8 > bytes)
-            break;
         udp = (udp_hdr *) ((u8 *) ip + ip->ip_hl * 4);
 
         if (TraceGroups.find (ip->ip_src.s_addr) != TraceGroups.end ())

_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: