Commit message: CVE-2017-12894/In lookup_bytestring(), take the length of the byte string into account.

Otherwise, if, in our search of the hash table, we come across a byte
string that's shorter than the string we're looking for, we'll search
past the end of the string in the hash table.

This fixes a buffer over-read discovered by Forcepoint's security
researchers Otto Airamo & Antti Levomäki.

Add a test using the capture file supplied by the reporter(s).
CVE: CVE-2017-12894
CWE: CWE-125
Vulnerability description: Several protocol parsers in tcpdump before 4.9.2 could cause a buffer over-read in addrtoname.c:lookup_bytestring().

Project: tcpdump
File: addrtoname.c

dnaddr_string(netdissect_options *ndo, u_short dnaddr)
{
	register struct hnamemem *tp;

	for (tp = &dnaddrtable[dnaddr & (HASHNAMESIZE-1)]; tp->nxt != NULL;
	     tp = tp->nxt)
		if (tp->addr == dnaddr)
			return (tp->name);

	tp->addr = dnaddr;
	tp->nxt = newhnamemem(ndo);
	if (ndo->ndo_nflag)
		tp->name = dnnum_string(ndo, dnaddr);
	else
		tp->name = dnname_string(ndo, dnaddr);

	return(tp->name);
}

Project: tcpdump
File: addrtoname.c

etheraddr_string(netdissect_options *ndo, register const u_char *ep)
{
	register int i;
	register char *cp;
	register struct enamemem *tp;
	int oui;
	char buf[BUFSIZE];

	tp = lookup_emem(ndo, ep);
	if (tp->e_name)
		return (tp->e_name);
#ifdef USE_ETHER_NTOHOST
	if (!ndo->ndo_nflag) {
		char buf2[BUFSIZE];

		if (ether_ntohost(buf2, (const struct ether_addr *)ep) == 0) {
			tp->e_name = strdup(buf2);
			if (tp->e_name == NULL)
				(*ndo->ndo_error)(ndo,
						  "etheraddr_string: strdup(buf2)");
			return (tp->e_name);
		}
	}
#endif
	cp = buf;
	oui = EXTRACT_24BITS(ep);
	*cp++ = hex[*ep >> 4 ];
	*cp++ = hex[*ep++ & 0xf];
	for (i = 5; --i >= 0;) {
		*cp++ = ':';
		*cp++ = hex[*ep >> 4 ];
		*cp++ = hex[*ep++ & 0xf];
	}

	if (!ndo->ndo_nflag) {
		snprintf(cp, BUFSIZE - (2 + 5*3), " (oui %s)",
		    tok2str(oui_values, "Unknown", oui));
	} else
		*cp = '\0';
	tp->e_name = strdup(buf);
	if (tp->e_name == NULL)
		(*ndo->ndo_error)(ndo, "etheraddr_string: strdup(buf)");
	return (tp->e_name);
}

Project: tcpdump
File: addrtoname.c

etherproto_string(netdissect_options *ndo, u_short port)
{
	register char *cp;
	register struct hnamemem *tp;
	register uint32_t i = port;
	char buf[sizeof("0000")];

	for (tp = &eprototable[i & (HASHNAMESIZE-1)]; tp->nxt; tp = tp->nxt)
		if (tp->addr == i)
			return (tp->name);

	tp->addr = i;
	tp->nxt = newhnamemem(ndo);

	cp = buf;
	NTOHS(port);
	*cp++ = hex[port >> 12 & 0xf];
	*cp++ = hex[port >> 8 & 0xf];
	*cp++ = hex[port >> 4 & 0xf];
	*cp++ = hex[port & 0xf];
	*cp++ = '\0';
	tp->name = strdup(buf);
	if (tp->name == NULL)
		(*ndo->ndo_error)(ndo, "etherproto_string: strdup(buf)");
	return (tp->name);
}

Project: tcpdump
File: addrtoname.c

