1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

Adding packet validity checking to readip_pcap() so the caller can assume the

packet is OK from the get-go rather than running basic checks of it's own.

In a nutshell this patch 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

For TCP, this checks that there is enough room for the header in the number
of bytes read, and that any option lengths are correct.  The options checked
are MSS, WScale, SackOK, Sack, and Timestamp.

This also fixes a bug I discovered while testing.  Since the Ethernet CRC
(and other datalink-layer data) could be read and counted, it was being
returned that there was more IP packet than there really was.  This didn't
cause an overrun of the buffer or anything, just that garbage data could have
easily been read instead of real packet data.  Now, if validity is checked for
and the number of total bytes read is larger than the IP's length, the length
is set to the IP header's total length field.

This seems to work great after doing what testing I could.  It's been out on
nmap-dev for a couple of weeks without any bad reports (none at all for that
matter).  I reviewed this patch again before committing and it looks good as
well.
This commit is contained in:
kris
2008-06-30 23:55:19 +00:00
parent ba44abab6e
commit 83ed199791
6 changed files with 181 additions and 38 deletions

View File

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

View File

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

View File

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

166
tcpip.cc
View File

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

View File

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

View File

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