diff --git a/idle_scan.cc b/idle_scan.cc index 195048995..2d6a1f06a 100644 --- a/idle_scan.cc +++ b/idle_scan.cc @@ -206,7 +206,7 @@ static int ipid_proxy_probe(struct idle_proxy_info *proxy, int *probes_sent, 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 @@ static void initialize_idleproxy(struct idle_proxy_info *proxy, char *proxyName, 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); diff --git a/osscan2.cc b/osscan2.cc index fd8ba7c98..4a5fb279d 100644 --- a/osscan2.cc +++ b/osscan2.cc @@ -3426,7 +3426,7 @@ static void doSeqTests(OsScanInfo *OSI, HostOsScan *HOS) { 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 @@ static void doSeqTests(OsScanInfo *OSI, HostOsScan *HOS) { 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 @@ static void doTUITests(OsScanInfo *OSI, HostOsScan *HOS) { 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 @@ static void doTUITests(OsScanInfo *OSI, HostOsScan *HOS) { timedout = true; } - if(bytes < 20 || bytes < (4 * ip->ip_hl) + 4U) + if(bytes < (4 * ip->ip_hl) + 4U) continue; memset(&sin, 0, sizeof(sin)); diff --git a/scan_engine.cc b/scan_engine.cc index 7502def8e..6a2c40d7d 100644 --- a/scan_engine.cc +++ b/scan_engine.cc @@ -3581,7 +3581,7 @@ static bool get_pcap_result(UltraScanInfo *USI, struct timeval *stime) { 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 @@ static bool get_pcap_result(UltraScanInfo *USI, struct timeval *stime) { 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 @@ static bool get_pcap_result(UltraScanInfo *USI, struct timeval *stime) { } 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 @@ static bool get_pcap_result(UltraScanInfo *USI, struct timeval *stime) { } } } 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)); @@ -3971,7 +3965,7 @@ static int get_ping_pcap_result(UltraScanInfo *USI, struct timeval *stime) { 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) { @@ -3988,17 +3982,11 @@ static int get_ping_pcap_result(UltraScanInfo *USI, struct timeval *stime) { 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)); @@ -4333,10 +4321,6 @@ static int get_ping_pcap_result(UltraScanInfo *USI, struct timeval *stime) { 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) diff --git a/tcpip.cc b/tcpip.cc index ea9a5e6e9..f0857af49 100644 --- a/tcpip.cc +++ b/tcpip.cc @@ -1898,6 +1898,149 @@ int pcap_select(pcap_t *p, long usecs) return pcap_select(p, &tv); } +/* Used by validatepkt() to validate the TCP header (including option lengths). + * The options checked are MSS, WScale, SackOK, Sack, and Timestamp. + */ +static bool validateTCPhdr(u8 *tcpc, unsigned len) +{ + struct tcp_hdr *tcp = (struct tcp_hdr *) tcpc; + unsigned hdrlen, optlen; + + hdrlen = tcp->th_off * 4; + + /* Check header length */ + 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) that next level headers seem reasonable (e.g. validateTCPhdr()) + * + * 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 (!validateTCPhdr(ipc + iphdrlen, iplen - iphdrlen)) { + if (o.debugging >= 3) + error("Rejecting TCP packet because of bad TCP header"); + 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 @@ int pcap_select(pcap_t *p, long usecs) 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 @@ struct timeval tv_start, tv_end; 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 @@ if (timedout) { } 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 diff --git a/tcpip.h b/tcpip.h index 49be4310c..8add07eb5 100644 --- a/tcpip.h +++ b/tcpip.h @@ -713,7 +713,7 @@ int get_link_offset(char *device); 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 diff --git a/traceroute.cc b/traceroute.cc index 9760dbec9..bcd9d201b 100644 --- a/traceroute.cc +++ b/traceroute.cc @@ -417,7 +417,7 @@ Traceroute::readTraceResponses () { 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 (); @@ -530,9 +530,6 @@ Traceroute::readTraceResponses () { } 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 ()) @@ -575,8 +572,6 @@ Traceroute::readTraceResponses () { } 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 ())