getname(netdissect_options *ndo, const u_char *ap)
{
	register struct hostent *hp;
	uint32_t addr;
	struct hnamemem *p;

	memcpy(&addr, ap, sizeof(addr));
	p = &hnametable[addr & (HASHNAMESIZE-1)];
	for (; p->nxt; p = p->nxt) {
		if (p->addr == addr)
			return (p->name);
	}
	p->addr = addr;
	p->nxt = newhnamemem(ndo);

	/*
	 * Print names unless:
	 *	(1) -n was given.
	 *      (2) Address is foreign and -f was given. (If -f was not
	 *	    given, f_netmask and f_localnet are 0 and the test
	 *	    evaluates to true)
	 */
	if (!ndo->ndo_nflag &&
	    (addr & f_netmask) == f_localnet) {
#ifdef HAVE_CASPER
		if (capdns != NULL) {
			hp = cap_gethostbyaddr(capdns, (char *)&addr, 4,
			    AF_INET);
		} else
#endif
			hp = gethostbyaddr((char *)&addr, 4, AF_INET);
		if (hp) {
			char *dotp;

			p->name = strdup(hp->h_name);
			if (p->name == NULL)
				(*ndo->ndo_error)(ndo,
						  "getname: strdup(hp->h_name)");
			if (ndo->ndo_Nflag) {
				/* Remove domain qualifications */
				dotp = strchr(p->name, '.');
				if (dotp)
					*dotp = '\0';
			}
			return (p->name);
		}
	}
	p->name = strdup(intoa(addr));
	if (p->name == NULL)
		(*ndo->ndo_error)(ndo, "getname: strdup(intoa(addr))");
	return (p->name);
}

Project: tcpdump
File: addrtoname.c

getname6(netdissect_options *ndo, const u_char *ap)
{
	register struct hostent *hp;
	union {
		struct in6_addr addr;
		struct for_hash_addr {
			char fill[14];
			uint16_t d;
		} addra;
	} addr;
	struct h6namemem *p;
	register const char *cp;
	char ntop_buf[INET6_ADDRSTRLEN];

	memcpy(&addr, ap, sizeof(addr));
	p = &h6nametable[addr.addra.d & (HASHNAMESIZE-1)];
	for (; p->nxt; p = p->nxt) {
		if (memcmp(&p->addr, &addr, sizeof(addr)) == 0)
			return (p->name);
	}
	p->addr = addr.addr;
	p->nxt = newh6namemem(ndo);

	/*
	 * Do not print names if -n was given.
	 */
	if (!ndo->ndo_nflag) {
#ifdef HAVE_CASPER
		if (capdns != NULL) {
			hp = cap_gethostbyaddr(capdns, (char *)&addr,
			    sizeof(addr), AF_INET6);
		} else
#endif
			hp = gethostbyaddr((char *)&addr, sizeof(addr),
			    AF_INET6);
		if (hp) {
			char *dotp;

			p->name = strdup(hp->h_name);
			if (p->name == NULL)
				(*ndo->ndo_error)(ndo,
						  "getname6: strdup(hp->h_name)");
			if (ndo->ndo_Nflag) {
				/* Remove domain qualifications */
				dotp = strchr(p->name, '.');
				if (dotp)
					*dotp = '\0';
			}
			return (p->name);
		}
	}
	cp = addrtostr6(ap, ntop_buf, sizeof(ntop_buf));
	p->name = strdup(cp);
	if (p->name == NULL)
		(*ndo->ndo_error)(ndo, "getname6: strdup(cp)");
	return (p->name);
}

Project: tcpdump
File: addrtoname.c

ieee8021q_tci_string(const uint16_t tci)
{
	static char buf[128];
	snprintf(buf, sizeof(buf), "vlan %u, p %u%s",
	         tci & 0xfff,
	         tci >> 13,
	         (tci & 0x1000) ? ", DEI" : "");
	return buf;
}

Project: tcpdump
File: addrtoname.c

init_eprotoarray(netdissect_options *ndo)
{
	register int i;
	register struct hnamemem *table;

	for (i = 0; eproto_db[i].s; i++) {
		int j = htons(eproto_db[i].p) & (HASHNAMESIZE-1);
		table = &eprototable[j];
		while (table->name)
			table = table->nxt;
		table->name = eproto_db[i].s;
		table->addr = htons(eproto_db[i].p);
		table->nxt = newhnamemem(ndo);
	}
}

Project: tcpdump
File: addrtoname.c

init_etherarray(netdissect_options *ndo)
{
	register const struct etherlist *el;
	register struct enamemem *tp;
#ifdef USE_ETHER_NTOHOST
	char name[256];
#else
	register struct pcap_etherent *ep;
	register FILE *fp;

	/* Suck in entire ethers file */
	fp = fopen(PCAP_ETHERS_FILE, "r");
	if (fp != NULL) {
		while ((ep = pcap_next_etherent(fp)) != NULL) {
			tp = lookup_emem(ndo, ep->addr);
			tp->e_name = strdup(ep->name);
			if (tp->e_name == NULL)
				(*ndo->ndo_error)(ndo,
						  "init_etherarray: strdup(ep->addr)");
		}
		(void)fclose(fp);
	}
#endif

	/* Hardwire some ethernet names */
	for (el = etherlist; el->name != NULL; ++el) {
		tp = lookup_emem(ndo, el->addr);
		/* Don't override existing name */
		if (tp->e_name != NULL)
			continue;

#ifdef USE_ETHER_NTOHOST
		/*
		 * Use YP/NIS version of name if available.
		 */
		if (ether_ntohost(name, (const struct ether_addr *)el->addr) == 0) {
			tp->e_name = strdup(name);
			if (tp->e_name == NULL)
				(*ndo->ndo_error)(ndo,
						  "init_etherarray: strdup(name)");
			continue;
		}
#endif
		tp->e_name = el->name;
	}
}

Project: tcpdump
File: addrtoname.c

init_ipxsaparray(netdissect_options *ndo)
{
	register int i;
	register struct hnamemem *table;

	for (i = 0; ipxsap_db[i].s != NULL; i++) {
		int j = htons(ipxsap_db[i].v) & (HASHNAMESIZE-1);
		table = &ipxsaptable[j];
		while (table->name)
			table = table->nxt;
		table->name = ipxsap_db[i].s;
		table->addr = htons(ipxsap_db[i].v);
		table->nxt = newhnamemem(ndo);
	}
}

Project: tcpdump
File: addrtoname.c

init_protoidarray(netdissect_options *ndo)
{
	register int i;
	register struct protoidmem *tp;
	const struct protoidlist *pl;
	u_char protoid[5];

	protoid[0] = 0;
	protoid[1] = 0;
	protoid[2] = 0;
	for (i = 0; eproto_db[i].s; i++) {
		u_short etype = htons(eproto_db[i].p);

		memcpy((char *)&protoid[3], (char *)&etype, 2);
		tp = lookup_protoid(ndo, protoid);
		tp->p_name = strdup(eproto_db[i].s);
		if (tp->p_name == NULL)
			(*ndo->ndo_error)(ndo,
					  "init_protoidarray: strdup(eproto_db[i].s)");
	}
	/* Hardwire some SNAP proto ID names */
	for (pl = protoidlist; pl->name != NULL; ++pl) {
		tp = lookup_protoid(ndo, pl->protoid);
		/* Don't override existing name */
		if (tp->p_name != NULL)
			continue;

		tp->p_name = pl->name;
	}
}

Project: tcpdump
File: addrtoname.c

init_servarray(netdissect_options *ndo)
{
	struct servent *sv;
	register struct hnamemem *table;
	register int i;
	char buf[sizeof("0000000000")];

	while ((sv = getservent()) != NULL) {
		int port = ntohs(sv->s_port);
		i = port & (HASHNAMESIZE-1);
		if (strcmp(sv->s_proto, "tcp") == 0)
			table = &tporttable[i];
		else if (strcmp(sv->s_proto, "udp") == 0)
			table = &uporttable[i];
		else
			continue;

		while (table->name)
			table = table->nxt;
		if (ndo->ndo_nflag) {
			(void)snprintf(buf, sizeof(buf), "%d", port);
			table->name = strdup(buf);
		} else
			table->name = strdup(sv->s_name);
		if (table->name == NULL)
			(*ndo->ndo_error)(ndo, "init_servarray: strdup");

		table->addr = port;
		table->nxt = newhnamemem(ndo);
	}
	endservent();
}

Project: tcpdump
File: addrtoname.c

ipxsap_string(netdissect_options *ndo, u_short port)
{
	register char *cp;
	register struct hnamemem *tp;
	register uint32_t i = port;
	char buf[sizeof("0000")];

	for (tp = &ipxsaptable[i & (HASHNAMESIZE-1)]; tp->nxt; tp = tp->nxt)
		if (tp->addr == i)
			return (tp->name);

	tp->addr = i;
	tp->nxt = newhnamemem(ndo);

	cp = buf;
	NTOHS(port);
	*cp++ = hex[port >> 12 & 0xf];
	*cp++ = hex[port >> 8 & 0xf];
	*cp++ = hex[port >> 4 & 0xf];
	*cp++ = hex[port & 0xf];
	*cp++ = '\0';
	tp->name = strdup(buf);
	if (tp->name == NULL)
		(*ndo->ndo_error)(ndo, "ipxsap_string: strdup(buf)");
	return (tp->name);
}

Project: tcpdump
File: addrtoname.c

isonsap_string(netdissect_options *ndo, const u_char *nsap,
	       register u_int nsap_length)
{
	register u_int nsap_idx;
	register char *cp;
	register struct enamemem *tp;

	if (nsap_length < 1 || nsap_length > ISONSAP_MAX_LENGTH)
		return ("isonsap_string: illegal length");

	tp = lookup_nsap(ndo, nsap, nsap_length);
	if (tp->e_name)
		return tp->e_name;

	tp->e_name = cp = (char *)malloc(sizeof("xx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xx"));
	if (cp == NULL)
		(*ndo->ndo_error)(ndo, "isonsap_string: malloc");

	for (nsap_idx = 0; nsap_idx < nsap_length; nsap_idx++) {
		*cp++ = hex[*nsap >> 4];
		*cp++ = hex[*nsap++ & 0xf];
		if (((nsap_idx & 1) == 0) &&
		     (nsap_idx + 1 < nsap_length)) {
		     	*cp++ = '.';
		}
	}
	*cp = '\0';
	return (tp->e_name);
}

Project: tcpdump
File: addrtoname.c

lookup_nsap(netdissect_options *ndo, register const u_char *nsap,
	    register u_int nsap_length)
{
	register u_int i, j, k;
	struct enamemem *tp;
	const u_char *ensap;

	if (nsap_length > 6) {
		ensap = nsap + nsap_length - 6;
		k = (ensap[0] << 8) | ensap[1];
		j = (ensap[2] << 8) | ensap[3];
		i = (ensap[4] << 8) | ensap[5];
	}
	else
		i = j = k = 0;

	tp = &nsaptable[(i ^ j) & (HASHNAMESIZE-1)];
	while (tp->e_nxt)
		if (tp->e_addr0 == i &&
		    tp->e_addr1 == j &&
		    tp->e_addr2 == k &&
		    tp->e_nsap[0] == nsap_length &&
		    memcmp((const char *)&(nsap[1]),
			(char *)&(tp->e_nsap[1]), nsap_length) == 0)
			return tp;
		else
			tp = tp->e_nxt;
	tp->e_addr0 = i;
	tp->e_addr1 = j;
	tp->e_addr2 = k;
	tp->e_nsap = (u_char *)malloc(nsap_length + 1);
	if (tp->e_nsap == NULL)
		(*ndo->ndo_error)(ndo, "lookup_nsap: malloc");
	tp->e_nsap[0] = (u_char)nsap_length;	/* guaranteed < ISONSAP_MAX_LENGTH */
	memcpy((char *)&tp->e_nsap[1], (const char *)nsap, nsap_length);
	tp->e_nxt = (struct enamemem *)calloc(1, sizeof(*tp));
	if (tp->e_nxt == NULL)
		(*ndo->ndo_error)(ndo, "lookup_nsap: calloc");

	return tp;
}

Project: tcpdump
File: addrtoname.c

newh6namemem(netdissect_options *ndo)
{
	register struct h6namemem *p;
	static struct h6namemem *ptr = NULL;
	static u_int num = 0;

	if (num  <= 0) {
		num = 64;
		ptr = (struct h6namemem *)calloc(num, sizeof (*ptr));
		if (ptr == NULL)
			(*ndo->ndo_error)(ndo, "newh6namemem: calloc");
	}
	--num;
	p = ptr++;
	return (p);
}

Project: tcpdump
File: addrtoname.c

newhnamemem(netdissect_options *ndo)
{
	register struct hnamemem *p;
	static struct hnamemem *ptr = NULL;
	static u_int num = 0;

	if (num  <= 0) {
		num = 64;
		ptr = (struct hnamemem *)calloc(num, sizeof (*ptr));
		if (ptr == NULL)
			(*ndo->ndo_error)(ndo, "newhnamemem: calloc");
	}
	--num;
	p = ptr++;
	return (p);
}

Project: tcpdump
File: addrtoname.c

protoid_string(netdissect_options *ndo, register const u_char *pi)
{
	register u_int i, j;
	register char *cp;
	register struct protoidmem *tp;
	char buf[sizeof("00:00:00:00:00")];

	tp = lookup_protoid(ndo, pi);
	if (tp->p_name)
		return tp->p_name;

	cp = buf;
	if ((j = *pi >> 4) != 0)
		*cp++ = hex[j];
	*cp++ = hex[*pi++ & 0xf];
	for (i = 4; (int)--i >= 0;) {
		*cp++ = ':';
		if ((j = *pi >> 4) != 0)
			*cp++ = hex[j];
		*cp++ = hex[*pi++ & 0xf];
	}
	*cp = '\0';
	tp->p_name = strdup(buf);
	if (tp->p_name == NULL)
		(*ndo->ndo_error)(ndo, "protoid_string: strdup(buf)");
	return (tp->p_name);
}

Project: tcpdump
File: addrtoname.c

tcpport_string(netdissect_options *ndo, u_short port)
{
	register struct hnamemem *tp;
	register uint32_t i = port;
	char buf[sizeof("00000")];

	for (tp = &tporttable[i & (HASHNAMESIZE-1)]; tp->nxt; tp = tp->nxt)
		if (tp->addr == i)
			return (tp->name);

	tp->addr = i;
	tp->nxt = newhnamemem(ndo);

	(void)snprintf(buf, sizeof(buf), "%u", i);
	tp->name = strdup(buf);
	if (tp->name == NULL)
		(*ndo->ndo_error)(ndo, "tcpport_string: strdup(buf)");
	return (tp->name);
}

Project: tcpdump
File: addrtoname.c

udpport_string(netdissect_options *ndo, register u_short port)
{
	register struct hnamemem *tp;
	register uint32_t i = port;
	char buf[sizeof("00000")];

	for (tp = &uporttable[i & (HASHNAMESIZE-1)]; tp->nxt; tp = tp->nxt)
		if (tp->addr == i)
			return (tp->name);

	tp->addr = i;
	tp->nxt = newhnamemem(ndo);

	(void)snprintf(buf, sizeof(buf), "%u", i);
	tp->name = strdup(buf);
	if (tp->name == NULL)
		(*ndo->ndo_error)(ndo, "udpport_string: strdup(buf)");
	return (tp->name);
}

Project: tcpdump
File: addrtoname.c

win32_gethostbyaddr(const char *addr, int len, int type)
{
	static struct hostent host;
	static char hostbuf[NI_MAXHOST];
	char hname[NI_MAXHOST];
	struct sockaddr_in6 addr6;

	host.h_name = hostbuf;
	switch (type) {
	case AF_INET:
		return gethostbyaddr(addr, len, type);
		break;
	case AF_INET6:
		memset(&addr6, 0, sizeof(addr6));
		addr6.sin6_family = AF_INET6;
		memcpy(&addr6.sin6_addr, addr, len);
		if (getnameinfo((struct sockaddr *)&addr6, sizeof(addr6),
		    hname, sizeof(hname), NULL, 0, 0)) {
			return NULL;
		} else {
			strcpy(host.h_name, hname);
			return &host;
		}
		break;
	default:
		return NULL;
	}
}